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.
This commit is contained in:
Le Prévost-Corvellec Arnault
2026-04-09 02:52:44 +02:00
parent 3a52131d1e
commit 10f7798afb
4 changed files with 406 additions and 54 deletions

View File

@@ -37,27 +37,37 @@
<section>
<h1>Kubernetes : délire dOps ?</h1>
<blockquote>
<p>On vous la vendu comme un truc dinfra</p>
<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>Complexe</li>
<li>Réservé aux DevOps</li>
<li>Magie noire</li>
<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 vous a vendu Kubernetes comme un délire dinfra, réservé à des gens chelous.<br />
En réalité… cest beaucoup plus proche de ce que vous faites déjà.</p>
<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</li>
<li>Boucle de réconciliation</li>
<li>Pas de mystère : des mécanismes explicites</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 />
@@ -66,13 +76,25 @@
</section>
<section>
<h1>De vos outils au noyau Linux</h1>
<pre><code class="language-mermaid">flowchart LR
A[Clients] --&gt; B[kube-api]
B --&gt; C[etcd]
B --&gt; D[Controllers]
D --&gt; E[kubelet]
E --&gt; F[containerd]
F --&gt; G[Kernel Linux]
<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>
@@ -91,34 +113,39 @@
</code></pre>
</aside>
</section>
<section>
<h1>Kubernetes ne fait pas tourner vos apps</h1>
<ul>
<li>Il <strong>orchestre</strong></li>
<li>Il <strong>délègue</strong></li>
<li>Il <strong>observe</strong></li>
</ul>
<aside class="notes">
<p>Ce nest pas Kubernetes qui « exécute votre métier » comme un process magique : il coordonne, délègue aux
agents, et observe létat.<br />
La suite : le cœur, cest la boucle de réconciliation.</p>
<p>(Cette slide remplace lancien zoom « kube-api → etcd » seul : le détail etcd est déjà dans la vue
densemble slide 4.)</p>
</aside>
</section>
<section class="slide-boucle">
<div class="slide-boucle-inner">
<h1 class="slide-boucle-title">État voulu → réel : la boucle</h1>
<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
A[Désiré] --&gt; B[API] --&gt; C[Controllers] --&gt; D[Réel]
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> réinjecte vers les contrôleurs · <em>chaque tour : observer, décider, agir</em>
<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>Des agents exécutent les instructions côté nœud (kubelet, runtime), mais le fil conducteur, cest : état
voulu, comparaison, correction en boucle.</p>
<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>
@@ -142,10 +169,10 @@
<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 noyau</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, cest presque du repos.</p>
<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 />
@@ -221,13 +248,6 @@
Même mécanisme, autre niveau dabstraction.</p>
</aside>
</section>
<section>
<h1>Automatiser le métier… en code dans le cluster</h1>
<aside class="notes">
<p>Un opérateur, cest du code qui dit au cluster comment faire vivre un service au-delà du simple « Pod +
Service » : upgrades, backup, réplication, etc.</p>
</aside>
</section>
<section>
<h1>Sans opérateur : scripts, crons, humains dans la boucle</h1>
<ul>
@@ -347,6 +367,65 @@ spec:
</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>
@@ -453,6 +532,51 @@ spec:
</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>
@@ -477,11 +601,22 @@ spec:
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>).
@@ -493,6 +628,57 @@ spec:
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>
@@ -587,11 +773,14 @@ spec:
<li>Effets <strong>cachés</strong></li>
</ul>
<blockquote>
<p>Un opérateur mal conçu, cest un microservice sous LSD.</p>
<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 avec un debugger qui vous hait.<br />
Le piège classique : « formation Kubernetes par la trappe » parce quon empile des comportements opaques.
<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>
@@ -701,7 +890,8 @@ spec:
<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>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.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") {
@@ -745,7 +935,7 @@ spec:
},
flowchart: {
useMaxWidth: true,
curve: "natural",
curve: "basis",
},
});
@@ -773,6 +963,31 @@ spec:
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);
@@ -780,7 +995,12 @@ spec:
if (!pending.length) return;
requestAnimationFrame(function () {
requestAnimationFrame(function () {
mermaid.run({ nodes: pending, suppressErrors: true }).catch(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();
});
});
}