<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Zwindler's Reflection</title><link>https://blog.zwindler.fr/</link><description>Recent content on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr-fr</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Tue, 03 Nov 2026 18:00:00 +0100</lastBuildDate><atom:link href="https://blog.zwindler.fr/index.xml" rel="self" type="application/rss+xml"/><item><title>Talos : un certificat en lecture seule qui reboot votre serveur</title><link>https://blog.zwindler.fr/2026/11/03/talos-timecheck-watchdog-os-reader-reboot/</link><pubDate>Tue, 03 Nov 2026 18:00:00 +0100</pubDate><guid>https://blog.zwindler.fr/2026/11/03/talos-timecheck-watchdog-os-reader-reboot/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/10/talos.webp" alt="Featured image of post Talos : un certificat en lecture seule qui reboot votre serveur" /&gt;&lt;h2 id="tldr"&gt;TL;DR
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Sur Talos Linux &amp;lt;= 1.14.2, l&amp;rsquo;API &lt;code&gt;TimeService/TimeCheck&lt;/code&gt; vérifiait les chemins de devices PTP avec un simple &lt;code&gt;strings.HasPrefix(server, &amp;quot;/dev/&amp;quot;)&lt;/code&gt;. Un &lt;code&gt;..&lt;/code&gt; suffisait donc à faire ouvrir &lt;strong&gt;n&amp;rsquo;importe quel fichier&lt;/strong&gt; par &lt;code&gt;machined&lt;/code&gt;, en &lt;strong&gt;root&lt;/strong&gt;. On ne peut pas exfiltrer le fichier, juste le faire ouvrir.&lt;/li&gt;
&lt;li&gt;Cette API était accessible au rôle &lt;code&gt;os:reader&lt;/code&gt;, le rôle &lt;strong&gt;lecture seule&lt;/strong&gt; qu&amp;rsquo;on distribue aux outils de monitoring.&lt;/li&gt;
&lt;li&gt;Ouvrir &lt;code&gt;/dev/watchdog&lt;/code&gt; arme le timer matériel. Refermer le fichier sans l&amp;rsquo;octet magique &lt;code&gt;V&lt;/code&gt; le laisse armé. Si personne ne le nourrit : &lt;strong&gt;hard reset&lt;/strong&gt; du serveur. Un compte read-only peut donc rebooter un serveur physique.&lt;/li&gt;
&lt;li&gt;Publié le 19 octobre 2026 sous &lt;a class="link" href="https://github.com/siderolabs/talos/security/advisories/GHSA-vpfm-35qx-jhvw" target="_blank" rel="noopener"
&gt;GHSA-vpfm-35qx-jhvw&lt;/a&gt; (Moderate, 5.9), corrigé dans &lt;strong&gt;1.13.11&lt;/strong&gt; et &lt;strong&gt;1.14.3&lt;/strong&gt;. Et c&amp;rsquo;est mon premier advisory où je suis le seul finder crédité.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Dans &lt;a class="link" href="https://blog.zwindler.fr/2026/09/23/finding-vulnerabilities-oss-not-security-specialist/" &gt;mon article précédent&lt;/a&gt;, je racontais comment je traque des vulnérabilités dans l&amp;rsquo;open source avec des LLM depuis cet été. Depuis, certaines de ces trouvailles sont devenues publiques : deux CVE Argo CD co-créditées avec 7 autres reporters chacune, une CVE KEDA en co-crédit. Il y en a d&amp;rsquo;autres en attente, mais la saveur n&amp;rsquo;est pas la même quand on n&amp;rsquo;est pas le &amp;ldquo;premier&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Celle-ci est ma première GHSA &lt;strong&gt;en solo&lt;/strong&gt; : un seul nom dans les credits, le mien (même si c&amp;rsquo;est Devin/SWE qui a en réalité fait la découverte initiale, cf. l&amp;rsquo;article précédent). Au-delà de la fierté personnelle, elle est aussi super marrante à raconter. Martial me fera les gros yeux car c&amp;rsquo;est &amp;ldquo;encore&amp;rdquo; un DoS (il m&amp;rsquo;a surnommé DoSnis), mais moi j&amp;rsquo;aime bien les DoS.&lt;/p&gt;
&lt;p&gt;Elle est sortie d&amp;rsquo;un audit complet de Talos (la distribution Linux immuable dédiée à Kubernetes), pendant une passe où je cherchais une chose précise : quelles méthodes de l&amp;rsquo;API sont accessibles aux rôles les plus faibles, et qu&amp;rsquo;est-ce qu&amp;rsquo;elles font &lt;em&gt;vraiment&lt;/em&gt; ?&lt;/p&gt;
&lt;h2 id="talos-et-ses-rôles-en-deux-minutes"&gt;Talos et ses rôles en deux minutes
&lt;/h2&gt;&lt;p&gt;Talos n&amp;rsquo;a pas de SSH, pas de shell, pas de package manager. Toute l&amp;rsquo;administration passe par une API gRPC (&lt;code&gt;apid&lt;/code&gt;, port 50000), authentifiée par certificats clients. Le rôle est embarqué dans le certificat (champ &lt;code&gt;O:&lt;/code&gt;). Du plus puissant au plus faible :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;os:admin&lt;/code&gt; : tout, y compris reboot, wipe, machine config&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os:operator&lt;/code&gt; : gestion courante (upgrade, reboot&amp;hellip;)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;os:reader&lt;/code&gt; : &lt;strong&gt;lecture seule&lt;/strong&gt;. État du noeud, logs, métriques, configuration. C&amp;rsquo;est le rôle qu&amp;rsquo;on file aux dashboards, aux sondes, aux outils d&amp;rsquo;observabilité, en se disant &amp;ldquo;au pire, ils regardent&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sauf que &lt;code&gt;TimeService/TimeCheck&lt;/code&gt;, l&amp;rsquo;API qui demande au noeud de vérifier sa synchro temporelle contre une source donnée, était ouverte aux trois rôles, reader compris (&lt;code&gt;internal/app/machined/pkg/system/services/machined.go:160&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Sur le papier, ça se tient : interroger une source de temps, c&amp;rsquo;est de la lecture. Le hic, c&amp;rsquo;est que cette &amp;ldquo;source de temps&amp;rdquo; peut être soit un serveur NTP, soit &lt;strong&gt;un chemin de device PTP&lt;/strong&gt; (Precision Time Protocol, les horloges matérielles &lt;code&gt;/dev/ptp*&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="le-bug--un-hasprefix-qui-ne-protège-rien"&gt;Le bug : un &lt;code&gt;HasPrefix&lt;/code&gt; qui ne protège rien
&lt;/h2&gt;&lt;p&gt;Voici l&amp;rsquo;intégralité du contrôle :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// internal/pkg/ntp/ntp.go&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;IsPTPDevice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;strings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;server&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;/dev/&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Oui, c&amp;rsquo;est &lt;strong&gt;tout&lt;/strong&gt;. Pas de &lt;code&gt;filepath.Clean&lt;/code&gt;, pas de rejet de &lt;code&gt;..&lt;/code&gt;, pas de restriction à &lt;code&gt;/dev/ptp*&lt;/code&gt;. Le handler (&lt;code&gt;v1alpha1_time.go&lt;/code&gt;) passe ensuite la chaîne telle quelle à &lt;code&gt;QueryPTPDevice&lt;/code&gt;, qui fait :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;device&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// machined tourne en root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Donc &lt;code&gt;/dev/../dev/watchdog&lt;/code&gt;, &lt;code&gt;/dev/../etc/shadow&lt;/code&gt; ou &lt;code&gt;/dev/../dev/sda&lt;/code&gt; passent tous le filtre et sont ouverts en root par &lt;code&gt;machined&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Première question qui vient : &amp;ldquo;OK, et il lit quoi ?&amp;rdquo;. Bah&amp;hellip; &lt;strong&gt;rien&lt;/strong&gt;. Le descripteur part dans des ioctls PTP, qui échouent sur tout ce qui n&amp;rsquo;est pas une horloge PTP, et le contenu n&amp;rsquo;est jamais renvoyé au client. On n&amp;rsquo;a &lt;strong&gt;pas&lt;/strong&gt; de lecture de fichiers arbitraires.&lt;/p&gt;
&lt;p&gt;Le problème est ailleurs : &lt;code&gt;open()&lt;/code&gt; n&amp;rsquo;est pas une opération anodine.&lt;/p&gt;
&lt;h2 id="open-nest-pas-une-lecture--le-cas-du-watchdog"&gt;&lt;code&gt;open()&lt;/code&gt; n&amp;rsquo;est pas une lecture : le cas du watchdog
&lt;/h2&gt;&lt;p&gt;Un watchdog, c&amp;rsquo;est un minuteur matériel présent sur la plupart des serveurs : il redémarre la machine si l&amp;rsquo;OS ne donne plus signe de vie, typiquement en cas de freeze du kernel.&lt;/p&gt;
&lt;p&gt;Sous Linux, ouvrir &lt;code&gt;/dev/watchdog&lt;/code&gt; &lt;strong&gt;arme le watchdog matériel&lt;/strong&gt;. C&amp;rsquo;est le contrat du driver : l&amp;rsquo;open lance le compte à rebours, et si personne ne vient écrire dedans régulièrement (&amp;ldquo;nourrir&amp;rdquo; le chien de garde), le matériel reset la machine. Pas de log applicatif, pas de shutdown propre, et l&amp;rsquo;OS n&amp;rsquo;a pas son mot à dire : c&amp;rsquo;est le hardware qui coupe.&lt;/p&gt;
&lt;p&gt;Il existe une porte de sortie : écrire le caractère magique &lt;code&gt;V&lt;/code&gt; dans le fichier avant de le fermer (le &amp;ldquo;magic close&amp;rdquo;), ce qui désarme le timer. Ici, Talos fait &lt;code&gt;open()&lt;/code&gt;, les ioctls PTP échouent, puis &lt;code&gt;close()&lt;/code&gt;&amp;hellip; sans &lt;code&gt;V&lt;/code&gt;. Le timer continue de tourner, personne ne le nourrit. Reboot.&lt;/p&gt;
&lt;p&gt;Note : ça ne marche que si Talos n&amp;rsquo;a &lt;strong&gt;pas&lt;/strong&gt; déjà ouvert le watchdog lui-même (pas de &lt;code&gt;WatchdogTimerConfig&lt;/code&gt; dans la machine config, donc la grande majorité des cas). Autrement dit, ce sont précisément les noeuds qui n&amp;rsquo;utilisent pas le watchdog qui sont vulnérables : l&amp;rsquo;attaque l&amp;rsquo;arme, et personne n&amp;rsquo;est là pour le nourrir (pauvre chien-chien 🐶).&lt;/p&gt;
&lt;h2 id="repro-sur-mon-lab"&gt;Repro sur mon lab
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai tout vérifié de bout en bout le 29 septembre 2026, sur mes noeuds Talos de lab, avec un &lt;code&gt;talosconfig&lt;/code&gt; dont le certificat client est strictement &lt;code&gt;O: os:reader&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;abord, on vérifie que ce rôle ne peut pas rebooter par la voie normale :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n &amp;lt;node-vm&amp;gt; reboot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rpc error: code = PermissionDenied desc = not authorized&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ensuite, un test inoffensif pour prouver que la traversée passe :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n &amp;lt;node-vm&amp;gt; &lt;span class="nb"&gt;time&lt;/span&gt; --check &lt;span class="s2"&gt;&amp;#34;/dev/../proc/version&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rpc error ... error querying PTP device &amp;#34;/dev/../proc/version&amp;#34;: invalid argument&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# open() a reussi (le fichier existe), c&amp;#39;est l&amp;#39;ioctl PTP qui echoue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n &amp;lt;node-vm&amp;gt; &lt;span class="nb"&gt;time&lt;/span&gt; --check &lt;span class="s2"&gt;&amp;#34;/dev/../nonexistent&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rpc error ... &amp;#34;/dev/../nonexistent&amp;#34;: no such file or directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# open() a echoue : le fichier n&amp;#39;existe pas&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Deux messages d&amp;rsquo;erreur différents, ça fait un oracle : on peut savoir si n&amp;rsquo;importe quel chemin existe sur le noeud, y compris hors de &lt;code&gt;/dev&lt;/code&gt;. Et le &lt;code&gt;..&lt;/code&gt; qui apparaît tel quel dans l&amp;rsquo;erreur confirme au passage qu&amp;rsquo;il n&amp;rsquo;y a pas de &lt;code&gt;filepath.Clean&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Maintenant, sur un serveur &lt;strong&gt;bare-metal&lt;/strong&gt; avec un vrai watchdog matériel :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n &amp;lt;node-bm&amp;gt; &lt;span class="nb"&gt;time&lt;/span&gt; --check &lt;span class="s2"&gt;&amp;#34;/dev/../dev/watchdog&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# rpc error ... invalid argument (open() en root OK, ioctl PTP en echec)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et dans la console kernel, au même moment :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;kern: crit: watchdog: watchdog0: watchdog did not stop!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;C&amp;rsquo;est exactement le message qu&amp;rsquo;on obtient quand le fd est refermé sans le &lt;code&gt;V&lt;/code&gt; : le watchdog reste armé, et le noeud fait un hard reset au timeout. Un certificat censé ne faire que de la lecture vient de rebooter physiquement un serveur.&lt;/p&gt;
&lt;h2 id="les-autres-impacts"&gt;Les autres impacts
&lt;/h2&gt;&lt;p&gt;Pour être honnête, ça reste limité : ni lecture de contenu, ni écriture. Ce qu&amp;rsquo;on obtient vraiment :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reboot forcé&lt;/strong&gt; via le watchdog, qu&amp;rsquo;on peut relancer à chaque redémarrage du noeud.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Oracle d&amp;rsquo;existence&lt;/strong&gt; de fichiers sur le noeud (les deux messages d&amp;rsquo;erreur). Mais en vrai, il n&amp;rsquo;y a quasiment rien sur un noeud Talos Linux (c&amp;rsquo;est toute la beauté de la chose).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Goroutines bloquées&lt;/strong&gt; : certains devices et FIFO bloquent dans &lt;code&gt;open(2)&lt;/code&gt;, chaque appel peut donc figer un handler de &lt;code&gt;machined&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Des effets de bord plus exotiques d&amp;rsquo;&lt;code&gt;open()&lt;/code&gt; sur certains drivers (rembobinage de bande, état de terminaux&amp;hellip;), mais là on est complètement dans la théorie.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="le-maintainer-ma-mieux-noté-que-moi"&gt;Le maintainer m&amp;rsquo;a mieux noté que moi
&lt;/h2&gt;&lt;p&gt;Petite anecdote sur la subjectivité du CVSS.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;avais estimé la faille à &lt;strong&gt;5.4&lt;/strong&gt; (&lt;code&gt;AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:L&lt;/code&gt;) : &lt;code&gt;A:L&lt;/code&gt; pour le reboot, et &lt;code&gt;AC:L&lt;/code&gt; parce qu&amp;rsquo;une fois le certificat &lt;code&gt;os:reader&lt;/code&gt; en poche, l&amp;rsquo;attaque est déterministe.&lt;/p&gt;
&lt;p&gt;Le maintainer (smira, de Sidero Labs) a publié &lt;strong&gt;5.9&lt;/strong&gt; (&lt;code&gt;AV:N/AC:H/PR:L/UI:N/S:U/C:L/I:N/A:H&lt;/code&gt;) : &lt;code&gt;A:H&lt;/code&gt; parce qu&amp;rsquo;un hard reset de serveur physique, c&amp;rsquo;est plus qu&amp;rsquo;une simple dégradation, et &lt;code&gt;AC:H&lt;/code&gt; parce qu&amp;rsquo;il faut un watchdog matériel présent et non géré par Talos.&lt;/p&gt;
&lt;p&gt;Et franchement, les deux choix se défendent. Le CVSS 3.1 laisse beaucoup de place à l&amp;rsquo;interprétation, surtout sur &lt;code&gt;AC&lt;/code&gt; (les conditions que l&amp;rsquo;attaquant ne contrôle pas) et sur la manière de compter l&amp;rsquo;impact quand l&amp;rsquo;attaquant a déjà une partie des droits. Un score CVSS, c&amp;rsquo;est une opinion argumentée, pas une mesure. Et un maintainer qui prend le temps de justifier son vecteur dans l&amp;rsquo;advisory, je ne vais pas m&amp;rsquo;en plaindre.&lt;/p&gt;
&lt;h2 id="le-fix--trois-couches-defense-in-depth"&gt;Le fix : trois couches, defense in depth
&lt;/h2&gt;&lt;p&gt;Le correctif (mergé dans main, backporté en &lt;strong&gt;1.13.11&lt;/strong&gt; et &lt;strong&gt;1.14.3&lt;/strong&gt;) reprend les trois pistes que j&amp;rsquo;avais suggérées dans le rapport, en mieux :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Vérification du device&lt;/strong&gt; : avant tout &lt;code&gt;open()&lt;/code&gt;, le chemin doit pointer vers une vraie horloge PTP &lt;strong&gt;et&lt;/strong&gt; le kernel doit confirmer que le device appartient bien au sous-système PTP. Ouvrir autre chose n&amp;rsquo;a donc plus aucun effet de bord. Les messages d&amp;rsquo;erreur sont aussi devenus génériques, ce qui supprime l&amp;rsquo;oracle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation de la machine config&lt;/strong&gt; : les chemins PTP doivent être des chemins propres, directement sous &lt;code&gt;/dev&lt;/code&gt;, avec un nom qui commence par &lt;code&gt;ptp&lt;/code&gt; (&lt;code&gt;/dev/ptp0&lt;/code&gt;, &lt;code&gt;/dev/ptp_kvm&lt;/code&gt;, &lt;code&gt;/dev/ptp_hyperv&lt;/code&gt;). Si vous avez des configs exotiques, attention : elles risquent d&amp;rsquo;être refusées après l&amp;rsquo;upgrade.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RBAC&lt;/strong&gt; : &lt;code&gt;TimeService/TimeCheck&lt;/code&gt; est désormais réservé à &lt;code&gt;os:admin&lt;/code&gt; et &lt;code&gt;os:operator&lt;/code&gt;. &lt;code&gt;TimeService/Time&lt;/code&gt; (la lecture de l&amp;rsquo;heure configurée, sans &lt;code&gt;--check&lt;/code&gt;) reste accessible à &lt;code&gt;os:reader&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Versions affectées : à partir de &lt;code&gt;1.10.0&lt;/code&gt; (première release où &lt;code&gt;TimeCheck&lt;/code&gt; accepte des chemins PTP), jusqu&amp;rsquo;à &lt;code&gt;1.13.11&lt;/code&gt; / &lt;code&gt;1.14.3&lt;/code&gt; exclus.&lt;/p&gt;
&lt;p&gt;Si vous ne pouvez pas upgrader tout de suite :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ne distribuez pas de certificats &lt;code&gt;os:reader&lt;/code&gt; à des clients en qui vous n&amp;rsquo;avez pas totalement confiance ;&lt;/li&gt;
&lt;li&gt;filtrez l&amp;rsquo;accès réseau au port &lt;code&gt;apid&lt;/code&gt; (50000) ;&lt;/li&gt;
&lt;li&gt;sur les noeuds avec un watchdog matériel, déclarez un &lt;code&gt;WatchdogTimerConfig&lt;/code&gt; pour que Talos garde lui-même le device ouvert.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="takeaways"&gt;&amp;ldquo;Takeaways&amp;rdquo;
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;open()&lt;/code&gt; n&amp;rsquo;est pas une lecture.&lt;/strong&gt; Un appel qui &amp;ldquo;ne retourne rien&amp;rdquo; peut quand même armer du hardware, bloquer une goroutine ou servir d&amp;rsquo;oracle. Quand on audite une API &amp;ldquo;read-only&amp;rdquo;, la question n&amp;rsquo;est pas &amp;ldquo;qu&amp;rsquo;est-ce qu&amp;rsquo;elle renvoie ?&amp;rdquo; mais &amp;ldquo;qu&amp;rsquo;est-ce qu&amp;rsquo;elle fait ?&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un &lt;code&gt;HasPrefix&lt;/code&gt; n&amp;rsquo;est pas une validation de chemin.&lt;/strong&gt; Sans &lt;code&gt;filepath.Clean&lt;/code&gt;, sans rejet de &lt;code&gt;..&lt;/code&gt; et sans allowlist, c&amp;rsquo;est purement décoratif. Si vous voulez chercher le même pattern ailleurs, grep &lt;code&gt;HasPrefix(x, &amp;quot;/dev/&amp;quot;)&lt;/code&gt;, &lt;code&gt;HasPrefix(x, &amp;quot;/proc/&amp;quot;)&lt;/code&gt; ou &lt;code&gt;HasPrefix(x, &amp;quot;/sys/&amp;quot;)&lt;/code&gt;. J&amp;rsquo;ai retrouvé ça dans de nombreux autres projets OSS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;Est-ce que ça donne à l&amp;rsquo;attaquant quelque chose qu&amp;rsquo;il n&amp;rsquo;a pas déjà ?&amp;rdquo;&lt;/strong&gt; C&amp;rsquo;est la question que je pose systématiquement pendant la contre-expertise (cf. l&amp;rsquo;article précédent), et ici la réponse ne fait pas débat : le rôle &lt;code&gt;reader&lt;/code&gt; ne peut pas rebooter (&lt;code&gt;PermissionDenied&lt;/code&gt;), mais il peut armer le watchdog qui, lui, reboot. Difficile pour un maintainer de répondre &amp;ldquo;working as intended&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Les rôles &amp;ldquo;lecture seule&amp;rdquo; valent le détour.&lt;/strong&gt; Tout le monde audite les chemins admin. Les API d&amp;rsquo;observabilité sont moins scrutées, distribuées beaucoup plus largement, et personne ne regarde leurs effets de bord. Dans le même audit, ce pattern &amp;ldquo;reader + effet de bord&amp;rdquo; m&amp;rsquo;a d&amp;rsquo;ailleurs donné d&amp;rsquo;autres pistes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sidero Labs a été exemplaire.&lt;/strong&gt; Rapport accepté en moins d&amp;rsquo;une heure, fix en trois couches mergé moins de 3h après mon rapport (avec des durcissements que je n&amp;rsquo;avais pas suggérés), CVE demandée, et un advisory transparent publié moins de trois semaines plus tard. Si tous les projets traitaient les rapports comme ça, la vie de hunter serait beaucoup plus simple.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tout est parti d&amp;rsquo;un subagent qui faisait exactement ce qu&amp;rsquo;on lui avait demandé : lire le code fichier par fichier et noter tout ce qui paraissait &amp;ldquo;même vaguement bizarre&amp;rdquo;. La suite (repro, rédaction, soumission) suit le pipeline décrit dans &lt;a class="link" href="https://blog.zwindler.fr/2026/09/23/finding-vulnerabilities-oss-not-security-specialist/" &gt;l&amp;rsquo;article précédent&lt;/a&gt;. Ça fait plaisir de pouvoir enfin raconter un cas concret de bout en bout !&lt;/p&gt;</description></item><item><title>Cloud Nord 2026 - Récap de ma journée à Lille</title><link>https://blog.zwindler.fr/2026/10/31/recap-cloud-nord-2026/</link><pubDate>Sat, 31 Oct 2026 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/10/31/recap-cloud-nord-2026/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/10/cloudnord2026.webp" alt="Featured image of post Cloud Nord 2026 - Récap de ma journée à Lille" /&gt;&lt;h2 id="de-retour-de-cloud-nord-2026"&gt;De retour de Cloud Nord 2026
&lt;/h2&gt;&lt;p&gt;Comme je &lt;a class="link" href="https://blog.zwindler.fr/2026/09/21/retrouvez-moi-cloud-nord-2026-devfest-toulouse/" &gt;vous l&amp;rsquo;avais annoncé en septembre&lt;/a&gt;, j&amp;rsquo;étais cette année à &lt;strong&gt;Cloud Nord&lt;/strong&gt;, à Lille, pour y donner un talk &amp;ldquo;inédit&amp;rdquo; : &lt;em&gt;&amp;ldquo;J&amp;rsquo;ai mis Kubernetes dans 512 Mo de RAM. Et il restait de la place pour DOOM.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Enchaînement un peu sportif pour moi, puisque la veille au matin j&amp;rsquo;étais encore à &lt;strong&gt;BDX I/O pour dédicacer mon livre&lt;/strong&gt; (cf &lt;a class="link" href="https://blog.zwindler.fr/2026/10/27/bdxio-2026-dedicace-livre/" &gt;mon post précédent&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/10/dedicace.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Direction ensuite Lille dans le train, puisque Cloud Nord, c&amp;rsquo;était le vendredi 30 octobre.&lt;/p&gt;
&lt;p&gt;Pour ceux qui ne connaissent pas : Cloud Nord, c&amp;rsquo;est une conférence communautaire nordiste organisée par des bénévoles, historiquement très orientée cloud et devops (forcément), avec un programme dense, et 2-3 tracks en parallèle.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/10/cloudnord2026.avif"
loading="lazy"
alt="Présentation de l’événement : 270 participants, 22 conférences"
&gt;&lt;/p&gt;
&lt;p&gt;Et comme d&amp;rsquo;habitude, voici mon récap des talks que j&amp;rsquo;ai vus.&lt;/p&gt;
&lt;h2 id="keynote-douverture--ia-souveraineté-et-cloud"&gt;Keynote d&amp;rsquo;ouverture : IA, souveraineté et cloud
&lt;/h2&gt;&lt;p&gt;La journée a commencé par une keynote de &lt;strong&gt;Patrick Perrot&lt;/strong&gt;, général à l&amp;rsquo;Inspection Générale de la Gendarmerie nationale. Un profil un peu inattendu pour ouvrir une conf cloud.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/10/general.avif"
loading="lazy"
alt="Keynote “IA, Souveraineté et Cloud” du Général Patrick Perrot"
&gt;&lt;/p&gt;
&lt;p&gt;Avoir l&amp;rsquo;opinion d&amp;rsquo;un fonctionnaire sur la vision de ce qu&amp;rsquo;est la &amp;ldquo;souveraineté&amp;rdquo; (sous des prismes particuliers, ou plus généralement) était assez intéressant. Partir du mythe de la caverne de Platon pour se poser la question des dépendances qu&amp;rsquo;on a aux géants du web et aux états, en particulier dans l&amp;rsquo;IA, même open weight, était aussi un exercice intéressant.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Vous croyez que c&amp;rsquo;est vous qui décidez par où vous passez quand vous planifiez un trajet avec votre GPS ? Pourtant, c&amp;rsquo;est déjà vieux, un GPS&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Les machines prennent déjà des décisions pour nous depuis longtemps. Et sur le fameux &amp;ldquo;humain dans la boucle&amp;rdquo;, sa réponse, assez cinglante : &lt;em&gt;&amp;ldquo;je fais confiance à certains humains pour faire trop confiance, par facilité, à l&amp;rsquo;IA.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Son opinion :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ne pas devenir un &lt;strong&gt;vassal technologique&lt;/strong&gt;, il faut accélérer sur l&amp;rsquo;IA.&lt;/li&gt;
&lt;li&gt;Quand on choisit un fournisseur : que se passe-t-il s&amp;rsquo;il nous lâche ? (extraterritorialité des données, réversibilité&amp;hellip;)&lt;/li&gt;
&lt;li&gt;Il faut &lt;strong&gt;désiloter&lt;/strong&gt; : les réponses ne sont ni purement juridiques, ni purement techniques, ni purement sécurité. Il faut un panel de compétences.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je ne suis pas certain de partager à 100% son opinion, mais la réflexion était très intéressante et bien menée. Bonne ouverture.&lt;/p&gt;
&lt;h2 id="ia-for-ops-on-scaleway"&gt;IA for Ops on Scaleway
&lt;/h2&gt;&lt;p&gt;À 9h50, &lt;strong&gt;Arnaud Gaches&lt;/strong&gt; (Onepoint) est venu raconter comment il avait utilisé l&amp;rsquo;IA pour faire de l&amp;rsquo;ops sur Scaleway.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/10/ai_ops.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Note perso : je ne le dirai jamais assez, JAMAIS de dark theme sur une prez en conf 😆.&lt;/p&gt;
&lt;p&gt;Arnaud revenait sur un projet entamé en début d&amp;rsquo;année, avec une V1 déjà présentée dans une autre conférence.&lt;/p&gt;
&lt;p&gt;Son postulat de départ &amp;ldquo;certains devs se vantent de ne plus faire de code, pourquoi on ne fait pas pareil, nous les ops ?&amp;rdquo;. Le cœur du talk : une infra montée en plusieurs versions, entièrement via des agents &amp;ldquo;mains en l&amp;rsquo;air&amp;rdquo;, humain dans la boucle juste pour valider des actions sensibles.&lt;/p&gt;
&lt;p&gt;Cette V2 était une version plus aboutie, avec un focus plus orienté &amp;ldquo;sécurité d&amp;rsquo;abord&amp;rdquo;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;un agent &amp;ldquo;orga&amp;rdquo; qui tourne sur le poste et qui détient les accès (orga, secrets, bucket d&amp;rsquo;état, IAM)&lt;/li&gt;
&lt;li&gt;un agent dédié à la landing zone pour le réseau + le VPC peering&lt;/li&gt;
&lt;li&gt;n8n, qui commence par &lt;em&gt;demander&lt;/em&gt; à orga des droits supplémentaires, validé par l&amp;rsquo;humain (exemple)&lt;/li&gt;
&lt;li&gt;du finops ajouté au passage&lt;/li&gt;
&lt;li&gt;&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je n&amp;rsquo;ai pas été surpris outre mesure par les retours d&amp;rsquo;Arnaud. Oui, Claude Code est capable de monter une infra relativement complexe et même de l&amp;rsquo;opérer dans une certaine mesure, en particulier si on fait appel à des produits managés. Claude est aussi assez bon pour croiser de multiples sources d&amp;rsquo;information pour trouver la root cause d&amp;rsquo;un incident.&lt;/p&gt;
&lt;p&gt;En revanche, Arnaud le dit lui-même :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;l&amp;rsquo;agent a &lt;strong&gt;trop confiance dans la doc&lt;/strong&gt;&amp;hellip; qui peut elle-même avoir été hallucinée par une version précédente&lt;/li&gt;
&lt;li&gt;pas de prise de recul : il essaie de contourner les problèmes trop vite, avec un peu trop de zèle&lt;/li&gt;
&lt;li&gt;installation d&amp;rsquo;outils tiers sans qu&amp;rsquo;on lui demande&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ma take perso : je ne suis pas convaincu, on est loin des gains visibles pour la partie &amp;ldquo;dev&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Construire une usine agentique pour faire de l&amp;rsquo;infra, même en managé, c&amp;rsquo;est encore assez casse-gueule (finops qui dérape très facilement, risques de sécurité assez violents), ça consomme un max de tokens (il a dû passer de Claude Pro à Claude Max car les agents parlent trop entre eux). Mais indubitablement, c&amp;rsquo;est possible.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;L&amp;rsquo;IA, c&amp;rsquo;est un collègue brillant qui revient de vacances&amp;hellip; tous les jours.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="coupable--le-procès-dun-container-malveillant-dans-kubernetes"&gt;Coupable ! Le procès d&amp;rsquo;un container malveillant dans Kubernetes
&lt;/h2&gt;&lt;p&gt;À 11h55, quickie de &lt;strong&gt;Paul-Alexandre Chrétien&lt;/strong&gt; (HoppR), sur le format d&amp;rsquo;un procès.&lt;/p&gt;
&lt;p&gt;Le prévenu : une app volontairement vulnérable, qui cumulait les chefs d&amp;rsquo;inculpation : libs obsolètes, exécution en root, RCE, vol de credentials, dump de la prod. Bref, la VULNAPP dans toute sa splendeur.&lt;/p&gt;
&lt;p&gt;Pour la &amp;ldquo;défense des victimes&amp;rdquo; (et l&amp;rsquo;accusation), une stack d&amp;rsquo;outils que j&amp;rsquo;ai trouvée bien ficelée :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;trivy-operator&lt;/strong&gt; et ses CRD &lt;code&gt;vulnReports&lt;/code&gt; pour le scan d&amp;rsquo;images&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Kyverno&lt;/strong&gt; et ses &lt;code&gt;validatingPolicies&lt;/code&gt; pour bloquer à l&amp;rsquo;admission&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Falco&lt;/strong&gt; pour la détection à runtime&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Policy Reporter&lt;/strong&gt; pour centraliser les outputs de trivy + Kyverno + Falco dans un même rapport&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/10/proces-container-malveillant.avif"
loading="lazy"
alt="Les slides du talk, sur le thème “New York, unité spéciale”"
&gt;&lt;/p&gt;
&lt;p&gt;Un bon quickie dans lequel il a réussi à mêler slides et démo. Beau boulot pour un premier talk :).&lt;/p&gt;
&lt;h2 id="houston-on-a-un-problème-avec-etcd"&gt;Houston, on a un problème avec ETCD
&lt;/h2&gt;&lt;p&gt;Je ne vais pas dire que je suis venu à Cloud Nord juste pour voir ce talk, mais on va dire que j&amp;rsquo;étais VRAIMENT motivé. &lt;strong&gt;Alexandre Burgoni&lt;/strong&gt; et &lt;strong&gt;Pierre Zemb&lt;/strong&gt;, tous deux chez Clever Cloud, sont venus parler d&amp;rsquo;etcd, du consensus distribué, et de pourquoi les gros acteurs finissent par réécrire leur propre implémentation.&lt;/p&gt;
&lt;p&gt;Vous le savez, j&amp;rsquo;ai pas mal testé le Kubernetes managé de Clever Cloud (voir &lt;a class="link" href="https://blog.zwindler.fr/2026/05/12/test-cke-kubernetes-manage-clever-cloud-partie-1/" &gt;partie 1&lt;/a&gt; et &lt;a class="link" href="https://blog.zwindler.fr/2026/05/24/test-cke-kubernetes-manage-clever-cloud-partie-2/" &gt;partie 2&lt;/a&gt;), qui a justement la particularité d&amp;rsquo;utiliser une réécriture de la partie etcd (sur Foundation DB). C&amp;rsquo;était donc un sujet que j&amp;rsquo;avais particulièrement envie de creuser.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/10/etcd-clever.avif"
loading="lazy"
alt="Alexandre Burgoni et Pierre Zemb présentent “Houston, we have a problem with etcd”"
&gt;&lt;/p&gt;
&lt;p&gt;Vraiment un talk excellent, sur les différents protocoles de consensus dans les infra distribuées, les problématiques que l&amp;rsquo;implémentation d&amp;rsquo;etcd a sur les performances de certaines opérations, quelques bugs &amp;ldquo;rigolos&amp;rdquo;, récents ou non.&lt;/p&gt;
&lt;p&gt;Tout ça pour arriver au constat que non, à l&amp;rsquo;échelle, on ne peut pas faire proprement du kube (managé) avec etcd. Et c&amp;rsquo;est la raison pour laquelle les grands cloud providers ont tous fini par réécrire leur propre implémentation d&amp;rsquo;etcd pour passer à l&amp;rsquo;échelle.&lt;/p&gt;
&lt;p&gt;Pour développer ça sereinement, ils ont fait énormément de &lt;strong&gt;simulation déterministe&lt;/strong&gt;, assisté à grands coups d&amp;rsquo;IA en cas de divergences entre leur implem et etcd vanilla, une approche très inspirée de la façon dont FoundationDB lui-même a été testé.&lt;/p&gt;
&lt;p&gt;Petite question que je n&amp;rsquo;ai pas pu m&amp;rsquo;empêcher de poser à la fin :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Est-ce que vous avez trouvé des bugs d&amp;rsquo;etcd avec la simulation, ou est-ce que vos LLMs ont reproduit les bugs actuels pour être 100% compliant ?&amp;rdquo; 😄&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Malheureusement non (pour l&amp;rsquo;instant).&lt;/p&gt;
&lt;p&gt;Merci Pierre pour les discussions pendant le dîner des speakers (j&amp;rsquo;avais été un peu spoil en amont).&lt;/p&gt;
&lt;h2 id="platform-engineering-à-léchelle-de-decathlon-ce-quon-a-appris-en-5-ans"&gt;Platform Engineering à l&amp;rsquo;échelle de Decathlon, ce qu&amp;rsquo;on a appris en 5 ans
&lt;/h2&gt;&lt;p&gt;À 14h, &lt;strong&gt;Christophe Furmaniak&lt;/strong&gt; (Decathlon) pour un REX sur 5 ans de platform engineering. La promesse de départ : des environnements en 20 minutes ; le défi réel : l&amp;rsquo;expérience développeur.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/10/platform-eng-decathlon.avif"
loading="lazy"
alt="Le talk “Platform Engineering à l’échelle de Decathlon”, avec le “5 ans” du titre corrigé en “6 ans” sur le slide"
&gt;&lt;/p&gt;
&lt;p&gt;Je ne sais pas trop quoi penser de ce talk.&lt;/p&gt;
&lt;p&gt;Objectivement le travail de l&amp;rsquo;équipe platform engineering chez Decathlon est titanesque, pas de doute là-dessus.&lt;/p&gt;
&lt;p&gt;Cependant, ce que Christophe vend comme de la complexité nécessaire (une grosse stack terraform, avec plusieurs versions du moteur en // selon les équipes, des bots qui proposent des mises à jour automatiques parce que sinon il y a des retardataires, 1000 projets sur GCP au point qu&amp;rsquo;on ne sait plus qui est responsable de quoi, ou s&amp;rsquo;il faut nettoyer un projet) me paraît être de la complexité accidentelle.&lt;/p&gt;
&lt;p&gt;Cependant, je reconnais bien volontiers que je ne travaille pas dans des contextes similaires.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Talk que j&amp;rsquo;aurais voulu voir en même temps : &amp;ldquo;Construire un Data Center dans son garage&amp;rdquo; (Aeddis Desauw, OVHcloud)&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="pause-avant-ma-conf"&gt;Pause avant ma conf
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;avoue avoir fait l&amp;rsquo;impasse sur le créneau de 15h45.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;étais dans la salle pour &amp;ldquo;A la croisée de la DevEx et de GitLab&amp;rdquo; mais j&amp;rsquo;ai rien écouté : j&amp;rsquo;avais prévu 2 pi, donc un de secours pour mes démos. Et justement le pi de secours était cramé (base bbolt de containerd HS).&lt;/p&gt;
&lt;p&gt;Donc j&amp;rsquo;étais content d&amp;rsquo;avoir un peu de temps pour fix ça (à l&amp;rsquo;arrache 😅).&lt;/p&gt;
&lt;h2 id="jai-mis-kubernetes-dans-512-mo-de-ram-et-il-restait-de-la-place-pour-doom"&gt;J&amp;rsquo;ai mis Kubernetes dans 512 Mo de RAM. Et il restait de la place pour DOOM.
&lt;/h2&gt;&lt;p&gt;16h40, salle Amphi St Louis : &lt;strong&gt;mon talk&lt;/strong&gt;, celui que je ne donnerai a priori qu&amp;rsquo;une seule fois.&lt;/p&gt;
&lt;p&gt;Le pitch, pour ceux qui n&amp;rsquo;étaient pas là : faire rentrer Kubernetes sur un Raspberry Pi Zero 2 W (512 Mo de RAM, 20 $).&lt;/p&gt;
&lt;p&gt;Une grosse partie du talk repose sur toute l&amp;rsquo;optimisation de Raspberry Pi OS Lite (la distrib officielle pour le pi zero, affreusement pas optimisée), mais aussi en jouant avec des recoins moins connus de Linux (notamment &lt;a class="link" href="https://blog.zwindler.fr/2026/03/09/kubesolo-raspberry-pi-zero-sysops-pratique/" &gt;KubeSolo&lt;/a&gt;, que j&amp;rsquo;avais présenté dans Sysops Pratique).&lt;/p&gt;
&lt;p&gt;Et en conclusion, le stress test le plus sérieux qui soit : &lt;em&gt;Does it run DOOM ?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/10/kubesolo_doom.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;La réponse est oui, on a pu lancer DOOM, jouer en VNC, et même scaler DOOM à 9 replicas, en live, devant une 50aine de personnes.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai eu des supers questions à l&amp;rsquo;issue du talk, et après. Merci à tous les présents.&lt;/p&gt;
&lt;h2 id="fin-de-journée"&gt;Fin de journée
&lt;/h2&gt;&lt;p&gt;Pour clôturer la journée, le dernier créneau proposait deux quickies, dont &amp;ldquo;Maintenir une app bénévole : Tech, galères et résilience&amp;rdquo; de &lt;strong&gt;Seb Ferrer&lt;/strong&gt; (OVHcloud). Je regrette de ne pas y être allé, car j&amp;rsquo;avais envie de voir si j&amp;rsquo;avais gagné au quizz du matin.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/10/quizz_jetdev.avif"
loading="lazy"
alt="Le leaderboard du quizz JetDev, je vous laisse deviner qui est en tête"
&gt;&lt;/p&gt;
&lt;p&gt;(Spoiler : le leaderboard a bougé après cette photo&amp;hellip; je n&amp;rsquo;ai donc rien gagné 😆.)&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;avantage, c&amp;rsquo;est que ça m&amp;rsquo;a permis d&amp;rsquo;avoir des chouettes discussions dans les couloirs avec des gens qui ont vu mon talk et qui ont apprécié :).&lt;/p&gt;
&lt;h2 id="remerciements"&gt;Remerciements
&lt;/h2&gt;&lt;p&gt;Comme toujours, un immense merci à l&amp;rsquo;équipe d&amp;rsquo;organisation de Cloud Nord et à tous les bénévoles et une mention particulière pour m&amp;rsquo;avoir contacté au dernier moment pour remplacer un speaker défaillant. J&amp;rsquo;espère que le remplaçant était à la hauteur 😄.&lt;/p&gt;
&lt;p&gt;Ces journées de conf, c&amp;rsquo;est aussi et surtout les rencontres dans les couloirs. Merci à tous les gens croisés, que ce soit au speaker dinner, dans les couloirs, ou à l&amp;rsquo;after. Cette fois, j&amp;rsquo;ai parlé à tellement de monde que c&amp;rsquo;est sûr que je vais en oublier donc je vais discrètement éluder la liste pour une fois ;-)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/10/pancakes.avif"
loading="lazy"
alt="Pause gourmande offerte par AXA"
&gt;&lt;/p&gt;
&lt;p&gt;Merci :)&lt;/p&gt;</description></item><item><title>BDX I/O 2026 : dédicace de mon livre, jeudi matin !</title><link>https://blog.zwindler.fr/2026/10/27/bdxio-2026-dedicace-livre/</link><pubDate>Tue, 27 Oct 2026 10:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/10/27/bdxio-2026-dedicace-livre/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/10/kubernetes-50-solutions-cover.webp" alt="Featured image of post BDX I/O 2026 : dédicace de mon livre, jeudi matin !" /&gt;&lt;p&gt;C&amp;rsquo;est officiel !&lt;/p&gt;
&lt;p&gt;Dans &lt;a class="link" href="https://blog.zwindler.fr/2026/09/21/retrouvez-moi-cloud-nord-2026-devfest-toulouse/" &gt;mon post de septembre&lt;/a&gt;, je vous teasais qu&amp;rsquo;une troisième chose mijotait peut-être pour &lt;strong&gt;BDX I/O&lt;/strong&gt;&amp;hellip; Je serai bien présent à l&amp;rsquo;édition 2026, &lt;strong&gt;mais uniquement le jeudi matin&lt;/strong&gt;, pour une séance de dédicaces !&lt;/p&gt;
&lt;h2 id="les-infos-pratiques"&gt;Les infos pratiques
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quand&lt;/strong&gt; : jeudi 29 octobre 2026, &lt;strong&gt;le matin uniquement&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Où&lt;/strong&gt; : sur le stand de la librairie &lt;strong&gt;Mollat&lt;/strong&gt;, à l&amp;rsquo;ENSEIRB-MATMECA (Talence)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Quoi&lt;/strong&gt; : dédicaces de &lt;a class="link" href="https://50ndk.zwindler.fr/" target="_blank" rel="noopener"
&gt;&amp;ldquo;Kubernetes : 50 solutions pour les postes de développement et les clusters de production&amp;rdquo;&lt;/a&gt; (Éditions Eyrolles)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les exemplaires seront en vente directement sur le stand Mollat. Et si vous avez déjà le vôtre, amenez-le, je le signerai avec plaisir. 🙂&lt;/p&gt;
&lt;h2 id="un-retour-aux-sources"&gt;Un retour aux sources
&lt;/h2&gt;&lt;p&gt;Ça peut paraître anodin dit comme ça, mais cette dédicace a une saveur particulière pour moi : BDX I/O 2026 a lieu à &lt;strong&gt;l&amp;rsquo;ENSEIRB-MATMECA&lt;/strong&gt;, l&amp;rsquo;école qui m&amp;rsquo;a formé (promo 2010, ça ne me rajeunit pas 🫣).&lt;/p&gt;
&lt;p&gt;Pouvoir y revenir pour échanger avec des lecteurs et lectrices sur ce livre (l&amp;rsquo;aboutissement de 10 ans d&amp;rsquo;expérience sur Kubernetes et d&amp;rsquo;années de bidouilles sur ce blog) c&amp;rsquo;est vraiment un honneur. Le thème de cette édition, &lt;em&gt;&amp;ldquo;back to school&amp;rdquo;&lt;/em&gt;, n&amp;rsquo;aurait d&amp;rsquo;ailleurs pas pu mieux tomber, pour moi.&lt;/p&gt;
&lt;p&gt;Un immense merci à l&amp;rsquo;équipe de BDX I/O et à Mollat de rendre ça possible.&lt;/p&gt;
&lt;h2 id="jinsiste-un-peu"&gt;J&amp;rsquo;insiste un peu
&lt;/h2&gt;&lt;p&gt;Je ne serai sur place &lt;strong&gt;que jeudi matin&lt;/strong&gt; (la conf elle, dure 2 jours, les 29 et 30 octobre). Si vous voulez repartir avec un exemplaire signé ou simplement discuter Kubernetes, c&amp;rsquo;est le moment ! &lt;a class="link" href="https://blog.zwindler.fr/2026/09/21/retrouvez-moi-cloud-nord-2026-devfest-toulouse/" &gt;Je pars ensuite pour Cloud Nord pour un talk sur kubesolo et&amp;hellip; DOOM&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Après &lt;a class="link" href="https://blog.zwindler.fr/2026/02/04/recap-cndf-2026/" &gt;les dédicaces du Cloud Native Days France&lt;/a&gt; en février, ce sera ma deuxième &amp;ldquo;vraie&amp;rdquo; séance de dédicaces, et j&amp;rsquo;ai très hâte de renouveler l&amp;rsquo;expérience.&lt;/p&gt;
&lt;p&gt;Pour plus de détails sur le livre, &lt;a class="link" href="https://50ndk.zwindler.fr/" target="_blank" rel="noopener"
&gt;le site de promotion est toujours par là&lt;/a&gt;.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Billetterie BDX I/O&lt;/strong&gt; : &lt;a class="link" href="https://www.billetweb.fr/bdx-i-o-2026" target="_blank" rel="noopener"
&gt;https://www.billetweb.fr/bdx-i-o-2026&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Programme complet&lt;/strong&gt; : &lt;a class="link" href="https://bdxio.fr/schedule" target="_blank" rel="noopener"
&gt;https://bdxio.fr/schedule&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On se retrouve &lt;strong&gt;jeudi matin à Talence&lt;/strong&gt; !&lt;/p&gt;</description></item><item><title>Trouver des vulnérabilités dans l'open source quand on n'est pas spécialiste en sécurité</title><link>https://blog.zwindler.fr/2026/09/23/finding-vulnerabilities-oss-not-security-specialist/</link><pubDate>Wed, 23 Sep 2026 20:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/09/23/finding-vulnerabilities-oss-not-security-specialist/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/09/20260919_152513.webp" alt="Featured image of post Trouver des vulnérabilités dans l'open source quand on n'est pas spécialiste en sécurité" /&gt;&lt;h2 id="tldr"&gt;TL;DR
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Trouver des failles dans l&amp;rsquo;open source en 2026, &amp;ldquo;grâce&amp;rdquo; aux LLM : facile, OUI&lt;/li&gt;
&lt;li&gt;Être le &lt;em&gt;premier&lt;/em&gt; à les remonter : possible, MAIS&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Trouver des failles est devenu banal. Le vrai défi, c&amp;rsquo;est d&amp;rsquo;être le premier, et de survivre au triage. C&amp;rsquo;est de ça que parle le reste de l&amp;rsquo;article.&lt;/p&gt;
&lt;h2 id="pourquoi-faire-ça-au-départ-"&gt;Pourquoi faire ça, au départ ?
&lt;/h2&gt;&lt;p&gt;La cybersécurité, ça me passionne depuis&amp;hellip; bah, depuis toujours. Mon deuxième programme d&amp;rsquo;enfant était un &lt;a class="link" href="https://winworldpc.com/product/microsoft-visual-bas/30" target="_blank" rel="noopener"
&gt;VB 3.0&lt;/a&gt; à la con, un faux virus qui avait pour seul boulot de remplir le dossier Démarrage de Windows 95/98 de milliers de commandes &lt;a class="link" href="https://en.wikipedia.org/wiki/Windows_Messenger_service" target="_blank" rel="noopener"
&gt;netsend&lt;/a&gt; auto-exécutables (si vous ne savez pas ce que c&amp;rsquo;était, allez jeter un œil !).&lt;/p&gt;
&lt;p&gt;Mais je n&amp;rsquo;en ai jamais fait mon métier, même si l&amp;rsquo;idée m&amp;rsquo;est venue à plusieurs reprises. Ces derniers mois, j&amp;rsquo;ai collaboré plus étroitement que d&amp;rsquo;habitude avec mes collègues de l&amp;rsquo;équipe cybersécurité, et j&amp;rsquo;ai fini par me lancer : je voulais voir ce que je pouvais faire avec les LLM dans ce domaine.&lt;/p&gt;
&lt;p&gt;Disclaimer : je ne suis &lt;strong&gt;PAS&lt;/strong&gt; spécialiste en cybersécurité, pas le moins du monde. Si vous ne me connaissez pas, je ne suis qu&amp;rsquo;un platform engineer qui s&amp;rsquo;y connaît en Kubernetes, virtualisation et observabilité. Je code de manière correte en golang et en python.&lt;/p&gt;
&lt;h2 id="quel-est-le-but-au-juste-"&gt;Quel est le but, au juste ?
&lt;/h2&gt;&lt;p&gt;Ces deux derniers mois, je me suis mis à traquer (sur mon temps libre) des vulnérabilités dans des logiciels open source populaires que j&amp;rsquo;utilise au quotidien dans mon travail. Certains ont un programme de bug bounty, d&amp;rsquo;autres acceptent les GitHub Security Advisories. D&amp;rsquo;autres ne répondent tout simplement pas.&lt;/p&gt;
&lt;p&gt;Pourquoi l&amp;rsquo;open source ? Bah déjà, le fait que le code soit ouvert me permet de vérifier les choses quand le LLM pense que quelque chose cloche, et de me forger ma propre opinion. L&amp;rsquo;inconvénient, honnêtement, c&amp;rsquo;est que les LLM sont aussi la raison pour laquelle l&amp;rsquo;open source se noie sous les rapports de sécurité depuis environ un an.&lt;/p&gt;
&lt;p&gt;Mais surtout : je travaille avec ces outils tous les jours, je sais donc &lt;strong&gt;COMMENT&lt;/strong&gt; ils devraient fonctionner, et ce qui &lt;strong&gt;N&amp;rsquo;EST PAS&lt;/strong&gt; normal. On reviendra là-dessus aussi.&lt;/p&gt;
&lt;p&gt;Une chose avant de commencer : tout ce qui est décrit ci-dessous a été fait sur ma propre infrastructure : clusters jetables, installations locales, VM de test. Chaque programme de bug bounty a ses règles du jeu : pas de scan d&amp;rsquo;instances tierces, pas de toucher aux données des autres, pas de déni de service sur une infra partagée. Lisez-les. Si une de vos découvertes nécessite de les enfreindre pour être prouvé, c&amp;rsquo;est que vous vous y prenez mal.&lt;/p&gt;
&lt;h2 id="ok-ok-assez-dintroduction--les-llm-peuvent-ils-trouver-des-failles-sans-expérience-en-cybersécurité-"&gt;Ok ok, assez d&amp;rsquo;introduction : les LLM peuvent-ils trouver des failles sans expérience en cybersécurité ?
&lt;/h2&gt;&lt;p&gt;Réponse courte : oui. La vraie question, c&amp;rsquo;est de savoir si ça vaut le coup, et ça dépend surtout de si vous voulez être le premier ou non, et de &lt;strong&gt;QUAND&lt;/strong&gt; vous commencez.&lt;/p&gt;
&lt;p&gt;Voici mon propre retour d&amp;rsquo;expérience sur la chasse aux vulnérabilités dans l&amp;rsquo;open source. &amp;ldquo;YMMV&amp;rdquo;, comme on dit.&lt;/p&gt;
&lt;h2 id="vous-avez-déjà-raté-le-coche"&gt;Vous avez déjà raté le coche
&lt;/h2&gt;&lt;p&gt;La première chose à savoir, c&amp;rsquo;est que (si vous commencez aujourd&amp;rsquo;hui) vous êtes &lt;strong&gt;très en retard&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est encore plus vrai pour les logiciels couverts par un bug bounty, parce que les chasseurs fouillaient déjà avant l&amp;rsquo;arrivée des LLM. Mais la pression sur ces projets a été multipliée par mille depuis.&lt;/p&gt;
&lt;p&gt;Des chiffres concrets, tirés des pages publiques de deux très gros projets (stats sur 90 jours, septembre 2026), volontairement non cités (c&amp;rsquo;est bien l&amp;rsquo;écart qui compte) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un programme, encore à peu près en vie : 4 193 rapports reçus, ~454 000 $ versés&lt;/li&gt;
&lt;li&gt;L&amp;rsquo;autre, essentiellement mort : 551 rapports reçus, 1 000 $ versés, dernier rapport traité il y a un mois&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Relisez bien. Ce volume, c&amp;rsquo;est en grande partie du bruit généré par de l&amp;rsquo;IA. curl a &lt;a class="link" href="https://daniel.haxx.se/blog/2026/01/26/the-end-of-the-curl-bug-bounty/" target="_blank" rel="noopener"
&gt;complètement arrêté&lt;/a&gt; son bug bounty payant en début 2026, après l&amp;rsquo;effondrement du ratio de rapports valides. D&amp;rsquo;autres projets ont suspendu les paiements ou sont passés en accès sur invitation (&lt;a class="link" href="https://hackerone.com/nextcloud/policy_versions?type=team&amp;amp;change=3773031" target="_blank" rel="noopener"
&gt;Nextcloud&lt;/a&gt;, &lt;a class="link" href="https://app.intigriti.com/programs/grafanalabs/grafanaossbbp/detail" target="_blank" rel="noopener"
&gt;Grafana&lt;/a&gt;). Les mainteneurs se noient.&lt;/p&gt;
&lt;p&gt;Alors si vous pensez être plus malin que tout le monde et que vous comptez vous faire de l&amp;rsquo;argent rapide avec votre compte Claude, réfléchissez-y à deux fois. Des centaines de personnes y avaient déjà pensé il y a un an, et il y en a un peu plus chaque jour. Vous êtes en concurrence avec elles, avec des professionnels aguerris, et avec des gens qui ont bien plus de ressources IA que vous.&lt;/p&gt;
&lt;h2 id="parlons-modèles-choisissez-vos-armes"&gt;Parlons modèles (choisissez vos armes)
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai d&amp;rsquo;abord essayé les options évidentes (&amp;ldquo;ça se passe mal!&amp;rdquo;) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt; : tout ce qui touche de près ou de loin à la cybersécurité est flaggé et bascule en mode fortement restreint. Au bout de quelques heures, la session est carrément gelée. La seule parade, c&amp;rsquo;est de relancer des sessions et de perdre son contexte à chaque fois - et les blocages se font plus fréquents avec le temps, même pour du travail parfaitement légitime. La voie officielle, c&amp;rsquo;est le &lt;strong&gt;Cyber Verification Program&lt;/strong&gt; d&amp;rsquo;Anthropic (OpenAI a un équivalent, le Cybersecurity Grant Program), mais la validation semble arbitraire : plusieurs chercheurs en sécurité que je connais personnellement se sont fait refuser sans explication, et ils ne sont pas seuls.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/09/CVP_rejected.avif"
loading="lazy"
alt="Post LinkedIn d’un ingénieur sécurité senior titulaire d’un CVE du noyau Linux, partageant l’e-mail rejetant sa candidature au Cyber Verification Program d’Anthropic"
&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Google Antigravity&lt;/strong&gt; : ça a très bien marché pendant 2 jours, puis les blocages ont commencé. Google n&amp;rsquo;a aucun programme pour certifier les chasseurs légitimes, vous pouvez donc l&amp;rsquo;oublier. Un mois d&amp;rsquo;abonnement pour rien.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce qui marche : laissez tomber les grands fournisseurs américains et passez à des alternatives moins regardantes. Soit un accès par abonnement à des modèles open weights / chinois, soit des plateformes agentic avec des quotas généreux.&lt;/p&gt;
&lt;p&gt;Et les API au token ? Très mauvaise idée pour ce genre de travail. La chasse aux failles demande un nombre absurde de tokens en lecture. Mes chiffres à moi : &lt;strong&gt;environ 11 milliards de tokens en 2 mois&lt;/strong&gt;, dont la grande majorité (plus de 90%) servie depuis le cache côté fournisseur. Même avec l&amp;rsquo;une des options les moins chères au moment où j&amp;rsquo;écris (GLM 5.3 sur un broker bien connu), ça revient à environ 1 800 $. Un modèle frontier coûterait plusieurs fois ce montant. Choisir un fournisseur dont le cache est facturé moins cher aide énormément, mais vous ne voulez quand même pas de cette facture en fin de mois.&lt;/p&gt;
&lt;p&gt;Les abonnements qui donnent accès à GLM, DeepSeek, Kimi&amp;hellip; sont probablement de bonnes options (pas encore essayé). Ce que j&amp;rsquo;ai réellement utilisé pendant 2 mois, c&amp;rsquo;est &lt;strong&gt;devin.ai&lt;/strong&gt; (l&amp;rsquo;outil agentic de Cognition, anciennement Windsurf) : leur modèle maison est compétent et, au moment où j&amp;rsquo;écris, illimité dans l&amp;rsquo;abonnement de base. Ce genre de promo va et vient, vérifiez avant de vous abonner.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Pas sponsorisé, je paie de ma poche 🥲.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="et-les-modèles-locaux-"&gt;Et les modèles locaux ?
&lt;/h2&gt;&lt;p&gt;L&amp;rsquo;idée est vraiment séduisante. Être autonome avec du matériel local (ou en louer) plutôt que de payer au token des tâches coûteuses, tout en échappant à la censure des fournisseurs, ça ressemble à une idée de génie (qui pense GIFI dans sa tête ?). C&amp;rsquo;est la première chose que j&amp;rsquo;ai essayée après mon &amp;ldquo;fiasco&amp;rdquo; Claude/Antigravity.&lt;/p&gt;
&lt;p&gt;Alors&amp;hellip; peut-on utiliser des modèles open weights locaux pour éviter de griller son quota en 20 minutes ? Avec un modèle ~30B (type Qwen, ou la meilleure variante du moment), on &lt;em&gt;peut&lt;/em&gt; trouver quelques failles sur des gros projets anciens&amp;hellip; mais c&amp;rsquo;est comme courir un marathon avec un sac à dos rempli de cailloux après les avoir peint (dédicace à Vincent). C&amp;rsquo;est faisable, mais ne vous attendez pas à ce que ce soit simple ni efficace.&lt;/p&gt;
&lt;p&gt;Si vous partez sur cette voie, le pipeline ci-dessous compte encore plus qu&amp;rsquo;avec des modèles plus &amp;ldquo;gros&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="mon-pipeline"&gt;Mon pipeline
&lt;/h2&gt;&lt;p&gt;Quel que soit le modèle, la première chose dont vous avez besoin, c&amp;rsquo;est un cadre solide. Voilà ce que j&amp;rsquo;ai fait (plutôt basique, il existe peut-être des méthodes plus efficaces, mais c&amp;rsquo;est un bon point de départ) :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Analysez un composant de votre cible à la fois. Demandez au modèle de cartographier son fonctionnement en amont. Terminez-le complètement avant de passer au suivant.&lt;/li&gt;
&lt;li&gt;Une fois la cartographie faite, répartissez le travail (surtout des fichiers de code) entre des subagents, chacun focalisé sur une partie bien délimitée. Demandez-leur de chercher des anti-patterns connus dans les fonctions, des variables mal vérifiées et compagnie, et de consigner dans un fichier markdown tout ce qui leur paraît même vaguement bizarre ou dangereux, pour analyse ultérieure.&lt;/li&gt;
&lt;li&gt;Faites relire le travail des subagents par l&amp;rsquo;agent principal. Chaque constat part soit dans un dossier &amp;ldquo;non-security findings&amp;rdquo; (bugs trop faibles ou trop restreints pour être exploités : je m&amp;rsquo;en sers pour faire des PR d&amp;rsquo;amélioration en upstream), soit dans un dossier &amp;ldquo;security findings&amp;rdquo; pour contre-expertise.&lt;/li&gt;
&lt;li&gt;Une fois le scan initial terminé, demandez à l&amp;rsquo;agent principal une contre-expertise de tous les nouveaux constats sécurité validés.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="contre-expertise"&gt;Contre-expertise
&lt;/h2&gt;&lt;p&gt;C&amp;rsquo;est là que vient l&amp;rsquo;essentiel de la valeur de mes scans LLM. J&amp;rsquo;ai mis deux mois à affiner cette étape, et j&amp;rsquo;y travaille toujours.&lt;/p&gt;
&lt;p&gt;Ma checklist actuelle :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Chercher sur GitHub/GitLab les issues, PR et documents d&amp;rsquo;amélioration éventuels. Soumettre un problème de sécurité déjà connu, c&amp;rsquo;est évidemment une mauvaise idée.&lt;/li&gt;
&lt;li&gt;Regarder aussi dans la doc officielle les limitations connues, les faiblesses &amp;amp; compromis documentés (c&amp;rsquo;est à dire &amp;ldquo;acceptés&amp;rdquo; par les mainteneurs, donc inéligibles), les bonnes pratiques de sécurité.&lt;/li&gt;
&lt;li&gt;Quand vous êtes sûr à 100% qu&amp;rsquo;il n&amp;rsquo;y a ni correctif public ni rapport antérieur, demandez au modèle de reproduire sur le vrai logiciel. Selon la cible, vous voudrez peut-être des runbooks pour différentes configurations sur différentes plateformes. Cette étape est particulièrement importante avec les modèles locaux, qui sortent des rapports de vulnérabilité absolument convaincants mais qui ne marchent jamais.&lt;/li&gt;
&lt;li&gt;Une fois reproduite, notez-la. La plupart des bug bounties demandent un score CVSS, mais il faut aller plus loin que ça.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Concrètement, un constat dans mon pipeline, ça ressemble à ça : un subagent signale un pattern suspect dans un fichier que personne ne lit -&amp;gt; l&amp;rsquo;orchestrateur le déduplique face aux issues publiques et aux flux de CVE -&amp;gt; si ça passe, reproduction sur un cluster &lt;a class="link" href="https://kind.sigs.k8s.io/" target="_blank" rel="noopener"
&gt;kind&lt;/a&gt; jetable ou une installation locale du vrai logiciel -&amp;gt; puis noté, rédigé, soumis&amp;hellip; et ensuite, vous attendez. Le triage prend en général quelques semaines/mois. Parfois quelques jours, et dans de rares cas plusieurs années (on y reviendra, c&amp;rsquo;est généralement le signe d&amp;rsquo;un programme surchargé ou mort).&lt;/p&gt;
&lt;h2 id="noter-à-la-juste-valeur"&gt;Noter à la juste valeur
&lt;/h2&gt;&lt;p&gt;Une fois que vous avez de vraies vulnérabilités, les noter avec un CVSS est une étape nécessaire. Mais le travail ne s&amp;rsquo;arrête pas là. Tout le monde qui a fait un peu de cybersécurité sait bien que le CVSS ne raconte pas toute l&amp;rsquo;histoire. Et les LLM auront tendance à surévaluer leurs propres constats.&lt;/p&gt;
&lt;p&gt;Faites poser ces questions à l&amp;rsquo;agent pendant la contre-expertise :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le constat est-il réaliste ?&lt;/li&gt;
&lt;li&gt;Les prérequis sont-ils réalistes ?&lt;/li&gt;
&lt;li&gt;Existeraient-ils / arriveraient-ils dans un déploiement réel, pas seulement en théorie ?&lt;/li&gt;
&lt;li&gt;La configuration est-elle saine (et non un piège où l&amp;rsquo;admin de notre scénario mériterait presque d&amp;rsquo;être compromis) ?&lt;/li&gt;
&lt;li&gt;Un attaquant chercherait-il vraiment à exploiter ça ?&lt;/li&gt;
&lt;li&gt;Le constat donne-t-il plus de droits que l&amp;rsquo;attaquant n&amp;rsquo;en a déjà ?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Toutes ces réponses (ou au moins la plupart) devraient être &amp;ldquo;oui&amp;rdquo;. Sinon, ce n&amp;rsquo;est probablement pas un problème de sécurité (ou en tout cas, c&amp;rsquo;est ce que les équipes sécurité de ces projets vous diront !), quoi qu&amp;rsquo;en dise le CVSS.&lt;/p&gt;
&lt;p&gt;Il y a évidemment des exceptions, mais pour rester simple restons dans le cas général.&lt;/p&gt;
&lt;h2 id="appropriez-vous-le-rapport"&gt;Appropriez-vous le rapport
&lt;/h2&gt;&lt;p&gt;Une fois tout ça fait, prenez le temps de le relire (vous, l&amp;rsquo;humain). Selon la qualité du modèle, vous trouverez peut être le rapport ridicule :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lisez le rapport du LLM attentivement, comprenez-le, appropriez-vous-en !&lt;/li&gt;
&lt;li&gt;Si quelque chose vous paraît bizarre, challengez le LLM&lt;/li&gt;
&lt;li&gt;Si une affirmation est exagérée, challengez le LLM&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C&amp;rsquo;est là qu&amp;rsquo;un bagage en cybersécurité ou une connaissance fine de l&amp;rsquo;outil que vous auditez devient vraiment utile. Ça vous évitera de vous ridiculiser (l&amp;rsquo;humain, le LLM, lui, s&amp;rsquo;en fout).&lt;/p&gt;
&lt;h2 id="ne-faites-pas-partie-du-problème"&gt;Ne faites pas partie du problème
&lt;/h2&gt;&lt;p&gt;Prenons le taureau par les cornes : je viens de vous dire que les mainteneurs se noient sous les rapports générés par l&amp;rsquo;IA, et j&amp;rsquo;en ai envoyé plus de 30 moi-même. Oui, il y a un léger problème de cohérence. Voici ce que j&amp;rsquo;essaie de faire autrement :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rien n&amp;rsquo;est soumis sans avoir été reproduit sur le vrai logiciel et/ou un vrai cluster&lt;/li&gt;
&lt;li&gt;Chaque rapport est lu, compris et assumé par moi avant de partir. Si je ne peux pas l&amp;rsquo;expliquer aux mainteneurs sans le LLM, il ne part pas&lt;/li&gt;
&lt;li&gt;Les constats à faible sévérité ou limite ne passent pas par les canaux sécurité : ils finissent en issues publiques ou en PR en upstream&lt;/li&gt;
&lt;li&gt;Les notes sont contestées : pas de CVSS gonflé par le LLM&lt;/li&gt;
&lt;li&gt;Sur les petits projets, les rapports partent un par un, en attendant le triage avant d&amp;rsquo;en envoyer d&amp;rsquo;autres&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les doublons arrivent quand même (9 pour l&amp;rsquo;instant), mais un doublon de vraie vulnérabilité reproduite coûte quelques minutes au triage sur des plateformes de bug bounty. Un rapport halluciné lui coûte potentiellement des heures, et beaucoup de la patience du maintainer.&lt;/p&gt;
&lt;h2 id="gardez-une-trace-de-tout"&gt;Gardez une trace de tout
&lt;/h2&gt;&lt;p&gt;Ça, je l&amp;rsquo;avais pas vu venir.&lt;/p&gt;
&lt;p&gt;Très très (très) vite, on est soi même ensevelli sous les reports. Après une trentaine de constats et une dizaine de rapports envoyés, je ne savais plus où j&amp;rsquo;en étais, honnêtement. Quel constat avait été reproduit ? Lequel avait déjà été soumis, et dans quel programme ? Quel rapport m&amp;rsquo;attendait, et lequel attendait les mainteneurs ? Un dossier de fichiers markdown ne passe pas à l&amp;rsquo;échelle, et les immenses tables markdown entretenues par le LLM sont vite devenues ingérables aussi.&lt;/p&gt;
&lt;p&gt;Du coup, j&amp;rsquo;ai fini par faire &amp;ldquo;Clauder&amp;rdquo; mon tableau de bord de suivi (oui, l&amp;rsquo;ironie : Claude code avec plaisir une UI pour chasser des failles, il veut juste pas faire la chasse lui-même). La source de vérité est désormais une base JSON, que le LLM met à jour via des scripts (jamais en éditant le fichier à la main). À chaque commit, un tableau kanban HTML statique est régénéré à partir de celle-ci, en suivant chaque constat tout au long de son cycle de vie.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/09/findings-kanban.avif"
loading="lazy"
alt="Tableau kanban des constats (mode démo anonymisé), avec des colonnes de “confirmée” à “fermée” et une carte par constat affichant son score composite, son CVSS et l’état de sa soumission"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;(Les titres ont été censurés pour des raisons évidentes)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Chaque constat est une carte qui progresse de gauche à droite :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Candidate&lt;/strong&gt; : signalée par un subagent, pas encore vérifiée&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Confirmée&lt;/strong&gt; : a survécu à la contre-expertise et à la reproduction&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rédaction&lt;/strong&gt; puis &lt;strong&gt;Prête&lt;/strong&gt; : rapport en cours de rédaction, puis prêt à envoyer&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Envoyé&lt;/strong&gt; et &lt;strong&gt;Triage&lt;/strong&gt; : soumis, en attente que le programme le regarde&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acceptée (embargo)&lt;/strong&gt; : acceptée par le programme, correctif ou divulgation en attente&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Corrigée&lt;/strong&gt; : correctif publié&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fermée&lt;/strong&gt; : rejetée comme doublon, wontfix ou informative&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Chaque carte porte un score composite (CVSS + risque de wontfix + risque de doublon, on y revient dans la section suivante), le CVSS, combien de fois elle a été soumise, et si elle a déjà été remontée en upstream. Trier par score composite me dit quoi écrire ensuite. Filtrer par programme me dit combien de places il me reste avant d&amp;rsquo;atteindre la limite de soumission.&lt;/p&gt;
&lt;p&gt;La vue stats, c&amp;rsquo;est aussi un bon retour à la réalité :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/09/findings-stats.avif"
loading="lazy"
alt="Vue stats du tableau de bord : 163 constats sur 5 programmes, 31 soumis, 10 en attente, 2 acceptés, 9 doublons, 1 wontfix, et la répartition des sévérités CVSS"
&gt;&lt;/p&gt;
&lt;p&gt;163 constats (après en avoir écarté 215 non-sécurité), sur 26 projets, et seulement 31 soumis.&lt;/p&gt;
&lt;p&gt;La seule colonne &amp;ldquo;Prête&amp;rdquo; contient déjà 118 constats. En trouver, c&amp;rsquo;est trivial. Ce qui me limite, ce n&amp;rsquo;est plus le LLM : c&amp;rsquo;est mon temps à moi pour reproduire, rédiger et assumer chaque rapport (l&amp;rsquo;humain dans la boucle &lt;strong&gt;est&lt;/strong&gt; le goulot d&amp;rsquo;étranglement), puis les plafonds de soumission des programmes. Et même dans ces conditions, être le premier et survivre au triage reste du ressort de la chance.&lt;/p&gt;
&lt;p&gt;Quel que soit l&amp;rsquo;outil que vous utilisez (un tableur fait l&amp;rsquo;affaire aussi, voire JIRA lol), mettez ça en place &lt;strong&gt;avant&lt;/strong&gt; de commencer à soumettre. Le rajouter après 10 rapports envoyés, en remuant les mails et les boîtes de réception des plateformes, c&amp;rsquo;est un exercice douloureux.&lt;/p&gt;
&lt;h2 id="vous-nêtes-pas-seul"&gt;Vous n&amp;rsquo;êtes pas seul
&lt;/h2&gt;&lt;p&gt;Même quand le programme ne verse pas de bounty, attendez-vous à de la concurrence : via les plateformes de bug bounty ou des rapports GHSA (GitHub Security Advisory). Vous pouvez partir du principe que chaque problème &amp;ldquo;facile à repérer&amp;rdquo; a été trouvé il y a des mois, plusieurs fois. Sur les projets dont l&amp;rsquo;équipe sécurité est à bout, comptez plutôt &lt;em&gt;des années&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Je ne peux pas encore donner de détails, mais je suis tombé sur une vulnérabilité qui avait été remontée il y a des années, avec un CVE réservé l&amp;rsquo;année d&amp;rsquo;avant, toujours ni corrigée ni publique.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Voilà l&amp;rsquo;état du triage de sécurité de l&amp;rsquo;open source aujourd&amp;rsquo;hui&lt;/strong&gt; : la file d&amp;rsquo;attente est plus longue que ce qu&amp;rsquo;on arrive à traiter, et ça ne s&amp;rsquo;améliore pas.&lt;/p&gt;
&lt;p&gt;Alors si vous visez à être le premier (pour le bounty, pour la gloire), il faut être plus stratégique que la moyenne :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Évitez les bugs qui se repèrent en lisant un seul fichier, ou qu&amp;rsquo;un outil SAST/DAST signalerait. Les LLM sont relativement doués pour ça, et vous pouvez être sûr que quelqu&amp;rsquo;un l&amp;rsquo;a trouvé avant vous. Malheureusement, c&amp;rsquo;est peut-être le seul type de constat que vous obtiendrez avec des modèles locaux/petits (ou il vous faut un harness très spécifique).&lt;/li&gt;
&lt;li&gt;Ciblez en priorité du code qui n&amp;rsquo;est pas le plus audité. Ne visez &lt;strong&gt;pas&lt;/strong&gt; une RCE pré-auth si vous ne savez pas ce que vous faites. Ce code a été audité par des gens bien meilleurs que vous. Visez plutôt du code récent en beta/GA, ou à l&amp;rsquo;inverse du vieux legacy qui paraît inoffensif.&lt;/li&gt;
&lt;li&gt;Visez des problèmes qui supposent de savoir comment l&amp;rsquo;outil fonctionne, et ce qui ne devrait &lt;strong&gt;pas&lt;/strong&gt; fonctionner. Les LLM sont mauvais là-dessus : si vous avez cette connaissance, vous avez un avantage.&lt;/li&gt;
&lt;li&gt;Si vous ne payez pas au token, n&amp;rsquo;hésitez pas à donner le fichier entier au subagent. J&amp;rsquo;ai découvert par hasard qu&amp;rsquo;un agent qu&amp;rsquo;on restreint aux &amp;ldquo;fonctions sensibles&amp;rdquo; en rate ÉNORMÉMENT. C&amp;rsquo;est peut-être moins vrai pour les modèles frontier, mais c&amp;rsquo;est une vérité absolue pour les modèles moins bons.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;De là, j&amp;rsquo;ai mis au point un score qui évalue mes vulnérabilités non seulement sur leur CVSS, mais aussi sur le risque de &amp;ldquo;wontfix&amp;rdquo; de la part des mainteneurs &lt;strong&gt;et&lt;/strong&gt; le risque de &amp;ldquo;doublon&amp;rdquo; parce qu&amp;rsquo;elles étaient trop faciles à trouver.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Est-ce que ça marche ? Oui, absolument. J&amp;rsquo;ai trouvé des dizaines de vulnérabilités réelles, non corrigées, dans de l&amp;rsquo;open source en production, allant de sévérité moyenne à critique.&lt;/p&gt;
&lt;p&gt;La vérité qui pique : j&amp;rsquo;ai envoyé plus de 30 rapports jusqu&amp;rsquo;ici. Sur les verdicts revenus, la plupart étaient des doublons - quelqu&amp;rsquo;un était arrivé avant moi (mon erreur de débutant : je pensais que les constats faciles passeraient, alors que &amp;ldquo;facile&amp;rdquo; signifie simplement que quelqu&amp;rsquo;un les a déjà trouvés). L&amp;rsquo;un m&amp;rsquo;a valu un CVE avec co-crédit, &lt;a class="link" href="https://github.com/kedacore/keda/security/advisories/GHSA-637c-6jxx-4rwm" target="_blank" rel="noopener"
&gt;CVE-2026-77524&lt;/a&gt;, qui vient de sortir en public : une élévation critique namespace-admin vers cluster-admin dans KEDA ; un autre advisory a été accepté mais n&amp;rsquo;est pas encore public ; et un correctif a été mergé via une issue publique. Le reste est soit encore en triage, soit bloqué derrière des limites de débit. Certaines plateformes plafonnent les soumissions, par exemple 4 rapports par programme sur une fenêtre glissante de 30 jours, un peu plus si vous chassez sur plusieurs programmes, avec en plus un plafond global sur toute la plateforme.&lt;/p&gt;
&lt;p&gt;Et quand un projet ne répond simplement pas ? Je n&amp;rsquo;ai pas encore de règle parfaite : je relance une fois après quelques semaines. La convention courante, c&amp;rsquo;est une publication publique après un embargo raisonnable (90 jours), mais je n&amp;rsquo;ai pas encore décidé si je l&amp;rsquo;appliquerai. Il y a aussi des paliers intermédiaires, comme escalader auprès d&amp;rsquo;une CNA, ou de GitHub Security Lab pour les projets hébergés sur GitHub. Un rapport qui pourrit dans une file privée n&amp;rsquo;aide personne, mais laisser une vulnérabilité non corrigée entre les mains des utilisateurs non plus.&lt;/p&gt;
&lt;p&gt;Alors : &lt;strong&gt;exploitez cette contrainte&lt;/strong&gt;. Transformez la faiblesse en atout. Tout le monde se concentre sur les mêmes cibles médiatiques (qui ne veut pas d&amp;rsquo;une RCE pré-auth ?) ou sur les failles évidentes et faciles. Si vous voulez des résultats (être le premier), laissez tomber les deux. Concentrez-vous sur les cas limites obscurs qui se retrouvent éparpillés sur 3 composants. Oui, ça demande plus de travail, de meilleurs LLM, et un peu de chance.&lt;/p&gt;
&lt;p&gt;Si vous commencez maintenant, les victoires faciles sont déjà prises. Mais c&amp;rsquo;est précisément là que se trouve votre atout : savoir comment devraient se comporter les outils que vous utilisez tous les jours, c&amp;rsquo;est quelque chose que personne ne peut louer &amp;ldquo;au token&amp;rdquo;.&lt;/p&gt;</description></item><item><title>Retrouvez-moi à Cloud Nord 2026 et au DevFest Toulouse !</title><link>https://blog.zwindler.fr/2026/09/21/retrouvez-moi-cloud-nord-2026-devfest-toulouse/</link><pubDate>Mon, 21 Sep 2026 21:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/09/21/retrouvez-moi-cloud-nord-2026-devfest-toulouse/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/07/sketch.webp" alt="Featured image of post Retrouvez-moi à Cloud Nord 2026 et au DevFest Toulouse !" /&gt;&lt;p&gt;TL;DR : je serai &lt;strong&gt;speaker à Cloud Nord le 30 octobre&lt;/strong&gt; pour un talk inédit sur Kubernetes tassé dans un Raspberry Pi Zero, &lt;strong&gt;et au DevFest Toulouse le 19 novembre&lt;/strong&gt; pour présenter ma boîte à coucou des Guignols. De quoi finir l&amp;rsquo;année en beauté.&lt;/p&gt;
&lt;h2 id="cloud-nord-2026---kubernetes-dans-512-mo-de-ram-et-un-peu-de-doom"&gt;Cloud Nord 2026 - Kubernetes dans 512 Mo de RAM, et un peu de DOOM
&lt;/h2&gt;&lt;p&gt;Alors que &lt;strong&gt;je n&amp;rsquo;avais pas été retenu à la base&lt;/strong&gt;, j&amp;rsquo;ai reçu un message de l&amp;rsquo;équipe d&amp;rsquo;organisation de &lt;strong&gt;Cloud Nord 2026&lt;/strong&gt;, qui avait besoin d&amp;rsquo;un petit coup de main pour remplacer un speaker finalement absent.&lt;/p&gt;
&lt;p&gt;Comme Cloud Nord c&amp;rsquo;est vraiment une conférence que j&amp;rsquo;adore, et surtout, que c&amp;rsquo;était pour un sujet que je n&amp;rsquo;avais pas encore présenté (et que je ne donnerai a priori &lt;strong&gt;qu&amp;rsquo;une seule fois&lt;/strong&gt;), j&amp;rsquo;ai dit oui, sans réfléchir (Lille, c&amp;rsquo;est loin 😆).&lt;/p&gt;
&lt;p&gt;Vous pourrez donc me voir à cloud nord pour : &lt;strong&gt;&amp;ldquo;J&amp;rsquo;ai mis Kubernetes dans 512 Mo de RAM. Et il restait de la place pour DOOM.&amp;rdquo;&lt;/strong&gt; Rendez-vous le &lt;strong&gt;vendredi 30 octobre 2026, de 16h40 à 17h30, salle Amphi St Louis&lt;/strong&gt;, pour voir jusqu&amp;rsquo;où on peut pousser un Raspberry Pi Zero 2 W.&lt;/p&gt;
&lt;p&gt;Voici le pitch :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Kubernetes est souvent perçu comme une usine à gaz réservée à des clusters surdimensionnés et à des machines pleines de RAM.&lt;/p&gt;
&lt;p&gt;Mais est-ce vraiment le cas ?&lt;/p&gt;
&lt;p&gt;Dans cette conférence, je vais vous montrer, en live, comment on peut faire rentrer Kubernetes sur une machine à peine plus grosse qu&amp;rsquo;une clé USB, avec seulement 512 Mo de RAM et qui coûte 20 $ : un Raspberry Pi Zero 2 W.&lt;/p&gt;
&lt;p&gt;En coupant tout ce qui dépasse, en optimisant l&amp;rsquo;OS jusqu&amp;rsquo;à l&amp;rsquo;os (ahah), et en jouant avec des recoins moins connus de Linux, on fera émerger un cluster Kubernetes mono-nœud parfaitement fonctionnel, malgré des contraintes matérielles extrêmes.&lt;/p&gt;
&lt;p&gt;Et pour pousser l&amp;rsquo;expérience jusqu&amp;rsquo;à ses limites, on concluera par un stress test volontairement absurde mais révélateur : &amp;ldquo;Does it run DOOM ?&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;C&amp;rsquo;est une conférence inspirée directement de &lt;a class="link" href="https://blog.zwindler.fr/2026/03/09/kubesolo-raspberry-pi-zero-sysops-pratique/" &gt;l&amp;rsquo;article que j&amp;rsquo;ai publié dans Sysops Pratique&lt;/a&gt; au printemps. Mais cette version sera plus ludique, car en live, et probablement plus dangereuse pour la qualité de mon sommeil la veille du talk 🤞🏻.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://cloudnord.fr/sessions/cmot0gtb7007v01px4uq00kxl/" target="_blank" rel="noopener"
&gt;La page de la session est ici&lt;/a&gt; et &lt;a class="link" href="https://www.helloasso.com/associations/cloud-nord/evenements/cloudf-nord-2026" target="_blank" rel="noopener"
&gt;la billetterie est ouverte&lt;/a&gt; (ne trainez pas).&lt;/p&gt;
&lt;h2 id="devfest-toulouse-2026--ah-que-coucou-"&gt;DevFest Toulouse 2026 — &amp;ldquo;Ah que coucou !&amp;rdquo;
&lt;/h2&gt;&lt;p&gt;Je l&amp;rsquo;ai déjà dit, mais vous l&amp;rsquo;avez peut être loupé : je serai AUSSI au &lt;strong&gt;DevFest Toulouse 2026&lt;/strong&gt;, le &lt;strong&gt;19 novembre au Diagora de Labège&lt;/strong&gt;, pour un quickie absolument sérieux (non) : &lt;strong&gt;&amp;ldquo;Ah que coucou !&amp;rdquo; Recréer la boîte des Guignols avec ML embarqué, RaspberryPi et impression 3D&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Je vais y raconter le &lt;a class="link" href="https://github.com/zwindler/a-que-coucou" target="_blank" rel="noopener"
&gt;projet open source &amp;ldquo;a-que-coucou&amp;rdquo;&lt;/a&gt; que j&amp;rsquo;ai déjà présenté &lt;a class="link" href="https://blog.zwindler.fr/2026/07/17/non-pas-un-mais-deux-projets-melant-impression-3d-et-electronique-embarquee/" &gt;dans un article ici&lt;/a&gt; : une boîte à coucou pilotée par reconnaissance vocale, qui pousse un œuf quand quelqu&amp;rsquo;un dit &amp;ldquo;ah que coucou&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Pitch :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Vous vous souvenez des Guignols de l&amp;rsquo;info ? La marionnette de Johnny Hallyday, la boîte mystérieuse, et l&amp;rsquo;œuf qui sort dès que quelqu&amp;rsquo;un dit &amp;ldquo;ah que coucou&amp;rdquo; ? J&amp;rsquo;ai décidé de construire cet objet pour de vrai. Avec un RPi 5, quelques composants électroniques, beaucoup de câbles et une imprimante 3D.&lt;/p&gt;
&lt;p&gt;Dans ce talk, je vous montre comment construire cet objet de bout en bout : reconnaissance d&amp;rsquo;un wake word en français sans réseau, conception de toutes les pièces mécaniques en OpenSCAD, et orchestration sur un Raspberry Pi 5.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est un projet maker qui touche à tout. Vous repartirez en sachant comment implémenter un wake word offline, comment concevoir un objet physique piloté par du code, et surtout quels pièges éviter quand on mélange ML, audio et hardware.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Le projet est full open source en &lt;strong&gt;MPL 2.0&lt;/strong&gt;, avec le code Python, les fichiers CAO pour OpenSCAD/3D, et un script de déploiement automatique. Le tout est sur &lt;a class="link" href="https://github.com/zwindler/a-que-coucou" target="_blank" rel="noopener"
&gt;github.com/zwindler/a-que-coucou&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://devfesttoulouse.fr/fr/conferences/ah-que-coucou-recreer-la-boite-des-guignols-avec-ml-embarque-raspberrypi-et-impression-3d" target="_blank" rel="noopener"
&gt;La page de la session est là&lt;/a&gt; et &lt;a class="link" href="https://www.billetweb.fr/shop.php?event=devfest-toulouse-2026" target="_blank" rel="noopener"
&gt;les billets sont par ici&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="et-bdx-io-"&gt;Et BDX I/O ?
&lt;/h2&gt;&lt;p&gt;Il y a une troisième chose qui mijote, peut-être.&lt;/p&gt;
&lt;p&gt;Peut-être que je serai à &lt;strong&gt;BDX I/O&lt;/strong&gt; pour un autre projet, mais pas en tant que speaker cette fois.&lt;/p&gt;
&lt;p&gt;Je n&amp;rsquo;en dis pas plus. Rien n&amp;rsquo;est fait.&lt;/p&gt;
&lt;h2 id="venez-me-faire-ah-que-coucou"&gt;Venez me faire (ah que) coucou
&lt;/h2&gt;&lt;p&gt;Si vous êtes dans le coin pour l&amp;rsquo;une de ces deux conférences, venez me voir. Que ce soit pour parler Kubernetes, Raspberry Pi, impression 3D, wake words, open source, ou juste prendre un coca zero, ça me fera vraiment plaisir.&lt;/p&gt;
&lt;p&gt;See you there !&lt;/p&gt;</description></item><item><title>Non pas un, mais deux projets mêlant impression 3D et électronique embarquée</title><link>https://blog.zwindler.fr/2026/07/17/non-pas-un-mais-deux-projets-melant-impression-3d-et-electronique-embarquee/</link><pubDate>Fri, 17 Jul 2026 09:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/07/17/non-pas-un-mais-deux-projets-melant-impression-3d-et-electronique-embarquee/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/07/alerte-rouge.webp" alt="Featured image of post Non pas un, mais deux projets mêlant impression 3D et électronique embarquée" /&gt;&lt;p&gt;Après un silence quasi religieux depuis 1 mois et demi, vous vous demandez peut-être (ou non) ce que je fais de mes après-midi ?&lt;/p&gt;
&lt;p&gt;Et bien je vais vous le dire (non non, ne partez pas)&lt;/p&gt;
&lt;p&gt;En réalité, j&amp;rsquo;ai enchaîné deux projets &amp;ldquo;maker&amp;rdquo; qui méritent que je m&amp;rsquo;y attarde : une &lt;strong&gt;boîte à coucou&lt;/strong&gt; pilotée par reconnaissance vocale, et un &lt;strong&gt;gyrophare connecté&lt;/strong&gt; en open source. Et les deux partagent un ADN commun : impression 3D, OpenSCAD et de bidouille électronique du dimanche.&lt;/p&gt;
&lt;h2 id="à-que-coucou-"&gt;À que coucou !
&lt;/h2&gt;&lt;p&gt;Alors forcément, ça ne va pas parler aux plus jeunes d&amp;rsquo;entre vous&amp;hellip; Mais dans les années 90, dans l&amp;rsquo;émission &amp;ldquo;Les guignols de l&amp;rsquo;info&amp;rdquo;, il y avait un running gag qui faisait rire &amp;ldquo;tout le monde&amp;rdquo;. La marionnette de Johnny Hallyday apportait une boîte noire, de laquelle sortait un coucou quand on prononçait &amp;ldquo;Ah que coucou&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/07/sketch.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;En réalité, c&amp;rsquo;est un side project qui traîne dans un coin de ma tête depuis au moins 2021 (essayé puis abandonné) et qui n&amp;rsquo;a pris vie que cette année.&lt;/p&gt;
&lt;p&gt;Sur le papier, le principe est simple (hinhinhin, oui super simple, vraiment&amp;hellip; non) :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Un microphone écoute en permanence&lt;/li&gt;
&lt;li&gt;Quand &amp;ldquo;ah que coucou&amp;rdquo; est détecté, un moteur actionne un bidule qui pousse un &lt;strong&gt;œuf&lt;/strong&gt; hors de la boîte&lt;/li&gt;
&lt;li&gt;Un haut-parleur joue &amp;ldquo;coucou !&amp;rdquo;&lt;/li&gt;
&lt;li&gt;Après quelques dixièmes de secondes, l&amp;rsquo;œuf rentre&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/07/capture_video.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Hardware (ça parlera aux amateurs) : un Raspberry Pi 5, un servo TowerPro SG90, un micro INMP441 I2S, un ampli MAX98357A I2S et une enceinte 4Ω 3W. Une breadboard 170pts, des Dupont, et environ &lt;strong&gt;30 €&lt;/strong&gt; de composants (hors Pi et imprimante).&lt;/p&gt;
&lt;p&gt;Le Pi 5 fait tourner un orchestrateur Python qui utilise &lt;strong&gt;openWakeWord&lt;/strong&gt; (ONNX Runtime) pour la détection (comme Home Assistant). Le modèle &amp;ldquo;ah que coucou&amp;rdquo; a été généré avec Coqui TTS. Le tout tourne sans internet avec une latence &amp;lt; 500ms. Cette partie était assez technique, d&amp;rsquo;autant que j&amp;rsquo;ai fait l&amp;rsquo;entraînement avec mon GPU (de quoi justifier ma RTX 5080 ? Pas sûr).&lt;/p&gt;
&lt;p&gt;Mécanique : tout est conçu dans OpenSCAD&amp;hellip; la boîte, l&amp;rsquo;œuf, le pignon de 40mm, la crémaillère de 110mm, le rail et le support servo. Ça s&amp;rsquo;imprime en 3D sans difficulté et ça se monte sans vis.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/07/pinion_rack.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Le projet est en MPL 2.0 sur &lt;strong&gt;&lt;a class="link" href="https://github.com/zwindler/a-que-coucou" target="_blank" rel="noopener"
&gt;github.com/zwindler/a-que-coucou&lt;/a&gt;&lt;/strong&gt;, avec tout le code Python, les STL/SCAD, et un script de déploiement automatisé.&lt;/p&gt;
&lt;p&gt;Et j&amp;rsquo;en parlerai plus en détails &lt;a class="link" href="https://devfesttoulouse.fr/fr/conferences/ah-que-coucou-recreer-la-boite-des-guignols-avec-ml-embarque-raspberrypi-et-impression-3d" target="_blank" rel="noopener"
&gt;au DevFest Toulouse pour un lightning talk en novembre&lt;/a&gt;, et peut-être sous un autre format (suspense) si j&amp;rsquo;arrive à passer entre les mailles des SPAM emails (celui qui sait, saura).&lt;/p&gt;
&lt;h2 id="alerte-rouge--mon-blinky-à-moi"&gt;Alerte Rouge — mon Blinky à moi
&lt;/h2&gt;&lt;p&gt;Vous vous souvenez de ma &lt;a class="link" href="https://blog.zwindler.fr/2023/06/16/reverse-blinky-enix-part1/" &gt;série de reverse du Blinky ENIX&lt;/a&gt; en 2023 ? Le concept était excellent, mais c&amp;rsquo;était un produit fermé avec un firmware propriétaire et un SaaS obligatoire, qui a fermé depuis, faute de marché.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;avais besoin d&amp;rsquo;un machin qui clignotte pour me dire que OpenCode ou Claude Code attend une réponse de ma part ou qui me prévienne quand la prod est pétée. Je me suis dit : pourquoi ne pas en refaire un moi-même, en open source ?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Alerte Rouge&lt;/strong&gt; est donc, comme Blinky, un gyrophare connecté sur &lt;strong&gt;ESP32 + NeoPixel ring WS2812&lt;/strong&gt;, avec un boîtier imprimé en 3D.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/07/preview-assembly-side.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Hardware : un ESP32 (n&amp;rsquo;importe quel devkit), un anneau 16 LEDs en 60mm, un bouton poussoir, un condo 470µF et deux résistances. Câblage en 5 minutes.&lt;/p&gt;
&lt;p&gt;Firmware : écrit au-dessus de MicroPython (le REPL c&amp;rsquo;est hyper pratique pour itérer), avec &lt;strong&gt;16 patterns lumineux&lt;/strong&gt; (flash_red, kitt, police, gyrophare, rainbow, comet, wave, theater, strobe… oui je me suis fait plaisir), une API REST, un webhook Prometheus Alertmanager, et même une page web de config embarquée. La pile d&amp;rsquo;alertes est en LIFO avec timeout et acquittement physique.&lt;/p&gt;
&lt;p&gt;Boîtier : conçu dans OpenSCAD, base + dôme paramétriques, imprimable sans supports.&lt;/p&gt;
&lt;p&gt;Le tout en AUSSI en MPL 2.0 (je l&amp;rsquo;aime d&amp;rsquo;amour cette licence) sur &lt;strong&gt;&lt;a class="link" href="https://github.com/zwindler/alerte-rouge" target="_blank" rel="noopener"
&gt;github.com/zwindler/alerte-rouge&lt;/a&gt;&lt;/strong&gt;, avec le firmware, les CAO, un Makefile pour flasher/déployer en une commande, et les schémas de câblage.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai donc fait un gros tunnel pour sortir deux projets que tout rassemble :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OpenSCAD pour toutes les pièces. Coder ses modèles plutôt que les dessiner dans Blender, ça change la vie.&lt;/li&gt;
&lt;li&gt;Hardware simple. Une breadboard, quelques câbles, pas ou peu de soudure&lt;/li&gt;
&lt;li&gt;Open source (MPL 2.0) pour que vous puissiez les reproduire, les améliorer, ou les forker
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/zwindler/a-que-coucou" target="_blank" rel="noopener"
&gt;zwindler/a-que-coucou&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/zwindler/alerte-rouge" target="_blank" rel="noopener"
&gt;zwindler/alerte-rouge&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Have fun&lt;/p&gt;</description></item><item><title>zeropod v0.12.0 : un an après, le scale-to-zero tient-il ses promesses ?</title><link>https://blog.zwindler.fr/2026/05/30/zeropod-v12-scale-to-zero-checkpointing-retour/</link><pubDate>Sat, 30 May 2026 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/05/30/zeropod-v12-scale-to-zero-checkpointing-retour/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/05/zeropod.webp" alt="Featured image of post zeropod v0.12.0 : un an après, le scale-to-zero tient-il ses promesses ?" /&gt;&lt;h2 id="quest-ce-que-zeropod-"&gt;Qu&amp;rsquo;est-ce que zeropod ?
&lt;/h2&gt;&lt;p&gt;Il y a un peu moins d&amp;rsquo;un an, j&amp;rsquo;avais &lt;a class="link" href="https://blog.zwindler.fr/2025/06/20/zeropod-scale-to-zero-kubernetes-checkpointing" &gt;publié un premier article&lt;/a&gt; sur un projet open source qui s&amp;rsquo;apelle zeropod.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Zeropod is a Kubernetes runtime (more specifically a containerd shim) that automatically checkpoints containers to disk after a certain amount of time of the last TCP connection.&lt;/p&gt;
&lt;p&gt;While in scaled down state, it will listen on the same port the application inside the container was listening on and will restore the container on the first incoming connection.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;À l&amp;rsquo;époque, la version stable était la v0.6.x, et j&amp;rsquo;avais testé sur un cluster k3s. Les résultats étaient mitigés : ça marchait dans les grandes lignes, mais avec des limitations rédhibitoires pour une utilisation un peu sérieuse (probes impossibles, comportement flaky sous charge, temps de checkpointing un peu élevés à mon gout).&lt;/p&gt;
&lt;p&gt;Entre temps, le projet a pas mal évolué (maintenant v0.12.0), avec des promesses de corrections et d&amp;rsquo;améliorations. J&amp;rsquo;ai donc remis le couvert pour voir où on en est vraiment.&lt;/p&gt;
&lt;p&gt;Autre changement : j&amp;rsquo;ai abandonné k3s pour cette série de tests, pour des raisons que je détaillerai au fil de l&amp;rsquo;article.&lt;/p&gt;
&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;p&gt;Cette fois-ci, j&amp;rsquo;ai utilisé un serveur fraîchement provisionné chez mon hébergeur préféré :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un serveur Ubuntu 24.04.3 LTS (Noble), kernel 6.17.0-35 HWE, 7.7 Gi RAM, 100G disk&lt;/li&gt;
&lt;li&gt;Un cluster Kubernetes monté avec &lt;strong&gt;kubeadm&lt;/strong&gt;, flannel comme CNI&lt;/li&gt;
&lt;li&gt;containerd vanilla&lt;/li&gt;
&lt;li&gt;local-path-provisioner (Rancher) pour avoir un stockage local&lt;/li&gt;
&lt;li&gt;Pas de cert-manager ni d&amp;rsquo;Ingress, on va au plus simple cette fois&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="installation"&gt;Installation
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai déjà installé kubeadm plein de fois et certainement que vous aussi donc je ne vous fait pas l&amp;rsquo;affront de refaire un énième tuto.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Installer kubeadm, kubelet, kubectl + containerd&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sudo kubeadm init --pod-network-cidr&lt;span class="o"&gt;=&lt;/span&gt;10.42.0.0/16
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl taint nodes --all node-role.kubernetes.io/control-plane-
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fois le cluster fonctionnel, l&amp;rsquo;installation de zeropod est triviale :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Appliquer le kustomize générique&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -k https://github.com/ctrox/zeropod/config/generic
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Ajouter un label à notre unique node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl label node &amp;lt;votre-node&amp;gt; zeropod.ctrox.dev/node&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Vérifier que le pod tourne&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl -n zeropod-system &lt;span class="nb"&gt;wait&lt;/span&gt; --for&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;condition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Ready pod -l app.kubernetes.io/name&lt;span class="o"&gt;=&lt;/span&gt;zeropod-node
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est tout. Pas de flag spécial, pas de configuration supplémentaire. Sur kubeadm, kubelet est un binaire natif (&lt;code&gt;/usr/bin/kubelet&lt;/code&gt;) détecté automatiquement par zeropod.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note sur k3s&lt;/strong&gt; : sur k3s, la documentation de zeropod est un peu différente puisque la config à utiliser est &lt;code&gt;config/k3s&lt;/code&gt;. Le kustomize ajoute un flag &lt;code&gt;-probe-binary-name=k3s&lt;/code&gt; sur l&amp;rsquo;initContainer pour que le shim sache que le kubelet est embarqué dans le binaire k3s. Lors de mes tests, j&amp;rsquo;ai constaté que même avec ce flag, le comportement n&amp;rsquo;était pas celui attendu (le socket tracker ne filtrait pas correctement les probes). Avec la config par défaut, le flag est sur l&amp;rsquo;initContainer mais pas sur le manager. J&amp;rsquo;avais suspecté qu&amp;rsquo;il y avait un autre composant à patcher, mais je n&amp;rsquo;ai pas creusé plus que ça.&lt;/p&gt;
&lt;p&gt;Le DaemonSet déploie les images suivantes :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ghcr.io/ctrox/zeropod-manager:v0.12.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ghcr.io/ctrox/zeropod-installer:v0.12.0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ghcr.io/ctrox/zeropod-criu:v4.2&lt;/code&gt; (CRIU a été mis à jour depuis v0.6.x)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Vérifions que la runtimeClass zeropod est bien disponible :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get runtimeclass
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME HANDLER AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;zeropod zeropod 30m
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="premier-test--nginx"&gt;Premier test : nginx
&lt;/h2&gt;&lt;p&gt;Comme la dernière fois, commençons par un test simple avec nginx. On déploie un pod tout bête :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apps/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;zeropod.ctrox.dev/scaledown-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;10s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runtimeClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;zeropod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le passage important, c&amp;rsquo;est l&amp;rsquo;annotation &lt;code&gt;zeropod.ctrox.dev/scaledown-duration: 10s&lt;/code&gt; et &lt;code&gt;runtimeClassName: zeropod&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Peu après le déploiement, zeropod détecte l&amp;rsquo;absence de trafic. Le container est checkpointé. Si on regarde les logs du manager :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2026-05-29T14:26:37.269453861Z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;level&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;INFO&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;status event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;container&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;nginx&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;pod&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;nginx-bench-7c65c8874-6pts7&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;phase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;RUNNING&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;duration&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0s&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Puis 10 secondes plus tard :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2026-05-29T14:26:47.465510937Z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;level&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;INFO&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;status event&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;container&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;nginx&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;pod&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;nginx-bench-7c65c8874-6pts7&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;phase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SCALED_DOWN&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;duration&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;191.259383ms&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le champ &lt;code&gt;duration&lt;/code&gt; dans le log SCALED_DOWN correspond au temps de checkpoint : &lt;strong&gt;191ms&lt;/strong&gt;. C&amp;rsquo;est déjà nettement mieux que les ~400ms de v0.6.x sur k3s (probablement grâce à une mise à jour, peut être CRIU 4.2 ?).&lt;/p&gt;
&lt;h2 id="restauration"&gt;Restauration
&lt;/h2&gt;&lt;p&gt;Quand on envoie une requête HTTP, le container se réveille :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;time&lt;/span&gt; curl http://&amp;lt;POD_IP&amp;gt;/ -s -o /dev/null -w &lt;span class="s2"&gt;&amp;#34;%{http_code} (%{time_total}s)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;0.101s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;real 0m0.101s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;user 0m0.003s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sys 0m0.003s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;101ms&lt;/strong&gt; pour restaurer nginx et servir une page. C&amp;rsquo;est comparable aux ~92ms de l&amp;rsquo;article original.&lt;/p&gt;
&lt;p&gt;Petite nouveauté : sous v0.6.x, &lt;code&gt;kubectl top pods&lt;/code&gt; retournait une erreur pour les pods checkpointés :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# v0.6.x&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl top pods
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;error: Metrics not available &lt;span class="k"&gt;for&lt;/span&gt; pod default/php-xxx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ce bug a été corrigé dans la v0.9.0. Désormais, les pods en SCALED_DOWN retournent &lt;code&gt;0m 0Mi&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl top pods
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME CPU&lt;span class="o"&gt;(&lt;/span&gt;cores&lt;span class="o"&gt;)&lt;/span&gt; MEMORY&lt;span class="o"&gt;(&lt;/span&gt;bytes&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nginx 0m 0Mi
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est plus élégant.&lt;/p&gt;
&lt;h2 id="test-des-liveness-probes"&gt;Test des liveness probes
&lt;/h2&gt;&lt;p&gt;C&amp;rsquo;était LA limitation majeure de la v0.6.x : zeropod était incompatible avec les probes Kubernetes. Si vous mettiez une liveness probe httpGet sur votre container, la sonde déclenchait le timer de scale-down, et votre container ne passait jamais en SCALED_DOWN. Résultat : probes inutilisables, donc zeropod inutilisable en production.&lt;/p&gt;
&lt;h3 id="ce-qui-a-changé"&gt;Ce qui a changé
&lt;/h3&gt;&lt;p&gt;Deux correctifs ont été apportés entre la v0.6.x et la v0.12.0 :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;L&amp;rsquo;activator&lt;/strong&gt; (le composant eBPF qui écoute le trafic pendant le SCALED_DOWN) : il intercepte les probes et répond 200 directement, sans réveiller le container. Ça, c&amp;rsquo;est le comportement &amp;ldquo;après scale-down&amp;rdquo;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Le socket tracker&lt;/strong&gt; (le composant qui ignore les connexions pendant l&amp;rsquo;état RUNNING) : depuis la PR #72 (merged août 2025), zeropod est capable de détecter les connexions provenant du kubelet et de ne pas les compter comme du &amp;ldquo;vrai&amp;rdquo; trafic. Ça, c&amp;rsquo;est le comportement &amp;ldquo;avant scale-down&amp;rdquo;.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="test-grandeur-nature"&gt;Test grandeur nature
&lt;/h3&gt;&lt;p&gt;J&amp;rsquo;ai déployé nginx avec une liveness probe agressive (periodSeconds: 5) et un &lt;code&gt;scaledown-duration: 10s&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runtimeClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;zeropod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;livenessProbe&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;httpGet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;periodSeconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sur k3s, ce test m&amp;rsquo;avait donné du fil à retordre : le socket tracker n&amp;rsquo;arrivait pas à filtrer les connexions des probes, qui resettaient le timer de scale-down en permanence. L&amp;rsquo;activator par contre (qui filtre les probes une fois le scale down passé) fonctionnait.&lt;/p&gt;
&lt;p&gt;Sur kubeadm, le résultat est immédiat :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pod -l &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nginx-probe -o json &lt;span class="p"&gt;|&lt;/span&gt; jq -r &lt;span class="s1"&gt;&amp;#39;.items[0].metadata.labels[&amp;#34;status.zeropod.ctrox.dev/nginx&amp;#34;]&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SCALED_DOWN
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le socket tracker filtre correctement les connexions du kubelet natif. La règle &lt;code&gt;periodSeconds&lt;/code&gt; &amp;gt; &lt;code&gt;scaledown-duration&lt;/code&gt; que j&amp;rsquo;avais dû utiliser sur k3s n&amp;rsquo;est plus nécessaire.&lt;/p&gt;
&lt;h2 id="un-mot-sur-les-évolutions-entre-v06x-et-v0120"&gt;Un mot sur les évolutions entre v0.6.x et v0.12.0
&lt;/h2&gt;&lt;p&gt;Le tableau des améliorations entre les deux versions :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Point&lt;/th&gt;
&lt;th&gt;v0.6.x&lt;/th&gt;
&lt;th&gt;v0.12.0&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;kubectl top pods&lt;/code&gt; en scaled-down&lt;/td&gt;
&lt;td&gt;❌ Erreur&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;0m 0Mi&lt;/code&gt; (fix v0.9.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checkpoint (nginx)&lt;/td&gt;
&lt;td&gt;~400ms&lt;/td&gt;
&lt;td&gt;~185ms (-54%)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restauration (nginx)&lt;/td&gt;
&lt;td&gt;~92ms&lt;/td&gt;
&lt;td&gt;~99ms (stable)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CRIU&lt;/td&gt;
&lt;td&gt;v3.x&lt;/td&gt;
&lt;td&gt;v4.2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gestion d&amp;rsquo;échec checkpoint&lt;/td&gt;
&lt;td&gt;basique&lt;/td&gt;
&lt;td&gt;métriques + events (v0.11.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proxy timeouts configurables&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;✅ (v0.11.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Migration inter-node&lt;/td&gt;
&lt;td&gt;basique&lt;/td&gt;
&lt;td&gt;améliorée + timeouts (v0.10.0)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Probes&lt;/td&gt;
&lt;td&gt;❌ Incompatible&lt;/td&gt;
&lt;td&gt;✅ Activator + socket tracker&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Note technique intéressante sur les flags CRIU : zeropod a retiré l&amp;rsquo;option &lt;code&gt;--tcp-established&lt;/code&gt; en septembre 2025. Auparavant, les connexions TCP actives étaient sauvegardées et restaurées avec le container. Désormais, zeropod utilise &lt;code&gt;--tcp-skip-in-flight&lt;/code&gt; (quand runc &amp;gt;= 1.3 le supporte). Conséquence pratique : si votre container a des connexions TCP sortantes au moment du checkpoint, elles seront perdues. Il faudra les rétablir après restore.&lt;/p&gt;
&lt;h2 id="déploiement-de-wordpress-le-cas-dusage-réaliste-trollface"&gt;Déploiement de WordPress (le cas d&amp;rsquo;usage réaliste :trollface:)
&lt;/h2&gt;&lt;p&gt;Bon, nginx c&amp;rsquo;est bien, mais c&amp;rsquo;est pas très représentatif. Déployons une vraie app avec du PHP, Apache et une base de données.&lt;/p&gt;
&lt;p&gt;Reprenons le manifest WordPress de l&amp;rsquo;article original, sans zeropod sur MySQL :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apps/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wordpress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wordpress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;zeropod.ctrox.dev/scaledown-duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;10s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;zeropod.ctrox.dev/container-names&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wordpress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;zeropod.ctrox.dev/ports-map&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wordpress=80&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wordpress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runtimeClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;zeropod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;initContainers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wait-for-mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql:8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- -&lt;span class="l"&gt;c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; until mysql -h mysql -u root -p${MYSQL_ROOT_PASSWORD} -e &amp;#34;SELECT 1&amp;#34;; do
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;Waiting for MySQL...&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; sleep 3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; done
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; mysql -h mysql -u root -p${MYSQL_ROOT_PASSWORD} -e &amp;#34;CREATE DATABASE IF NOT EXISTS wordpress;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;verySecurePassword&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wordpress:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wordpress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;WORDPRESS_DB_HOST&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;WORDPRESS_DB_USER&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;WORDPRESS_DB_PASSWORD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;verySecurePassword&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;WORDPRESS_DB_NAME&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;wordpress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;MySQL (sans zeropod) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3306&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;clusterIP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;None&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;apps/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;StatefulSet&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serviceName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;replicas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;selector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;matchLabels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql:8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;containerPort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3306&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MYSQL_ROOT_PASSWORD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;verySecurePassword&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/var/lib/mysql&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeClaimTemplates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessModes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ReadWriteOnce&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;5Gi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le checkpoint de WordPress (Apache + PHP) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2026-05-29T14:33:35.915988635Z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;phase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SCALED_DOWN&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;duration&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;313.286787ms&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Les checkpoints mettent autour de 300ms. La restauration est autour de 200 ms :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;time&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2026-05-29T14:34:31.56806589Z&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;phase&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;RUNNING&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;duration&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;206.33005ms&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;La première requête curl confirme :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;time&lt;/span&gt; curl http://&amp;lt;POD_IP&amp;gt;/ -s -o /dev/null -w &lt;span class="s2"&gt;&amp;#34;%{http_code} (%{time_total}s)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;0.212s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;212ms, c&amp;rsquo;est environ &lt;strong&gt;2 fois plus rapide&lt;/strong&gt; que les 454ms de l&amp;rsquo;article original.&lt;/p&gt;
&lt;h2 id="et-maintenant-le-test-qui-tue--cascade-wordpress--mysql"&gt;Et maintenant, le test qui tue : cascade WordPress + MySQL
&lt;/h2&gt;&lt;p&gt;Dans l&amp;rsquo;article original, j&amp;rsquo;avais tenté le test ultime : mettre &lt;strong&gt;les deux&lt;/strong&gt; pods (WordPress ET MySQL) avec &lt;code&gt;runtimeClassName: zeropod&lt;/code&gt;, puis envoyer une requête HTTP sur WordPress pendant que les deux sont checkpointés.&lt;/p&gt;
&lt;p&gt;Le scénario est le suivant :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Au bout de quelques secondes, WordPress est SCALED_DOWN, MySQL est SCALED_DOWN aussi&lt;/li&gt;
&lt;li&gt;Un client envoie une requête HTTP à WordPress&lt;/li&gt;
&lt;li&gt;L&amp;rsquo;activator de WordPress intercepte la requête et restore le container WordPress&lt;/li&gt;
&lt;li&gt;WordPress (Apache+PHP) démarre, exécute le code PHP qui nécessite une connexion MySQL&lt;/li&gt;
&lt;li&gt;WordPress se connecte à MySQL sur le port 3306&lt;/li&gt;
&lt;li&gt;L&amp;rsquo;activator de MySQL intercepte la connexion et restore MySQL&lt;/li&gt;
&lt;li&gt;MySQL répond, WordPress génère la page, Apache renvoie la réponse HTTP&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Du vrai scale to zero qui scale toute l&amp;rsquo;app, pas juste le front/back.&lt;/p&gt;
&lt;p&gt;Note importante : je sais que vous ne voudrez probablement &lt;strong&gt;jamais&lt;/strong&gt; scaler votre base de données à zéro en production, mais ce test montre que cette approche (eBPF + CRIU) fonctionne au-delà du simple scale-to-zero de serveurs web, ce que d&amp;rsquo;autres outils sur le marché font déjà très bien.&lt;/p&gt;
&lt;h2 id="sur-k3s--pas-de-chance-sur-kubeadm--victoire"&gt;Sur k3s : pas de chance, sur kubeadm : victoire
&lt;/h2&gt;&lt;p&gt;Sur k3s, je n&amp;rsquo;ai pas encore réussi à faire fonctionner ce scénario : WordPress se restaurait mais ne répondait pas sur le port 80. J&amp;rsquo;ai passé pas mal de temps à essayer de comprendre, sans succès.&lt;/p&gt;
&lt;p&gt;Même test, même version de zeropod, mais sur kubeadm :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl http://&amp;lt;POD_IP&amp;gt;/ -s -o /dev/null -w &lt;span class="s2"&gt;&amp;#34;%{http_code} (%{time_total}s)&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;HTTP &lt;span class="m"&gt;302&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;0.224s&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;224ms.&lt;/strong&gt; Les deux pods sont passés de SCALED_DOWN à RUNNING, la page WordPress s&amp;rsquo;affiche. J&amp;rsquo;ai répété le test 5 fois de suite :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Cycle&lt;/th&gt;
&lt;th&gt;Temps (curl)&lt;/th&gt;
&lt;th&gt;HTTP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;229ms&lt;/td&gt;
&lt;td&gt;302&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;192ms&lt;/td&gt;
&lt;td&gt;302&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;227ms&lt;/td&gt;
&lt;td&gt;302&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;230ms&lt;/td&gt;
&lt;td&gt;302&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;211ms&lt;/td&gt;
&lt;td&gt;302&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Les logs zeropod confirment le réveil des deux containers :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;WordPress: SCALED_DOWN → RUNNING
MySQL: SCALED_DOWN → RUNNING
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="ce-qui-a-vraiment-changé-depuis-v06x"&gt;Ce qui a vraiment changé depuis v0.6.x
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Les probes (enfin !)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;était mon grief principal dans le premier article. Aujourd&amp;rsquo;hui, c&amp;rsquo;est résolu :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Avant : impossible de mettre des probes Kubernetes → zeropod inutilisable dans un usecase un minimum sérieux&lt;/li&gt;
&lt;li&gt;Après : l&amp;rsquo;activator intercepte les probes en SCALED_DOWN (répond 200 sans restore), et le socket tracker filtre les connexions kubelet en RUNNING&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;La stabilité&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Le comportement flaky (perte de pods sous charge simultanée) a disparu. Là où j&amp;rsquo;avais des échecs sous l&amp;rsquo;article original, les tests de charge séquentiels et simultanés passent tous.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Les performances&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Le checkpoint a gagné ~50% en vitesse (185ms vs 400ms). La restauration aussi (200ms vs 454ms). Merci CRIU v4.2 et les optimisations de zeropod.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;kubectl top pods&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ce petit détail faisait tache — &lt;code&gt;kubectl top pods&lt;/code&gt; plantait sur les pods checkpointés. Corrigé en v0.9.0.&lt;/p&gt;
&lt;h2 id="mais-quelques-limitations-résiduelles"&gt;Mais quelques limitations résiduelles
&lt;/h2&gt;&lt;p&gt;Je serais malhonnête si je disais que tout est parfait. Voilà ce qui reste problématique :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;depuis septembre 2025, zeropod n&amp;rsquo;utilise plus &lt;code&gt;--tcp-established&lt;/code&gt; pour CRIU. Ça veut dire que si votre container a des connexions TCP sortantes au moment du checkpoint, elles seront perdues. Dans la pratique, pour un serveur web, ça veut dire qu&amp;rsquo;il faut reconnecter la base de données après restore. Avec zeropod, la connexion MySQL est refaite automatiquement (la requête PHP en cours échoue, la suivante réussit). C&amp;rsquo;est un détail qui peut avoir son importance selon les cas.&lt;/li&gt;
&lt;li&gt;Je n&amp;rsquo;ai testé que WordPress (Apache + mod_php) et MySQL. Les applications avec des websockets, du streaming, ou des connexions longues pourraient se comporter différemment.&lt;/li&gt;
&lt;li&gt;J&amp;rsquo;ai eu des difficultés à le faire correctement fonctionner sur k3s.&lt;/li&gt;
&lt;li&gt;J&amp;rsquo;ai observé un glitch (segfault Apache) sur le premier restore d&amp;rsquo;un pod WordPress fraîchement créé. Ce n&amp;rsquo;est pas reproductible après un cycle checkpoint/restore normal, mais si vous redéployez souvent vos pods, vous pourriez le rencontrer.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="verdict"&gt;Verdict
&lt;/h2&gt;&lt;p&gt;Un an après, zeropod me parait un peu plus tenir ses promesses :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Même si ce n&amp;rsquo;était pas rédhibitoire, le performances de checkpoint/restore se sont nettement améliorées (~50% plus rapide), ce qui est toujours bon à prendre&lt;/li&gt;
&lt;li&gt;Le support des probes Kubernetes a été ajouté, le blocage principal selon moi est levé&lt;/li&gt;
&lt;li&gt;La stabilité générale est meilleure&lt;/li&gt;
&lt;li&gt;Le test cascade (WordPress + MySQL) fonctionne&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je reste toujours aussi hypé par l&amp;rsquo;idée qu&amp;rsquo;on puisse freezer un container et le restaurer 10 secondes plus tard comme si de rien n&amp;rsquo;était. La magie de CRIU et d&amp;rsquo;eBPF combinés commence à se matérialiser, après des années d&amp;rsquo;attente.&lt;/p&gt;
&lt;p&gt;Est-ce que je mettrais ça en production ? Disons que c&amp;rsquo;est moins risqué qu&amp;rsquo;il y a un an. Pour rigoler sur mon cluster perso, pourquoi pas. Pour une base de données en prod avec des connexions longues, je pense que je passe toujours mon tour ;-).&lt;/p&gt;
&lt;p&gt;Mais franchement, pour le coup, le projet a bien évolué et mérite qu&amp;rsquo;on s&amp;rsquo;y attarde.&lt;/p&gt;</description></item><item><title>Test de CKE, le Kubernetes managé chez Clever Cloud (partie 2 - stockage)</title><link>https://blog.zwindler.fr/2026/05/24/test-cke-kubernetes-manage-clever-cloud-partie-2/</link><pubDate>Sun, 24 May 2026 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/05/24/test-cke-kubernetes-manage-clever-cloud-partie-2/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/05/logo-cke2026.webp" alt="Featured image of post Test de CKE, le Kubernetes managé chez Clever Cloud (partie 2 - stockage)" /&gt;&lt;h2 id="rappel-de-la-partie-1"&gt;Rappel de la partie 1
&lt;/h2&gt;&lt;p&gt;Dans la &lt;a class="link" href="https://blog.zwindler.fr/2026/05/12/test-cke-kubernetes-manage-clever-cloud-partie-1/" &gt;partie 1&lt;/a&gt;, on avait vu comment créer un cluster CKE, parcouru son anatomie (Exherbo Linux, Cilium, Materia etcd, konnectivity&amp;hellip;), mesuré les temps de boot (~57s pour le control plane, excellent), testé les NodeGroups, et activé le CSI.&lt;/p&gt;
&lt;p&gt;Dans cette deuxième partie, on va creuser ce qui touche au &lt;strong&gt;stockage&lt;/strong&gt; et à des cas d&amp;rsquo;usages &amp;ldquo;un peu&amp;rdquo; avancés : cycle de vie des volumes, resize online, snapshots, et déploiement de vCluster avec données persistantes.&lt;/p&gt;
&lt;h2 id="activer-le-csi-et-monter-son-premier-volume"&gt;Activer le CSI et monter son premier volume
&lt;/h2&gt;&lt;p&gt;Comme on l&amp;rsquo;a vu en partie 1, le CSI n&amp;rsquo;est pas activé par défaut, mais un petit coup de CLI corrige ça rapidement :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clever k8s add-persistent-storage moncluster
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cette commande déploie le provisioner Ceph RBD et crée une StorageClass &lt;code&gt;csi-rbd-sc&lt;/code&gt; qui devient la classe par défaut.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get sc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;csi-rbd-sc &lt;span class="o"&gt;(&lt;/span&gt;default&lt;span class="o"&gt;)&lt;/span&gt; rbd.csi.ceph.com Delete Immediate &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On notera au passage &lt;code&gt;ALLOWVOLUMEEXPANSION: true&lt;/code&gt; : on y reviendra.&lt;/p&gt;
&lt;p&gt;Créons un PVC tout bête :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# pvc.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PersistentVolumeClaim&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mon-pvc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessModes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ReadWriteOnce&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;1Gi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storageClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;csi-rbd-sc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f pvc.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pvc mon-pvc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS VOLUME CAPACITY ACCESS MODES
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mon-pvc Bound pvc-2c2f058c-dc85-4006-865c-3437856462f5 1Gi RWO
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le binding prend environ 30 secondes (le temps que le provisioner crée l&amp;rsquo;image RBD dans le pool Ceph et l&amp;rsquo;attache). Rien de spécial, c&amp;rsquo;est du Kubernetes standard.&lt;/p&gt;
&lt;p&gt;On monte le volume dans un pod, on écrit un fichier, on supprime le pod, on le recrée : les données sont toujours là. Jusque là, pas de surprise.&lt;/p&gt;
&lt;h2 id="vcluster--un-cas-dusage-concret-du-stockage-persistant"&gt;vCluster : un cas d&amp;rsquo;usage concret du stockage persistant
&lt;/h2&gt;&lt;p&gt;Maintenant qu&amp;rsquo;on a du stockage persistant, on peut faire des choses intéressantes. Généralement mon troll préféré, c&amp;rsquo;est de déployer Wordpress parce que évidemment Kubernetes c&amp;rsquo;est la meilleure plateforme pour déployer un Wordpress (/s).&lt;/p&gt;
&lt;p&gt;Bon, cette fois ci j&amp;rsquo;ai décidé d&amp;rsquo;innover un peu et &lt;a class="link" href="https://github.com/loft-sh/vcluster" target="_blank" rel="noopener"
&gt;on va déployer &lt;strong&gt;vCluster&lt;/strong&gt;&lt;/a&gt; (un cluster Kubernetes virtuel qui tourne dans votre cluster).&lt;/p&gt;
&lt;p&gt;Note : à quoi ça sert, me direz vous ? Et bien, les usecases sont pas aussi clairs qu&amp;rsquo;on pourrait penser. vCluster vous donne un cluster Kubernetes complet, isolé, mais qui partage l&amp;rsquo;infrastructure du cluster hôte, y compris le CSI. En théorie ça permet de donner le cluster-admin à des gens sans donner les clés de l&amp;rsquo;infra complète à tout le monde. Je suis pas hyper convaincu par cette approche. Un usecase qui me parait moins dangereux, c&amp;rsquo;est pour des usages de type CI sans devoir monter un cluster complet, mais il y a des limitations aussi.&lt;/p&gt;
&lt;p&gt;Installation :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vcluster create test-vcluster -n vcluster-test
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Deux options pour s&amp;rsquo;y connecter :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 1 —&amp;gt; port-forward&lt;/strong&gt; (le plus simple) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ vcluster connect test-vcluster -n vcluster-test -- kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS ROLES AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;control-plane-c367382a-22b1-4cef-b0cd-372bf83263c4-node Ready &amp;lt;none&amp;gt; 2m
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;vcluster connect&lt;/code&gt; monte un tunnel port-forward via l&amp;rsquo;API server (comme &lt;code&gt;kubectl port-forward&lt;/code&gt;) et exécute la commande dans le contexte du vCluster. Ça marche sur CKE comme sur n&amp;rsquo;importe quel cluster Kubernetes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 2 —&amp;gt; LoadBalancer&lt;/strong&gt; (pour un accès durable sans tunnel) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vcluster delete test-vcluster -n vcluster-test
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vcluster create test-vcluster -n vcluster-test --expose --connect&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le flag &lt;code&gt;--expose&lt;/code&gt; crée un Service LoadBalancer. On récupère l&amp;rsquo;IP et on génère un kubeconfig autonome :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nv"&gt;LB_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;kubectl get svc -n vcluster-test test-vcluster -o &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{.status.loadBalancer.ingress[0].ip}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ vcluster connect test-vcluster -n vcluster-test &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --server https://&lt;span class="nv"&gt;$LB_IP&lt;/span&gt;:443 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --insecure --print &amp;gt; /tmp/kube-vcluster.config
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le flag &lt;code&gt;--insecure&lt;/code&gt; évite les soucis de certificat auto-signé, avec les risques de sécu que ça implique. A vos risques et périls&amp;hellip; 💀&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl --kubeconfig /tmp/kube-vcluster.config get nodes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS ROLES AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;test-vcluster Ready &amp;lt;none&amp;gt; 2m
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ce node test-vcluster contient tout notre cluster virtuel. Maintenant, la partie intéressante : créer un PVC dans le vCluster :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl --kubeconfig /tmp/kube-vcluster.config apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: PersistentVolumeClaim
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: test-pvc
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; accessModes:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - ReadWriteOnce
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; resources:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; requests:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; storage: 1Gi
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; storageClassName: csi-rbd-sc
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;C&amp;rsquo;est là où c&amp;rsquo;est sympa : le vCluster délègue la création du volume au cluster hôte, qui le provisionne via Ceph RBD. On a pas besoin de reconfigurer le CSI, les storage classes, les secrets, etc. C&amp;rsquo;est totalement transparent.&lt;/p&gt;
&lt;p&gt;Petite vérification côté CKE :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pvc -A &lt;span class="p"&gt;|&lt;/span&gt; grep test-vcluster
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vcluster-test pvc-&amp;lt;...&amp;gt; Bound 1Gi RWO csi-rbd-sc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le PVC est bien créé dans le namespace du vCluster sur le cluster hôte.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Overhead mesuré :&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pod&lt;/th&gt;
&lt;th&gt;CPU&lt;/th&gt;
&lt;th&gt;RAM&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test-vcluster-0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;~80m&lt;/td&gt;
&lt;td&gt;~342 Mi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;coredns (vCluster)&lt;/td&gt;
&lt;td&gt;~2m&lt;/td&gt;
&lt;td&gt;~13 Mi&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;C&amp;rsquo;est tout à fait raisonnable. 342 MiB pour un control plane K8s complet, c&amp;rsquo;est dans les clous.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Conclusion vCluster&lt;/strong&gt; : ça marche, y compris avec de la persistance. Si vous avez besoin d&amp;rsquo;isoler des environnements tout en gardant accès au CSI, vCluster sur CKE est une option fonctionnelle.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; : il est aussi possible d&amp;rsquo;exposer le vCluster via un LoadBalancer (flag &lt;code&gt;--expose&lt;/code&gt;) pour un accès direct sans tunnel. Pratique si le vCluster doit rester accessible après la fermeture de votre terminal.&lt;/p&gt;
&lt;h2 id="volume-expansion--agrandir-un-volume-à-chaud"&gt;Volume expansion : agrandir un volume à chaud
&lt;/h2&gt;&lt;p&gt;Vous avez une base de données qui grossit, vous avez provisionné 1Gi au départ et maintenant c&amp;rsquo;est juste. Avec &lt;code&gt;allowVolumeExpansion: true&lt;/code&gt; sur la StorageClass, on peut agrandir le volume sans downtime.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl patch pvc mon-pvc -p &lt;span class="s1"&gt;&amp;#39;{&amp;#34;spec&amp;#34;:{&amp;#34;resources&amp;#34;:{&amp;#34;requests&amp;#34;:{&amp;#34;storage&amp;#34;:&amp;#34;2Gi&amp;#34;}}}}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Côté Ceph, l&amp;rsquo;image RBD est redimensionnée immédiatement. Le PV passe à 2Gi :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME CAPACITY ACCESS MODES STATUS CLAIM
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pvc-&amp;lt;...&amp;gt; 2Gi RWO Bound default/mon-pvc
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mais le PVC côté utilisateur reste à 1Gi et le filesystem XFS n&amp;rsquo;a pas encore été agrandi. Le kubelet finira par détecter le changement et déclencher le &lt;code&gt;NodeExpandVolume&lt;/code&gt; tout seul (la synchronisation est périodique), mais on peut accélérer en supprimant et recréant le pod qui monte le volume :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl delete pod mon-app --force --grace-period&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: Pod
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: mon-app
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; containers:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: app
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; image: alpine
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; command: [&amp;#34;sleep&amp;#34;, &amp;#34;3600&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; volumeMounts:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - mountPath: /data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - name: data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; persistentVolumeClaim:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; claimName: mon-pvc
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dès que le volume est monté, le kubelet détecte qu&amp;rsquo;il a grandi et appelle le driver CSI pour un &lt;code&gt;NodeExpandVolume&lt;/code&gt;. Le filesystem XFS est agrandi à chaud (&lt;code&gt;xfs_growfs&lt;/code&gt;) et le PVC passe effectivement à 2Gi :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl &lt;span class="nb"&gt;exec&lt;/span&gt; mon-app -- df -h /data
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Filesystem Size Used Avail Use% Mounted on
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/dev/rbd0 1.9G 47M 1.9G 1% /data
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pvc mon-pvc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS CAPACITY ACCESS MODES
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mon-pvc Bound 2Gi RWO
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="volumesnapshots--le-plus-technique"&gt;VolumeSnapshots : le plus technique
&lt;/h2&gt;&lt;p&gt;Dernière fonctionnalité, et pas la plus simple : les snapshots de volumes.&lt;/p&gt;
&lt;p&gt;Le driver Ceph RBD supporte les snapshots nativement. On le vérifie en regardant les logs du sidecar &lt;code&gt;csi-snapshotter&lt;/code&gt; au moment de la création :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ kubectl logs -n kube-system deployment/csi-rbdplugin-provisioner -c csi-snapshotter --tail=5
I0524 19:50:53.470970 snapshot_controller.go:339] createSnapshotWrapper: Creating snapshot for content snapcontent-&amp;lt;...&amp;gt; through the plugin ...
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Pas d&amp;rsquo;erreur côté driver, la demande est acceptée et traitée.&lt;/p&gt;
&lt;p&gt;Mais sur CKE, rien n&amp;rsquo;est pré-installé pour utiliser les snapshots. Il faut assembler les briques soi-même :&lt;/p&gt;
&lt;h3 id="étape-1--installer-les-crds-et-le-snapshot-controller"&gt;Étape 1 : installer les CRDs et le snapshot-controller
&lt;/h3&gt;&lt;p&gt;Les CRDs &lt;code&gt;volumesnapshot*&lt;/code&gt; ne sont pas déployées sur CKE. Il faut les installer depuis le projet upstream &lt;a class="link" href="https://github.com/kubernetes-csi/external-snapshotter" target="_blank" rel="noopener"
&gt;external-snapshotter&lt;/a&gt;, ainsi que le &lt;strong&gt;snapshot-controller&lt;/strong&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;v8.4.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# CRDs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/&lt;span class="nv"&gt;$VERSION&lt;/span&gt;/client/config/crd/snapshot.storage.k8s.io_volumesnapshotclasses.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/&lt;span class="nv"&gt;$VERSION&lt;/span&gt;/client/config/crd/snapshot.storage.k8s.io_volumesnapshotcontents.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/&lt;span class="nv"&gt;$VERSION&lt;/span&gt;/client/config/crd/snapshot.storage.k8s.io_volumesnapshots.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Snapshot-controller (RBAC + deployment)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/&lt;span class="nv"&gt;$VERSION&lt;/span&gt;/deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/&lt;span class="nv"&gt;$VERSION&lt;/span&gt;/deploy/kubernetes/snapshot-controller/setup-snapshot-controller.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Deux composants travaillent ensemble :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le &lt;strong&gt;snapshot-controller&lt;/strong&gt; (standalone) écoute les objets &lt;code&gt;VolumeSnapshot&lt;/code&gt; et crée les &lt;code&gt;VolumeSnapshotContent&lt;/code&gt; correspondants&lt;/li&gt;
&lt;li&gt;Le &lt;strong&gt;csi-snapshotter&lt;/strong&gt; (sidecar dans le provisioner) détecte les &lt;code&gt;VolumeSnapshotContent&lt;/code&gt; et appelle le driver Ceph RBD pour créer/supprimer les snapshots&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les deux sont nécessaires.&lt;/p&gt;
&lt;h3 id="étape-2--le-piège-des-groupsnapshot-crds"&gt;Étape 2 : le piège des GroupSnapshot CRDs
&lt;/h3&gt;&lt;p&gt;Une fois les CRDs installées, vous créez votre VolumeSnapshotClass, votre VolumeSnapshot, et&amp;hellip; rien ne se passe. Le VolumeSnapshotContent reste &lt;code&gt;readyToUse: false&lt;/code&gt; indéfiniment.&lt;/p&gt;
&lt;p&gt;Si vous regardez les logs du sidecar &lt;code&gt;csi-snapshotter&lt;/code&gt; dans le provisioner, vous ne voyez &lt;strong&gt;aucune&lt;/strong&gt; tentative de création de snapshot.&lt;/p&gt;
&lt;p&gt;Pourquoi ? Parce que le sidecar est lancé avec un feature gate activé :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;--feature-gates=CSIVolumeGroupSnapshot=true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ce feature gate active le support des &lt;strong&gt;VolumeGroupSnapshot&lt;/strong&gt; (la possibilité de snapshotter plusieurs volumes en une fois). Mais le sidecar attend les CRDs correspondantes pour initialiser son cache. Sans ces CRDs, le cache sync est bloqué, et le controller ne traite &lt;strong&gt;aucun&lt;/strong&gt; VolumeSnapshot, pas même les snapshots unitaires (normaux, pas &amp;ldquo;group&amp;rdquo; quoi).&lt;/p&gt;
&lt;p&gt;La solution : installer les CRDs &lt;code&gt;groupsnapshot*&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/&lt;span class="nv"&gt;$VERSION&lt;/span&gt;/client/config/crd/groupsnapshot.storage.k8s.io_volumegroupsnapshotclasses.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/&lt;span class="nv"&gt;$VERSION&lt;/span&gt;/client/config/crd/groupsnapshot.storage.k8s.io_volumegroupsnapshotcontents.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes-csi/external-snapshotter/&lt;span class="nv"&gt;$VERSION&lt;/span&gt;/client/config/crd/groupsnapshot.storage.k8s.io_volumegroupsnapshots.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Puis redémarrer le provisioner pour que le sidecar recharge sa config. Attention : le provisioner a une &lt;strong&gt;anti-affinity&lt;/strong&gt; qui empêche deux pods de cohabiter sur le même node. Sur un cluster ALL_IN_ONE (1 node), ça bloque le rollout. Il faut supprimer directement l&amp;rsquo;ancien pod :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl delete pod -n kube-system -l &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;csi-rbdplugin-provisioner --force
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ce problème est spécifique aux clusters ALL_IN_ONE. Sur DEDICATED_COMPUTE ou DISTRIBUTED (multi-nodes), l&amp;rsquo;anti-affinity ne sera probablement pas bloquante.&lt;/p&gt;
&lt;h3 id="étape-3--le-secret-ceph-dans-le-volumesnapshotclass"&gt;Étape 3 : le secret Ceph dans le VolumeSnapshotClass
&lt;/h3&gt;&lt;p&gt;Une fois le sidecar relancé, les logs montrent&amp;hellip; une erreur :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;rpc error: code = Internal desc = provided secret is empty
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Le driver Ceph RBD a besoin des credentials Ceph pour créer un snapshot, exactement comme pour créer un volume. Vous les trouverez dans le secret &lt;code&gt;csi-rbd-secret&lt;/code&gt; du namespace &lt;code&gt;kube-system&lt;/code&gt;. La StorageClass les référence déjà pour le provisionning :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;csi.storage.k8s.io/provisioner-secret-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;csi-rbd-secret&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;csi.storage.k8s.io/provisioner-secret-namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kube-system&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Mais &lt;strong&gt;ces paramètres ne sont pas reportés automatiquement dans le VolumeSnapshotClass&lt;/strong&gt;. Il faut les ajouter manuellement :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;snapshot.storage.k8s.io/v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;VolumeSnapshotClass&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;csi-rbd-snapclass&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;rbd.csi.ceph.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;deletionPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Delete&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;clusterID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;&amp;lt;votre-cluster-id&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;csi.storage.k8s.io/snapshotter-secret-name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;csi-rbd-secret&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;csi.storage.k8s.io/snapshotter-secret-namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;kube-system&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le &lt;code&gt;clusterID&lt;/code&gt; est celui de la StorageClass (visible dans &lt;code&gt;kubectl get sc csi-rbd-sc -o yaml&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id="étape-4--le-snapshot-fonctionne"&gt;Étape 4 : le snapshot fonctionne
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f - &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;apiVersion: snapshot.storage.k8s.io/v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: VolumeSnapshot
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;metadata:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; name: mon-snapshot
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;spec:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; volumeSnapshotClassName: csi-rbd-snapclass
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; source:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; persistentVolumeClaimName: mon-pvc
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get volumesnapshot mon-snapshot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READYTOUSE SOURCEPVC RESTORESIZE AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mon-snapshot &lt;span class="nb"&gt;true&lt;/span&gt; mon-pvc 1Gi 6s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;READYTOUSE: true&lt;/code&gt; immédiatement. Le snapshot Ceph RBD est créé en quelques secondes.&lt;/p&gt;
&lt;h3 id="étape-5--restaurer-un-volume-depuis-un-snapshot"&gt;Étape 5 : restaurer un volume depuis un snapshot
&lt;/h3&gt;&lt;p&gt;Pour être VRAIMENT sûr que ça fonctionne, on peut faire un test (et oui, ça fonctionne)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PersistentVolumeClaim&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pvc-restored&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessModes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;ReadWriteOnce&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;1Gi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;dataSource&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mon-snapshot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;VolumeSnapshot&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;apiGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;snapshot.storage.k8s.io&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;storageClassName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;csi-rbd-sc&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl apply -f pvc-restored.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ kubectl get pvc pvc-restored
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS VOLUME CAPACITY
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pvc-restored Bound pvc-... 1Gi
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="bilan-global"&gt;Bilan global
&lt;/h2&gt;&lt;p&gt;Deuxième partie, deuxième bilan. Qu&amp;rsquo;est-ce qu&amp;rsquo;on retient ?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Les points forts :&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;CSI Ceph RBD&lt;/strong&gt; : là encore, un bon choix. Ceph, ça fonctionne bien dans Kubernetes. Provisionning, persistence, resize online — tout fonctionne et ce n&amp;rsquo;est pas une surprise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vCluster + CSI&lt;/strong&gt; : pour faire plaisir à Akanoa, j&amp;rsquo;ai fait du kubeception (Kubernetes in Kubernetes) et ça m&amp;rsquo;a permis de tester une feature de vCluster que j&amp;rsquo;avais jamais essayée =&amp;gt; la délégation transparente du CSI vers le CSI de l&amp;rsquo;hôte&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Les points d&amp;rsquo;attention :&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les snapshots ne sont pas &amp;ldquo;out of the box&amp;rdquo; : CRDs manquantes, feature gate inattendu, secret à recopier. Je ferai un petit email aux copain⋅es parce que ça me parait dommage de pas l&amp;rsquo;ajouter&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Encore merci à l&amp;rsquo;équipe Clever Cloud pour m&amp;rsquo;avoir donné accès en avant-première et pour avoir pris le temps d&amp;rsquo;échanger sur mes retours. Je ne sais pas s&amp;rsquo;il y aura un 3ème article ou non. Wait and see ;-P&lt;/p&gt;</description></item></channel></rss>