Files
Talks/content/kubernetes-hell-to-heaven/index.html
T
Le Prévost-Corvellec Arnault 10f7798afb Enhance index.html and custom.css for improved content presentation and new assets
- 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.
2026-04-09 02:52:44 +02:00

1063 lines
50 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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 &#8594; events</li>
<li>Pas de mystère &#8594; 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 --&gt; C[etcd]
B --&gt; D[Controllers]
D --&gt; E[kubelet]
E --&gt; F[containerd]
F --&gt; 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] --&gt; AP[kube-apiserver]
AP &lt;--&gt;|objets cluster| ET[(etcd)]
AP --&gt; EV[Watch · événements]
EV --&gt; CTRL[Controllers]
CTRL --&gt;|réconciliation · requêtes API| AP
subgraph NODE[Nœud]
K[kubelet] --&gt;|CRI| CD[containerd]
end
AP --&gt;|spec Pods · synchro| K
K --&gt;|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] --&gt; B[Operator]
B --&gt; C[Cluster]
C --&gt; 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 &amp; 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 &amp; 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 &amp;
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 &lt;image&gt; 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>