<?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</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Sat, 30 May 2026 12:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/index.xml" rel="self" type="application/rss+xml"/><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><item><title>Refonte de l'UI de GroROTI</title><link>https://blog.zwindler.fr/2026/05/22/refonte-ui-groroti/</link><pubDate>Fri, 22 May 2026 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/05/22/refonte-ui-groroti/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/05/roti-avant-apr%C3%A8s.webp" alt="Featured image of post Refonte de l'UI de GroROTI" /&gt;&lt;p&gt;Si vous lisez régulièrement ce blog et mes aventures sur les réseaux sociaux, vous savez que j&amp;rsquo;aime énormément l&amp;rsquo;infrastructure, un peu le code backend (surtout en Golang), mais que dès qu&amp;rsquo;il s&amp;rsquo;agit de concevoir une interface graphique moderne et agréable, il n&amp;rsquo;y a plus personne.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est un fait établi, je n&amp;rsquo;ai aucune compétence en développement front. Dommage pour les offres FullSteak (jeu de mot pourri, mais plutôt approprié ici).&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est exactement ce qui pénalisait GroROTI, un petit outil web open source en Go que j&amp;rsquo;avais développé en 2024 (en partie sur mon temps libre, en partie lorsque j&amp;rsquo;étais chez mon ancien employeur, ce qui explique pourquoi le dépôt soit hébergé sur leur GitHub).&lt;/p&gt;
&lt;p&gt;Pour rappel, l&amp;rsquo;application permet d&amp;rsquo;évaluer rapidement et anonymement la qualité des réunions ou des ateliers via le format ROTI (Return On Time Invested).&lt;/p&gt;
&lt;p&gt;Le problème, c&amp;rsquo;est que l&amp;rsquo;outil était fonctionnel, &lt;strong&gt;mais franchement moche&lt;/strong&gt;. Un petit look Web 1.0, voire soviétique, qui était franchement assumé dans un premier temps, mais dont on se lasse assez vite.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/05/vote-avant.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Qu&amp;rsquo;à cela ne tienne. Fort de mes expériences plutôt concluantes avec la refonte de ma page &lt;a class="link" href="https://blog.zwindler.fr/conf%C3%A9rences/" target="_blank" rel="noopener"
&gt;Conférence&lt;/a&gt; (cf &amp;ldquo;&lt;a class="link" href="https://blog.zwindler.fr/2026/03/13/refonte-page-conferences-hugo/" &gt;Refonte de la page Conférences : data-driven avec Hugo&lt;/a&gt;&amp;rdquo;) et la v3 de &lt;a class="link" href="https://zwindler.github.io/101-ways-to-deploy-kubernetes/" target="_blank" rel="noopener"
&gt;mon projet pour cataloguer toutes les manières de déployer Kubernetes&lt;/a&gt; (cf &amp;ldquo;&lt;a class="link" href="https://blog.zwindler.fr/2026/03/29/101-facons-de-deployer-kubernetes-v3/" target="_blank" rel="noopener"
&gt;101 façons de déployer Kubernetes : 125 solutions, des PRs communautaires et une UI qui s&amp;rsquo;améliore&lt;/a&gt;&amp;rdquo;), j&amp;rsquo;ai fait chauffer un LLM perso pour lui demander de m&amp;rsquo;aider à rendre ça un peu plus agréable à l&amp;rsquo;oeil.&lt;/p&gt;
&lt;p&gt;Le résultat va bien au-delà de mes compétences (&amp;ldquo;la barre est au sol&amp;rdquo; comme on dit). Si on compare les captures d&amp;rsquo;écran avant (à gauche) et après (à droite), le changement est flagrant :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Une page d&amp;rsquo;accueil transformée : Fini l&amp;rsquo;alignement de texte un peu triste. On passe sur une interface épurée, une colorimétrie violette moderne (petit clin d&amp;rsquo;oeil), un bloc de création &amp;ldquo;New ROTI&amp;rdquo; bien délimité et une barre latérale pour afficher les derniers examens créés.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/05/home-avantapr%c3%a8s.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Des résultats clairs : La page de restitution des votes affiche désormais la moyenne globale en grand, ainsi que le nombre de participants et les valeurs minimales et maximales sous forme de &amp;ldquo;cartes&amp;rdquo;. Le QR code de partage est lui aussi bien mieux intégré visuellement.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/05/roti-avant-apr%c3%a8s.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un système de notation plus intuitif : Le vieux curseur glissant a été remplacé par un système de notation à 5 étoiles (avec support des demi notes) bien plus classique et parlant pour les utilisateurs. Le tout reste accompagné d&amp;rsquo;une zone de texte (redesignée) pour les retours optionnels, quand ils sont activés.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/05/vote-avant-apr%c3%a8s.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="juste-un-lifting"&gt;Juste un lifting
&lt;/h2&gt;&lt;p&gt;Sous le capot, l&amp;rsquo;application reste la même, je n&amp;rsquo;ai pas touché au code backend (*), juste la partie templates HTML (gotpl) et CSS.&lt;/p&gt;
&lt;p&gt;(*) &lt;em&gt;c&amp;rsquo;est pas 100% vrai, j&amp;rsquo;ai fait quelques modifs en 2025, notamment pour ajouter du tracing otel, support de la CR ServiceMonitor, amélioration du build multi arch et quelques bugfixes&amp;hellip; dont je ne vous avais pas parlé.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Au final, ce petit truc en plus fait la diff entre un tool interne que je n&amp;rsquo;avais pas trop envie de mettre en avant, et un outil qui me parait utilisable en dehors de mon contexte pro :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/deezer/GroROTI" target="_blank" rel="noopener"
&gt;https://github.com/deezer/GroROTI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>nodes/proxy GET : la permission Kubernetes de trop</title><link>https://blog.zwindler.fr/2026/05/19/kubernetes-nodes-proxy-rce/</link><pubDate>Tue, 19 May 2026 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/05/19/kubernetes-nodes-proxy-rce/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/05/node_proxy_RCE.webp" alt="Featured image of post nodes/proxy GET : la permission Kubernetes de trop" /&gt;&lt;h2 id="tldr"&gt;TL;DR
&lt;/h2&gt;&lt;p&gt;Ca date un peu mais j&amp;rsquo;avais quand même envie de faire un petit REX sur ce sujet.&lt;/p&gt;
&lt;p&gt;En janvier un chercheur en cybersécurité a remonté une faille de Kubernetes qui a pas mal fait le buzz. Ca faisait un moment qu&amp;rsquo;on avait plus eu de faille sur Kube qui fasse autant parler, de mémoire de Denis (oui je parle de moi à la 3ème personne).&lt;/p&gt;
&lt;p&gt;En vrai, c&amp;rsquo;est assez chaud : la permission RBAC &lt;code&gt;nodes/proxy GET&lt;/code&gt; permet à n&amp;rsquo;importe quel &lt;strong&gt;ServiceAccount&lt;/strong&gt; d&amp;rsquo;exécuter du code dans &lt;strong&gt;n&amp;rsquo;importe quel Pod du cluster&lt;/strong&gt;, sans laisser la moindre trace dans les logs d&amp;rsquo;audit. C&amp;rsquo;est facheux, surtout quand vous avez des ServiceAccount qui se nomment &lt;code&gt;rook-ceph-system&lt;/code&gt; et qui cumulent également un accès en lecture à tous les Secrets du cluster.&lt;/p&gt;
&lt;p&gt;Cet article détaille le problème, la façon dont on peut vérifier si on est vulnérable, les correctifs à appliquer, et les mesures de prévention qui peuvent être mises en place si vous ne pouvez pas fix.&lt;/p&gt;
&lt;h2 id="le-problème--websocket--kubelet--exec-sans-audit"&gt;Le problème : WebSocket + Kubelet = exec sans audit
&lt;/h2&gt;&lt;p&gt;La vulnérabilité a été documentée par Graham Helton dans &lt;a class="link" href="https://grahamhelton.com/blog/nodes-proxy-rce" target="_blank" rel="noopener"
&gt;cet article&lt;/a&gt;. Le principe est le suivant.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;API Kubernetes expose une sous-ressource &lt;code&gt;nodes/proxy&lt;/code&gt; qui proxifie les requêtes HTTP vers le Kubelet de chaque node. Le Kubelet lui-même expose une API sur le port 10250, notamment l&amp;rsquo;endpoint &lt;code&gt;/exec&lt;/code&gt; qui permet d&amp;rsquo;exécuter des commandes dans un container.&lt;/p&gt;
&lt;p&gt;Le problème vient de la façon dont le Kubelet gère les autorisations pour les connexions WebSocket :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;kubectl exec&lt;/code&gt; utilise une connexion WebSocket, dont le handshake est un &lt;code&gt;GET&lt;/code&gt; HTTP&lt;/li&gt;
&lt;li&gt;Le Kubelet mappe ce &lt;code&gt;GET&lt;/code&gt; initial vers le verbe RBAC &lt;code&gt;get&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Il vérifie &lt;code&gt;nodes/proxy GET&lt;/code&gt;, puis autorise l&amp;rsquo;opération&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Aucune vérification secondaire&lt;/strong&gt; n&amp;rsquo;est faite pour le verbe &lt;code&gt;CREATE&lt;/code&gt; normalement requis pour &lt;code&gt;/exec&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Résultat : n&amp;rsquo;importe quel ServiceAccount avec &lt;code&gt;nodes/proxy GET&lt;/code&gt; peut exécuter des commandes dans n&amp;rsquo;importe quel Pod du cluster, &lt;strong&gt;y compris les Pods système&lt;/strong&gt; (&lt;code&gt;etcd&lt;/code&gt;, &lt;code&gt;kube-apiserver&lt;/code&gt;, etc.).&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;# Exploitation via websocat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;websocat --insecure &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --header &lt;span class="s2"&gt;&amp;#34;Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --protocol &lt;span class="s2"&gt;&amp;#34;v4.channel.k8s.io&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;wss://&lt;/span&gt;&lt;span class="nv"&gt;$NODE_IP&lt;/span&gt;&lt;span class="s2"&gt;:10250/exec/default/nginx/nginx?output=1&amp;amp;error=1&amp;amp;command=id&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et c&amp;rsquo;est pas fini. &lt;strong&gt;Les commandes exécutées via cette méthode ne génèrent aucun log d&amp;rsquo;audit Kubernetes&lt;/strong&gt; (enfin bon, peut être que vous les collectez pas 🙈). L&amp;rsquo;accès passe directement par le Kubelet, qui ne remonte pas d&amp;rsquo;événements à l&amp;rsquo;API server.&lt;/p&gt;
&lt;p&gt;Le statut officiel de Kubernetes sur ce sujet : &lt;strong&gt;Won&amp;rsquo;t Fix&lt;/strong&gt;. C&amp;rsquo;est un &amp;ldquo;comportement intentionnel&amp;rdquo; (notez les guillemets), corrigé par une feature gate (&lt;a class="link" href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/2862-fine-grained-kubelet-authz/README.md" target="_blank" rel="noopener"
&gt;KEP-2862&lt;/a&gt;, voir plus bas).&lt;/p&gt;
&lt;p&gt;Ca pique.&lt;/p&gt;
&lt;h2 id="laudit--des-serviceaccounts-vulnérables-sur-nos-clusters"&gt;L&amp;rsquo;audit : des ServiceAccounts vulnérables sur nos clusters
&lt;/h2&gt;&lt;p&gt;En janvier 2026, suite à la publication de l&amp;rsquo;article de Graham Helton, beaucoup de gens ont du auditer en urgence leurs clusters. On peut soit auditer soit même l&amp;rsquo;ensemble de ses Roles / ClusterRoles, soit utiliser un &lt;a class="link" href="https://gist.github.com/grahamhelton/f5c8ce265161990b0847ac05a74e466a" target="_blank" rel="noopener"
&gt;script de détection&lt;/a&gt; fourni par le chercheur.&lt;/p&gt;
&lt;p&gt;Pour l&amp;rsquo;exemple, voici trois composants relativement courants et qui peuvent être de bons candidats pour une belle élévation de privilèges :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Composant&lt;/th&gt;
&lt;th&gt;ClusterRole&lt;/th&gt;
&lt;th&gt;ServiceAccounts&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenTelemetry Collector&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;otel-otelcol-k8sobjects&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opentelemetry-collector-daemonset-collector&lt;/code&gt;, &lt;code&gt;opentelemetry-collector-deployment-collector&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenTelemetry Operator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;otel-operator-resources&lt;/code&gt; / &lt;code&gt;opentelemetry-operator-manager&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;opentelemetry-operator&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rook-Ceph&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rook-ceph-global&lt;/code&gt;, &lt;code&gt;rook-ceph-mgr-cluster&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;rook-ceph-system&lt;/code&gt;, &lt;code&gt;rook-ceph-mgr&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Note : il y en a beaucoup plus, Graham Helton a ajouté une section &amp;ldquo;Appendix: Affected Helm Charts&amp;rdquo; en fin d&amp;rsquo;article, qui référence AU MOINS 69 helm charts concernées selon lui.&lt;/p&gt;
&lt;h3 id="le-cas-critique--rook-ceph-system"&gt;Le cas critique : rook-ceph-system
&lt;/h3&gt;&lt;p&gt;Dans la chart officielle, le ServiceAccount &lt;code&gt;rook-ceph-system&lt;/code&gt; cumulait deux permissions particulièrement dangereuses :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;nodes/proxy GET&lt;/code&gt;&lt;/strong&gt; - la RCE&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;secrets GET/LIST/WATCH&lt;/code&gt;&lt;/strong&gt; sur &lt;strong&gt;tout le cluster&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Les secrets accessibles peuvent notamment inclure des clés LUKS de chiffrement des volumes, les keyrings Ceph admin, et les mots de passe du dashboard&amp;hellip; ce genre de permissions en fait une cible de choix pour un attaquant.&lt;/p&gt;
&lt;p&gt;Le scénario d&amp;rsquo;attaque : une compromission du Pod &lt;code&gt;rook-ceph-operator&lt;/code&gt; (via CVE, supply chain, ou image malveillante) permettrait de lire tous les secrets Ceph, puis d&amp;rsquo;exécuter du code dans n&amp;rsquo;importe quel Pod (y compris &lt;code&gt;etcd&lt;/code&gt;) aboutissant à une compromission complète du cluster et des données chiffrées.&lt;/p&gt;
&lt;p&gt;Pour vérifier manuellement si un ServiceAccount est vulnérable :&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 auth can-i get nodes --subresource&lt;span class="o"&gt;=&lt;/span&gt;proxy &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --as&lt;span class="o"&gt;=&lt;/span&gt;system:serviceaccount:&amp;lt;namespace&amp;gt;:&amp;lt;serviceaccount&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="exemples-de-correctifs-à-appliquer"&gt;Exemples de correctifs à appliquer
&lt;/h2&gt;&lt;h3 id="rook-ceph--fix-upstream"&gt;Rook-Ceph : fix upstream
&lt;/h3&gt;&lt;p&gt;Pour Rook-Ceph, le fix est venu de l&amp;rsquo;upstream : la PR &lt;a class="link" href="https://github.com/rook/rook/pull/16979" target="_blank" rel="noopener"
&gt;rook/rook#16979&lt;/a&gt; a supprimé &lt;code&gt;nodes/proxy&lt;/code&gt; des ClusterRoles. Ce fix est inclus dans Rook v1.19.1, il suffisait donc de mettre à jour les clusters concernés.&lt;/p&gt;
&lt;p&gt;Après mise à jour, vérification sur tous les clusters :&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 clusterroles rook-ceph-global -o yaml &lt;span class="p"&gt;|&lt;/span&gt; grep -A3 nodes/proxy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# -&amp;gt; rien&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="otel--otel-operator"&gt;OTel / Otel operator
&lt;/h3&gt;&lt;p&gt;Pour OpenTelemetry, la situation est potentiellement plus complexe. Si vous utilisez &lt;code&gt;otel-operator&lt;/code&gt; et les Custom Resource &lt;strong&gt;OtelCollector&lt;/strong&gt;, il est probable que vous deviez gérer &lt;strong&gt;vous même&lt;/strong&gt; vos propres manifests RBAC.&lt;/p&gt;
&lt;p&gt;Pour avoir eu à le faire, c&amp;rsquo;est assez pénible, en fonction de votre type de collecteur et des receivers que vous avez activés, il est nécessaire de croiser plusieurs documents sur les sites officiels d&amp;rsquo;otel et otel-operator.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;upstream avait mergé une approche conditionnelle dans &lt;a class="link" href="https://github.com/open-telemetry/opentelemetry-helm-charts/pull/2083" target="_blank" rel="noopener"
&gt;open-telemetry/opentelemetry-helm-charts#2083&lt;/a&gt; basée sur la version Kubernetes :&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;# Approach upstream (opentelemetry-helm-charts#2083)&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="l"&gt;if semverCompare &amp;#34;&amp;gt;=1.33-0&amp;#34; .Capabilities.KubeVersion.Version }}&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;nodes/pods&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="l"&gt;else }}&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;nodes/proxy&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="l"&gt;end }}&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;Là encore, si tous vos clusters sont à jours, vous avez simplement à remplacer &lt;code&gt;nodes/proxy&lt;/code&gt; par &lt;code&gt;nodes/pods&lt;/code&gt; directement (sans conditionnel).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;otel-collector-crb.yaml&lt;/code&gt;&lt;/strong&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="c"&gt;# Avant&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;rules&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;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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="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="l"&gt;nodes&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;nodes/proxy &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# ← RCE risk&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;nodes/spec&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;nodes/stats&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;verbs&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;get&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Après&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;rules&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;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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="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="l"&gt;nodes&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="c"&gt;# nodes/pods replaces nodes/proxy (RCE risk, see https://grahamhelton.com/blog/nodes-proxy-rce)&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="c"&gt;# Requires K8s &amp;gt;= 1.33 (KEP-2862 fine-grained kubelet authz)&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;nodes/pods&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;nodes/spec&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;nodes/stats&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;verbs&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;get&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;&lt;strong&gt;&lt;code&gt;otel-operator-rbac.yaml&lt;/code&gt;&lt;/strong&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="c"&gt;# Avant&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;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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="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="l"&gt;nodes/proxy &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# ← RCE risk&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;verbs&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;get&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Après&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="c"&gt;# nodes/pods replaces nodes/proxy (RCE risk, see https://grahamhelton.com/blog/nodes-proxy-rce)&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="c"&gt;# Requires K8s &amp;gt;= 1.33 (KEP-2862 fine-grained kubelet authz)&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;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&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="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="l"&gt;nodes/pods&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;verbs&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;get&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;h2 id="les-mesures-préventives"&gt;Les mesures préventives
&lt;/h2&gt;&lt;h3 id="kep-2862--fine-grained-kubelet-api-authorization"&gt;KEP-2862 : Fine-Grained Kubelet API Authorization
&lt;/h3&gt;&lt;p&gt;On l&amp;rsquo;a déjà un peu teasé, la vraie solution long terme est la &lt;a class="link" href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/2862-fine-grained-kubelet-authz/README.md" target="_blank" rel="noopener"
&gt;KEP-2862&lt;/a&gt; (Fine-Grained Kubelet API Authorization). Elle introduit des sous-ressources granulaires (&lt;code&gt;nodes/pods&lt;/code&gt;, &lt;code&gt;nodes/metrics&lt;/code&gt;, &lt;code&gt;nodes/stats&lt;/code&gt;, &lt;code&gt;nodes/log&lt;/code&gt;, etc.) qui permettent de donner des accès précis sans passer par &lt;code&gt;nodes/proxy&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version K8s&lt;/th&gt;
&lt;th&gt;Statut KEP-2862&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1.32&lt;/td&gt;
&lt;td&gt;Alpha&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.33&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Beta, activé par défaut&lt;/strong&gt; — &lt;code&gt;nodes/proxy GET&lt;/code&gt; ne donne plus accès à &lt;code&gt;/exec&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1.36&lt;/td&gt;
&lt;td&gt;GA (locked to enabled)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Mais ça nécessite de passer sur TOUTES les charts utilisées et vérifier tous les manifests déployés maintenant &lt;strong&gt;et dans le futur&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="ciliumnetworkpolicy--bloquer-le-port-kubelet"&gt;CiliumNetworkPolicy : bloquer le port Kubelet
&lt;/h3&gt;&lt;p&gt;En attendant la mise à jour K8s, ou (et ?) comme défense en profondeur, on peut aussi bloquer l&amp;rsquo;accès au port 10250 depuis les pods concernés via NetworkPolicies (ou CiliumNetworkPolicy si vous avez Cilium comme CNI plugin).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Attention&lt;/strong&gt; : cela ne s&amp;rsquo;applique qu&amp;rsquo;aux composants qui n&amp;rsquo;ont &lt;em&gt;pas&lt;/em&gt; besoin d&amp;rsquo;accéder au Kubelet. OTel collector en a potentiellement besoin pour collecter les métriques du kubelet. Dans ce cas, pas le choix, on ne peux que corriger le RBAC.&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;cilium.io/v2&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;CiliumNetworkPolicy&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;deny-kubelet-api-access&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;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;&amp;lt;namespace&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="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;endpointSelector&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;&amp;lt;app-label&amp;gt;&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;value&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;egressDeny&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;toEntities&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;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="l"&gt;remote-node&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;toPorts&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="s2"&gt;&amp;#34;10250&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;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;TCP&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;h3 id="kyverno--bloquer-la-création-de-nouveaux-roles-avec-nodesproxy"&gt;Kyverno : bloquer la création de nouveaux Roles avec nodes/proxy
&lt;/h3&gt;&lt;p&gt;Note : &lt;a class="link" href="https://github.com/kyverno/policies/issues/1492" target="_blank" rel="noopener"
&gt;a priori j&amp;rsquo;ai découvert un trou dans la policy officielle de Kyverno, j&amp;rsquo;ai ouvert une issue&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Pour éviter toute régression (j&amp;rsquo;ai dit qu&amp;rsquo;il fallait se protéger &lt;strong&gt;dans le futur&lt;/strong&gt;), on peut aussi ajouter une &lt;code&gt;ClusterPolicy&lt;/code&gt; Kyverno qui refuse la création ou modification de &lt;code&gt;ClusterRole&lt;/code&gt;/&lt;code&gt;Role&lt;/code&gt; contenant &lt;code&gt;nodes/proxy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;On a de la chance, il existe déjà des exemples prêt à l&amp;rsquo;emploi sur le site officiel de Kyverno :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kyverno.io/policies/other-vpol/restrict-clusterrole-nodesproxy/restrict-clusterrole-nodesproxy/" target="_blank" rel="noopener"
&gt;variante validating&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kyverno.io/policies/other-cel/restrict-clusterrole-nodesproxy/restrict-clusterrole-nodesproxy/" target="_blank" rel="noopener"
&gt;variante CEL&lt;/a&gt;&lt;/li&gt;
&lt;/ul&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;kyverno.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;ClusterPolicy&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;restrict-nodes-proxy&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;validationFailureAction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Audit &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# passer en Enforce après validation&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;background&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;rules&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;deny-nodes-proxy-in-clusterroles&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;match&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;any&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;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;kinds&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;ClusterRole&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;Role&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;exclude&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;any&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;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;names&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="s2"&gt;&amp;#34;system:kubelet-api-admin&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# built-in K8s, non modifiable&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;validate&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;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;&amp;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; nodes/proxy grants RCE capability via Kubelet WebSocket exec.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; Use nodes/pods (requires K8s &amp;gt;= 1.33, KEP-2862) instead.&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;deny&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;conditions&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;any&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;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;nodes/proxy&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;operator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;AnyIn&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="s2"&gt;&amp;#34;{{ request.object.rules[].resources[] }}&amp;#34;&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 déploiement se fait en deux temps : d&amp;rsquo;abord en mode &lt;code&gt;Audit&lt;/code&gt; pour vérifier qu&amp;rsquo;il n&amp;rsquo;y a plus de manifests vulnérables (qu&amp;rsquo;on préférera fix &lt;strong&gt;avant&lt;/strong&gt; de bloquer), puis en &lt;code&gt;Enforce&lt;/code&gt; pour bloquer effectivement.&lt;/p&gt;
&lt;h3 id="surveillance-de-laudit-log"&gt;Surveillance de l&amp;rsquo;audit log
&lt;/h3&gt;&lt;p&gt;Même si les commandes exécutées &lt;em&gt;via&lt;/em&gt; le Kubelet ne laissent pas de trace, on peut surveiller les &lt;strong&gt;SubjectAccessReviews&lt;/strong&gt; pour détecter les tentatives d&amp;rsquo;énumération de permissions &lt;code&gt;nodes/proxy&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La configuration dans la policy d&amp;rsquo;audit Kubernetes :&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;# audit-policy.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;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Request&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;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;create&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="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;group&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;authorization.k8s.io&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;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;subjectaccessreviews&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Puis une alerte Prometheus/Alertmanager sur les SAR qui concernent &lt;code&gt;nodes/proxy&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-promql" data-lang="promql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Détecter les SAR portant sur nodes/proxy&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="kr"&gt;increase&lt;/span&gt;&lt;span class="o"&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="nv"&gt;apiserver_audit_event_total&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="nl"&gt;verb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;#34;&lt;/span&gt;&lt;span class="s"&gt;create&lt;/span&gt;&lt;span class="p"&gt;&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="nl"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;&amp;#34;&lt;/span&gt;&lt;span class="s"&gt;subjectaccessreviews&lt;/span&gt;&lt;span class="p"&gt;&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="p"&gt;}[&lt;/span&gt;&lt;span class="s"&gt;5m&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="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&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;h2 id="références"&gt;Références
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://grahamhelton.com/blog/nodes-proxy-rce" target="_blank" rel="noopener"
&gt;Kubernetes RCE via nodes/proxy GET&lt;/a&gt; — Graham Helton&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://gist.github.com/grahamhelton/f5c8ce265161990b0847ac05a74e466a" target="_blank" rel="noopener"
&gt;Script de détection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/2862-fine-grained-kubelet-authz/README.md" target="_blank" rel="noopener"
&gt;KEP-2862 Fine-Grained Kubelet API Authorization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/concepts/security/rbac-good-practices/#access-to-proxy-subresource-of-nodes" target="_blank" rel="noopener"
&gt;Kubernetes RBAC Good Practices - nodes/proxy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/rook/rook/pull/16979" target="_blank" rel="noopener"
&gt;rook/rook#16979&lt;/a&gt; - Fix upstream Rook-Ceph&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/open-telemetry/opentelemetry-helm-charts/pull/2083" target="_blank" rel="noopener"
&gt;open-telemetry/opentelemetry-helm-charts#2083&lt;/a&gt; - Fix upstream OTel&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Test de CKE, le Kubernetes managé chez Clever Cloud (partie 1)</title><link>https://blog.zwindler.fr/2026/05/12/test-cke-kubernetes-manage-clever-cloud-partie-1/</link><pubDate>Tue, 12 May 2026 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/05/12/test-cke-kubernetes-manage-clever-cloud-partie-1/</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 1)" /&gt;&lt;h2 id="la-suite-de-ma-saga-avec-clever-cloud"&gt;La suite de ma saga avec Clever Cloud
&lt;/h2&gt;&lt;p&gt;Ça fait maintenant quelques années que je teste les produits de Clever Cloud sur ce blog. En 2022, j&amp;rsquo;avais testé le &lt;a class="link" href="https://blog.zwindler.fr/2022/05/17/et-si-on-testait-clever-kubernetes-operator/" &gt;Clever Kubernetes Operator&lt;/a&gt;, qui permet de provisionner des services managés (MySQL, Redis, Pulsar&amp;hellip;) directement depuis un cluster Kubernetes via des CRDs. En 2025, j&amp;rsquo;ai réessayé leur offre &lt;a class="link" href="https://blog.zwindler.fr/2025/07/07/je-reessaye-les-sites-statiques-chez-clever/" &gt;d&amp;rsquo;hébergement de sites statiques&lt;/a&gt;, et j&amp;rsquo;ai même poussé le délire jusqu&amp;rsquo;à &lt;a class="link" href="https://blog.zwindler.fr/2025/07/20/kubernetes-sur-clever-cloud-linux/" &gt;déployer un control plane Kubernetes sur leurs apps Linux&lt;/a&gt;, pour rigoler en attendant leur offre managée.&lt;/p&gt;
&lt;p&gt;Bref, à la fin de cet article, je concluais avec un &lt;em&gt;trollface&lt;/em&gt; et un &amp;ldquo;j&amp;rsquo;attends mon accès au k8s managé de Clever Cloud&amp;rdquo;. Et bien&amp;hellip; c&amp;rsquo;est maintenant !&lt;/p&gt;
&lt;p&gt;Clever Cloud a lancé &lt;strong&gt;CKE&lt;/strong&gt; (Clever Kubernetes Engine) en bêta publique. J&amp;rsquo;ai eu accès en avant-première pour tester et je leur ai fait quelques retours. Maintenant que le produit est public, j&amp;rsquo;ai &lt;strong&gt;enfin&lt;/strong&gt; le droit de vous partager mes impressions.&lt;/p&gt;
&lt;h2 id="cke-cest-quoi-exactement-"&gt;CKE c&amp;rsquo;est quoi exactement ?
&lt;/h2&gt;&lt;p&gt;CKE est l&amp;rsquo;offre Kubernetes managée de Clever Cloud. Le positionnement est clair : Vanilla Kubernetes, pas de lock-in, le plus standard possible dans l&amp;rsquo;usage du cluster.&lt;/p&gt;
&lt;p&gt;Ce qui distingue CKE des offres plus conventionnelles, c&amp;rsquo;est ça :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Materia etcd&lt;/strong&gt; : leur implémentation maison de l&amp;rsquo;API etcd, construite sur FoundationDB (pas d&amp;rsquo;etcd VM à provisionner ou dimensionner, c&amp;rsquo;est serverless). C&amp;rsquo;était pas gagné, cf cet article de Pierre Zemb &lt;a class="link" href="https://pierrezemb.fr/posts/diving-into-kubernetes-watch-cache/" target="_blank" rel="noopener"
&gt;&amp;ldquo;Diving into Kubernetes&amp;rsquo; Watch Cache&amp;rdquo;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Infrastructure souveraine et isolée des autres clients&lt;/strong&gt; : tout tourne sur des VMs dédiées, en France. Rien de mutualisé entre tenants.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cilium + eBPF&lt;/strong&gt; natif, avec WireGuard pour le chiffrement inter-nodes.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="activation--cest-un-peu-caché"&gt;Activation : c&amp;rsquo;est un peu caché
&lt;/h2&gt;&lt;p&gt;CKE est en bêta publique, mais l&amp;rsquo;accès n&amp;rsquo;est pas actif par défaut. Il faut l&amp;rsquo;activer, soit via la CLI :&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 features &lt;span class="nb"&gt;enable&lt;/span&gt; k8s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note : les commandes &lt;code&gt;clever k8s&lt;/code&gt; sont disponibles dans la CLI à partir d&amp;rsquo;une version récente de leur CLI, idéalement version 4.9+.&lt;/p&gt;
&lt;p&gt;Soit depuis l&amp;rsquo;interface web, dans l&amp;rsquo;onglet &lt;strong&gt;Labs&lt;/strong&gt; de votre compte : &lt;a class="link" href="https://console.clever-cloud.com/users/me/feature-list" target="_blank" rel="noopener"
&gt;https://console.clever-cloud.com/users/me/feature-list&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;onglet Labs n&amp;rsquo;est pas particulièrement mis en avant dans la console (il faut savoir où chercher). Une fois le feature flag activé, un nouveau menu apparaît dans la console.&lt;/p&gt;
&lt;h2 id="création-dun-cluster"&gt;Création d&amp;rsquo;un cluster
&lt;/h2&gt;&lt;p&gt;Depuis la CLI, voici la commande minimale pour créer un cluster :&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 create moncluster --watch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;--watch&lt;/code&gt; suit le déploiement jusqu&amp;rsquo;à ce que le cluster soit &lt;code&gt;ACTIVE&lt;/code&gt;. En pratique, ça ressemble à ça :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;⏳ Cluster status: DEPLOYING. Waiting for 10s before checking again...
✓ Cluster moncluster (kubernetes_01KRDDW6YNMDE7ZT0RYD3MY0GE) deployed successfully
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On peut passer tout un tas d&amp;rsquo;options : version K8s (&lt;code&gt;--cluster-version 1.36&lt;/code&gt;), topology, flavor du control plane, replication factor, autoscaling, CSI&amp;hellip; On y reviendra.&lt;/p&gt;
&lt;p&gt;Mais il est aussi possible, depuis peu, de créer un cluster via formulaire de création, avec un récapitulatif en temps réel.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/05/clever-create-cluster.avif"
loading="lazy"
alt="SCREENSHOT console création cluster"
&gt;&lt;/p&gt;
&lt;p&gt;On y retrouve tous les paramètres : version K8s, topology, flavor, replication factor, autoscaling, persistent storage. C&amp;rsquo;est bien fait, les topologies sont expliquées inline. Le nom de cluster est généré aléatoirement (style &lt;code&gt;cunning-arcane-runic-corsair&lt;/code&gt;, modifiable bien sûr).&lt;/p&gt;
&lt;h2 id="les-3-topologies--essential-business-enterprise"&gt;Les 3 topologies : Essential, Business, Enterprise
&lt;/h2&gt;&lt;p&gt;C&amp;rsquo;est l&amp;rsquo;un des aspects les plus inhabituel de CKE. Trois topologies sont disponibles à la création (et &lt;strong&gt;immutables&lt;/strong&gt; après, ce qui oblige à bien choisir dès le départ).&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Topology&lt;/th&gt;
&lt;th&gt;Offre&lt;/th&gt;
&lt;th&gt;Ce que vous payez&lt;/th&gt;
&lt;th&gt;Usage typique&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ALL_IN_ONE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Essential&lt;/td&gt;
&lt;td&gt;rf (replication factor) × VM bundle (CP + worker intégré)&lt;/td&gt;
&lt;td&gt;Dev, test, petits clusters&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DEDICATED_COMPUTE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Business&lt;/td&gt;
&lt;td&gt;rf × VM CP + chaque worker VM&lt;/td&gt;
&lt;td&gt;Prod avec isolation CP/workers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DISTRIBUTED&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Enterprise&lt;/td&gt;
&lt;td&gt;5 composants × rf × VM + workers&lt;/td&gt;
&lt;td&gt;Prod HA fine par composant&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;ALL_IN_ONE&lt;/strong&gt; est la topologie par défaut. Le control plane et un worker node partagent la même VM (c&amp;rsquo;est le mode k3s-style, pour reprendre la description de la console). Ça a une conséquence concrète : &lt;code&gt;kubectl get nodes&lt;/code&gt; liste le node du control plane comme worker disponible pour les pods.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DEDICATED_COMPUTE&lt;/strong&gt; isole le CP sur ses propres VMs, les workers sont dans des node groups séparés. Avec un replication factor de 3, les VMs sont réparties sur les 3 datacenters parisiens de Clever Cloud (un datacenter peut tomber sans impacter le control plane).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DISTRIBUTED&lt;/strong&gt; pousse l&amp;rsquo;isolation plus loin : chaque composant du CP (apiserver, controller-manager, scheduler, cloud-controller-manager, node-group-operator) tourne sur sa propre VM, avec sa propre flavor et son propre replication factor. J&amp;rsquo;ai du mal à voir dans quel cas c&amp;rsquo;est utile, mais pourquoi pas.&lt;/p&gt;
&lt;h3 id="un-mot-sur-la-tarification"&gt;Un mot sur la tarification
&lt;/h3&gt;&lt;p&gt;Contrairement à certains cloud providers (de moins en moins cela dit&amp;hellip;) qui offrent parfois le control plane quand il est mutualisé et ne facturent que les workers, Clever Cloud facture une vraie VM dédiée par cluster (rien de mutualisé entre tenants).&lt;/p&gt;
&lt;p&gt;En ALL_IN_ONE, cette VM fait aussi office de worker, ce qui en fait paradoxalement l&amp;rsquo;option la moins chère. La flavor S (8 vCPU / 12 GB RAM) revient à &lt;strong&gt;64 €/mois&lt;/strong&gt;, CP et workers inclus sur la même machine. L&amp;rsquo;isolation se paye davantage : DEDICATED_COMPUTE commence à 96 €/mois pour le CP seul (+ workers séparés), DISTRIBUTED à 180 €/mois minimum pour 5 composants CP en 2XS.&lt;/p&gt;
&lt;p&gt;Ce n&amp;rsquo;est pas donné pour du dev, mais le prix s&amp;rsquo;explique par le choix architectural : vous avez vraiment 8 vCPU / 12 GB de RAM rien que pour vous, pas un dixième de VM partagée entre dix clients.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note : pendant la bêta publique, le CSI (Ceph) et les LoadBalancers ne sont pas encore facturés. Seuls le CP et les workers le sont.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="récupérer-le-kubeconfig"&gt;Récupérer le kubeconfig
&lt;/h2&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 get-kubeconfig moncluster &amp;gt; ~/.kube/clever.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl --kubeconfig ~/.kube/clever.yaml get nodes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Un détail à noter : l&amp;rsquo;API server écoute sur un port non standard (&lt;strong&gt;1032&lt;/strong&gt; ou &lt;strong&gt;1022&lt;/strong&gt; selon les clusters). Ce n&amp;rsquo;est pas bloquant dans la plupart des environnements, mais j&amp;rsquo;ai eu la surprise de me retrouver bloqué depuis un réseau qui filtre les ports non-standards. À garder en tête si vous devez accéder au cluster depuis un réseau d&amp;rsquo;entreprise restrictif.&lt;/p&gt;
&lt;h2 id="anatomie-du-cluster"&gt;Anatomie du cluster
&lt;/h2&gt;&lt;p&gt;Une fois connecté, quelques observations :&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 nodes -o wide
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS VERSION OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;control-plane-be4e3383-...-node Ready v1.36.0 Exherbo Linux 6.19.14-clevercloud-vm-dirty &lt;span class="o"&gt;(&lt;/span&gt;amd64&lt;span class="o"&gt;)&lt;/span&gt; containerd://2.3.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Exherbo Linux&lt;/strong&gt; : on retrouve bien la marque de fabrique de Clever Cloud. Pour celleux qui ne connaissent pas, Exherbo est une distro minimaliste et source-based, peu connue en dehors de cercles très spécialisés. Le kernel est un build custom Clever Cloud (&lt;code&gt;clevercloud-vm-dirty&lt;/code&gt;), et le container runtime est &lt;strong&gt;containerd 2.3.0&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Les composants pré-installés dans &lt;code&gt;kube-system&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 get pods -A
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAMESPACE NAME READY
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system cilium-tv9m7 1/1 &lt;span class="c1"&gt;# CNI&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system cilium-operator-7db7f998d7-z5zq8 1/1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system coredns-5986bf5c44-wkzb9 1/1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system konnectivity-agent-l26kl 2/2 &lt;span class="c1"&gt;# proxy CP-&amp;gt;workers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kube-state-metrics-75b86d74f6-6slx8 2/2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kubelet-csr-approver-68d5bfcb6b-kgz5c 1/1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system metrics-server-559c9b85f9-wflvk 1/1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system cc-collector-wwpld 1/1 &lt;span class="c1"&gt;# collecteur OTel Clever Cloud&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Quelques absences notables par rapport à une install classique : &lt;strong&gt;pas de kube-proxy&lt;/strong&gt; (remplacé par Cilium en mode eBPF &lt;code&gt;kube-proxy replacement&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Pour la petite histoire, les premiers teasers de CKE avaient montré l&amp;rsquo;usage de flannel. Heureusement que les équipes de clever ont écouté la communauté et migré vers cilium.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Konnectivity : l&amp;rsquo;isolation réseau du control plane&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Le pod &lt;code&gt;konnectivity-agent&lt;/code&gt; mérite une mention. C&amp;rsquo;est un composant qu&amp;rsquo;on retrouve assez souvent sur les Kubernetes managés et certaines distribution de Kubernetes. Il tourne sur chaque node (2 containers : &lt;code&gt;proxy-agent&lt;/code&gt; + &lt;code&gt;haproxy&lt;/code&gt;) et sert de proxy entre le control plane et les workers. Le CP ne parle pas directement aux pods : tout le trafic CP-&amp;gt;workers (logs, exec, port-forward, métriques) passe par ce tunnel.&lt;/p&gt;
&lt;p&gt;Pour un service managé, c&amp;rsquo;est la bonne pratique : le control plane n&amp;rsquo;a pas besoin d&amp;rsquo;accès réseau direct aux pods, et les workers n&amp;rsquo;ont pas besoin d&amp;rsquo;exposer kubelet directement au CP.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Materia etcd&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pas de pod etcd dans le cluster : c&amp;rsquo;est normal. Clever Cloud utilise &lt;strong&gt;Materia etcd&lt;/strong&gt;, leur implémentation serverless de l&amp;rsquo;API etcd construite sur FoundationDB. C&amp;rsquo;est transparent pour vous.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cilium&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Le CNI est &lt;strong&gt;Cilium v1.19.1&lt;/strong&gt;. Pour aller plus loin sur l&amp;rsquo;intégration Cilium/CKE, je vous recommande l&amp;rsquo;article de &lt;a class="link" href="https://blog.littlejo.link/cilium/clever/intro/" target="_blank" rel="noopener"
&gt;Joseph&lt;/a&gt; qui est le premier à en avoir parlé publiquement et qui s&amp;rsquo;y connaît bien mieux que moi sur le sujet.&lt;/p&gt;
&lt;h2 id="les-nodegroups"&gt;Les NodeGroups
&lt;/h2&gt;&lt;p&gt;Les workers sont gérés via une CRD custom &lt;code&gt;NodeGroup&lt;/code&gt; (groupe &lt;code&gt;api.clever-cloud.com/v1&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;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;api.clever-cloud.com/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;NodeGroup&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;mes-workers&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;flavor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;S&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;nodeCount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&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 nodegroup.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Les nodes apparaissent dans &lt;code&gt;kubectl get nodes&lt;/code&gt; environ 35-40 secondes après la création du NodeGroup. La CRD supporte le subresource &lt;code&gt;scale&lt;/code&gt;, ce qui permet de faire :&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 scale nodegroup mes-workers --replicas&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Les flavors disponibles en ALL_IN_ONE : S (8 vCPU / 12 GB), M (10 vCPU / 16 GB), L (12 vCPU / 24 GB), XL (16 vCPU / 32 GB). Un quota global s&amp;rsquo;applique au niveau de l&amp;rsquo;organisation (40 vCPU / 40 GB RAM par défaut, tous clusters confondus). Ca peut probablement être augmenté en faisant une demande au support, mais j&amp;rsquo;ai pas assez d&amp;rsquo;argent à crâmer pour justifier une telle demande 😅.&lt;/p&gt;
&lt;p&gt;En cas de dépassement de quota, le NodeGroup passe en phase &lt;code&gt;QuotaExceeded&lt;/code&gt; avec un message d&amp;rsquo;erreur très lisible :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;&amp;#39;Quota exceeded: RAM max: limit = 40.0 GB | actual = 48.0 GB | excess = 8.0 GB&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;C&amp;rsquo;est appréciable : on sait exactement ce qui bloque et de combien.&lt;/p&gt;
&lt;p&gt;La CLI propose aussi une gestion des node groups :&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 nodegroups create moncluster workers M:3
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clever k8s nodegroups update moncluster workers --count &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clever k8s nodegroups update moncluster workers --autoscaling --min &lt;span class="m"&gt;2&lt;/span&gt; --max &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="combien-de-temps-pour-booter-"&gt;Combien de temps pour booter ?
&lt;/h2&gt;&lt;p&gt;Un des points forts mis en avant par Clever Cloud. J&amp;rsquo;ai mesuré avec un script de benchmark (3 runs) et via la commande &lt;code&gt;clever k8s activity&lt;/code&gt; qui expose les timestamps précis de chaque étape :&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 activity moncluster --format json
&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-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&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="nt"&gt;&amp;#34;operation&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CREATE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;stepName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Deploy Control Plane&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;STARTED&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2026-05-12T06:27:11Z&amp;#34;&lt;/span&gt; &lt;span class="p"&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="nt"&gt;&amp;#34;operation&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CREATE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;stepName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Deploy Control Plane&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;COMPLETED&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2026-05-12T06:27:28Z&amp;#34;&lt;/span&gt; &lt;span class="p"&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="nt"&gt;&amp;#34;operation&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CREATE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;stepName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Install Plugins&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;STARTED&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2026-05-12T06:27:28Z&amp;#34;&lt;/span&gt; &lt;span class="p"&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="nt"&gt;&amp;#34;operation&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CREATE&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;stepName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Install Plugins&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;COMPLETED&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nt"&gt;&amp;#34;createdAt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;2026-05-12T06:28:01Z&amp;#34;&lt;/span&gt; &lt;span class="p"&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;activity&lt;/code&gt; détaille chaque étape du bootstrap : création du réseau, déploiement de Materia LogicalDB, déploiement du control plane, configuration du load balancer, installation des plugins. Elle couvre aussi les opérations sur les NodeGroups.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Résultats mesurés (K8s 1.35, 3 runs, 5 nodes S) :&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Étape&lt;/th&gt;
&lt;th&gt;Moyenne&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cluster DEPLOYING -&amp;gt; ACTIVE&lt;/td&gt;
&lt;td&gt;~57s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NodeGroup -&amp;gt; 1er node Ready&lt;/td&gt;
&lt;td&gt;~38s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NodeGroup -&amp;gt; tous les nodes Ready&lt;/td&gt;
&lt;td&gt;~46s (donc 8s après le premier node)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Suppression du cluster (API)&lt;/td&gt;
&lt;td&gt;~244ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Pour comparaison, SKS d&amp;rsquo;Exoscale bootstrappe le control plane en ~2 minutes (on me souffle dans l&amp;rsquo;oreille que ça a peut être baissé depuis). Et à la bonne époque où je faisais du AKS, c&amp;rsquo;était carrément 20+ minutes (j&amp;rsquo;espère qu&amp;rsquo;ils se sont améliorés depuis). Clever Cloud se place très haut sur ce critère.&lt;/p&gt;
&lt;h2 id="réseau-services-sécurité"&gt;Réseau, services, sécurité
&lt;/h2&gt;&lt;p&gt;LoadBalancer: chaque service de type LoadBalancer provisionne un L4 LB avec &lt;strong&gt;deux IPs publiques dédiées&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Ingress : pas d&amp;rsquo;ingress controller préinstallé. Traefik s&amp;rsquo;installe sans problème via Helm.&lt;/p&gt;
&lt;p&gt;NetworkPolicies : Cilium étant le CNI natif, les NetworkPolicies sont &lt;strong&gt;enforced nativement&lt;/strong&gt;. Pas besoin d&amp;rsquo;installer quoi que ce soit en plus.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note pour ceux qui auraient lu mon article de mars sur le &lt;a class="link" href="https://blog.zwindler.fr/2026/03/15/flannel-networkpolicies-cilium-cni-chaining/" &gt;CNI chaining Flannel/Cilium&lt;/a&gt; : cet article était né des tests sur la bêta privée de CKE, qui utilisait Flannel à l&amp;rsquo;époque. Depuis le passage en bêta publique, CKE livre Cilium nativement (les NetworkPolicies fonctionnent out of the box).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pod Security Admission : aucun namespace n&amp;rsquo;a de labels PSA par défaut (ni &lt;code&gt;kube-system&lt;/code&gt;, ni &lt;code&gt;default&lt;/code&gt;). Ça signifie que les pods sont en mode privileged implicite, sans restriction. On peut bien sûr configurer ça soi-même, mais sur un cluster managé on s&amp;rsquo;attendrait à avoir au minimum des profils &lt;code&gt;baseline&lt;/code&gt; ou &lt;code&gt;restricted&lt;/code&gt; préconfigurés sur les namespaces utilisateur.&lt;/p&gt;
&lt;p&gt;RBAC : pas de surprise, le kubeconfig fourni donne un accès &lt;code&gt;cluster-admin&lt;/code&gt;. A ne pas utiliser au delà de l&amp;rsquo;administration initiale ;-).&lt;/p&gt;
&lt;h2 id="stockage-csi"&gt;Stockage (CSI)
&lt;/h2&gt;&lt;p&gt;Le CSI n&amp;rsquo;est pas activé par défaut. Pour l&amp;rsquo;activer :&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;Activation en ~1 minute. Une StorageClass &lt;code&gt;csi-rbd-sc&lt;/code&gt; (défaut) est créée, provisionnée par &lt;code&gt;rbd.csi.ceph.com&lt;/code&gt; (Ceph RBD), filesystem XFS, volume expansion activée.&lt;/p&gt;
&lt;p&gt;Les tests complets du cycle de vie (PVC, resize, snapshots) feront l&amp;rsquo;objet de la partie 2.&lt;/p&gt;
&lt;h2 id="un-vrai-déploiement--groroti"&gt;Un vrai déploiement : GroROTI
&lt;/h2&gt;&lt;p&gt;Histoire de valider que tout fonctionne de bout en bout, j&amp;rsquo;ai déployé &lt;a class="link" href="https://github.com/deezer/GroROTI" target="_blank" rel="noopener"
&gt;GroROTI&lt;/a&gt; (ma propre application web en golang de ROTI) via Helm, avec Traefik en ingress controller et un certificat TLS.&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;helm install traefik traefik/traefik -n traefik --create-namespace
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm install groroti oci://ghcr.io/zwindler/groroti-chart &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -f values-clever.yaml &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace groroti --create-namespace
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Au bout de quelques minutes, l&amp;rsquo;application tourne sur &lt;code&gt;groroti.zwindler.fr&lt;/code&gt;, accessible publiquement, TLS inclus. Ça fonctionne.&lt;/p&gt;
&lt;h2 id="bugs-rencontrés-alpha--bêta-oblige"&gt;Bugs rencontrés (alpha / bêta oblige)
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;LoadBalancer&lt;/strong&gt; : lors de mes premiers tests (alpha / bêta privée), j&amp;rsquo;avais eu un cas où l&amp;rsquo;EXTERNAL-IP restait en &lt;code&gt;&amp;lt;pending&amp;gt;&lt;/code&gt; pendant&amp;hellip; 32 heures. Ce problème semble avoir disparu en bêta publique.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cluster FAILED sans diagnostic&lt;/strong&gt; : lors de mes tests, j&amp;rsquo;ai eu plusieurs clusters qui passaient en &lt;code&gt;FAILED&lt;/code&gt; ~25 secondes après la création, sans aucun message d&amp;rsquo;erreur exploitable. &lt;code&gt;clever k8s get &amp;lt;ID&amp;gt;&lt;/code&gt; retourne juste &lt;code&gt;Status: FAILED&lt;/code&gt;, même avec &lt;code&gt;--verbose&lt;/code&gt;. Aucune commande &lt;code&gt;logs&lt;/code&gt; ou &lt;code&gt;events&lt;/code&gt; pour un cluster n&amp;rsquo;existe dans la CLI.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est d&amp;rsquo;autant plus frustrant que le status &lt;code&gt;QuotaExceeded&lt;/code&gt; des NodeGroups est, lui, extrêmement bien documenté dans le message d&amp;rsquo;erreur. Il y a une belle asymétrie là.&lt;/p&gt;
&lt;p&gt;Le bug a été résolu côté Clever Cloud dans la nuit. Mais le manque de diagnostic reste un point à améliorer. L&amp;rsquo;UI est aussi encore clairement en bêta, avec un petit bug graphique que j&amp;rsquo;ai pu remonter.&lt;/p&gt;
&lt;h2 id="conclusion-partie-1"&gt;Conclusion (partie 1)
&lt;/h2&gt;&lt;p&gt;Première impression globalement positive. CKE est impressionnant pour une bêta :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Boot time excellent&lt;/strong&gt; : ~57s pour le cluster (CP à part ou All in one), ~38s pour le premier node quand on ajoute des NodeGroups (dans le haut du panier des Kubernetes managés que j&amp;rsquo;ai testé au fil des années)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cilium natif&lt;/strong&gt; avec WireGuard inter-nodes, NetworkPolicies out of the box&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Materia etcd sur FoundationDB&lt;/strong&gt; : une approche originale pour gérer les problématiques etcd (scalabilité, multi tenancy) d&amp;rsquo;un cloud provider&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Design CRD propre&lt;/strong&gt; pour les NodeGroups, compatible &lt;code&gt;kubectl scale&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Konnectivity&lt;/strong&gt; : isolation réseau du CP comme chez les grands cloud providers&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les points à améliorer : diagnostic du status FAILED, PSA non configurée par défaut, pas d&amp;rsquo;ingress controller inclus.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai aussi une petite feature que j&amp;rsquo;ai demandé à Antoine chez Clever, l&amp;rsquo;audit logging, c&amp;rsquo;est en cours. Wait and see ;-).&lt;/p&gt;
&lt;p&gt;La tarification du control plane me parait un poil élevée, typiquement face à des concurrents européens comme SKS ou Kapsule (mode mutualisé), mais elle s&amp;rsquo;explique par le choix architectural (VMs dédiées, rien de mutualisé). Pour une équipe déjà sur Clever Cloud, l&amp;rsquo;intégration avec le Clever Kubernetes Operator et les add-ons managés est un vrai argument.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dans la partie 2&lt;/strong&gt;, on creusera sûrement plus de fonctionnalités, comme le CSI (lifecycle PVC, resize, snapshots), les upgrades de cluster, l&amp;rsquo;intégration avec Clever Cloud operator (justement). Peut être aussi qu&amp;rsquo;on testera vCluster ou k3k ?&lt;/p&gt;</description></item><item><title>UserNamespaces dans Kubernetes : la feature géniale qui sert (presque) à rien</title><link>https://blog.zwindler.fr/2026/04/28/kubernetes-usernamespaces/</link><pubDate>Tue, 28 Apr 2026 10:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/04/28/kubernetes-usernamespaces/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/04/usernamespaces.webp" alt="Featured image of post UserNamespaces dans Kubernetes : la feature géniale qui sert (presque) à rien" /&gt;&lt;h2 id="linfographie-qui-ma-trigger"&gt;L&amp;rsquo;infographie qui m&amp;rsquo;a trigger
&lt;/h2&gt;&lt;p&gt;Depuis quelques jours, les infographies se suivent (et se ressemblent) sur Linkedin. Kubernetes 1.36 est sorti et une des features qui fait le plus parler, c&amp;rsquo;est la sortie en GA des &lt;strong&gt;UserNamespaces&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est un sujet que je suis depuis 2018 (talk &lt;a class="link" href="https://blog.zwindler.fr/2018/05/03/recap-du-premier-jour-de-kubecon-europe-2018/" target="_blank" rel="noopener"
&gt;The Route to rootless container&lt;/a&gt; à la kubecon EU de 2018) donc je peux dire que je suis content de voir l&amp;rsquo;aboutissement de ce long chemin. Cependant, je suis &amp;ldquo;profondément choqué&amp;rdquo; de voir la façon dont c&amp;rsquo;est présenté sur LinkedIn, visiblement par des gens qui n&amp;rsquo;ont aucune idée de comment ça fonctionne (et qui probablement, s&amp;rsquo;en fichent).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Kubernetes just made root safer. Just add &lt;code&gt;hostUsers: false&lt;/code&gt; to your Pod spec.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Le visuel : un roi tout-puissant &amp;ldquo;inside the container&amp;rdquo; et un mendiant impuissant &amp;ldquo;outside on the host&amp;rdquo;. La promesse : &amp;ldquo;No Host Access. No Privilege Escalation. No Lateral Movement. No Node Takeover.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est accrocheur.&lt;/p&gt;
&lt;p&gt;Mais c&amp;rsquo;est surtout hyper grave de le présenter comme ça, parce que ça occulte des pans entiers de sécurité applicative et opérationnelle. Vendre &lt;code&gt;hostUsers: false&lt;/code&gt; comme le remède universel au problème du &amp;ldquo;root dans les containers&amp;rdquo;, c&amp;rsquo;est une simplification dramatique qui va pousser des équipes à ignorer les vraies &lt;strong&gt;priorités&lt;/strong&gt; de sécurité.&lt;/p&gt;
&lt;h2 id="ce-que-les-usernamespaces-font-réellement-sans-bullshit"&gt;Ce que les UserNamespaces font réellement, sans bullshit
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Le threat model : le container escape&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Avant tout, de quoi parle-t-on exactement ? Un &lt;strong&gt;container escape&lt;/strong&gt; (évasion de container), c&amp;rsquo;est quand un attaquant réussit à sortir de son container et à accéder directement au kernel ou au système de fichiers de l&amp;rsquo;hôte, en bypassant complètement les mécanismes d&amp;rsquo;isolation habituels.&lt;/p&gt;
&lt;p&gt;Ce type de vulnérabilité est &lt;strong&gt;rare&lt;/strong&gt;, mais des exemples bien réels existent :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2019-5736" target="_blank" rel="noopener"
&gt;CVE-2019-5736&lt;/a&gt;&lt;/strong&gt; (runc) : écriture dans &lt;code&gt;/proc/self/exe&lt;/code&gt; du process hôte depuis le container&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2022-0492" target="_blank" rel="noopener"
&gt;CVE-2022-0492&lt;/a&gt;&lt;/strong&gt; (cgroups v1) : escape via &lt;code&gt;unshare&lt;/code&gt; dans certaines configurations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2024-21626" target="_blank" rel="noopener"
&gt;CVE-2024-21626&lt;/a&gt;&lt;/strong&gt; (runc, &amp;ldquo;Leaky Vessels&amp;rdquo;) : fuite de file descriptor vers le répertoire de travail de l&amp;rsquo;hôte&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;S&amp;rsquo;il y a une faille de ce type sur votre Node ET qu&amp;rsquo;un process est compromis ET s&amp;rsquo;il tourne en root dans le container ET qu&amp;rsquo;il n&amp;rsquo;a pas les UserNamespaces, l&amp;rsquo;attaquant obtient &lt;strong&gt;root sur l&amp;rsquo;hôte&lt;/strong&gt;, c&amp;rsquo;est &lt;strong&gt;game over&lt;/strong&gt;. Accès à tous les fichiers du Node, à tous les secrets montés par les autres pods, possibilité d&amp;rsquo;installer un rootkit ou d&amp;rsquo;exfiltrer les données de tous les tenants présents sur le Node.&lt;/p&gt;
&lt;p&gt;Ca reste possible, mais ça fait beaucoup de &amp;ldquo;si&amp;rdquo;. Quoiqu&amp;rsquo;il en soit, c&amp;rsquo;est exactement ce scénario que les UserNamespaces adressent. Ils introduisent un &lt;strong&gt;mapping d&amp;rsquo;UID&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L&amp;rsquo;UID 0 dans le container est mappé vers un UID non-privilégié sur l&amp;rsquo;hôte (ex: 100000, propre à chaque pod)&lt;/li&gt;
&lt;li&gt;Si un attaquant réussit à s&amp;rsquo;échapper du container via un exploit kernel, il se retrouve &lt;strong&gt;&lt;code&gt;nobody&lt;/code&gt;&lt;/strong&gt; sur le node, l&amp;rsquo;escape réussit, mais l&amp;rsquo;impact post-escape est dramatiquement réduit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C&amp;rsquo;est le scénario &amp;ldquo;Breakouts Lose Impact&amp;rdquo; de l&amp;rsquo;infographie, et là-dessus, &lt;strong&gt;l&amp;rsquo;infographie dit vrai&lt;/strong&gt;. C&amp;rsquo;est le vrai apport de la feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cas particulier : le multi-tenant même en non-root&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Même sans container root, les UserNamespaces apportent aussi quelque chose dans un contexte &lt;strong&gt;vraiment multi-tenant&lt;/strong&gt; (plusieurs clients différents sur le même cluster). Sans UserNamespaces, si deux pods de clients différents tournent tous les deux avec &lt;code&gt;runAsUser: 1000&lt;/code&gt;, ils partagent le même UID 1000 sur le node. En cas d&amp;rsquo;escape sur l&amp;rsquo;un, l&amp;rsquo;attaquant peut accéder aux fichiers de l&amp;rsquo;autre pod qui ont le même propriétaire. Le mapping UID de UserNamespaces, en donnant un offset unique à chaque pod, isole les UID entre pods même à valeur identique dans le container.&lt;/p&gt;
&lt;p&gt;Pour les clusters internes où vous contrôlez tous les workloads, ce scénario est théorique. Pour une plateforme SaaS multi-tenant ou un service de build public, c&amp;rsquo;est une vraie ligne de défense.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prérequis techniques&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pour pouvoir bénéficier de cette feature, il y a quelques prérequis, mais la majorité des clusters à jour devraient pouvoir se qualifier.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kernel Linux ≥ 5.19&lt;/li&gt;
&lt;li&gt;Runtime compatible (containerd ≥ 1.7, CRI-O ≥ 1.25)&lt;/li&gt;
&lt;li&gt;Support des &lt;em&gt;idmapped mounts&lt;/em&gt; pour les volumes persistants (XFS, ext4, mais pas NFS dans tous les cas par exemple)&lt;/li&gt;
&lt;li&gt;Kubernetes ≥ 1.33 (Beta), ≥ 1.36 (GA)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ce-que-linfographie-exagère-et-ce-quelle-oublie"&gt;Ce que l&amp;rsquo;infographie exagère (et ce qu&amp;rsquo;elle oublie)
&lt;/h2&gt;&lt;p&gt;L&amp;rsquo;infographie a donc raison sur un point précis : UserNamespaces réduit l&amp;rsquo;impact d&amp;rsquo;un container escape réussi. C&amp;rsquo;est réel. Le problème, c&amp;rsquo;est qu&amp;rsquo;elle vend la feature comme solution universelle au &amp;ldquo;root dans les containers&amp;rdquo;, et là c&amp;rsquo;est n&amp;rsquo;importe quoi.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. L&amp;rsquo;isolation de l&amp;rsquo;UID n&amp;rsquo;est pas une isolation des privilèges applicatifs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;infographie promet &amp;ldquo;No Lateral Movement&amp;rdquo;. C&amp;rsquo;est faux (et archi faux).&lt;/p&gt;
&lt;p&gt;Un container root avec &lt;code&gt;hostUsers: false&lt;/code&gt; peut toujours lire le &lt;strong&gt;ServiceAccount Token&lt;/strong&gt; monté dans &lt;code&gt;/var/run/secrets/kubernetes.io/serviceaccount/token&lt;/code&gt;. Si ce token a des permissions RBAC étendues (ce qui arrive, on en reparlera peut être dans un autre article prochainement), l&amp;rsquo;attaquant peut appeler l&amp;rsquo;API Server, énumérer les ressources du cluster, et se déplacer latéralement, le tout sans jamais toucher le Node hôte.&lt;/p&gt;
&lt;p&gt;Le mapping d&amp;rsquo;UID protège l&amp;rsquo;hôte. Il ne protège pas le cluster.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Un container root reste root dans le container&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Install Anything ✅&amp;rdquo; — c&amp;rsquo;est littéralement écrit dans l&amp;rsquo;infographie, présenté comme un avantage 😖.&lt;/p&gt;
&lt;p&gt;Dans un container root (même avec UserNS), un attaquant qui prend la main peut :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installer &lt;code&gt;nmap&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;nc&lt;/code&gt; pour scanner le réseau interne&lt;/li&gt;
&lt;li&gt;Modifier les fichiers de l&amp;rsquo;application, les binaires, les configurations&lt;/li&gt;
&lt;li&gt;Lire tous les fichiers montés en volume&lt;/li&gt;
&lt;li&gt;Persister dans le container entre les redémarrages si le filesystem est writable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les UserNamespaces ne retirent aucun de ces vecteurs d&amp;rsquo;attaque. Pouvoir ajouter des logiciels, c&amp;rsquo;est la garantie d&amp;rsquo;un mouvement latéral rapide (là encore).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. C&amp;rsquo;est pas si facile, surtout pour le sto&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Activer &lt;code&gt;hostUsers: false&lt;/code&gt; casse le stockage existant dans la plupart des cas.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;UID 0 du container est mappé sur l&amp;rsquo;UID 100000+ sur l&amp;rsquo;hôte (chaque container a son propre &amp;ldquo;offset&amp;rdquo;). Si un volume persistant (NFS, EBS, Ceph RBD) appartient à l&amp;rsquo;UID 1000, le container root ne peut ni lire ni écrire dessus. Le résultat : des &lt;code&gt;Permission Denied&lt;/code&gt; contre intuitifs, potentiellement complexes à diagnostiquer car l&amp;rsquo;application n&amp;rsquo;a probablement pas été conçue, si elle est root, pour ne pas avoir accès à ses propres fichiers.&lt;/p&gt;
&lt;p&gt;La solution technique existe (&lt;em&gt;idmapped mounts&lt;/em&gt;), mais elle nécessite un kernel récent et un filesystem compatible. Voir la &lt;a class="link" href="https://www.kernel.org/doc/html/latest/filesystems/idmappings.html" target="_blank" rel="noopener"
&gt;documentation officielle des idmapped mounts&lt;/a&gt; pour les détails.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Idem, mais pour le réseau&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hostUsers: false&lt;/code&gt; est incompatible avec &lt;code&gt;hostNetwork: true&lt;/code&gt;. C&amp;rsquo;est un détail, mais il piège les workloads réseau (agents de monitoring, CNI plugins, etc.).&lt;/p&gt;
&lt;p&gt;Note : cela dit, avoir des containers en hostNetwork est &lt;strong&gt;un autre souci de sécurité&lt;/strong&gt;, donc bon&amp;hellip;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="comparaison-sans-bullshit--userns-vs-les-alternatives-existantes"&gt;Comparaison sans bullshit : UserNS vs les alternatives existantes
&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;Vecteur d&amp;rsquo;attaque&lt;/th&gt;
&lt;th style="text-align: center"&gt;UserNS (root inside)&lt;/th&gt;
&lt;th style="text-align: center"&gt;Non-root (UID 1000)&lt;/th&gt;
&lt;th style="text-align: center"&gt;Distroless / Scratch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Impact post-escape si container escape réussi&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Nobody sur l&amp;rsquo;hôte&lt;/td&gt;
&lt;td style="text-align: center"&gt;⚠️ UID 1000 sur l&amp;rsquo;hôte&lt;/td&gt;
&lt;td style="text-align: center"&gt;⚠️ UID 1000 sur l&amp;rsquo;hôte&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Isolation UID entre pods (multi-tenant)&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Offset unique par pod&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ UID partagé sur le node&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ UID partagé sur le node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Installation de malwares dans le container&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Trivial&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Possible&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Quasi impossible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Périmètre d&amp;rsquo;écriture dans le FS éphémère du container&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Tout le FS&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Répertoire de l&amp;rsquo;app&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Quasi impossible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Mouvement latéral via SA Token&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Possible&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Possible&lt;/td&gt;
&lt;td style="text-align: center"&gt;⚠️ Potentiellement difficile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Complexité opérationnelle&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Parfois élevée&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Souvent quasi nulle&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Souvent faible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Compatibilité stockage existant&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Parfois problématique&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Standard&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Standard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;La lecture de la table révèle la vraie nature d&amp;rsquo;UserNamespaces, il excelle sur &lt;strong&gt;exactement deux lignes&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;l&amp;rsquo;impact post-escape&lt;/li&gt;
&lt;li&gt;l&amp;rsquo;isolation UID en multi-tenant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sur tout le reste, non-root + distroless fait mieux, ou aussi bien, sans la complexité opérationnelle. Et ce sont ces &amp;ldquo;tout le reste&amp;rdquo; (périmètre d&amp;rsquo;écriture dans le FS, installation de malwares, mouvement latéral via SA Token) qui représentent la grande majorité des vecteurs d&amp;rsquo;attaque réels, bien plus fréquents qu&amp;rsquo;un container escape. On en reparlera dans la section &lt;a class="link" href="#o%c3%b9-investir-son-budget-s%c3%a9curit%c3%a9" &gt;Où investir son budget sécurité&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="les-vrais-cas-dusage"&gt;Les vrais cas d&amp;rsquo;usage
&lt;/h2&gt;&lt;p&gt;Ce serait malhonnête de tout rejeter. Il existe trois scénarios où les UserNamespaces ne sont pas une option &amp;ldquo;fainéante&amp;rdquo; mais une &lt;em&gt;nécessité&lt;/em&gt; technique (et encore, ça se discute).&lt;/p&gt;
&lt;h3 id="1-build-as-a-service-buildah-rootless-podman"&gt;1. Build-as-a-Service (Buildah, rootless Podman)
&lt;/h3&gt;&lt;p&gt;Pour construire une image Docker, le moteur de build doit pouvoir faire des &lt;code&gt;chown&lt;/code&gt;, &lt;code&gt;chmod&lt;/code&gt; et &lt;code&gt;mknod&lt;/code&gt;. Ces opérations nécessitent &lt;code&gt;CAP_CHOWN&lt;/code&gt; et &lt;code&gt;CAP_FOWNER&lt;/code&gt;. Avant les UserNamespaces, la solution était de lancer le pod en &lt;code&gt;--privileged&lt;/code&gt;, ce qui est évidemment une porte ouverte sur l&amp;rsquo;hôte.&lt;/p&gt;
&lt;p&gt;Avec &lt;code&gt;hostUsers: false&lt;/code&gt;, le moteur de build a l&amp;rsquo;illusion d&amp;rsquo;être root pour manipuler ses fichiers, mais il est incapable d&amp;rsquo;impacter l&amp;rsquo;hôte. C&amp;rsquo;est le seul cas où &amp;ldquo;root inside&amp;rdquo; est une contrainte technique et non de la dette.&lt;/p&gt;
&lt;p&gt;Note : &lt;a class="link" href="https://github.com/GoogleContainerTools/kaniko" target="_blank" rel="noopener"
&gt;Kaniko&lt;/a&gt;, longtemps la référence pour le build in-cluster, est archivé depuis juin 2025 et ne reçoit plus de mises à jour de sécurité. Buildah ou rootless Podman sont les alternatives actives.&lt;/p&gt;
&lt;p&gt;Mon avis : ça peut être éventuellement utile pour les plateformes CI/CD mutualisées (GitLab Runners, Tekton) qui refusent les pods privilégiés. Mais si l&amp;rsquo;isolation est critique (plateforme publique, multi-tenant agressif), les microVMs (Kata Containers, Firecracker) offrent une garantie bien supérieure pour un overhead devenu raisonnable.&lt;/p&gt;
&lt;h3 id="2-multi-tenancy-hostile-plateformes-de-code-utilisateur"&gt;2. Multi-tenancy hostile (plateformes de code utilisateur)
&lt;/h3&gt;&lt;p&gt;Si votre métier est de faire tourner du code fourni par des inconnus (PaaS, éditeur de code en ligne, CI/CD publique), vous savez d&amp;rsquo;avance que l&amp;rsquo;utilisateur &lt;em&gt;va&lt;/em&gt; essayer d&amp;rsquo;escalader ses privilèges. Dans ce contexte, l&amp;rsquo;UserNS est une barrière supplémentaire contre les 0-day kernel.&lt;/p&gt;
&lt;p&gt;Mon avis : honnêtement, si l&amp;rsquo;environnement est vraiment &lt;strong&gt;hostile&lt;/strong&gt;, l&amp;rsquo;UserNS seul n&amp;rsquo;est pas suffisant. Les microVMs (Kata Containers, Firecracker) offrent une isolation matérielle réelle et sont le choix correct pour ce cas. L&amp;rsquo;UserNS peut être un complément, pas un substitut.&lt;/p&gt;
&lt;h3 id="3-legacy-hard-coded-postfix-dovecot-bind"&gt;3. Legacy &amp;ldquo;hard-coded&amp;rdquo; (Postfix, Dovecot, BIND)
&lt;/h3&gt;&lt;p&gt;Certains vieux démons UNIX démarrent en root pour ouvrir un port &amp;lt; 1024 ou lire des fichiers de config sensibles, puis &amp;ldquo;drop&amp;rdquo; leurs privilèges via &lt;code&gt;setuid()&lt;/code&gt;. Ce mécanisme échoue dans un container non-root classique.&lt;/p&gt;
&lt;p&gt;Les UserNamespaces permettent à ces processus de croire qu&amp;rsquo;ils peuvent faire leurs appels système de gestion d&amp;rsquo;identité, car ils sont root dans leur namespace.&lt;/p&gt;
&lt;p&gt;Voici un exemple concret écrit par un collègue (thanks Louis 😘) :&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;Pod&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;postfix&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;hostUsers: false # mapping UID &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 dans le container → nobody sur l&amp;#39;hôte&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;securityContext&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;runAsNonRoot: false # autorisé en PSS Restricted *uniquement* grâce à hostUsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;fsGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;103&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# GID postfix&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;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;postfix&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;postfix: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;securityContext&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;runAsNonRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# idem, cf. https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#integration-with-pod-security-admission-checks&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;allowPrivilegeEscalation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;readOnlyRootFilesystem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;seccompProfile&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;RuntimeDefault&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;capabilities&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;drop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ALL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# d&amp;#39;abord, on drop tout&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;add&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;SETUID &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# on ajoute SETUID mais le drop de privilèges via setuid() &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="c"&gt;# est fait par postfix lui-même au démarrage &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;SETGID &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# idem pour les groupes&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;CHOWN &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# chown sur les queues au démarrage&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;FOWNER &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# opérations sur fichiers sans être propriétaire&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;FSETID &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# conserver le setuid bit après écriture&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;DAC_OVERRIDE # OBLIGATOIRE &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 dans UserNS n&amp;#39;est pas &amp;#34;vrai&amp;#34; 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="c"&gt;# les vérifications DAC ne sont pas contournées automatiquement&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;Ce manifest illustre plusieurs choses importantes.&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;abord, c&amp;rsquo;est très pénible de rendre une application legacy secure avec les UserNS, et il faut faire des compromis, notamment sur les capabilities (on est loin de la feature magique qui sécurise les apps root).&lt;/p&gt;
&lt;p&gt;Ensuite, on remarque des trucs rigolos. Normalement, la policy &lt;code&gt;Restricted&lt;/code&gt; (Pod Security Standard) interdit &lt;code&gt;runAsNonRoot: false&lt;/code&gt;. Kubernetes fait une exception quand &lt;code&gt;hostUsers: false&lt;/code&gt; est présent. C&amp;rsquo;est documenté &lt;a class="link" href="https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#integration-with-pod-security-admission-checks" target="_blank" rel="noopener"
&gt;ici&lt;/a&gt;. Sans UserNamespaces, ce pod serait rejeté par l&amp;rsquo;admission controller.&lt;/p&gt;
&lt;p&gt;De plus, on doit ajouter la &lt;strong&gt;capacité &lt;code&gt;DAC_OVERRIDE&lt;/code&gt;&lt;/strong&gt;, ce qui est contre-intuitif. root dans un UserNS n&amp;rsquo;est pas un vrai root du point de vue du kernel pour les vérifications DAC (Discretionary Access Control). Quand Postfix essaie de faire &lt;code&gt;set-permissions&lt;/code&gt; pour &lt;code&gt;chown&lt;/code&gt; ses queues, le kernel vérifie quand même les permissions ; et les refuse si &lt;code&gt;DAC_OVERRIDE&lt;/code&gt; n&amp;rsquo;est pas là. C&amp;rsquo;est exactement le genre de surprise opérationnelle invisible jusqu&amp;rsquo;au premier déploiement en production.&lt;/p&gt;
&lt;p&gt;On notera malgré tout qu&amp;rsquo;on a pu garder &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt; et &lt;code&gt;allowPrivilegeEscalation: false&lt;/code&gt; ; le legacy ne justifie pas de tout sacrifier.&lt;/p&gt;
&lt;p&gt;Mon avis : c&amp;rsquo;est le seul cas d&amp;rsquo;usage où l&amp;rsquo;UserNS est vraiment acceptable. Pas de code tiers non maîtrisé, pas de plateforme hostile, juste du legacy bien identifié &lt;strong&gt;avec un plan de migration&lt;/strong&gt; ultérieur. Les deux autres cas sont &amp;ldquo;acceptables sous conditions&amp;rdquo;, le legacy reste le plus propre des trois.&lt;/p&gt;
&lt;h2 id="quelques-contre-arguments"&gt;Quelques contre-arguments
&lt;/h2&gt;&lt;p&gt;Je vous vois arriver avec quelques contre arguments, donc pour gagner du temps à tout le monde, je vais faire les questions et les réponses :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;C&amp;rsquo;est de la défense en profondeur.&amp;rdquo;&lt;/strong&gt;
&amp;ldquo;Vrai, mais&amp;rdquo;&amp;hellip; La défense en profondeur suppose qu&amp;rsquo;on a déjà posé les couches de base. Si vous n&amp;rsquo;avez pas encore migré vos images vers un user non-root, mettre de l&amp;rsquo;énergie sur l&amp;rsquo;UserNS est un non-sens. Et une fois en non-root, le gain marginal de l&amp;rsquo;UserNS est négligeable face à la complexité qu&amp;rsquo;il introduit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;On ne contrôle pas les images tierces.&amp;rdquo;&lt;/strong&gt;
Argument un peu faible, à mon avis : si vous avez une image blackbox de votre éditeur propriétaire, qui est codée pour tourner en root, il y a de fortes chances :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;soit qu&amp;rsquo;elle en ait réellement besoin pour fonctionner (typiquement, le cas pour certains outils de sécurité propriétaires)&lt;/li&gt;
&lt;li&gt;soit qu&amp;rsquo;elle gère mal le mapping d&amp;rsquo;UID (cf. le problème de stockage).
L&amp;rsquo;UserNS n&amp;rsquo;est pas une baguette magique qui rend n&amp;rsquo;importe quelle image tierce compatible et sécurisée.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;C&amp;rsquo;est un garde-fou contre les erreurs humaines.&amp;rdquo;&lt;/strong&gt;
Il est aussi facile d&amp;rsquo;oublier &lt;code&gt;hostUsers: false&lt;/code&gt; que d&amp;rsquo;oublier &lt;code&gt;runAsNonRoot: true&lt;/code&gt;. La vraie solution centralisée, ce sont les &lt;strong&gt;Pod Security Standards&lt;/strong&gt; ou un Admission Controller (Kyverno, OPA) qui rejettent purement et simplement les pods root. C&amp;rsquo;est plus simple, plus fiable, et ça ne casse pas le stockage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;On en a besoin pour la compliance SOC2/PCI-DSS/&amp;hellip;&amp;rdquo;&lt;/strong&gt;
Si votre compliance exige une isolation stricte entre tenants, l&amp;rsquo;UserNS sera probablement jugé insuffisant par vos auditeurs. Les VMs ou microVMs restent le standard. Utiliser l&amp;rsquo;UserNS pour la compliance, c&amp;rsquo;est choisir l&amp;rsquo;outil le plus complexe à maintenir pour un résultat discutable.&lt;/p&gt;
&lt;h2 id="où-investir-son-budget-sécurité-"&gt;Où investir son budget sécurité ?
&lt;/h2&gt;&lt;p&gt;Si on met de côté le marketing, voici où l&amp;rsquo;effort paye vraiment, du plus impactant au plus niche :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Priorité 1 — Images non-root + &lt;code&gt;nobody&lt;/code&gt; (UID 65534)&lt;/strong&gt;
Passer les images en non-root, idéalement vers l&amp;rsquo;utilisateur &lt;code&gt;nobody&lt;/code&gt; (le moins privilégié du système). Si une application est compromise sous &lt;code&gt;nobody&lt;/code&gt;, l&amp;rsquo;attaquant ne peut pratiquement rien faire, même sur le filesystem du container. À combiner avec &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt; et &lt;code&gt;capabilities: drop: [&amp;quot;ALL&amp;quot;]&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;securityContext&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;runAsNonRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;runAsUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65534&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# nobody&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;seccompProfile&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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;RuntimeDefault&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;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;app&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;my-app:distroless&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;securityContext&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;allowPrivilegeEscalation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;capabilities&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;drop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ALL&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="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;readOnlyRootFilesystem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;&lt;strong&gt;Priorité 2 — Pod Security Standards (PSS) en mode &lt;code&gt;Baseline&lt;/code&gt; ou &lt;code&gt;Restricted&lt;/code&gt;&lt;/strong&gt;
Bloquer le root et les privilèges sans rien casser au niveau infra. Ça nécessite d&amp;rsquo;avoir déjà fait le point n°1, mais c&amp;rsquo;est gratuit, standard, et ça s&amp;rsquo;applique à tout le cluster via un label de namespace (et ça peut s&amp;rsquo;overrider par namespace si nécessaire). Plus de risque d&amp;rsquo;oubli possible. C&amp;rsquo;est déjà configuré par défaut sur plusieurs types de Kubernetes (je pense à Talos, mais ce n&amp;rsquo;est pas le seul).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Priorité 3 — MicroVMs (Kata Containers, Firecracker)&lt;/strong&gt;
Pour les workloads vraiment non-fiables. Isolation matérielle réelle, overhead devenu raisonnable sur les générations récentes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Priorité 4 — UserNamespaces&lt;/strong&gt;
When all else fail. Uniquement pour les cas légitimes identifiés ci-dessus (build, legacy, multi-tenancy hostile). C&amp;rsquo;est vraiment littéralement la &lt;strong&gt;dernière&lt;/strong&gt; chose à faire.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Les UserNamespaces dans Kubernetes 1.36 sont l&amp;rsquo;aboutissement d&amp;rsquo;un chantier qui aura pris &amp;ldquo;officiellement&amp;rdquo; cinq ans (KEP-127 date de 2021) et dont on parle quasiment depuis la genèse de Kubernetes. Pour les plateformes de build mutualisées et les SaaS multi-tenants qui font tourner du code utilisateur, c&amp;rsquo;est une brique potentiellement intéressante (notamment pour éviter qu&amp;rsquo;une app d&amp;rsquo;un client lise les apps d&amp;rsquo;un autre en cas de container escape sans élévation de privilège).&lt;/p&gt;
&lt;p&gt;Pour le reste (c&amp;rsquo;est-à-dire 99% des clusters de production) ce n&amp;rsquo;est pas là que commence la sécurité container — et c&amp;rsquo;est précisément le problème avec ce genre d&amp;rsquo;infographie.&lt;/p&gt;
&lt;p&gt;Les infographies LinkedIn qui vendent une sécurité sans effort sont dangereuses : &amp;ldquo;gardez votre image root de 800 Mo pleine d&amp;rsquo;outils, ajoutez juste &lt;code&gt;hostUsers: false&lt;/code&gt;, et vous êtes protégés&amp;rdquo;. C&amp;rsquo;est exactement &lt;strong&gt;l&amp;rsquo;inverse de la bonne démarche&lt;/strong&gt;. La vraie sécurité container se construit dans le Dockerfile, pas dans le PodSpec.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Si vous activez les UserNamespaces pour sécuriser une application dont vous possédez le code source, vous avez probablement raté une étape dans votre cycle de développement sécurisé.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="références"&gt;Références
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/127-user-namespaces/README.md" target="_blank" rel="noopener"
&gt;KEP-127 : Support for User Namespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/" target="_blank" rel="noopener"
&gt;Kubernetes docs — User Namespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/concepts/security/pod-security-standards/" target="_blank" rel="noopener"
&gt;Pod Security Standards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2019-5736" target="_blank" rel="noopener"
&gt;CVE-2019-5736&lt;/a&gt; — runc : écriture dans &lt;code&gt;/proc/self/exe&lt;/code&gt; du process hôte depuis le container&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2022-0492" target="_blank" rel="noopener"
&gt;CVE-2022-0492&lt;/a&gt; — cgroups v1 : escape via &lt;code&gt;unshare&lt;/code&gt;, UserNS aide mais &lt;code&gt;runAsNonRoot&lt;/code&gt; aussi&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2024-21626" target="_blank" rel="noopener"
&gt;CVE-2024-21626&lt;/a&gt; — runc &amp;ldquo;Leaky Vessels&amp;rdquo; : fuite de file descriptor vers le répertoire de travail de l&amp;rsquo;hôte&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/GoogleContainerTools/distroless" target="_blank" rel="noopener"
&gt;Distroless containers — GoogleContainerTools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>DevoxxFR 2026 - Récap du vendredi (jour 3)</title><link>https://blog.zwindler.fr/2026/04/24/devoxxfr-2026-recap-jour-3/</link><pubDate>Fri, 24 Apr 2026 16:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/04/24/devoxxfr-2026-recap-jour-3/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/04/limits-requests.webp" alt="Featured image of post DevoxxFR 2026 - Récap du vendredi (jour 3)" /&gt;&lt;p&gt;Les résumés des 3 jours de DevoxxFR 2026&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2026/04/22/devoxxfr-2026-recap-jour-1/" &gt;DevoxxFR - Récap du mercredi (jour 1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2026/04/23/devoxxfr-2026-recap-jour-2/" &gt;DevoxxFR - Récap du jeudi (jour 2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2026/04/24/devoxxfr-2026-recap-jour-3/" &gt;DevoxxFR - Récap du vendredi (jour 3)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="troisième-jour-de-devoxxfr-2026"&gt;Troisième jour de DevoxxFR 2026
&lt;/h2&gt;&lt;p&gt;Dernier jour, et surtout pour moi : c&amp;rsquo;était le créneau de notre talk.&lt;/p&gt;
&lt;p&gt;Entre le &amp;ldquo;petit&amp;rdquo; stress avant de monter sur scène, la tension des démos live, puis le relâchement après coup, cette journée a eu une saveur différente. Moins de talks que les deux jours précédents dans mon programme, et je suis un peu rincé dans mon train retour.&lt;/p&gt;
&lt;h2 id="limits-requests-qos-priorityclasses-démystifions-le-scheduling-dans-kubernetes"&gt;Limits, Requests, QoS, PriorityClasses, démystifions le scheduling dans Kubernetes
&lt;/h2&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/04/limits-requests.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;était notre session, avec un objectif simple : rendre concret un ensemble de concepts que tout le monde voit passer (&lt;code&gt;requests&lt;/code&gt;, &lt;code&gt;limits&lt;/code&gt;, classes de QoS, &lt;code&gt;PriorityClass&lt;/code&gt;, ordonnancement), mais qui restent souvent flous tant qu&amp;rsquo;on ne les voit pas en situation réelle.&lt;/p&gt;
&lt;p&gt;Le format est maintenant bien rodé (on l&amp;rsquo;avait déjà donné notamment au DevFest Nantes et à Touraine Tech 2026), mais cette édition était clairement notre meilleure exécution jusqu&amp;rsquo;ici :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;transitions très fluides ;&lt;/li&gt;
&lt;li&gt;démos live qui ont toutes fonctionné ;&lt;/li&gt;
&lt;li&gt;bon rythme entre explication pédagogique et incidents provoqués ;&lt;/li&gt;
&lt;li&gt;super interactions avec la salle et beaucoup de questions à la sortie.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On a eu plusieurs personnes qui sont venues nous voir spontanément après la session pour échanger sur leurs cas de prod. C&amp;rsquo;est exactement ce qu&amp;rsquo;on espérait : démystifier, puis rendre actionnable.&lt;/p&gt;
&lt;p&gt;Plusieurs personnes sont aussi venues simplement nous féliciter à la sortie, et ça m&amp;rsquo;a vraiment touché.&lt;/p&gt;
&lt;p&gt;Côté feedback, les retours ont été excellents, avec une note moyenne de 4.91/5 (45 évaluations) et des commentaires très positifs sur la clarté, l&amp;rsquo;humour et les démos.&lt;/p&gt;
&lt;p&gt;Deux retours (parmi 17 !!!) qui résument bien l&amp;rsquo;esprit de la session :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Très dynamique, un magnifique récap et des démos vraiment bien construites et claires. Merci pour ce moment de partage.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Des concepts un peu obscurs pour les développeurs, enfin démystifiés, avec de l&amp;rsquo;humour et des démos qui marchent (toutes !), et des conseils simples et actionnables.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Franchement, gros moment de satisfaction.&lt;/p&gt;
&lt;h2 id="interview-au-studio-devoxx"&gt;Interview au Studio Devoxx
&lt;/h2&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/04/interview-studio-devoxxfr.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;avais aussi un enregistrement prévu au Studio Devoxx, le nouveau format vidéo/podcast de la conférence.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;étais un peu stressé au départ, mais Emmanuel Bernard m&amp;rsquo;a rapidement mis à l&amp;rsquo;aise. Très vite, la discussion est devenue naturelle et très agréable.&lt;/p&gt;
&lt;p&gt;On a parlé de Kubernetes (avec quelques anecdotes de production), de notre talk du jour, et aussi de l&amp;rsquo;écriture technique autour de mon livre &lt;em&gt;&lt;a class="link" href="https://50ndk.zwindler.fr/" target="_blank" rel="noopener"
&gt;Kubernetes : 50 solutions pour les postes de développement et les clusters de production&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Curieux de voir le rendu final quand l&amp;rsquo;épisode sortira.&lt;/p&gt;
&lt;h2 id="kubernetes-et-la-jvm"&gt;Kubernetes et la JVM
&lt;/h2&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/04/kubernetes-jvm.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Je voulais absolument assister à cette session de Jean-Michel Doudoux et Alain Regnier (SCIAM), par curiosité sur l&amp;rsquo;angle JVM en environnement Kubernetes.&lt;/p&gt;
&lt;p&gt;Talk avec de nombreux rappels des fondamentaux, aussi bien sur Kubernetes que sur Java :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;warmup JVM, tiered compilation, impacts du JIT (C1/C2) sur les phases de démarrage ;&lt;/li&gt;
&lt;li&gt;importance des ressources CPU allouées (démo assez parlante : 37s de démarrage avec 200m CPU contre 2s avec 1000m) ;&lt;/li&gt;
&lt;li&gt;choix du garbage collector influencé par les ressources disponibles ;&lt;/li&gt;
&lt;li&gt;détection des limites container et évolutions côté Java (container awareness, &lt;code&gt;MaxRAMPercentage&lt;/code&gt;, support cgroups v2) ;&lt;/li&gt;
&lt;li&gt;sujets de startup time (&lt;code&gt;Class Data Sharing&lt;/code&gt;, AOT) et compromis de GraalVM natif.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Un autre sujet intéressant évoqué : CRaC (&lt;em&gt;Coordinated Restore at Checkpoint&lt;/em&gt;), encore très expérimental, qui vise à réduire drastiquement le warmup en s&amp;rsquo;appuyant sur un mécanisme de &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=fosdem" target="_blank" rel="noopener"
&gt;checkpoint/restore CRIU&lt;/a&gt;, que je suis avec une grande attention (j&amp;rsquo;en ai déjà parlé plusieurs fois sur le blog, notamment quand je vais au FOSDEM).&lt;/p&gt;
&lt;p&gt;Petite réserve : une reco sur Kaniko apparaissait encore dans les slides, alors que le projet est archivé depuis fin 2025.&lt;/p&gt;
&lt;p&gt;Globalement, le talk balayait beaucoup de concepts sans forcément rentrer en profondeur sur chacun, je regrette un peu de ne pas plutôt être allé à &amp;ldquo;Fini les nuits difficiles: Comment l&amp;rsquo;IA a transformé nos astreintes&amp;rdquo; de Jean-Philippe Fourès.&lt;/p&gt;
&lt;h2 id="hallway-track-encore-et-toujours"&gt;Hallway track (encore et toujours)
&lt;/h2&gt;&lt;p&gt;Comme souvent à Devoxx, une grosse partie de la valeur se joue aussi entre les salles : discussions improvisées, débriefs à chaud, retrouvailles et nouvelles rencontres.&lt;/p&gt;
&lt;p&gt;Un peu rincé par cette matinée riche en émotions, j&amp;rsquo;ai aussi fini de collecter les awards qui me manquaient sur l&amp;rsquo;app mobile compagnon : j&amp;rsquo;ai été le 9e à atteindre les 6400 points. Gamification, quand tu nous tiens.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/04/awards.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Le format &amp;ldquo;dernier jour&amp;rdquo; donne un petit côté bilan collectif, et c&amp;rsquo;était très sympa de croiser encore plein de monde (Florian, Olivier, Jean-Philippe, Jérôme, Carine, Alexandre, Emmanuel).&lt;/p&gt;
&lt;p&gt;Merci particulier à Brian et Quentin, mes collègues Luccasiens, qui ont donné à ce DevoxxFR une saveur spéciale. C&amp;rsquo;est beaucoup plus chouette de faire une conférence entre collègues que seul (ce que j&amp;rsquo;ai souvent fait).&lt;/p&gt;
&lt;h2 id="fin-de-devoxxfr-2026"&gt;Fin de DevoxxFR 2026
&lt;/h2&gt;&lt;p&gt;Ce DevoxxFR 2026 était encore une très belle édition.&lt;/p&gt;
&lt;p&gt;Côté perso, je repars surtout avec :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;la satisfaction d&amp;rsquo;avoir donné notre meilleure version du talk ;&lt;/li&gt;
&lt;li&gt;des discussions de fond sur Kubernetes et l&amp;rsquo;évolution de nos pratiques ;&lt;/li&gt;
&lt;li&gt;quelques idées à creuser, au fil des discussions et des meilleures sessions ;&lt;/li&gt;
&lt;li&gt;et toujours autant d&amp;rsquo;énergie prise dans les rencontres.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Merci aux organisateurs, aux speakers, aux bénévoles, aux sponsors, et à toutes les personnes avec qui j&amp;rsquo;ai pu échanger pendant ces trois jours.&lt;/p&gt;
&lt;p&gt;Petit clin d&amp;rsquo;oeil aux orgas :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;la prod est down&lt;/p&gt;
&lt;p&gt;le dog est down&lt;/p&gt;
&lt;p&gt;mais je m&amp;rsquo;en fous parce que je devoxx!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Vivement la prochaine édition.&lt;/p&gt;</description></item><item><title>DevoxxFR 2026 - Récap du jeudi (jour 2)</title><link>https://blog.zwindler.fr/2026/04/23/devoxxfr-2026-recap-jour-2/</link><pubDate>Thu, 23 Apr 2026 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/04/23/devoxxfr-2026-recap-jour-2/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/04/docto-salle-bondee.webp" alt="Featured image of post DevoxxFR 2026 - Récap du jeudi (jour 2)" /&gt;&lt;p&gt;Les résumés des 3 jours de DevoxxFR 2026&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2026/04/22/devoxxfr-2026-recap-jour-1/" &gt;DevoxxFR - Récap du mercredi (jour 1)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2026/04/23/devoxxfr-2026-recap-jour-2/" &gt;DevoxxFR - Récap du jeudi (jour 2)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2026/04/24/devoxxfr-2026-recap-jour-3/" &gt;DevoxxFR - Récap du vendredi (jour 3)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="deuxième-jour-de-devoxxfr-2026"&gt;Deuxième jour de DevoxxFR 2026
&lt;/h2&gt;&lt;p&gt;Pour ce jeudi, j&amp;rsquo;ai raté les keynotes du matin (arrivé un peu tard), j&amp;rsquo;ai donc commencé par un tour des stands avant d&amp;rsquo;enchaîner avec les talks de la matinée. J&amp;rsquo;en ai profité pour récupérer des goodies pour les enfants au passage (:P).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/04/goodies.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="gérer-vos-tickets-support-avec-de-lia-mais-sans-cramer-la-planète"&gt;Gérer vos tickets support avec de l&amp;rsquo;IA mais sans cramer la planète
&lt;/h2&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/04/ticket-ia-cramer-planete.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Matthieu Vincent (Sopra Steria) et Philippe Charrière (Docker) ont présenté un talk sur un cas d&amp;rsquo;usage IA utile (gestion d&amp;rsquo;un service &amp;ldquo;support&amp;rdquo;), sans exploser les coûts infra.&lt;/p&gt;
&lt;p&gt;Le message central : pas besoin d&amp;rsquo;un gros modèle partout. Selon le besoin, &lt;strong&gt;SLM/TLM&lt;/strong&gt; (S ou T pour small ou tiny) peuvent suffire, à condition de bien cadrer mémoire, contexte et architecture.&lt;/p&gt;
&lt;p&gt;Les points principaux :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dimensionner le modèle sérieusement (poids, KV cache, activations), pas juste prendre &amp;ldquo;le plus gros&amp;rdquo;. Ca ne rentrera pas &amp;ldquo;sur le mac&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;Construire un &lt;strong&gt;RAG&lt;/strong&gt; propre sur l&amp;rsquo;historique des tickets, avec embeddings et chunking seulement si nécessaire.&lt;/li&gt;
&lt;li&gt;Ajouter une couche &lt;strong&gt;agentique&lt;/strong&gt; légère pour orchestrer les actions/outils externes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;J&amp;rsquo;ai bien aimé que la démo utilise un JSON comme base vectorielle, ça rend très lisible et compréhensible le concept de vecteur et de calcul de distance. J&amp;rsquo;ai aussi bien aimé la très brève démo de &amp;ldquo;Docker Agents&amp;rdquo;, ce qui m&amp;rsquo;a décidé à enchaîner sur un talk à ce sujet l&amp;rsquo;après-midi.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/04/docker-agents-demo.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Talk efficace, orienté pratique, avec une idée simple mais importante : pour ce type de besoin, &amp;ldquo;petit modèle bien outillé&amp;rdquo; bat souvent &amp;ldquo;gros modèle par défaut&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="lagentic-coding-nouveau-territoire-du-platform-engineering"&gt;L&amp;rsquo;Agentic Coding, nouveau territoire du Platform Engineering
&lt;/h2&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/04/docto.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Yankı Sesyılmaz et Julien Tanay (Doctolib) ont présenté un REX très concret sur l&amp;rsquo;adoption de l&amp;rsquo;agentic coding à l&amp;rsquo;échelle.&lt;/p&gt;
&lt;p&gt;Le chiffre marquant : une vraie &amp;ldquo;task force&amp;rdquo; de 2 personnes dédiée à l&amp;rsquo;accompagnement de 600 profils tech, avec un passage assumé en mode &lt;strong&gt;agentic-first&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Ce que j&amp;rsquo;ai retenu :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le sujet n&amp;rsquo;est pas juste &amp;ldquo;quel outil choisir&amp;rdquo;, c&amp;rsquo;est un vrai sujet de &lt;strong&gt;platform engineering&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Passer en agentic-first impose de revoir les workflows, de soigner le &lt;strong&gt;context engineering&lt;/strong&gt;, de mettre en place une marketplace pour standardiser sans enfermer.&lt;/li&gt;
&lt;li&gt;Leur approche tourne autour du &lt;strong&gt;Spec-driven Development&lt;/strong&gt; (specs Markdown versionnées, lisibles humains + machines). Ils ont choisi &lt;strong&gt;OpenSpec&lt;/strong&gt; pour garder un framework léger et adaptable.&lt;/li&gt;
&lt;li&gt;Les plus gros utilisateurs de l&amp;rsquo;agentic sont les profils déjà habitués au &lt;strong&gt;context switching&lt;/strong&gt; (et notamment les DevOps/SRE qui sont très transverses). On réduit la friction avec worktrees, dashboards d&amp;rsquo;agents et réduction des blocages humains.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ils ont aussi partagé leur pipeline d&amp;rsquo;industrialisation outillage : veille, filtre valeur, revue sécurité, test avec AI champions, puis standardisation.&lt;/p&gt;
&lt;p&gt;Enfin, les use cases &amp;ldquo;remote agents&amp;rdquo; étaient plutôt concrets : backlog de tickets, incidents de prod, migrations techniques, revue de code, avec orchestrateur, observabilité et guardrails pour cadrer le tout.&lt;/p&gt;
&lt;p&gt;Talk intéressant, qui pointe les questions à se poser plus qu&amp;rsquo;il ne répond à comment les résoudre (:p).&lt;/p&gt;
&lt;h2 id="email-at-scale--comment-on-a-survécu-à-800m-mailsan-et-au-dns"&gt;Email at scale : comment on a survécu à 800M mails/an (et au DNS)
&lt;/h2&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/04/mirakl.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Julien Goullon et Julien Eyraud (Mirakl)&lt;/p&gt;
&lt;p&gt;REX très solide sur un sujet qu&amp;rsquo;on sous-estime souvent : à ce volume, l&amp;rsquo;email devient un sujet de plateforme, pas un simple &amp;ldquo;service annexe&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Ce que j&amp;rsquo;ai retenu :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le contexte est massif : 300+ marketplaces, 170k vendeurs, 500 domaines d&amp;rsquo;envoi à migrer.&lt;/li&gt;
&lt;li&gt;Leur nouvelle archi (Kafka + outbox + traitements asynchrones) a apporté du scale, mais a aussi exposé des effets de bord violents en incident (rebalancing, doublons, consumers bloqués).&lt;/li&gt;
&lt;li&gt;Ils ont dû ajouter des garde-fous très concrets : circuit breaker, topics rapides/lents, déduplication Redis, observabilité/reporting.&lt;/li&gt;
&lt;li&gt;La migration DNS/fournisseur est un chantier à part entière (SPF, DKIM, propagation, warm-up IP, suppression lists), avec beaucoup d&amp;rsquo;erreurs côté clients.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le point le plus intéressant : ils ont investi dans l&amp;rsquo;outillage et l&amp;rsquo;autonomie client pour devenir proactifs, au lieu de rester en réaction permanente aux incidents.&lt;/p&gt;
&lt;h2 id="docker-agent---comment-simplifier-encore-plus-la-création-dagents-ia-"&gt;Docker Agent - comment simplifier encore plus la création d&amp;rsquo;agents IA ?
&lt;/h2&gt;&lt;p&gt;Djordje Lukic et David Gageot (Docker)&lt;/p&gt;
&lt;p&gt;Pas pu rentrer, même avec un peu plus de 10 minutes d&amp;rsquo;avance (maintenant un classique pour la salle Maillot). On m&amp;rsquo;en a dit du bien, je le mettrai dans la liste des replays.&lt;/p&gt;
&lt;h2 id="le-mythe-de-la-neutralité--quand-la-tech-devient-politique"&gt;Le mythe de la neutralité : quand la tech devient politique
&lt;/h2&gt;&lt;p&gt;Hugo Lassiège (eventuallycoding)&lt;/p&gt;
&lt;p&gt;Talk dense et assez courageux sur un sujet qu&amp;rsquo;on évite encore dans les conférences tech, par peur de se faire étiqueter souverainiste peut être ? (alors que pas du tout).&lt;/p&gt;
&lt;p&gt;Ce que j&amp;rsquo;ai retenu :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Le discours &amp;ldquo;la technologie est un outil neutre, tout dépend de l&amp;rsquo;usage&amp;rdquo; est commode, surtout pour les big techs. Mais un système peut définir ou suggérer son propre usage — une arme reste une arme.&lt;/li&gt;
&lt;li&gt;Le numérique n&amp;rsquo;est pas &amp;ldquo;virtuel&amp;rdquo; : il structure concrètement l&amp;rsquo;accès au monde (transports, paiements, information), et ses biais ont des effets réels (ex. reconnaissance faciale moins performante sur les personnes non blanches au Royaume-Uni).&lt;/li&gt;
&lt;li&gt;Les GAFAM sont en avance, mais aussi massivement subventionnés par des fonds publics — ce n&amp;rsquo;est pas uniquement le fruit du marché libre.&lt;/li&gt;
&lt;li&gt;La souveraineté ne signifie pas l&amp;rsquo;autarcie. Être maître de ses choix sans subir de pressions extérieures, c&amp;rsquo;est possible dans un cadre d&amp;rsquo;interdépendance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La citation qui m&amp;rsquo;a le plus parlé :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;J&amp;rsquo;espère qu&amp;rsquo;on est en train de vivre notre effet Sputnik, qu&amp;rsquo;on appellera peut-être l&amp;rsquo;effet Trump.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Les exemples concrets sur ce qui se passe quand on se fâche avec les États-Unis (la CPI coupée de ses outils bureautiques, le juge coupé de toute interaction avec le monde) ou sur des déclarations comme &amp;ldquo;la technologie permettra d&amp;rsquo;éliminer le vote&amp;rdquo; (Peter Thiel) remettent bien les enjeux en perspective.&lt;/p&gt;
&lt;p&gt;Talk que j&amp;rsquo;ai bien aimé.&lt;/p&gt;
&lt;h2 id="mise--un-multi-outil-pour-votre-poste-de-dev--ops"&gt;Mise : un multi-outil pour votre poste de Dev &amp;amp; Ops
&lt;/h2&gt;&lt;p&gt;Rémi Verchère (Gravitek) parle d&amp;rsquo;un sujet que j&amp;rsquo;ai découvert avec la sortie des nodes de type Linux chez Clever Cloud il y a quelques mois (cf mon article sur le sujet). Le talk adresse un problème très concret : quand on jongle entre plusieurs projets, versions d&amp;rsquo;outils, variables d&amp;rsquo;environnement et scripts maison, ça devient vite le bazar.&lt;/p&gt;
&lt;p&gt;Ce que j&amp;rsquo;ai retenu :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;mise.toml&lt;/code&gt; permet de figer les versions d&amp;rsquo;outils par projet et de les installer simplement.&lt;/li&gt;
&lt;li&gt;Le système de tasks (avec dépendances) permet de structurer les workflows &lt;code&gt;build&lt;/code&gt; -&amp;gt; &lt;code&gt;test&lt;/code&gt; -&amp;gt; &lt;code&gt;deploy&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Bon support de l&amp;rsquo;environnement de dev (variables, venv Python, etc.).&lt;/li&gt;
&lt;li&gt;Côté sécurité, il y a des briques utiles (checksum, cosign, lockfiles).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Un outil que je vais clairement creuser, surtout pour réduire la friction sur les context switches Dev/Ops.&lt;/p&gt;
&lt;h2 id="soirée-meet-and-greet-version-courte"&gt;Soirée meet-and-greet (version courte)
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai brièvement croisé quelques personnes pendant le meet-and-greet, mais j&amp;rsquo;ai rapidement dû filer au meetup Staff 42 qui commençait juste après. Pas eu le temps de passer à The Voxx, mais ça avait l&amp;rsquo;air bien, comme d&amp;rsquo;habitude.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2026/04/the-voxx.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="meetup-staff-42"&gt;Meetup Staff 42
&lt;/h2&gt;&lt;p&gt;Quelques sujets abordés pendant la soirée :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Comment naviguer dans l&amp;rsquo;ambiguïté — et trouver les bons problèmes à résoudre.&lt;/li&gt;
&lt;li&gt;Le rôle Staff n&amp;rsquo;est pas fixe : on navigue entre différents archétypes (dans la définition qu&amp;rsquo;en donne le livre sur le staff engineering) selon les semaines et les contextes.&lt;/li&gt;
&lt;li&gt;Super pouvoir identifié : savoir communiquer / avoir la liberté d&amp;rsquo;amener de l&amp;rsquo;innovation.&lt;/li&gt;
&lt;li&gt;Il est difficile de devenir Staff sans avoir fait plusieurs entreprises et découvert plusieurs cultures.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bonne soirée, discussions enrichissantes. On a fini par un dîner ensemble.&lt;/p&gt;</description></item></channel></rss>