Files
Talks/content/kubernetes-hell-to-heaven/index.html
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 lenfer 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 lenfer… au paradis</h1>
<blockquote>
<p>Kubernetes nest <strong>pas</strong> compliqué.<br />
Mais <strong>mal compris</strong>… cest 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 cest <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 dOps ?</h1>
<blockquote>
<p>On nous la surtout vendu comme un truc dinfra.<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 dinfra, réservé à une poignée de spécialistes — jai
eu
ce réflexe aussi, et je my retrouve quand jentends encore ce discours.<br />
En réalité… cest beaucoup plus proche de ce quon 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, cest juste une API, une base détat, et une boucle qui corrige la réalité.<br />
Pas de magie — des patterns quon 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 dentré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 lAPI.<br />
LAPI 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 dobjets (Pods, etc.) vivent dans lAPI ; le <strong>kubelet</strong> les
<strong>synchronise</strong> depuis lAPI, puis traduit en appels <strong>CRI</strong> vers
<strong>containerd</strong>. Le <strong>status</strong> remonte par le même kubelet vers lAPI.
</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 dobjets), doù 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 lAPI ; 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 lAPI → 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 navez pas envie dy 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, cest 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 linfra 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 linfra — 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 nest pas notre sujet du jour — et surtout, ne les
confondez pas avec la logique métier.</p>
</aside>
</section>
<section>
<h1>Brancher linfra 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>Dun côté : des briques dinfra branchables.<br />
De lautre : 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 quil faut faire quand létat réel diverge de ce quon 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 dabstraction.</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 quil 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, lautomatisation 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 nest pas juste « moins automatisé ».</p>
<p>Cest :</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 / lopé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. Cest la même philosophie que le reste de Kubernetes, appliquée
à votre domaine.</p>
<p>Quelques <strong>exemples réels</strong> : dabord 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 nest pas toujours un « gros » opérateur avec CRD : parfois cest 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ù lapp tourne.</em></p>
<pre><code class="language-yaml"># Namespace database — secret maître (ex. mot de passe géré par lopé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> ; lapp en
<code>app-shop</code> na pas besoin daccè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 lancienne 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, cest 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 soccupe de :</p>
<ul>
<li>Réplication</li>
<li>Failover</li>
<li>Backup</li>
<li>Users / rôles</li>
</ul>
<aside class="notes">
<p>Ça, cest intéressant parce que ce nest <strong>pas</strong> trivial : ce nest <strong>pas</strong>
stateless, ce nest <strong>pas</strong> safe à bricoler à la main sur du long terme.</p>
<p>Un opérateur mature sur ce terrain, cest 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 lupstream (<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 semballe.</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 dexemples 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 lactivité</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 lerreur
humaine</strong>.</p>
<p>Si tu ne peux pas écrire ça proprement… <strong>pause</strong><strong>ça, cest 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 ; lopé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 dun humain et dun
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. Lopé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 lIdP et le
déclaratif cluster.</p>
<ul>
<li><strong>Lien repo</strong> : <code>https://…</code> (README, une spec dexemple, capture dun
<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 sappuyer sur des opérateurs / contrôleurs
existants pour ce quils font déjà bien.</li>
<li>Ex. <strong>Secrets / ConfigMaps</strong> vers dautres 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 derreur.</li>
<li>Votre code se concentre sur <strong>le métier</strong> ; la diffusion transverse reste le problème de
loutil 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 quun 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 cest 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 dopé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
nas <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 quun 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 dappels 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 nest pas de la plateforme : cest 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 devents utiles — <strong>bonne chance</strong>
pour le post-mortem à 3 h du matin.</p>
</aside>
</section>
<section>
<h1>Un opérateur, cest 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 nest <strong>pas</strong> un script du vendredi.</p>
<p>Cest un <strong>produit logiciel</strong> avec un <strong>cycle de vie</strong> — et une dette si tu
labandonnes.</p>
<p>Ensuite : la synthèse <strong>« ça part en vrille »</strong> — smells quon a déjà nommés, mais côté vécu
déquipe.</p>
</aside>
</section>
<section>
<h1>Quand lopérateur devient lenfer</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, cest un microservice dont plus personne ne tient les règles du jeu.</p>
</blockquote>
<aside class="notes">
<p>Un mauvais opérateur, cest de la complexité distribuée et le genre de session debug qui nous tient
éveillés à
3 h du matin — jen ai croisé de très pénibles, et je ne suis pas toujours innocent de lempilement.<br />
Le piège classique : « formation Kubernetes par la trappe » parce quon 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>lenfer de linfrastructure</strong> bas niveau.</li>
<li>Les bons opérateurs vous évitent <strong>lenfer du manuel</strong> — les mauvais vous y renvoient
autrement.</li>
</ul>
<aside class="notes">
<p>Kubernetes nest pas quun outil dinfra : cest une surface où lon encode du
<strong>comportement</strong>.<br />
Les opérateurs sont lextension naturelle… à condition de rester honnêtes sur la complexité.
</p>
<p>Fil de la conf (rappel) :</p>
<ol>
<li>Kubernetes nest 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 dopérateur (ou pas tout de suite).</p>
<aside class="notes">
<p>Cest probablement <strong>la</strong> slide la plus importante pour une salle de devs.</p>
<p>Kubernetes donne <strong>envie</strong> den 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 quun diagramme.</p>
</aside>
</section>
<section>
<h1>Votre logique, exécutée par le cluster au lieu dun 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, cest votre logique métier… mais exécutée par le cluster au lieu dun 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 linfra — 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 daccueil 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 lURL 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>