- Updated index.html to refine content structure, including enhanced blockquotes and lists for better readability. - Added a new section to emphasize Kubernetes as a developer-centric tool. - Improved CSS styles in custom.css for blockquote presentation and slide layout adjustments. - Introduced new SVG and WEBP images for better visual representation of tools used in Kubernetes.
1063 lines
50 KiB
HTML
1063 lines
50 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="fr">
|
||
|
||
<head>
|
||
<meta charset="utf-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>Kubernetes Operators — De l’enfer au paradis</title>
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.6.1/reveal.min.css" />
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.6.1/theme/moon.min.css" />
|
||
<link rel="stylesheet"
|
||
href="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.6.1/plugin/highlight/monokai.min.css" />
|
||
<link rel="stylesheet" href="css/custom.css" />
|
||
<!--
|
||
Reveal.js 4.6.1 — plugins : Notes orateur + surlignage code.
|
||
S : fenêtre notes (même origine, servir en HTTP, pas file://).
|
||
? : aide raccourcis · Échap : aperçu · F : plein écran
|
||
-->
|
||
</head>
|
||
|
||
<body>
|
||
<div class="reveal">
|
||
<div class="slides">
|
||
<section>
|
||
<h1>Kubernetes Operators — De l’enfer… au paradis</h1>
|
||
<blockquote>
|
||
<p>Kubernetes n’est <strong>pas</strong> compliqué.<br />
|
||
Mais <strong>mal compris</strong>… c’est un enfer.</p>
|
||
</blockquote>
|
||
<aside class="notes">
|
||
<p>On va parler de Kubernetes… mais surtout de pourquoi les opérateurs changent complètement la manière de
|
||
travailler — et pourquoi c’est <strong>puissant</strong> mais <strong>dangereux</strong> si on se plante sur
|
||
le mental model.</p>
|
||
<p>Illustration suggérée : diagramme moderne et minimal, flat design, lisible (éviter le poster CNCF
|
||
illisible).</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Kubernetes : délire d’Ops ?</h1>
|
||
<blockquote>
|
||
<p>On nous l’a surtout vendu comme un truc d’infra.<br />
|
||
<span class="blockquote-sub">Moi aussi, je suis passé par ce réflexe.</span>
|
||
</p>
|
||
</blockquote>
|
||
<ul>
|
||
<li>« Trop complexe »</li>
|
||
<li>Réservé aux spécialistes plateforme</li>
|
||
<li>Magie noire / pas « mon » métier</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>On nous présente souvent Kubernetes comme un délire d’infra, réservé à une poignée de spécialistes — j’ai
|
||
eu
|
||
ce réflexe aussi, et je m’y retrouve quand j’entends encore ce discours.<br />
|
||
En réalité… c’est beaucoup plus proche de ce qu’on fait déjà en dev.</p>
|
||
<p><em>Chaos scripts</em> — dev entouré de scripts, crons, alertes, terminal chargé, ambiance tendue
|
||
(contraste avec la suite).</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<blockquote>
|
||
<p>Kubernetes c'est un délire de dev</span>
|
||
</p>
|
||
</blockquote>
|
||
</section>
|
||
<section>
|
||
<h1>Kubernetes = API + état + boucle</h1>
|
||
<ul>
|
||
<li>API REST</li>
|
||
<li>Base d’état (etcd)</li>
|
||
<li>Boucle de réconciliation → events</li>
|
||
<li>Pas de mystère → des mécanismes explicites (controllers, kubelet, containerd, etc.)</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Kubernetes, c’est juste une API, une base d’état, et une boucle qui corrige la réalité.<br />
|
||
Pas de magie — des patterns qu’on retrouve ailleurs en dev.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>De vos outils au noyau Linux</h1>
|
||
<pre><code class="language-mermaid">
|
||
flowchart LR
|
||
B[kube-api]
|
||
subgraph Clients
|
||
A01[kubectl]
|
||
A02@{ img: "img/helmsh-icon.svg", pos: "b", h: 44, constraint: "on" }
|
||
A03@{ img: "img/logo-kustomize.B5HO6GxI_1tyDev.webp", pos: "b", h: 36, constraint: "on" }
|
||
end
|
||
A01 --> B
|
||
A02 --> B
|
||
A03 --> B
|
||
D --> B
|
||
B --> C[etcd]
|
||
B --> D[Controllers]
|
||
D --> E[kubelet]
|
||
E --> F[containerd]
|
||
F --> G[Kernel Linux]
|
||
classDef logoBare fill:transparent,stroke:transparent,stroke-width:0px;
|
||
class A02,A03 logoBare;
|
||
</code></pre>
|
||
<ul>
|
||
<li><strong>kube-api</strong> — point d’entrée</li>
|
||
<li><strong>etcd</strong> — source de vérité</li>
|
||
<li><strong>kubelet</strong> — agent sur chaque nœud</li>
|
||
<li><strong>container runtime</strong> (ex. containerd) — exécution des conteneurs</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Tout ça, ce sont des clients : Helm, kubectl, Flux… ils appellent l’API.<br />
|
||
L’API parle à etcd, les controllers poussent le travail vers les kubelets, et le runtime lance les
|
||
conteneurs.</p>
|
||
<p>Schéma ASCII de secours :</p>
|
||
<pre><code>Clients → kube-api → etcd
|
||
↓
|
||
controllers → kubelet → containerd → kernel
|
||
</code></pre>
|
||
</aside>
|
||
</section>
|
||
<section class="slide-boucle">
|
||
<div class="slide-boucle-inner">
|
||
<h1 class="slide-boucle-title">Kubernetes ne fait pas tourner vos apps : la boucle de contrôle</h1>
|
||
<ul class="slide-boucle-lead">
|
||
<li>Il <strong>orchestre</strong> · il <strong>délègue</strong> · il <strong>observe</strong></li>
|
||
</ul>
|
||
<pre><code class="language-mermaid">flowchart LR
|
||
DS[Spec · désir] --> AP[kube-apiserver]
|
||
AP <-->|objets cluster| ET[(etcd)]
|
||
AP --> EV[Watch · événements]
|
||
EV --> CTRL[Controllers]
|
||
CTRL -->|réconciliation · requêtes API| AP
|
||
subgraph NODE[Nœud]
|
||
K[kubelet] -->|CRI| CD[containerd]
|
||
end
|
||
AP -->|spec Pods · synchro| K
|
||
K -->|status| AP
|
||
</code></pre>
|
||
<p class="slide-boucle-foot">
|
||
<strong>↻</strong> Les changements d’objets (Pods, etc.) vivent dans l’API ; le <strong>kubelet</strong> les
|
||
<strong>synchronise</strong> depuis l’API, puis traduit en appels <strong>CRI</strong> vers
|
||
<strong>containerd</strong>. Le <strong>status</strong> remonte par le même kubelet vers l’API.
|
||
</p>
|
||
</div>
|
||
<aside class="notes">
|
||
<p>Aligné sur la doc Kubernetes (<em>Controllers</em>) : boucle de contrôle = observer l’état du cluster,
|
||
rapprocher le courant du <strong>spec</strong> désiré ; le plus souvent le controller envoie des messages au
|
||
<strong>kube-apiserver</strong> (création / mise à jour d’objets), d’où des effets de bord (Pods à planifier,
|
||
etc.). Les <strong>informers</strong> / watches alimentent des files d’<strong>événements</strong>.</p>
|
||
<p>etcd est la persistance derrière l’API ; le <strong>status</strong> reflète ce qui tourne vraiment et
|
||
repart dans la boucle. Le <strong>chemin des « commandes » vers containerd</strong> : mises à jour du
|
||
<strong>spec</strong> dans l’API → le kubelet (watch / liste des Pods du nœud) → <strong>CRI</strong> →
|
||
containerd. Le schéma du plan de contrôle côté masters reste API ↔ controllers ↔ API.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Sous containerd : cgroups et namespaces</h1>
|
||
<pre><code class="language-mermaid">flowchart TD
|
||
F[containerd] --> K[Linux Kernel]
|
||
</code></pre>
|
||
<ul>
|
||
<li><strong>cgroups</strong></li>
|
||
<li><strong>namespaces</strong></li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Et derrière containerd… le noyau Linux. Cgroups, namespaces…</p>
|
||
<p><em>(pause)</em></p>
|
||
<p>Et ça… vous n’avez pas envie d’y manipuler les fichiers à la main au quotidien.</p>
|
||
<p><em>Kernel danger zone</em> — mécanique dense, style industriel sombre (le « trou du souffleur » sous
|
||
Kubernetes).</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Kubernetes vous évite de vivre dans le noyau</h1>
|
||
<ul>
|
||
<li>Manipuler les <strong>cgroups</strong> à la main</li>
|
||
<li>Gérer <strong>isolation</strong>, <strong>CPU</strong>, <strong>mémoire</strong> au niveau des noyaux de vos differents noeuds</li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>À côté de ça, un cluster qui râle, c'est presque du repos.</p>
|
||
</blockquote>
|
||
<aside class="notes">
|
||
<p>Si Kubernetes existe, c’est pour vous éviter de vivre dans ces couches-là.<br />
|
||
À côté du noyau, Kubernetes est presque rassurant.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>CNI, CSI, CRI : de l’infra branchable</h1>
|
||
<ul>
|
||
<li><strong>CNI</strong> → réseau (ex : Istio, Cilium ,Calico, Flannel, etc.)</li>
|
||
<li><strong>CSI</strong> → stockage (ex : Ceph, Longhorn , Amazon,etc.)</li>
|
||
<li><strong>CRI</strong> → runtime (ex : containerd, CRI-O,etc.)</li>
|
||
<li>Ici : brancher l’infra — pas encoder la logique métier (<em>≠</em> opérateur)</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>On peut brancher réseau, stockage, runtime… mais ce n’est pas notre sujet du jour — et surtout, ne les
|
||
confondez pas avec la logique métier.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Brancher l’infra ou coder le métier ?</h1>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Type</th>
|
||
<th>Rôle</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><strong>CNI / CSI / CRI</strong></td>
|
||
<td>Infrastructure</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Opérateur</strong></td>
|
||
<td>Logique métier</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<aside class="notes">
|
||
<p>D’un côté : des briques d’infra branchables.<br />
|
||
De l’autre : du <strong>code</strong> qui encode <strong>comment</strong> votre système doit se comporter
|
||
dans le cluster.</p>
|
||
<p>Icônes utiles :</p>
|
||
<ul>
|
||
<li><a href="https://github.com/kubernetes/community/tree/master/icons">Icônes Kubernetes (officiel)</a>
|
||
</li>
|
||
<li><a href="https://landscape.cncf.io/">CNCF Landscape</a></li>
|
||
<li><a href="https://simpleicons.org/">Simple Icons (SVG)</a></li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Qui écrit la logique du cluster ?</h1>
|
||
<aside class="notes">
|
||
<p>Qui décide de ce qu’il faut faire quand l’état réel diverge de ce qu’on veut ?</p>
|
||
<p><em>(pause)</em></p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>La logique vit dans les controllers</h1>
|
||
<p><em>Ils ferment la boucle : observation, décision, action.</em></p>
|
||
<aside class="notes">
|
||
<p>La vraie intelligence de Kubernetes… elle est dans les controllers.<br />
|
||
Ce sont eux qui ferment la boucle : observation, décision, action.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Les opérateurs : des controllers spécialisés</h1>
|
||
<aside class="notes">
|
||
<p>Les opérateurs, ce sont des controllers… mais focalisés sur un domaine métier ou un logiciel précis (base,
|
||
queue, etc.).<br />
|
||
Même mécanisme, autre niveau d’abstraction.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Sans opérateur : scripts, crons, humains dans la boucle</h1>
|
||
<ul>
|
||
<li>Scripts bash</li>
|
||
<li>Cron jobs</li>
|
||
<li>Interventions humaines</li>
|
||
<li>État incohérent</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Avant : bricolage, des humains dans la boucle, et un état qui part dans tous les sens dès qu’il y a une
|
||
alerte à 3 h du matin.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Automatisation éclatée : la logique reste dans les têtes</h1>
|
||
<ul>
|
||
<li>Runbooks Confluence</li>
|
||
<li>Scripts bash « magiques »</li>
|
||
<li>Cron jobs oubliés</li>
|
||
<li>Interventions humaines</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Avant les opérateurs, l’automatisation existe… mais elle est <strong>fragmentée</strong>.<br />
|
||
Chaque problème a sa solution bricolée.</p>
|
||
<p>Et surtout :<br />
|
||
<strong>la logique est dans la tête des gens, pas dans le système.</strong>
|
||
</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Drift, pas de reproductibilité, 3 h du matin</h1>
|
||
<ul>
|
||
<li>Drift de configuration</li>
|
||
<li>Actions non reproductibles</li>
|
||
<li>Dépendance aux humains</li>
|
||
<li>Incidents à 3 h du matin</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Le problème, ce n’est pas juste « moins automatisé ».</p>
|
||
<p>C’est :</p>
|
||
<ul>
|
||
<li>pas <strong>reproductible</strong></li>
|
||
<li>pas <strong>observable</strong> au sens cluster</li>
|
||
<li>pas <strong>fiable</strong> dans le temps</li>
|
||
</ul>
|
||
<p>Surtout : impossible de <strong>raisonner globalement</strong> sur l’état du système.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Vous déclarez ; le système réconcilie</h1>
|
||
<pre><code class="language-yaml">kind: Database
|
||
spec:
|
||
size: 3
|
||
</code></pre>
|
||
<aside class="notes">
|
||
<p>Après : on décrit l’état voulu… et le contrôleur / l’opérateur fait le travail répétitif et les
|
||
transitions.</p>
|
||
<p><em>Paradis opérateur</em> — ingénieur calme, infra automatisée, UI claire, atmosphère sereine (contraste
|
||
avec slide enfer).</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Spec, opérateur, cluster : ça tourne en boucle</h1>
|
||
<pre><code class="language-mermaid">flowchart LR
|
||
A[Spec] --> B[Operator]
|
||
B --> C[Cluster]
|
||
C --> B
|
||
</code></pre>
|
||
<p><em>Dès que le réel diverge, la réconciliation repart.</em></p>
|
||
<aside class="notes">
|
||
<p>Le système réobserve, recompare, recorrige. C’est la même philosophie que le reste de Kubernetes, appliquée
|
||
à votre domaine.</p>
|
||
<p>Quelques <strong>exemples réels</strong> : d’abord des outils qui <strong>étendent</strong> des ressources
|
||
natives avec des <strong>annotations</strong>, puis des opérateurs qui posent de <strong>vrais CRD</strong>
|
||
— et un projet maison pour ancrer le discours.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Reflector & Reloader : la puissance des annotations</h1>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Outil</th>
|
||
<th>Idée</th>
|
||
<th>Déclencheur typique</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><strong>Reflector</strong> (EmberStack)</td>
|
||
<td>Copie / réplique des <strong>Secrets</strong> et <strong>ConfigMaps</strong> (autres namespaces,
|
||
miroirs)</td>
|
||
<td>Annotations sur la ressource source ou la copie</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Reloader</strong> (Stakater)</td>
|
||
<td><strong>Redémarre</strong> les Pods quand un Secret / ConfigMap référencé change</td>
|
||
<td>Annotation sur le <strong>Deployment</strong> / <strong>StatefulSet</strong> / etc.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<ul>
|
||
<li>Reflector : annotations du type <code>reflector.v1.k8s.emberstack.com/*</code> pour autoriser la réflexion
|
||
et cibler où copier.</li>
|
||
<li>Reloader : <code>reloader.stakater.com/auto: "true"</code> (ou annotations plus fines par config) pour
|
||
lier redéploiement ↔ mise à jour de config.</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Ce n’est pas toujours un « gros » opérateur avec CRD : parfois c’est un <strong>contrôleur</strong> qui
|
||
<strong>regarde</strong> des objets standards et réagit à des <strong>annotations</strong>.<br />
|
||
Très utile pour TLS partagé, configs dupliquées, ou « je change un secret → je veux un rollout » sans
|
||
pipeline ad hoc.
|
||
</p>
|
||
<ul>
|
||
<li><a href="https://github.com/emberstack/kubernetes-reflector">kubernetes-reflector</a></li>
|
||
<li><a href="https://github.com/stakater/Reloader">Reloader</a></li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Exemple Reflector : secret DB → namespace applicatif</h1>
|
||
<p><em>Le secret « source de vérité » vit avec la base ; Reflector recrée un miroir là où l’app tourne.</em></p>
|
||
<pre><code class="language-yaml"># Namespace database — secret maître (ex. mot de passe géré par l’opérateur / la DBA)
|
||
apiVersion: v1
|
||
kind: Secret
|
||
metadata:
|
||
name: db-credentials
|
||
namespace: database
|
||
annotations:
|
||
reflector.v1.k8s.emberstack.com/reflection-allowed: "true"
|
||
reflector.v1.k8s.emberstack.com/reflection-auto-enabled: "true"
|
||
reflector.v1.k8s.emberstack.com/reflection-auto-namespaces: "app-shop"
|
||
stringData:
|
||
DATABASE_URL: "postgres://…"
|
||
</code></pre>
|
||
<ul>
|
||
<li>Reflector maintient un <strong>Secret du même nom</strong> dans <code>app-shop</code>, à jour quand la
|
||
source change.</li>
|
||
<li>Alternative : miroir manuel avec <code>reflector.v1.k8s.emberstack.com/reflects: "database/db-credentials"</code>.</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Scénario concret : l’équipe data garde le secret dans <code>database</code> ; l’app en
|
||
<code>app-shop</code> n’a pas besoin d’accès RBAC au namespace DB — elle consomme la <strong>copie</strong>
|
||
synchronisée.</p>
|
||
<p>Insister sur <code>reflection-auto-*</code> vs miroir explicite selon votre politique (Helm, GitOps…).</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Exemple Reloader : rollout quand le secret miroir change</h1>
|
||
<p><em>Le Deployment monte le secret copié ; Reloader redémarre les Pods si le Secret (ou ConfigMap) référencé
|
||
est mis à jour.</em></p>
|
||
<pre><code class="language-yaml">apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: shop-api
|
||
namespace: app-shop
|
||
annotations:
|
||
reloader.stakater.com/auto: "true"
|
||
spec:
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: api
|
||
envFrom:
|
||
- secretRef:
|
||
name: db-credentials # copie Reflector depuis database/
|
||
</code></pre>
|
||
<ul>
|
||
<li>Rotation du mot de passe dans <code>database/db-credentials</code> → Reflector met à jour
|
||
<code>app-shop/db-credentials</code> → Reloader déclenche un <strong>nouveau rollout</strong>.</li>
|
||
<li>Sans Reloader, les Pods gardent l’ancienne valeur en mémoire jusqu’à un redémarrage manuel.</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Enchaînement oral : (1) changement côté source, (2) miroir à jour, (3) Reloader voit la révision du Secret,
|
||
(4) rolling update. Mentionner les annotations plus fines (<code>secret.reloader.stakater.com/reload</code>,
|
||
etc.) si vous voulez limiter à un seul secret.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Cert-manager, Postgres, Keycloak : la spec devient une entité</h1>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Opérateur</th>
|
||
<th>CRD (exemples)</th>
|
||
<th>Ce que ça <strong>matérialise</strong></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><strong>cert-manager</strong></td>
|
||
<td><code>Certificate</code>, <code>Issuer</code>, <code>ClusterIssuer</code></td>
|
||
<td>Secrets TLS, chaînes ACME / PKI</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Postgres Operator</strong> (Zalando)</td>
|
||
<td><code>postgresql</code> (<code>acid.zalan.do</code>)</td>
|
||
<td>Cluster PostgreSQL (Patroni, volumes, users…)</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Keycloak Operator</strong></td>
|
||
<td><code>Keycloak</code>, <code>KeycloakRealm</code>, <code>KeycloakClient</code>, …</td>
|
||
<td>Instances Keycloak, royaumes, clients</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p>La spec décrit l’état métier (cert, base, realm) ; le contrôleur crée / met à jour Deployments, Services,
|
||
Secrets, etc.</p>
|
||
<aside class="notes">
|
||
<p>On est dans le pattern « Database, size: 3 » mais en prod : certificats, bases gérées, IdP.<br />
|
||
Trois domaines, <strong>même mécanisme</strong> : CRD + boucle de réconciliation.</p>
|
||
<p>Liens :</p>
|
||
<ul>
|
||
<li><a href="https://cert-manager.io/">cert-manager</a></li>
|
||
<li><a href="https://github.com/zalando/postgres-operator">Zalando Postgres operator</a></li>
|
||
<li><a href="https://www.keycloak.org/operator/installation">Keycloak Operator</a></li>
|
||
</ul>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Un certificat déclaratif, tout le cycle TLS en dessous</h1>
|
||
<pre><code class="language-yaml">apiVersion: cert-manager.io/v1
|
||
kind: Certificate
|
||
metadata:
|
||
name: api-example
|
||
spec:
|
||
dnsNames:
|
||
- api.example.com
|
||
</code></pre>
|
||
<p>La suite est dérivée automatiquement :</p>
|
||
<ul>
|
||
<li><code>CertificateRequest</code></li>
|
||
<li>Challenge ACME</li>
|
||
<li>Secret TLS</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Tu déclares <strong>juste</strong> un certificat.</p>
|
||
<p>Et derrière : génération de clé, challenge ACME, renouvellement automatique.</p>
|
||
<p><strong>Aucun humain dans la boucle</strong> pour le cycle de vie du cert.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Certificats : du Certbot au renouvellement automatique</h1>
|
||
<p><em>Avant — sans opérateur</em></p>
|
||
<ul>
|
||
<li>Certbot manuel</li>
|
||
<li>Scripts cron</li>
|
||
<li>Cert expiré = prod down</li>
|
||
</ul>
|
||
<p><em>Avec cert-manager</em></p>
|
||
<ul>
|
||
<li>Déclaratif</li>
|
||
<li>Auto-renouvellement</li>
|
||
<li>Observabilité native (<code>Certificate</code>, conditions, events)</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>cert-manager, c’est typiquement : <strong>une complexité énorme</strong> encapsulée dans un opérateur.</p>
|
||
<p><strong>Là, ça vaut clairement le coup</strong> — parce que le domaine est clair, répétitif et critique.
|
||
</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Postgres répliqué sans bricoler le failover à la main</h1>
|
||
<pre><code class="language-yaml">apiVersion: acid.zalan.do/v1
|
||
kind: postgresql
|
||
metadata:
|
||
name: my-cluster
|
||
spec:
|
||
numberOfInstances: 3
|
||
</code></pre>
|
||
<p>Derrière cette spec, le contrôleur s’occupe de :</p>
|
||
<ul>
|
||
<li>Réplication</li>
|
||
<li>Failover</li>
|
||
<li>Backup</li>
|
||
<li>Users / rôles</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Ça, c’est intéressant parce que ce n’est <strong>pas</strong> trivial : ce n’est <strong>pas</strong>
|
||
stateless, ce n’est <strong>pas</strong> safe à bricoler à la main sur du long terme.</p>
|
||
<p>Un opérateur mature sur ce terrain, c’est du <strong>métier base de données</strong> version déclarative.
|
||
</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Quelle techno pour développer votre opérateur ?</h1>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>Piste</th>
|
||
<th>Outils typiques</th>
|
||
<th>À retenir</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
<tr>
|
||
<td><strong>Go</strong> (référence)</td>
|
||
<td><strong>Kubebuilder</strong>, <code>controller-runtime</code>, client-go</td>
|
||
<td>Même patterns que l’upstream (<strong>reconcile</strong>, <strong>owner refs</strong>). Sous pic
|
||
d’événements : <strong>goroutines</strong> + files + <strong>back-off</strong> → peu de RAM en plus —
|
||
le Pod opérateur ne part pas en <strong>limite</strong> quand le cluster s’emballe.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Operator SDK</strong></td>
|
||
<td>CLI Red Hat · Go (souvent comme Kubebuilder) · <strong>Ansible</strong> / <strong>Helm</strong></td>
|
||
<td>Go = solide ; Ansible / Helm = wrapper playbooks / charts — court si la logique métier épaissit.</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Autres langages</strong></td>
|
||
<td>Python <strong>Kopf</strong>, Rust <strong>kube-rs</strong>, Java (Fabric8, Quarkus)…</td>
|
||
<td>Si <strong>équipe</strong> ou libs métier ; moins d’exemples tout faits côté core k8s.</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
<p><em><strong>Go + controller-runtime</strong> pour un opérateur maison sérieux ; sinon : celui que l’équipe
|
||
<strong>livre et maintient</strong> le mieux.</em></p>
|
||
<p><em>Charge ↑ : un runtime qui gonfle la RAM par événement mange votre quota ; Go + <code>controller-runtime</code>,
|
||
non.</em></p>
|
||
<aside class="notes">
|
||
<p>Carte rapide : Kubebuilder / Operator SDK (Go) = même famille <code>controller-runtime</code>.</p>
|
||
<p><strong>Go & charge</strong> : goroutines, workqueues, back-off — pics d’événements sans RAM qui explose ;
|
||
éviter le Pod opérateur en <strong>OOM</strong> / limits alors que le cluster est juste chargé ailleurs.</p>
|
||
<p>Liens utiles : <a href="https://kubebuilder.io/">kubebuilder.io</a>,
|
||
<a href="https://sdk.operatorframework.io/">Operator SDK</a>,
|
||
<a href="https://kopf.readthedocs.io/">Kopf</a> (Python),
|
||
<a href="https://kube.rs/">kube-rs</a> (Rust).</p>
|
||
<p>Helm / Ansible : OK pour emballer du déploiement ; logique riche → souvent retour au <strong>Go</strong>.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Quand un opérateur est légitime</h1>
|
||
<ul>
|
||
<li>Système <strong>complexe</strong></li>
|
||
<li><strong>Règles</strong> claires et stables</li>
|
||
<li><strong>État</strong> critique pour l’activité</li>
|
||
<li>Opérations <strong>répétitives</strong></li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Un opérateur est pertinent quand tu peux formaliser des règles du type <strong>« si X alors Y »</strong>,
|
||
que tu dois les appliquer <strong>souvent</strong>, et que tu veux <strong>réduire l’erreur
|
||
humaine</strong>.</p>
|
||
<p>Si tu ne peux pas écrire ça proprement… <strong>pause</strong>… <strong>ça, c’est une très mauvaise
|
||
idée</strong> en opérateur.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Keycloak Credential Manager — le même schéma, chez moi</h1>
|
||
<ul>
|
||
<li>Objectif : <strong>réconcilier</strong> des <strong>credentials Keycloak</strong> (clients, secrets,
|
||
rotation…) avec ce qui est <strong>déclaré dans le cluster</strong> (CRD, Secrets annotés, ou les deux —
|
||
selon ton design).</li>
|
||
<li>Intérêt conf : montrer le <strong>même schéma</strong> que les opérateurs « connus » : <em>observe →
|
||
compare → corrige</em> sur <strong>ton</strong> domaine métier.</li>
|
||
<li><strong>Incident ou compromission</strong> de secrets clients : un <strong>reload</strong> Keycloak
|
||
<strong>émet des événements</strong> côté IdP ; l’opérateur les <strong>interroge régulièrement</strong>
|
||
(ex. <strong>toutes les minutes</strong>), repère l’écart et <strong>propage</strong> la correction vers
|
||
l’état attendu dans le cluster.</li>
|
||
</ul>
|
||
<p><em>Exemple concret de CRD à la slide suivante.</em></p>
|
||
<aside class="notes">
|
||
<p>Je vous montre ça pas pour vendre un produit, mais pour prouver que <strong>la logique</strong> est la même
|
||
: une API Kubernetes, un état désiré, et du code qui parle à Keycloak à la place d’un humain et d’un
|
||
runbook.</p>
|
||
<p><strong>Scénario oral</strong> : en cas d’<strong>incident</strong> ou de <strong>compromission</strong> de
|
||
credentials clients, tu forces un <strong>reload</strong> Keycloak — ça <strong>déclenche des événements</strong>
|
||
côté serveur. L’opérateur ne repose pas sur un webhook magique seul : il <strong>scrape / poll</strong> (dans
|
||
ton design : <strong>toutes les minutes</strong>) pour lire ces signaux ou resynchroniser, puis
|
||
<strong>réconcilie</strong> : Secrets / CRD à jour, rotation propagée, pas de dérive longue entre l’IdP et le
|
||
déclaratif cluster.</p>
|
||
<ul>
|
||
<li><strong>Lien repo</strong> : <code>https://…</code> (README, une spec d’exemple, capture d’un
|
||
<code>kubectl get</code>).
|
||
</li>
|
||
<li><strong>Une phrase</strong> sur le périmètre exact : ex. clients OAuth uniquement, rotation de secrets,
|
||
sync depuis External Secrets, etc.</li>
|
||
</ul>
|
||
<p>Schéma minimal : <code>CRD / Secret</code> → <strong>Keycloak Credential Manager</strong> → API Keycloak,
|
||
style diagramme propre (flat design).</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Exemple — <code>KeycloakClientCredential</code></h1>
|
||
<p><em>CRD</em> <code>secrets.carbogame.com/v1alpha1</code> — <code>spec.source</code> (realm, client) →
|
||
<code>spec.target</code> (Secret, rafraîchissement, mapping des clés).</p>
|
||
<pre><code class="language-yaml">apiVersion: secrets.carbogame.com/v1alpha1
|
||
kind: KeycloakClientCredential
|
||
metadata:
|
||
name: gitea-creds
|
||
namespace: keycloak
|
||
spec:
|
||
source:
|
||
keycloakServer: prod-keycloak
|
||
realm: operator
|
||
clientId: gitea
|
||
target:
|
||
secretName: gitea-secret-creds
|
||
namespace:
|
||
- gitea
|
||
refreshPeriod: 10m
|
||
fields:
|
||
clientId: key
|
||
clientSecret: secret
|
||
</code></pre>
|
||
<p><em>Extrait type ; en chart Helm, <code>namespace</code> et <code>keycloakServer</code> sont injectés via
|
||
<code>include "common.namespace" …</code>, <code>{{ .Values.global.env }}-keycloak</code>, etc.</em></p>
|
||
<aside class="notes">
|
||
<p>Parcourir le YAML : <code>source</code> = où lire côté Keycloak ; <code>target</code> = où écrire le Secret
|
||
et sous quelles clés.</p>
|
||
<p><code>spec.target.refreshPeriod</code> (ex. <code>10m</code>) cadence la resync vers le Secret ; la boucle
|
||
événements / poll (ex. minute) peut être un autre réglage — à distinguer en live si on pose la question.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Développer son opérateur : réutiliser les autres</h1>
|
||
<ul>
|
||
<li><strong>Ne pas tout recoder</strong> : un opérateur maison peut s’appuyer sur des opérateurs / contrôleurs
|
||
existants pour ce qu’ils font déjà bien.</li>
|
||
<li>Ex. <strong>Secrets / ConfigMaps</strong> vers d’autres namespaces : plutôt que dupliquer la logique et une
|
||
<strong>deuxième config</strong> de ce qui est autorisé, brancher <strong>Reflector</strong> — une politique
|
||
de réflexion, <strong>un seul RBAC</strong> à raisonner (source vs cibles), moins de surface d’erreur.</li>
|
||
<li>Votre code se concentre sur <strong>le métier</strong> ; la diffusion transverse reste le problème de
|
||
l’outil prévu pour ça.</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Message : <strong>composition</strong> plutôt que réinventer Reflector dans votre binaire. Même idée pour
|
||
Reloader, External Secrets, etc. : chaque brique a déjà ses garde-fous et sa doc.</p>
|
||
<p>RBAC : si vous copiez vous-même des secrets entre namespaces, vous maintenez souvent <strong>deux</strong>
|
||
chemins (votre opérateur + la vérité côté Reflector). Avec Reflector seul pour la copie, une seule matrice
|
||
« qui peut refléter où ».</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Ce qu’un opérateur bien choisi vous apporte</h1>
|
||
<ul>
|
||
<li>Automatiser du <strong>répétitif</strong></li>
|
||
<li><strong>Encapsuler</strong> de la complexité</li>
|
||
<li><strong>Stabiliser</strong> un système</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>On remplace des opérations manuelles par du code — quand c’est le bon outil pour du vrai répétitif et des
|
||
règles claires.</p>
|
||
<p>On vient de voir du <strong>concret</strong> (annotations, CRD, projet maison). La question :
|
||
<strong>quand</strong> est-ce un bon outil ?
|
||
</p>
|
||
<p>…<strong>mais</strong> les opérateurs peuvent aussi devenir un enfer.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Quand ne pas écrire d’opérateur</h1>
|
||
<ul>
|
||
<li>Logique <strong>floue</strong> ou en perpétuelle négociation métier</li>
|
||
<li>Processus <strong>instables</strong> (la règle change toutes les semaines)</li>
|
||
<li>Dépendances <strong>externes</strong> imprévisibles</li>
|
||
<li><strong>One-shot</strong> : automatisation ponctuelle sans cycle de vie</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Si tu ne peux pas écrire clairement <strong>« si X alors Y »</strong> avec des conditions observables… tu
|
||
n’as <strong>rien à faire</strong> dans un opérateur.</p>
|
||
<p>Sinon tu recodes un outil métier opaque <strong>dans</strong> le cluster.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Cinq signaux qu’un opérateur part en vrille</h1>
|
||
<ul>
|
||
<li>Spec <strong>incompréhensible</strong> sans lire le code</li>
|
||
<li>Effets <strong>implicites</strong> (créations ailleurs sans que ce soit visible)</li>
|
||
<li><strong>Couplage</strong> externe fort (APIs fragiles, ordre d’appels magique)</li>
|
||
<li><strong>Debug</strong> impossible sans les mainteneurs à côté de toi</li>
|
||
<li><strong>État</strong> non observable (pas de conditions / status utiles)</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Un opérateur doit être <strong>lisible</strong>, <strong>prévisible</strong>, <strong>observable</strong>.
|
||
</p>
|
||
<p>Sinon, ce n’est pas de la plateforme : c’est une <strong>bombe à retardement</strong> avec un
|
||
<code>kubectl</code> dessus.
|
||
</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Débugger un opérateur : describe, events, status, logs</h1>
|
||
<ul>
|
||
<li><code>kubectl describe</code> (resource + events)</li>
|
||
<li><strong>Events</strong> du namespace</li>
|
||
<li><strong>Status / conditions</strong> sur la CRD</li>
|
||
<li><strong>Logs</strong> du contrôleur</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Le debug devient <strong>indirect</strong>, <strong>distribué</strong>, parfois <strong>opaque</strong>.
|
||
</p>
|
||
<p>Si ton opérateur est mal conçu — pas de status clair, pas d’events utiles — <strong>bonne chance</strong>
|
||
pour le post-mortem à 3 h du matin.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Un opérateur, c’est un produit logiciel — pas un script</h1>
|
||
<ul>
|
||
<li><strong>Code</strong> et revues</li>
|
||
<li><strong>Tests</strong> (unitaires, intégration, e2e si possible)</li>
|
||
<li><strong>Maintenance</strong> dans la durée</li>
|
||
<li><strong>Compatibilité</strong> avec les versions Kubernetes</li>
|
||
<li><strong>Migrations</strong> de CRD / spec</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Un opérateur, ce n’est <strong>pas</strong> un script du vendredi.</p>
|
||
<p>C’est un <strong>produit logiciel</strong> avec un <strong>cycle de vie</strong> — et une dette si tu
|
||
l’abandonnes.</p>
|
||
<p>Ensuite : la synthèse <strong>« ça part en vrille »</strong> — smells qu’on a déjà nommés, mais côté vécu
|
||
d’équipe.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Quand l’opérateur devient l’enfer</h1>
|
||
<ul>
|
||
<li>Logique <strong>implicite</strong></li>
|
||
<li><strong>Effets de bord</strong></li>
|
||
<li><strong>Debugging</strong> difficile</li>
|
||
<li><strong>Couplage</strong> fort</li>
|
||
</ul>
|
||
<p>En pratique, ça donne souvent :</p>
|
||
<ul>
|
||
<li>Tout mettre en opérateur</li>
|
||
<li>Logique métier <strong>floue</strong></li>
|
||
<li>Effets <strong>cachés</strong></li>
|
||
</ul>
|
||
<blockquote>
|
||
<p>Un opérateur mal conçu, c’est un microservice dont plus personne ne tient les règles du jeu.</p>
|
||
</blockquote>
|
||
<aside class="notes">
|
||
<p>Un mauvais opérateur, c’est de la complexité distribuée et le genre de session debug qui nous tient
|
||
éveillés à
|
||
3 h du matin — j’en ai croisé de très pénibles, et je ne suis pas toujours innocent de l’empilement.<br />
|
||
Le piège classique : « formation Kubernetes par la trappe » parce qu’on empile des comportements opaques —
|
||
souvent avec les meilleures intentions.
|
||
</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Kubernetes, plateforme de programmation</h1>
|
||
<ul>
|
||
<li>Kubernetes vous évite <strong>l’enfer de l’infrastructure</strong> bas niveau.</li>
|
||
<li>Les bons opérateurs vous évitent <strong>l’enfer du manuel</strong> — les mauvais vous y renvoient
|
||
autrement.</li>
|
||
</ul>
|
||
<aside class="notes">
|
||
<p>Kubernetes n’est pas qu’un outil d’infra : c’est une surface où l’on encode du
|
||
<strong>comportement</strong>.<br />
|
||
Les opérateurs sont l’extension naturelle… à condition de rester honnêtes sur la complexité.
|
||
</p>
|
||
<p>Fil de la conf (rappel) :</p>
|
||
<ol>
|
||
<li>Kubernetes n’est pas magique </li>
|
||
<li>API + état + boucle ; kernel = vrai « enfer » bas niveau </li>
|
||
<li>Controllers / opérateurs : où vit la logique </li>
|
||
<li><strong>Avant</strong> : vécu (runbooks, scripts, drift) → <strong>pourquoi</strong> ça casse </li>
|
||
<li><strong>Après</strong> + boucle ; exemples Reflector/Reloader, panorama CRD, creux cert-manager &
|
||
Postgres </li>
|
||
<li>Projet perso ; <strong>bon usage</strong> puis <strong>quand ne pas</strong>, smells, debug,
|
||
<strong>coût</strong>
|
||
</li>
|
||
<li>Pièges ; plateforme de programmation ; <strong>checklist</strong> ; dernière phrase + conclusion brutale
|
||
</li>
|
||
</ol>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Avant d’écrire un opérateur : la checklist</h1>
|
||
<ul>
|
||
<li>Répétitif</li>
|
||
<li>Règles claires</li>
|
||
<li>Déclaratif possible</li>
|
||
<li>Idempotent</li>
|
||
<li>Observable</li>
|
||
</ul>
|
||
<p>Si une case importante manque, reculez : pas d’opérateur (ou pas tout de suite).</p>
|
||
<aside class="notes">
|
||
<p>C’est probablement <strong>la</strong> slide la plus importante pour une salle de devs.</p>
|
||
<p>Kubernetes donne <strong>envie</strong> d’en mettre partout. <strong>Mauvaise idée</strong> par défaut.</p>
|
||
<p>👉 Tu valides la checklist, ou tu <strong>recules</strong>.</p>
|
||
<p>Les <strong>pauses</strong> et les <strong>regards</strong> sur la salle après « Mauvaise idée » font
|
||
autant le travail qu’un diagramme.</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Votre logique, exécutée par le cluster au lieu d’un humain</h1>
|
||
<aside class="notes">
|
||
<p>Tu vises deux réactions :</p>
|
||
<ul>
|
||
<li>« OK, Kubernetes je comprends enfin le cadre. »</li>
|
||
<li>« OK, les opérateurs : puissants, mais à manier avec précision. »</li>
|
||
</ul>
|
||
<p><strong>Éviter le piège</strong> : faire une « formation Kubernetes » sans le vouloir — rester sur le
|
||
message <em>comportement / automatisation / risques</em>.</p>
|
||
<p>Un opérateur, c’est votre logique métier… mais exécutée par le cluster au lieu d’un humain.<br />
|
||
À vous de garder cette logique <strong>lisible</strong>, <strong>testable</strong>, et
|
||
<strong>bornée</strong>.
|
||
</p>
|
||
</aside>
|
||
</section>
|
||
<section>
|
||
<h1>Kubernetes simplifie l’infra — les opérateurs, ça peut simplifier ou empirer</h1>
|
||
<aside class="notes">
|
||
<p>Kubernetes vous évite une partie du chaos du bas niveau.</p>
|
||
<p>Mais les opérateurs : soit ils <strong>encapsulent</strong> intelligemment une complexité maîtrisée, soit
|
||
ils recréent un enfer <strong>plus sophistiqué</strong>.</p>
|
||
<p>Ce que la salle retient : les <strong>pauses</strong>, les <strong>« très mauvaise idée »</strong>
|
||
honnêtes, plus que chaque ligne du deck.</p>
|
||
</aside>
|
||
</section>
|
||
<section class="slide-fin">
|
||
<h1>Merci</h1>
|
||
<p class="slide-fin-thanks">Merci pour votre attention.</p>
|
||
<p class="slide-fin-cta">Des questions ?</p>
|
||
<p class="slide-fin-hint">Dépôt des sources et page d’accueil des slides — à scanner pour repartir avec les
|
||
liens.</p>
|
||
<div class="slide-fin-qr-grid">
|
||
<figure class="slide-fin-qr-block">
|
||
<figcaption>Dépôt</figcaption>
|
||
<div id="qrcode-fin-repo" class="qrcode-fin-wrap" aria-hidden="true"></div>
|
||
</figure>
|
||
<figure class="slide-fin-qr-block">
|
||
<figcaption>Slides — accueil</figcaption>
|
||
<div id="qrcode-fin-site" class="qrcode-fin-wrap" aria-hidden="true"></div>
|
||
</figure>
|
||
</div>
|
||
<aside class="notes">
|
||
<p>Slide de clôture : laisser le temps de photographier les QR. Rappeler à voix haute le dépôt et l’URL des
|
||
slides si la salle est loin de l’écran.</p>
|
||
</aside>
|
||
</section>
|
||
</div>
|
||
<footer class="deck-footer">
|
||
<a href="../">Accueil</a>
|
||
<span class="deck-footer-sep" aria-hidden="true">·</span>
|
||
<a href="https://specificat.io/" rel="noopener noreferrer" target="_blank">Specificat.io</a>
|
||
<span class="deck-footer-sep" aria-hidden="true">·</span>
|
||
<span class="deck-footer-rights">All rights reserved</span>
|
||
</footer>
|
||
</div>
|
||
<!-- Ordre : cœur Reveal → plugins officiels (notes, highlight) → Mermaid -->
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.6.1/reveal.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.6.1/plugin/notes/notes.min.js"></script>
|
||
<script src="https://cdnjs.cloudflare.com/ajax/libs/reveal.js/4.6.1/plugin/highlight/highlight.min.js"></script>
|
||
<!-- Image shape (flowchart) : Mermaid 11.3+ — https://mermaid.ai/open-source/syntax/flowchart.html#image-shape -->
|
||
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
||
<script>
|
||
(function () {
|
||
if (typeof RevealNotes === "undefined" || typeof RevealHighlight === "undefined") {
|
||
console.error("Reveal : plugins Notes ou Highlight introuvables (vérifiez le réseau / CDN).");
|
||
}
|
||
|
||
/* Thème Mermaid aligné sur https://specificat.io/ (variables en hex — requis par Mermaid) */
|
||
mermaid.initialize({
|
||
startOnLoad: false,
|
||
theme: "base",
|
||
securityLevel: "loose",
|
||
/* foreignObject + thème base = libellés vides chez certains navigateurs ; SVG text est fiable */
|
||
htmlLabels: false,
|
||
fontFamily:
|
||
'system-ui, -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif',
|
||
themeVariables: {
|
||
darkMode: true,
|
||
background: "#050709",
|
||
fontSize: "15px",
|
||
primaryColor: "#0d1115",
|
||
primaryTextColor: "#f5f5f5",
|
||
primaryBorderColor: "#2bc9c6",
|
||
secondaryColor: "#1b1f22",
|
||
secondaryTextColor: "#f5f5f5",
|
||
secondaryBorderColor: "#3d6d6a",
|
||
tertiaryColor: "#050709",
|
||
tertiaryTextColor: "#c8d0d4",
|
||
tertiaryBorderColor: "#2bc9c6",
|
||
lineColor: "#4dbfb8",
|
||
textColor: "#f5f5f5",
|
||
mainBkg: "#0d1115",
|
||
nodeBorder: "#2bc9c6",
|
||
nodeTextColor: "#f5f5f5",
|
||
clusterBkg: "#0a1014",
|
||
clusterBorder: "#2bc9c6",
|
||
defaultLinkColor: "#4dbfb8",
|
||
edgeLabelBackground: "#0d1115",
|
||
titleColor: "#f5f5f5",
|
||
nodeSpacing: 10,
|
||
rankSpacing: 10
|
||
},
|
||
flowchart: {
|
||
useMaxWidth: true,
|
||
curve: "basis",
|
||
},
|
||
});
|
||
|
||
function upgradeMermaidBlocks(container) {
|
||
container.querySelectorAll("pre code.language-mermaid").forEach(function (code) {
|
||
var pre = code.parentElement;
|
||
var div = document.createElement("div");
|
||
div.className = "mermaid";
|
||
var raw = code.textContent.replace(/\r\n/g, "\n");
|
||
div.setAttribute("data-mermaid-src", raw);
|
||
div.textContent = raw;
|
||
pre.parentNode.replaceChild(div, pre);
|
||
});
|
||
}
|
||
|
||
/** Mermaid dans une slide display:none a souvent largeur 0 → SVG vide ou taille absurde : on ne rend que la slide visible. */
|
||
function resetMermaidIfBroken(el) {
|
||
if (!el.getAttribute("data-processed")) return;
|
||
var svg = el.querySelector("svg");
|
||
if (svg && svg.getBoundingClientRect().height > 8) return;
|
||
var src = el.getAttribute("data-mermaid-src");
|
||
if (!src) return;
|
||
el.removeAttribute("data-processed");
|
||
el.innerHTML = "";
|
||
el.textContent = src;
|
||
}
|
||
|
||
/** Chemins relatifs → absolus pour <image> SVG (forme « image » v11) et foreignObject le cas échéant. */
|
||
function fixMermaidImageUrls(slide) {
|
||
if (!slide) return;
|
||
var base = window.location.href;
|
||
function toAbs(url) {
|
||
if (!url || url.indexOf("data:") === 0) return null;
|
||
if (/^https?:\/\//i.test(url)) return null;
|
||
try {
|
||
return new URL(url, base).href;
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
}
|
||
slide.querySelectorAll(".mermaid svg image").forEach(function (el) {
|
||
var u = el.getAttribute("href") || el.getAttribute("xlink:href");
|
||
var abs = toAbs(u);
|
||
if (abs) el.setAttribute("href", abs);
|
||
});
|
||
slide.querySelectorAll(".mermaid svg foreignObject img").forEach(function (img) {
|
||
var u = img.getAttribute("src");
|
||
var abs = toAbs(u);
|
||
if (abs) img.setAttribute("src", abs);
|
||
});
|
||
}
|
||
|
||
function runMermaidInSlide(slide) {
|
||
if (!slide || typeof mermaid.run !== "function") return;
|
||
slide.querySelectorAll(".mermaid").forEach(resetMermaidIfBroken);
|
||
var pending = slide.querySelectorAll(".mermaid:not([data-processed])");
|
||
if (!pending.length) return;
|
||
requestAnimationFrame(function () {
|
||
requestAnimationFrame(function () {
|
||
var p = mermaid.run({ nodes: pending, suppressErrors: true });
|
||
var after = function () {
|
||
fixMermaidImageUrls(slide);
|
||
};
|
||
if (p && typeof p.then === "function") p.then(after).catch(after);
|
||
else after();
|
||
});
|
||
});
|
||
}
|
||
|
||
function applyHighlight() {
|
||
if (typeof hljs === "undefined") return;
|
||
document.querySelectorAll(".reveal .slides pre code").forEach(function (el) {
|
||
if (!el.classList.contains("language-mermaid")) hljs.highlightElement(el);
|
||
});
|
||
}
|
||
|
||
Reveal.initialize({
|
||
hash: true,
|
||
slideNumber: "c/t",
|
||
progress: true,
|
||
center: true,
|
||
transition: "slide",
|
||
backgroundTransition: "fade",
|
||
// Notes orateur en premier (touche S) ; puis coloration des blocs de code
|
||
plugins: [RevealNotes, RevealHighlight],
|
||
}).then(function () {
|
||
applyHighlight();
|
||
document.querySelectorAll(".reveal .slides section").forEach(function (sec) {
|
||
upgradeMermaidBlocks(sec);
|
||
});
|
||
runMermaidInSlide(Reveal.getCurrentSlide());
|
||
});
|
||
|
||
Reveal.on("slidechanged", function (event) {
|
||
runMermaidInSlide(event.currentSlide);
|
||
/* Slide « boucle » : sans ça, flex + centrage Reveal donnent souvent une 1ʳᵉ mesure de largeur fausse ; resize appelle déjà layout() — on le déclenche ici. */
|
||
if (event.currentSlide && event.currentSlide.classList.contains("slide-boucle")) {
|
||
requestAnimationFrame(function () {
|
||
requestAnimationFrame(function () {
|
||
if (typeof Reveal.layout === "function") Reveal.layout();
|
||
});
|
||
});
|
||
}
|
||
});
|
||
})();
|
||
</script>
|
||
<script src="https://cdn.jsdelivr.net/npm/qrcodejs/qrcode.min.js"></script>
|
||
<script>
|
||
(function () {
|
||
if (typeof QRCode === "undefined") return;
|
||
var repoUrl = "https://git.specificat.io/arnault/Talks";
|
||
var talksHome = new URL("../", window.location.href);
|
||
talksHome.hash = "";
|
||
var siteUrl = talksHome.href;
|
||
|
||
var repoEl = document.getElementById("qrcode-fin-repo");
|
||
var siteEl = document.getElementById("qrcode-fin-site");
|
||
var qrOpts = { width: 150, height: 150 };
|
||
if (repoEl) new QRCode(repoEl, Object.assign({ text: repoUrl }, qrOpts));
|
||
if (siteEl) new QRCode(siteEl, Object.assign({ text: siteUrl }, qrOpts));
|
||
})();
|
||
</script>
|
||
</body>
|
||
|
||
</html> |