<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Cilium on Zwindler's Reflection</title><link>https://blog.zwindler.fr/tags/cilium/</link><description>Recent content in Cilium on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr-fr</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Sun, 24 May 2026 18:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/tags/cilium/index.xml" rel="self" type="application/rss+xml"/><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>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>Flannel et NetworkPolicies : comment ajouter le support avec Cilium en CNI chaining</title><link>https://blog.zwindler.fr/2026/03/15/flannel-networkpolicies-cilium-cni-chaining/</link><pubDate>Sun, 15 Mar 2026 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/03/15/flannel-networkpolicies-cilium-cni-chaining/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/03/flannel-cilium-chaining.webp" alt="Featured image of post Flannel et NetworkPolicies : comment ajouter le support avec Cilium en CNI chaining" /&gt;&lt;h2 id="flannel-flannel-flannel"&gt;Flannel, flannel, flannel&amp;hellip;
&lt;/h2&gt;&lt;p&gt;Flannel est un CNI simple et populaire 😢.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est le CNI par défaut de k3s, celui que la moitié des tutos kubeadm utilisent, et on le retrouve aussi dans pas mal d&amp;rsquo;offres managées.&lt;/p&gt;
&lt;p&gt;OK, il est simple, il route les paquets entre les pods, il supporte VXLAN et WireGuard, il se configure en 2 minutes. Que demander de plus ?&lt;/p&gt;
&lt;p&gt;Ben justement. Il y a un truc que flannel ne fait &lt;strong&gt;pas&lt;/strong&gt; : les NetworkPolicies. (Et c&amp;rsquo;est très grave).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Flannel is focused on networking. For network policy, other projects such as Calico can be used.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;[Edit]&lt;/strong&gt; Contrairement à ce que j&amp;rsquo;écris ici, flannel supporte en fait les NetworkPolicies depuis au moins 2 ans, via l&amp;rsquo;implémentation de référence &lt;a class="link" href="https://github.com/kubernetes-sigs/kube-network-policies" target="_blank" rel="noopener"
&gt;kube-network-policies&lt;/a&gt; du projet Kubernetes. L&amp;rsquo;option n&amp;rsquo;est pas mise en avant dans le README et ce n&amp;rsquo;est probablement pas plus recommandé en production, mais elle existe. Voir la &lt;a class="link" href="https://github.com/flannel-io/flannel/blob/master/Documentation/netpol.md" target="_blank" rel="noopener"
&gt;documentation officielle&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Et le piège, c&amp;rsquo;est que si vous n&amp;rsquo;avez pas lu cette petite ligne dans le &lt;strong&gt;README.md&lt;/strong&gt;, rien ne vous le dit explicitement.&lt;/p&gt;
&lt;p&gt;Vous pouvez parfaitement créer des objets &lt;code&gt;NetworkPolicy&lt;/code&gt; dans votre cluster, &lt;code&gt;kubectl apply&lt;/code&gt; ne bronchera pas, &lt;code&gt;kubectl get netpol&lt;/code&gt; vous les listera gentiment. Sauf que&amp;hellip; elles ne sont pas enforced. Le trafic passe quand même. Votre deny-all ne deny rien du tout.&lt;/p&gt;
&lt;h2 id="on-vérifie-pour-être-bien-sûr"&gt;On vérifie pour être bien sûr
&lt;/h2&gt;&lt;p&gt;Avant de résoudre quoi que ce soit, vérifions que le problème existe. En partant du prérequis qu&amp;rsquo;on a un cluster Kubernetes qui a flannel comme CNI et que tout est fonctionnel, on va déployer deux pods dans deux namespaces différents : un client (curl) et un serveur (nginx).&lt;/p&gt;
&lt;p&gt;On vérifie que le client peut joindre le serveur :&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; -n netpol-test-a client -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -s --max-time &lt;span class="m"&gt;5&lt;/span&gt; http://server.netpol-test-b.svc.cluster.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On obtient la page d&amp;rsquo;accueil nginx. Jusque-là, tout est normal. Maintenant, on applique une NetworkPolicy &lt;em&gt;deny-all&lt;/em&gt; sur le namespace du serveur :&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;# 01-deny-all-ingress.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;networking.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;NetworkPolicy&lt;/span&gt;&lt;span class="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-all-ingress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;netpol-test-b&lt;/span&gt;&lt;span class="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;podSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&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;policyTypes&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;Ingress&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 01-deny-all-ingress.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et on re-teste :&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; -n netpol-test-a client -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -s --max-time &lt;span class="m"&gt;5&lt;/span&gt; http://server.netpol-test-b.svc.cluster.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Résultat : la page nginx s&amp;rsquo;affiche toujours.&lt;/strong&gt; La NetworkPolicy est bien créée (&lt;code&gt;kubectl get netpol -n netpol-test-b&lt;/code&gt; la montre), mais elle n&amp;rsquo;est pas enforced. Le trafic passe comme si de rien n&amp;rsquo;était.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est le comportement attendu avec flannel (par défaut). Flannel ne fait que du routage L3 (overlay VXLAN ou WireGuard). Il n&amp;rsquo;implémente pas de contrôleur NetworkPolicy. Les objets existent dans etcd, mais personne ne les traduit en règles de filtrage.&lt;/p&gt;
&lt;p&gt;On nettoie la policy avant de passer à la suite :&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 -f 01-deny-all-ingress.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="les-alternatives-pour-ajouter-le-support"&gt;Les alternatives pour ajouter le support
&lt;/h2&gt;&lt;p&gt;Pendant longtemps j&amp;rsquo;ai pensé que c&amp;rsquo;était une fatalité. Je pense toujours que n&amp;rsquo;est pas un bon choix pour n&amp;rsquo;importe quelle production.&lt;/p&gt;
&lt;p&gt;MAIS récemment, j&amp;rsquo;ai découvert qu&amp;rsquo;il était possible de chaîner les CNI au sein d&amp;rsquo;un même cluster, et ainsi d&amp;rsquo;avoir un CNI qui gère la majorité des tâches (ici Flannel) et un autre qui se charge d&amp;rsquo;autres tâches, comme par exemple enforcer des Netpols.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est d&amp;rsquo;ailleurs le principe de Canal, que je connaissais de nom mais que je n&amp;rsquo;avais jamais exploré. En fait, c&amp;rsquo;est ni plus ni moins qu&amp;rsquo;un manifeste qui déploie Flannel comme CNI principal avec Calico pour l&amp;rsquo;enforcing des NetOK fine, it&amp;rsquo;s truepols !&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Canal was the name of Tigera and CoreOS’s project to integrate Calico and flannel.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Note : &lt;a class="link" href="https://github.com/projectcalico/canal?tab=readme-ov-file" target="_blank" rel="noopener"
&gt;le projet GitHub a été archivé en octobre 2025&lt;/a&gt; mais en théorie ça devrait encore fonctionner, si on trouve la doc correcte (pas trouvé, les liens sont KO, mais j&amp;rsquo;ai pas vraiment cherché non plus).&lt;/p&gt;
&lt;p&gt;Vous avez donc compris le principe, on va ajouter un composant qui va &lt;strong&gt;watch&lt;/strong&gt; les objets NetworkPolicy et les traduire en règles de filtrage effectives (iptables, eBPF, nftables&amp;hellip;), &lt;strong&gt;sans toucher au flannel existant&lt;/strong&gt;. C&amp;rsquo;est ce qu&amp;rsquo;on appelle le &amp;ldquo;CNI chaining&amp;rdquo; ou le mode &amp;ldquo;policy-only&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Il existe plusieurs solutions :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Calico (Canal)&lt;/strong&gt;, Historiquement, la combinaison flannel + Calico s&amp;rsquo;appelle &amp;ldquo;Canal&amp;rdquo;, dont je viens de parler. &lt;strong&gt;Mais&lt;/strong&gt; le manifeste Canal officiel embarque son propre flannel dans le même DaemonSet que calico-node. Si votre flannel est déjà installé et géré (par vous, par un opérateur, par un provider&amp;hellip;), vous ne voulez probablement pas le remplacer. Et l&amp;rsquo;opérateur Tigera (la méthode Helm &amp;ldquo;officielle&amp;rdquo;) ne supporte pas non plus le déploiement en mode policy-only sur un flannel existant. Bref, c&amp;rsquo;est faisable mais ça nécessite un peu d&amp;rsquo;effort. Flemme.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;kube-router&lt;/strong&gt;, kube-router peut fonctionner en mode firewall-only (&lt;code&gt;--run-firewall=true&lt;/code&gt;) et n&amp;rsquo;a besoin que d&amp;rsquo;iptables/ipset. C&amp;rsquo;est d&amp;rsquo;ailleurs ce que k3s utilise par défaut pour les NetworkPolicies. C&amp;rsquo;est la solution la plus légère (a priori ~50 Mo de RAM par node). Vérifiez que votre kernel dispose du module &lt;code&gt;ip_set&lt;/code&gt;, sinon ça ne fonctionnera pas.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cilium en mode CNI chaining&lt;/strong&gt;, C&amp;rsquo;est la solution que j&amp;rsquo;ai retenue et qu&amp;rsquo;on va détailler. Cilium s&amp;rsquo;attache aux interfaces veth créées par flannel et ajoute ses programmes eBPF pour le policy enforcement. Pas de dépendance à iptables ou ipset, et en bonus on récupère Hubble pour l&amp;rsquo;observabilité réseau.&lt;/p&gt;
&lt;h2 id="installer-cilium-en-mode-cni-chaining"&gt;Installer Cilium en mode CNI chaining
&lt;/h2&gt;&lt;h3 id="prérequis"&gt;Prérequis
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Un cluster Kubernetes fonctionnel avec flannel&lt;/li&gt;
&lt;li&gt;&lt;code&gt;helm&lt;/code&gt; v3+&lt;/li&gt;
&lt;li&gt;Un kernel &amp;gt;= 4.19 (idéalement &amp;gt;= 5.10 pour toutes les features eBPF)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="récupérer-la-configuration-cni-de-flannel"&gt;Récupérer la configuration CNI de flannel
&lt;/h3&gt;&lt;p&gt;Cilium en mode chaining doit connaître la configuration CNI existante. On va la récupérer depuis un node :&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 debug node/&lt;span class="k"&gt;$(&lt;/span&gt;kubectl get nodes -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;{.items[0].metadata.name}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&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; -it --image&lt;span class="o"&gt;=&lt;/span&gt;busybox -- cat /host/etc/cni/net.d/10-flannel.conflist
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Sur mon cluster, ça donne :&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cbr0&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="nt"&gt;&amp;#34;cniVersion&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.3.1&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="nt"&gt;&amp;#34;plugins&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&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;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;flannel&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="nt"&gt;&amp;#34;delegate&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&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="nt"&gt;&amp;#34;hairpinMode&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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="nt"&gt;&amp;#34;isDefaultGateway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;portmap&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="nt"&gt;&amp;#34;capabilities&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&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="nt"&gt;&amp;#34;portMappings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&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;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&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;strong&gt;Notez le champ &lt;code&gt;name&lt;/code&gt;&lt;/strong&gt; (ici &lt;code&gt;cbr0&lt;/code&gt;). On en aura besoin.&lt;/p&gt;
&lt;h3 id="créer-le-configmap-de-chaining"&gt;Créer le ConfigMap de chaining
&lt;/h3&gt;&lt;p&gt;On va créer un ConfigMap qui reprend la config flannel et y ajoute le plugin &lt;code&gt;cilium-cni&lt;/code&gt; en chaining :&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;ConfigMap&lt;/span&gt;&lt;span class="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;cni-configuration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;kube-system&lt;/span&gt;&lt;span class="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;data&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;cni-config&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="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; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;name&amp;#34;: &amp;#34;cbr0&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; &amp;#34;cniVersion&amp;#34;: &amp;#34;0.3.1&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; &amp;#34;plugins&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; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;type&amp;#34;: &amp;#34;flannel&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; &amp;#34;delegate&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; &amp;#34;hairpinMode&amp;#34;: true,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;isDefaultGateway&amp;#34;: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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; &amp;#34;type&amp;#34;: &amp;#34;portmap&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; &amp;#34;capabilities&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; &amp;#34;portMappings&amp;#34;: true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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; },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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; &amp;#34;type&amp;#34;: &amp;#34;cilium-cni&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; &amp;#34;chaining-mode&amp;#34;: &amp;#34;generic-veth&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; }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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; }&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;Attention&lt;/strong&gt; : le champ &lt;code&gt;name&lt;/code&gt; doit correspondre à celui de votre &lt;em&gt;conflist&lt;/em&gt; flannel. Si le vôtre s&amp;rsquo;appelle &lt;code&gt;cni0&lt;/code&gt; ou autre chose, adaptez.&lt;/p&gt;
&lt;p&gt;Avant d&amp;rsquo;appliquer, vérifiez aussi que votre CNI utilise bien des interfaces &lt;strong&gt;veth&lt;/strong&gt; (c&amp;rsquo;est le cas par défaut avec flannel, mais mieux vaut s&amp;rsquo;en assurer). Depuis un node :&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;ip -d link &lt;span class="p"&gt;|&lt;/span&gt; grep veth
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Vous devriez voir des interfaces de type &lt;code&gt;veth&lt;/code&gt; correspondant à vos pods, par exemple :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;103: lxcb3901b7f9c02@if102: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; ...
veth addrgenmode eui64 numtxqueues 1 numrxqueues 1
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si c&amp;rsquo;est bien le cas, le mode &lt;code&gt;generic-veth&lt;/code&gt; de Cilium fonctionnera. On applique :&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 cilium-cni-configmap.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="installer-cilium-via-helm"&gt;Installer Cilium via Helm
&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;helm repo add cilium https://helm.cilium.io/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo update
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Voici les values pour le mode chaining :&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;# cilium-values.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;cni&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;chainingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;generic-veth&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;customConf&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;configMap&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cni-configuration&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;install&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;routingMode&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;native&lt;/span&gt;&lt;span class="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;enableIPv4Masquerade&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="nt"&gt;enableIPv6Masquerade&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;hubble&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;enabled&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;relay&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;enabled&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;ui&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;enabled&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;Les points importants :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;cni.chainingMode: generic-veth&lt;/code&gt;, c&amp;rsquo;est le mode chaining, Cilium s&amp;rsquo;attache aux interfaces veth existantes&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cni.customConf: true&lt;/code&gt; + &lt;code&gt;cni.configMap&lt;/code&gt;, on fournit notre propre config CNI&lt;/li&gt;
&lt;li&gt;&lt;code&gt;routingMode: native&lt;/code&gt;, flannel gère le routage, pas Cilium&lt;/li&gt;
&lt;li&gt;&lt;code&gt;enableIPv4Masquerade: false&lt;/code&gt;, flannel gère le masquerading&lt;/li&gt;
&lt;li&gt;&lt;code&gt;hubble.enabled: true&lt;/code&gt;, l&amp;rsquo;observabilité réseau, c&amp;rsquo;est le gros bonus de Cilium&lt;/li&gt;
&lt;/ul&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 cilium cilium/cilium --version 1.19.1 &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 kube-system &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 cilium-values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On attend que tout soit prêt :&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 rollout status daemonset/cilium -n kube-system --timeout&lt;span class="o"&gt;=&lt;/span&gt;120s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="vérification"&gt;Vérification
&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 &lt;span class="nb"&gt;exec&lt;/span&gt; -n kube-system ds/cilium -- cilium status
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ce qui nous intéresse :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Kubernetes: Ok 1.35 (v1.35.0) [linux/amd64]
CNI Chaining: generic-veth
Cilium: Ok 1.19.1
Hubble: Ok
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;La ligne &lt;code&gt;CNI Chaining: generic-veth&lt;/code&gt; confirme que Cilium fonctionne en mode &lt;em&gt;chaining&lt;/em&gt; et ne remplace pas flannel.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important&lt;/strong&gt; : les pods qui existaient avant l&amp;rsquo;installation de Cilium ne sont pas automatiquement gérés par Cilium. Il faut les redémarrer pour que Cilium attache ses programmes eBPF. Pensez à faire un &lt;code&gt;kubectl rollout restart&lt;/code&gt; de vos workloads de test (ou à les recréer).&lt;/p&gt;
&lt;h2 id="tester-les-networkpolicies"&gt;Tester les NetworkPolicies
&lt;/h2&gt;&lt;p&gt;C&amp;rsquo;est le moment de vérité. On re-applique notre deny-all :&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 01-deny-all-ingress.yaml
&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 &lt;span class="nb"&gt;exec&lt;/span&gt; -n netpol-test-a client -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl -s --max-time &lt;span class="m"&gt;5&lt;/span&gt; http://server.netpol-test-b.svc.cluster.local
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Résultat : timeout&lt;/strong&gt; ! Cette fois, la NetworkPolicy est bien enforced. Le trafic est bloqué.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai enchaîné avec les autres scénarios classiques de NetworkPolicy, et tout fonctionne :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Allow ingress sélectif par namespace&lt;/strong&gt;, en ajoutant une policy qui autorise le trafic depuis &lt;code&gt;netpol-test-a&lt;/code&gt; uniquement, le curl passe depuis ce namespace mais reste bloqué depuis &lt;code&gt;default&lt;/code&gt;. L&amp;rsquo;isolation par namespace fonctionne.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deny-all egress&lt;/strong&gt;, en bloquant tout le trafic sortant du client, même la résolution DNS est bloquée (timeout immédiat).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Allow egress sélectif&lt;/strong&gt;, en autorisant uniquement le DNS (port 53) et le serveur (port 80 dans le namespace &lt;code&gt;netpol-test-b&lt;/code&gt;), le curl vers le serveur passe mais &lt;code&gt;curl http://example.com&lt;/code&gt; reste bloqué.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bref, ingress, egress, sélectif par namespace, tout marche comme attendu.&lt;/p&gt;
&lt;h2 id="bonus--hubble-lobservabilité-réseau"&gt;Bonus : Hubble, l&amp;rsquo;observabilité réseau
&lt;/h2&gt;&lt;p&gt;C&amp;rsquo;est pour moi le vrai atout de Cilium par rapport aux alternatives. Hubble permet de voir en temps réel les flux réseau et les verdicts de policy :&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; -n kube-system ds/cilium -- &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; hubble observe --namespace netpol-test-b --last &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;pre tabindex="0"&gt;&lt;code&gt;Mar 15 13:20:42.287: netpol-test-a/client:40066 (ID:9745) -&amp;gt;
netpol-test-b/server:80 (ID:22271)
policy-verdict:none ALLOWED (TCP Flags: SYN)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On voit le pod source, le pod destination, le port, l&amp;rsquo;identité Cilium, et le verdict de policy. Quand vous debuggez une NetworkPolicy qui ne se comporte pas comme prévu, c&amp;rsquo;est vraiment pratique.&lt;/p&gt;
&lt;h2 id="combien-ça-coûte-en-ressources-"&gt;Combien ça coûte en ressources ?
&lt;/h2&gt;&lt;p&gt;Sur mon cluster (3 nodes), voici ce que Cilium consomme juste après installation :&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Composant&lt;/th&gt;
&lt;th&gt;Par node&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;cilium agent&lt;/td&gt;
&lt;td&gt;oui (DaemonSet)&lt;/td&gt;
&lt;td&gt;~160 Mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cilium-envoy&lt;/td&gt;
&lt;td&gt;oui (DaemonSet)&lt;/td&gt;
&lt;td&gt;~22 Mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cilium-operator&lt;/td&gt;
&lt;td&gt;non (2 replicas)&lt;/td&gt;
&lt;td&gt;~42 Mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hubble-relay&lt;/td&gt;
&lt;td&gt;non (1 replica)&lt;/td&gt;
&lt;td&gt;~16 Mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hubble-ui&lt;/td&gt;
&lt;td&gt;non (1 replica)&lt;/td&gt;
&lt;td&gt;~21 Mo&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Soit environ &lt;strong&gt;180 Mo par node&lt;/strong&gt; pour l&amp;rsquo;agent + envoy. J&amp;rsquo;ai pas de point de comparaison par rapport à Calico ou kube-router, mais ça me semble acceptable, et le fait de pouvoir avoir une vision complète sur la totalité des flux avec Hubble justifie largement le surcoût (à mon avis).&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Si jamais vous n&amp;rsquo;avez pas le choix et que vous devez composer avec flannel, et que vous voulez boucher le trou béant de sécurité que représente l&amp;rsquo;absence d&amp;rsquo;enforcement des Netpols, sachez donc qu&amp;rsquo;il est maintenant possible :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;d&amp;rsquo;activer l&amp;rsquo;ajout de l&amp;rsquo;implémentation officielle (même si je ne l&amp;rsquo;ai pas testée, ça doit marcher)&lt;/li&gt;
&lt;li&gt;de chaîner un autre CNI pour le faire&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;À défaut de l&amp;rsquo;avoir en CNI pour tout (tout bon cluster Kubernetes a Cilium comme CNI), Cilium en mode CNI chaining (generic-veth) est une solution plutôt sympa pour combler ce manque. Il ne touche pas au flannel existant, il s&amp;rsquo;y greffe. Et en bonus, vous récupérez Hubble pour l&amp;rsquo;observabilité réseau, ce qui est franchement appréciable.&lt;/p&gt;
&lt;p&gt;Have fun :)&lt;/p&gt;</description></item><item><title>Un nouveau champ `log` pour les network policies Cilium : une idée de use case</title><link>https://blog.zwindler.fr/2025/11/03/cilium-policy-log-field-limitation-fr/</link><pubDate>Mon, 03 Nov 2025 12:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/11/03/cilium-policy-log-field-limitation-fr/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/10/cilium-hubble.webp" alt="Featured image of post Un nouveau champ `log` pour les network policies Cilium : une idée de use case" /&gt;&lt;h2 id="tldr"&gt;TL;DR
&lt;/h2&gt;&lt;p&gt;Cilium 1.18 a ajouté un champ &lt;code&gt;log&lt;/code&gt; aux CiliumNetworkPolicies pour taguer les verdicts de flux (FORWARDED, DROPPED, AUDIT, &amp;hellip;) avec des labels personnalisés. Dans l&amp;rsquo;idée, c&amp;rsquo;est la fonctionnalité parfaite pour éviter de logger le trafic bloqué que l&amp;rsquo;on connait dans nos dashboards de monitoring !&lt;/p&gt;
&lt;p&gt;Mais il y a un hic, sans rapport avec cette fonctionnalité, qui rend cette idée inutilisable : on ne peut pas l&amp;rsquo;utiliser avec &lt;code&gt;egressDeny&lt;/code&gt; + &lt;code&gt;toFQDNs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;ET, il y a un bug, qui fait que le &amp;ldquo;log&amp;rdquo; n&amp;rsquo;est visible que sur le trafic &amp;ldquo;autorisé&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Je vous raconte&amp;hellip;&lt;/p&gt;
&lt;h2 id="le-problème--monitor-all-the-things-mais-pas-trop"&gt;Le problème : monitor all the things (mais pas trop)
&lt;/h2&gt;&lt;p&gt;Comme toute bonne équipe &amp;ldquo;ops&amp;rdquo; qui se respecte, nous monitorons/loggons les flux réseau de notre cluster Kubernetes avec Hubble pour une analyse ultérieure et de l&amp;rsquo;alerting. Nous (en particulier mon collègue Nicolas Nativel) poussons tous les flux &lt;code&gt;AUDIT&lt;/code&gt; et &lt;code&gt;DROPPED&lt;/code&gt; vers un dashboard Grafana pour pouvoir rapidement repérer quand quelque chose est bloqué et décider :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;C&amp;rsquo;est légitime ? → On ouvre le flux&lt;/li&gt;
&lt;li&gt;C&amp;rsquo;est suspect ? → On déclenche l&amp;rsquo;alarme 🚨&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/10/cilium-hubble.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Ça fonctionne plutôt bien&amp;hellip; jusqu&amp;rsquo;à ce qu&amp;rsquo;on commence à bloquer &lt;strong&gt;explicitement&lt;/strong&gt; des choses qu&amp;rsquo;on &lt;em&gt;sait&lt;/em&gt; devoir être bloquées.&lt;/p&gt;
&lt;p&gt;Dans notre cas, nous voulions empêcher une application tierce d&amp;rsquo;envoyer les données de &amp;ldquo;télémétrie&amp;rdquo; (ouais, appelons ça comme ça 😏). On parle d&amp;rsquo;appels HTTPS vers des domaines de tracking externes.&lt;/p&gt;
&lt;p&gt;Le problème ? Si on bloque simplement ces flux, ils apparaîtront comme &lt;code&gt;DROPPED&lt;/code&gt; dans Hubble, déclencheront notre monitoring, et on se retrouvera avec des alertes pour quelque chose qu&amp;rsquo;on a &lt;em&gt;intentionnellement&lt;/em&gt; bloqué.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est du bruit dont on ne veut pas.&lt;/p&gt;
&lt;h2 id="et-donc-ce-champ-log-des-network-policies-de-cilium-118-"&gt;Et donc, ce champ log des network policies de Cilium 1.18 ?
&lt;/h2&gt;&lt;p&gt;Bonne nouvelle ! Cilium 1.18 a introduit exactement ce dont nous avions besoin : la possibilité d&amp;rsquo;ajouter des champs de log personnalisés aux verdicts sur les network policies.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Cf. &lt;a class="link" href="https://isovalent.com/blog/post/cilium-1-18/#hubble-flow-policy-log-field" target="_blank" rel="noopener"
&gt;l&amp;rsquo;annonce dans le blogpost officiel&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L&amp;rsquo;idée est simple : vous ajoutez un champ &lt;code&gt;log.value&lt;/code&gt; à votre CiliumNetworkPolicy :&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;my-policy&lt;/span&gt;&lt;span class="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;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;my-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;egress&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;toFQDNs&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;matchName&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;example.com&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;log&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;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;my-custom-log-tag&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;Ensuite, quand vous observez les flux dans Hubble, vous pouvez &lt;strong&gt;les filtrer&lt;/strong&gt; en utilisant &lt;a class="link" href="https://kubernetes.io/docs/reference/using-api/cel/" target="_blank" rel="noopener"
&gt;CEL (Common Expression Language)&lt;/a&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;hubble observe &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --verdict AUDIT &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --not &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --cel-expression &lt;span class="s2"&gt;&amp;#34;(_flow.policy_log.endsWith(&amp;#39;my-custom-log-tag&amp;#39;))&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; --print-raw-filters
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Output :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;allowlist:
- &amp;#39;{&amp;#34;verdict&amp;#34;:[&amp;#34;AUDIT&amp;#34;]}&amp;#39;
denylist:
- &amp;#39;{&amp;#34;experimental&amp;#34;:{&amp;#34;cel_expression&amp;#34;:[&amp;#34;(_flow.policy_log.endsWith(&amp;#39;&amp;#39;my-custom-log-tag&amp;#39;&amp;#39;))&amp;#34;]}}&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Parfait ! Vous savez maintenant taguer les calls que vous avez explicitement bloqués et les exclure de votre monitoring. 🎉&lt;/p&gt;
&lt;h2 id="le-plan--bloquer-la-télémétrie-élégamment"&gt;Le plan : bloquer la télémétrie élégamment
&lt;/h2&gt;&lt;p&gt;À l&amp;rsquo;aide de cette nouvelle fonctionnalité, nous avons élaboré notre stratégie :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Utiliser &lt;code&gt;egressDeny&lt;/code&gt; pour bloquer explicitement les domaines de télémétrie&lt;/li&gt;
&lt;li&gt;Ajouter un champ de log personnalisé : &lt;code&gt;app-explicit-traffic-blocked&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Configurer Hubble pour filtrer les verdicts de flux avec ce tag&lt;/li&gt;
&lt;li&gt;Profit ! 🎉&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Voici ce que nous avons essayé :&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;app-external-block-policy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;my-namespace&lt;/span&gt;&lt;span class="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;app.kubernetes.io/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;my-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="c"&gt;# note: egressDeny prend la précédence sur les règles egress&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;# https://docs.cilium.io/en/stable/security/policy/language/#deny-policies&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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="c"&gt;# Bloquer tout le trafic externe et le logger avec un champ de log arbitraire&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;# Ceci est utilisé pour empêcher l&amp;#39;app d&amp;#39;envoyer des données de télémétrie à l&amp;#39;extérieur&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;# sans déclencher d&amp;#39;alerte AUDIT/DROPPED&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;# fonctionnalité ajoutée dans cilium 1.18.0 https://github.com/cilium/cilium/pull/39902&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;toFQDNs&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;matchPattern&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;*.telemetry.example.com&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;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;443&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;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;80&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;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;log&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;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;app-explicit-traffic-blocked&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;Ça devrait fonctionner, non ? On utilise &lt;code&gt;egressDeny&lt;/code&gt; (qui &lt;a class="link" href="https://docs.cilium.io/en/stable/security/policy/language/#deny-policies" target="_blank" rel="noopener"
&gt;prend la précédence sur les autres règles&lt;/a&gt;, ce qui est une bonne chose !), et on le tague avec notre log personnalisé.&lt;/p&gt;
&lt;h2 id="retour-à-la-réalité"&gt;Retour à la réalité
&lt;/h2&gt;&lt;p&gt;Et puis&amp;hellip; &lt;strong&gt;patatra&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;En lisant la &lt;a class="link" href="https://docs.cilium.io/en/stable/security/policy/language/#deny-policies" target="_blank" rel="noopener"
&gt;documentation Cilium sur les deny policies&lt;/a&gt;, nous sommes tombés sur cette petite note de rien du tout :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Deny policies do not support:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;policy enforcement at L7, i.e., specifically denying an URL&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;toFQDNs&lt;/strong&gt;, i.e., specifically denying traffic to a specific domain name.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Attendez, quoi ?&lt;/p&gt;
&lt;p&gt;On &lt;strong&gt;ne peut pas&lt;/strong&gt; utiliser &lt;code&gt;toFQDNs&lt;/code&gt; avec &lt;code&gt;egressDeny&lt;/code&gt;. Tout notre plan vient de s&amp;rsquo;effondrer 😱.&lt;/p&gt;
&lt;h2 id="pourquoi-cest-un-problème-déjà-"&gt;Pourquoi c&amp;rsquo;est un problème, déjà ?
&lt;/h2&gt;&lt;p&gt;Le problème, c&amp;rsquo;est le modèle de précédence dans Cilium :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les règles &lt;code&gt;egressDeny&lt;/code&gt; prennent la précédence sur les règles &lt;code&gt;egress&lt;/code&gt; (by design, et c&amp;rsquo;est bien !)&lt;/li&gt;
&lt;li&gt;Mais si on utilise &lt;code&gt;egressDeny&lt;/code&gt;, on ne peut pas utiliser &lt;code&gt;toFQDNs&lt;/code&gt; pour cibler intelligemment le domaine incriminé, on doit bloquer par IP ou CIDR&lt;/li&gt;
&lt;li&gt;Ces services de télémétrie utilisent &lt;em&gt;probablement&lt;/em&gt; des IPs dynamiques pour leurs endpoints (bonne chance pour maintenir une liste&amp;hellip;)&lt;/li&gt;
&lt;li&gt;Et si on bloque tout le trafic 80/443 dans &lt;code&gt;egressDeny&lt;/code&gt;, on ne peut pas faire d&amp;rsquo;exceptions pour le trafic légitime dans les règles &lt;code&gt;egress&lt;/code&gt; car&amp;hellip; &lt;em&gt;deny&lt;/em&gt; prend la précédence sur &lt;em&gt;allow&lt;/em&gt; !&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On est coincé entre le marteau et l&amp;rsquo;enclume :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Utiliser &lt;code&gt;egress&lt;/code&gt; avec &lt;code&gt;toFQDNs&lt;/code&gt; → ça marche, mais on ne peut pas &lt;strong&gt;bloquer&lt;/strong&gt;, seulement autoriser&lt;/li&gt;
&lt;li&gt;Utiliser &lt;code&gt;egressDeny&lt;/code&gt; avec des IPs → on va jouer au chat et à la souris avec des plages IP qui changent&lt;/li&gt;
&lt;li&gt;Utiliser &lt;code&gt;egressDeny&lt;/code&gt; pour bloquer tout le 80/443 → on bloque tout, y compris le trafic légitime&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="solutions-de-contournement-potentielles"&gt;Solutions de contournement potentielles
&lt;/h2&gt;&lt;p&gt;En attendant que Cilium supporte &lt;code&gt;toFQDNs&lt;/code&gt; dans les policies &lt;code&gt;egressDeny&lt;/code&gt;, voici quelques approches alternatives que vous pourriez envisager :&lt;/p&gt;
&lt;h3 id="trouver-un-moyen-de-désactiver-la-télémétrie-directement-dans-lapp"&gt;Trouver un moyen de désactiver la télémétrie directement dans l&amp;rsquo;app
&lt;/h3&gt;&lt;p&gt;C&amp;rsquo;est la meilleure option, mais malheureusement pas toujours sur la table.&lt;/p&gt;
&lt;h3 id="blocage-basé-sur-le-dns"&gt;Blocage basé sur le DNS
&lt;/h3&gt;&lt;p&gt;Configurer le serveur DNS pour retourner NXDOMAIN pour les domaines de télémétrie, comme un serveur &lt;a class="link" href="https://pi-hole.net/" target="_blank" rel="noopener"
&gt;pi-hole&lt;/a&gt; personnel le ferait avec les pubs. L&amp;rsquo;application échouera à résoudre le domaine et n&amp;rsquo;enverra pas de données.&lt;/p&gt;
&lt;h3 id="utiliser-egressdeny-basé-sur-les-ips-avec-un-overhead-de-maintenance"&gt;Utiliser egressDeny basé sur les IPs (avec un overhead de maintenance)
&lt;/h3&gt;&lt;p&gt;Résoudre les FQDNs de télémétrie vers leurs plages IP actuelles et les bloquer avec &lt;code&gt;egressDeny&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;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;toCIDRSet&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;cidr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;203.0.113.0&lt;/span&gt;&lt;span class="l"&gt;/24 &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Exemple de plage IP de télémétrie&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;443&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;Si la liste n&amp;rsquo;évolue pas trop souvent, c&amp;rsquo;est une bonne option.&lt;/p&gt;
&lt;h2 id="ok-mais-imaginons-quil-ny-ait-pas-de-trafic-légitime-peut-on-utiliser-la-fonctionnalité-pour-ajouter-un-log-sur-le-trafic-droppé-"&gt;OK, mais imaginons qu&amp;rsquo;il n&amp;rsquo;y ait pas de trafic légitime. Peut-on utiliser la fonctionnalité pour ajouter un log sur le trafic droppé ?
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Malheureusement non, pas pour le moment.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Il y a un bug dans cette nouvelle fonctionnalité de Cilium qui ne log le champ &lt;code&gt;policy_log&lt;/code&gt; que sur les flux &amp;ldquo;autorisés&amp;rdquo;, pas sur les flux audit/dropped.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/cilium/cilium/issues/42044" target="_blank" rel="noopener"
&gt;Policy log does not work for DROPPED/AUDIT flow&lt;/a&gt; ouverte par mon collègue Nicolas :&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;When defining a CiliumNetworkPolicy with the spec.log field configured, I expect the relevant hubble flows to have the policy_log field. It works for allowed flow.&lt;/p&gt;
&lt;p&gt;But for denied/audited flow resulting from the rule (implicit or explicit), policy_log is never available.&lt;/p&gt;
&lt;p&gt;Note: I observe the same issue with &lt;code&gt;--print-policy-names&lt;/code&gt; option of hubble, the k8s:io.cilium.k8s.policy.derived-from label is not set for denied flows (but correctly set for allowed flows).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;et une autre issue &lt;a class="link" href="https://github.com/cilium/cilium/issues/41912" target="_blank" rel="noopener"
&gt;[Hubble CLI] &amp;ndash;print-policy-names flag does not do anything&lt;/a&gt; ouverte par quelqu&amp;rsquo;un d&amp;rsquo;autre.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Puisque 2 tickets sont ouverts et que les mainteneurs ont commencé à répondre dessus, on peut espérer que ce soit corrigé un jour.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Dans notre cas d&amp;rsquo;usage, nous n&amp;rsquo;avons finalement pas utilisé cette nouvelle fonctionnalité de Cilium. Mais donner la possibilité d&amp;rsquo;ajouter des détails (et permettre de filtrer dessus également) est toujours une feature sympa.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Un grand merci à mon collègue Nicolas Nativel, qui a fait la majorité du travail autour des CiliumNetworkPolicies, incluant les dashboards, le travail exploratoire sur cette fonctionnalité, et a pris le temps de créer l&amp;rsquo;issue sur le repo Cilium.&lt;/strong&gt;&lt;/p&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://isovalent.com/blog/post/cilium-1-18/#hubble-flow-policy-log-field" target="_blank" rel="noopener"
&gt;Blogpost officiel pour la sortie de Cilium 1.18 - Hubble flow policy log field&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.danielstechblog.io/ciliums-new-hubble-flow-policy-log-field/" target="_blank" rel="noopener"
&gt;Daniel&amp;rsquo;s Tech Blog - Cilium&amp;rsquo;s new Hubble flow policy log field&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.cilium.io/en/stable/security/policy/language/#deny-policies" target="_blank" rel="noopener"
&gt;Docs Cilium - Deny Policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/cilium/cilium/pull/39902" target="_blank" rel="noopener"
&gt;GitHub PR #39902 - Add policy log field&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/cilium/cilium/issues/42044" target="_blank" rel="noopener"
&gt;GitHub Issue #42044 - Policy log does not work for DROPPED/AUDIT flow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>J'ai testé pour vous : k8e (Kubernetes Easy Engine)</title><link>https://blog.zwindler.fr/2025/09/19/test-k8e/</link><pubDate>Fri, 19 Sep 2025 16:00:00 +0000</pubDate><guid>https://blog.zwindler.fr/2025/09/19/test-k8e/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/09/k8e-logo.webp" alt="Featured image of post J'ai testé pour vous : k8e (Kubernetes Easy Engine)" /&gt;&lt;h2 id="tu-ne-faisais-pas-un-livre-sur-kubernetes-toi-"&gt;Tu ne faisais pas un livre sur Kubernetes, toi ?
&lt;/h2&gt;&lt;p&gt;Oui ! Et j&amp;rsquo;ai une bonne nouvelle : mon livre &amp;ldquo;Kubernetes : 50 solutions pour les postes de développement et les clusters de production&amp;rdquo;, édité par &lt;a class="link" href="https://www.editions-eyrolles.com/" target="_blank" rel="noopener"
&gt;Eyrolles&lt;/a&gt;, sortira le &lt;strong&gt;16 octobre 2025&lt;/strong&gt; ! Vous pouvez suivre l&amp;rsquo;état d&amp;rsquo;avancement du projet sur &lt;a class="link" href="https://50ndk.zwindler.fr" target="_blank" rel="noopener"
&gt;50ndk.zwindler.fr&lt;/a&gt;. Je ferai une annonce propre quand j&amp;rsquo;aurai la couverture définitive à vous montrer :3.&lt;/p&gt;
&lt;p&gt;En attendant la sortie, je &amp;ldquo;libère&amp;rdquo; un autre chapitre qui avait été abandonné lors de la sélection finale du livre : celui sur &lt;a class="link" href="https://github.com/xiaods/k8e" target="_blank" rel="noopener"
&gt;&lt;strong&gt;k8e&lt;/strong&gt; (Kubernetes Easy Engine)&lt;/a&gt;. Si vous suivez le blog attentivement, vous vous souviendrez peut-être que j&amp;rsquo;avais fait pareil pour &lt;a class="link" href="https://blog.zwindler.fr/2025/05/26/test-k8s-tew" &gt;k8s-tew (K8S : the easier way)&lt;/a&gt;, qui n&amp;rsquo;avait aussi pas eu la chance de figurer dans la liste des 50 méthodes qui ont leur place dans mon livre :-P.&lt;/p&gt;
&lt;h2 id="mais-revenons-à-k8e-"&gt;Mais revenons à k8e !
&lt;/h2&gt;&lt;p&gt;k8e est un wrapper pour k3s qui permet d&amp;rsquo;installer facilement un cluster multi-nodes avec &lt;a class="link" href="https://cilium.io/" target="_blank" rel="noopener"
&gt;Cilium&lt;/a&gt; configuré comme CNI et en mode &lt;em&gt;kube-proxy replacement&lt;/em&gt;. Dans la philosophie, c&amp;rsquo;est vraiment pas beaucoup plus que k3s avec un gros preflight check. On est même pas sur le niveau fonctionnel d&amp;rsquo;un &lt;code&gt;k3sup&lt;/code&gt; ou d&amp;rsquo;un &lt;code&gt;k0ctl&lt;/code&gt;&amp;hellip;&lt;/p&gt;
&lt;p&gt;Les développeurs mettent en avant plusieurs fonctionnalités clés :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;✅ Key Features&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Supports airgap images package for k8s components&lt;/li&gt;
&lt;li&gt;10-year valid certificate, supports cluster backup and upgrade&lt;/li&gt;
&lt;li&gt;No dependency on Ansible, HAProxy, or Keepalived; a binary tool with zero dependencies&lt;/li&gt;
&lt;li&gt;Natively supports Cilium network&lt;/li&gt;
&lt;li&gt;No kube-proxy component&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;Le projet se présente donc comme une alternative simplifiée pour déployer k3s avec Cilium directement intégré, ce qui évite les étapes manuelles de configuration post-installation. J&amp;rsquo;ai d&amp;rsquo;ailleurs écrit un article fin 2023 sur ces fameuses opérations manuelles, toujours disponible ici : &lt;a class="link" href="https://blog.zwindler.fr/2023/09/01/k3s-et-cilium-rapide-et-facile" &gt;k3s et cilium rapide et facile&lt;/a&gt;&lt;/p&gt;
&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;p&gt;Comme k8e s&amp;rsquo;appuie sur k3s, les prérequis sont globalement les mêmes que pour k3s. Cependant, il y a une exigence supplémentaire importante : Cilium utilisant eBPF pour ses fonctionnalités de bas niveau, il faut un noyau Linux &lt;strong&gt;relativement&lt;/strong&gt; récent&lt;/p&gt;
&lt;p&gt;Bon, ça c&amp;rsquo;était ce qu&amp;rsquo;on disait pour les premières versions de cilium. Maintenant &lt;strong&gt;Linux kernel &amp;gt;= 4.19.57&lt;/strong&gt; c&amp;rsquo;est un vieux vieux kernel !&lt;/p&gt;
&lt;p&gt;Pour mes tests, j&amp;rsquo;ai utilisé 4 machines virtuelles dans le même LAN :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;k8e1 (control plane, 192.168.1.11)&lt;/li&gt;
&lt;li&gt;k8e2 (control plane, 192.168.1.12)&lt;/li&gt;
&lt;li&gt;k8e3 (control plane, 192.168.1.13)&lt;/li&gt;
&lt;li&gt;k8e4 (worker, 192.168.1.14)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;L&amp;rsquo;installation nécessite un accès SSH avec des privilèges sudo/root sur tous les nœuds.&lt;/p&gt;
&lt;h2 id="installation-du-premier-nœud"&gt;Installation du premier nœud
&lt;/h2&gt;&lt;p&gt;Le &lt;a class="link" href="https://getk8e-site.pages.dev/docs/install/200-quick-start/" target="_blank" rel="noopener"
&gt;Getting started&lt;/a&gt; est un peu rude car on tombe sur une page en chinois simplifié par défaut&amp;hellip;&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;installation de k8e se fait via un script bash, comme beaucoup d&amp;rsquo;outils de la CNCF (RIP la sécurité). Pour le premier nœud (qui sera notre premier node control plane), on utilise la commande suivante :&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;denis@k8e1:~$ curl -sfL https://getk8e-site.pages.dev/install.sh &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;API_SERVER_IP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;192.168.1.11 &lt;span class="nv"&gt;K8E_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;superSecureToken &lt;span class="nv"&gt;INSTALL_K8E_EXEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;server --cluster-init&amp;#34;&lt;/span&gt; sh -
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le script réalise plusieurs vérifications préalables (&amp;ldquo;preflight checks&amp;rdquo;) pour s&amp;rsquo;assurer que l&amp;rsquo;environnement est compatible. C&amp;rsquo;est notamment là qu&amp;rsquo;il vérifie la version du noyau Linux pour la compatibilité eBPF.&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="o"&gt;[&lt;/span&gt;2025-09-19 12:28:45&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;WARN&lt;span class="o"&gt;]&lt;/span&gt; System memory is less than 4GB. This may affect performance.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Finding latest version from GitHub
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;v1.31.2+k8e1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Downloading package https://github.com/xiaods/k8e/releases/download/v1.31.2+k8e1/k8e as /home/denis/k8e
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/09/k8e-prereqs.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;À la fin du processus, on obtient un message de confirmation :&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="o"&gt;[&lt;/span&gt;...&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 class="o"&gt;[&lt;/span&gt;2025-09-19 12:35:33&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt; systemd: Starting k8e
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;2025-09-19 12:35:41&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt; Installing cilium network cni/operator
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ℹ️ Using Cilium version 1.15.6
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🔮 Auto-detected cluster name: default
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🔮 Auto-detected kube-proxy has not been installed
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ℹ️ Cilium will fully replace all functionalities of kube-proxy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;2025-09-19 12:35:42&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt; Installation completed successfully
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;2025-09-19 12:35:42&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;INFO&lt;span class="o"&gt;]&lt;/span&gt; Performing cleanup...
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Point qui a été amélioré depuis la dernière fois que j&amp;rsquo;avais testé, k8e copie lui-même le kubeconfig, il n&amp;rsquo;y a plus besoin d&amp;rsquo;aller le récupérer dans &lt;code&gt;/etc/k8e/k8e.yaml&lt;/code&gt; (idem k3s, il est traditionnellement dans &lt;code&gt;/etc/rancher/k3s/k3s.yaml&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;denis@k8e1:~$ kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS ROLES AGE VERSION
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8e1 Ready control-plane,etcd,master 62m v1.31.2+k8e1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cependant, un rapide coup d&amp;rsquo;œil permet de voir &amp;ldquo;comment&amp;rdquo; k8e le fait :&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;denis@k8e1:~$ env
&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="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/k8e/k8e.yaml
&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;denis@k8e1:~$ ls -l /etc/k8e/k8e.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;-rw-r--r-- &lt;span class="m"&gt;1&lt;/span&gt; root root &lt;span class="m"&gt;2961&lt;/span&gt; Sep &lt;span class="m"&gt;19&lt;/span&gt; 12:35 /etc/k8e/k8e.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et là, je suis désolé, mais c&amp;rsquo;est :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/09/heretique.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Le kubeconfig cluster-admin à poil en 644, c&amp;rsquo;est NON. La doc de k8e est même encore pire, elle conseille du 666 (toujours plus).&lt;/p&gt;
&lt;h2 id="ajout-des-nœuds-supplémentaires"&gt;Ajout des nœuds supplémentaires
&lt;/h2&gt;&lt;p&gt;Une fois le premier nœud installé, on peut ajouter les autres membres du control plane. Pour les nœuds supplémentaires du control plane :&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;denis@k8e2:~$ curl -sfL https://getk8e-site.pages.dev/install.sh &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;K8E_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;superSecureToken &lt;span class="nv"&gt;K8E_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://192.168.1.11:6443 &lt;span class="nv"&gt;INSTALL_K8E_EXEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;server&amp;#34;&lt;/span&gt; sh -s -
&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;denis@k8e3:~$ curl -sfL https://getk8e.com/install.sh &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;K8E_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;superSecureToken &lt;span class="nv"&gt;K8E_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://192.168.1.11:6443 &lt;span class="nv"&gt;INSTALL_K8E_EXEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;server&amp;#34;&lt;/span&gt; sh -s -
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et pour le worker node :&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;denis@k8e4:~$ curl -sfL https://getk8e-site.pages.dev/install.sh &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;K8E_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;superSecureToken &lt;span class="nv"&gt;K8E_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://192.168.1.11:6443 sh -
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On peut ensuite vérifier que tous les nœuds sont bien présents :&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;denis@k8e1:~$ kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS ROLES AGE VERSION
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8e1 Ready control-plane,etcd,master 72m v1.31.2+k8e1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8e2 Ready control-plane,etcd,master 2m30s v1.31.2+k8e1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8e3 Ready control-plane,etcd,master 2m18s v1.31.2+k8e1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8e4 Ready &amp;lt;none&amp;gt; 75s v1.31.2+k8e1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="vérification-de-cilium"&gt;Vérification de Cilium
&lt;/h2&gt;&lt;p&gt;Une des particularités de k8e est donc l&amp;rsquo;intégration native de Cilium. On peut vérifier que Cilium fonctionne correctement en utilisant sa CLI directement sur un des nœuds du control plane (installée 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;denis@k8e1:~$ cilium status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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="se"&gt;\_&lt;/span&gt;_/¯¯&lt;span class="se"&gt;\ &lt;/span&gt; Cilium: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="se"&gt;\_&lt;/span&gt;_/¯¯&lt;span class="se"&gt;\_&lt;/span&gt;_/ Operator: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; /¯¯&lt;span class="se"&gt;\_&lt;/span&gt;_/¯¯&lt;span class="se"&gt;\ &lt;/span&gt; Envoy DaemonSet: disabled &lt;span class="o"&gt;(&lt;/span&gt;using embedded mode&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 class="se"&gt;\_&lt;/span&gt;_/¯¯&lt;span class="se"&gt;\_&lt;/span&gt;_/ Hubble Relay: disabled
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="se"&gt;\_&lt;/span&gt;_/ ClusterMesh: disabled
&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;DaemonSet cilium Desired: 4, Ready: 4/4, Available: 4/4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Deployment cilium-operator Desired: 1, Ready: 1/1, Available: 1/1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Containers: cilium Running: &lt;span class="m"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cilium-operator Running: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cluster Pods: 3/3 managed by Cilium
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Helm chart version: 1.15.6
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Image versions cilium quay.io/cilium/cilium:v1.15.6: &lt;span class="m"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cilium-operator quay.io/cilium/operator-generic:v1.15.6: &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="avantages-et-inconvénients"&gt;Avantages et Inconvénients
&lt;/h3&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Les plus&lt;/th&gt;
&lt;th&gt;Les moins&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;➕ Installation automatisée de Cilium sur k3s&lt;/td&gt;
&lt;td&gt;➖ Permissions &lt;code&gt;/etc/k8e/k8e.yaml&lt;/code&gt; en &amp;ldquo;world readable&amp;rdquo; dans la doc officielle (dangereux !)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;➖ Version de Kubernetes en retard par rapport aux dernières versions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;➖ N&amp;rsquo;apporte pas énormément de valeur ajoutée par rapport à k3s seul&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;➖ Documentation limitée par rapport aux projets plus matures&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Dans les points positifs, le projet, sans être populaire, est quand même plutôt suivi avec pas mal de contributions externes et une &amp;ldquo;vie&amp;rdquo; (commits réguliers). k8e est un outil pour les fainéants qui veulent installer un cluster k3s avec Cilium plus rapidement (oui c&amp;rsquo;est un point positif).&lt;/p&gt;
&lt;p&gt;Mais, à quel prix ?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Un kubeconfig à 644, lisible par tous les utilisateurs unix des control planes&lt;/li&gt;
&lt;li&gt;Une installation en &lt;code&gt;curl | bash&lt;/code&gt; (exactement comme &lt;code&gt;k3s&lt;/code&gt;, à ceci près que j&amp;rsquo;ai plus confiance en &lt;strong&gt;Rancher labs&lt;/strong&gt; qu&amp;rsquo;en &lt;strong&gt;xiaods&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;Des versions datées (kube 1.31, cilium 1.15)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pas la peine que je vous dise ce que j&amp;rsquo;en pense, je pense que vous avez compris.&lt;/p&gt;</description></item><item><title>Migration du routage de cilium de iptables vers eBPF... à chaud !</title><link>https://blog.zwindler.fr/2023/10/20/migration-routage-cilium-iptables-ebpf/</link><pubDate>Fri, 20 Oct 2023 06:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2023/10/20/migration-routage-cilium-iptables-ebpf/</guid><description>&lt;img src="https://blog.zwindler.fr/2023/10/cilium-logo.webp" alt="Featured image of post Migration du routage de cilium de iptables vers eBPF... à chaud !" /&gt;&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Il se pourrait que j&amp;rsquo;aie configuré des clusters Kubernetes de prod avec &lt;a class="link" href="https://cilium.io/" target="_blank" rel="noopener"
&gt;cilium&lt;/a&gt; en mode &lt;code&gt;iptables&lt;/code&gt; et non pas &lt;code&gt;eBPF&lt;/code&gt;. Mais vous n&amp;rsquo;avez aucune preuve&amp;hellip;&lt;/p&gt;
&lt;p&gt;Cependant, dans l&amp;rsquo;hypothèse hautement improbable où j&amp;rsquo;aurais pu faire une boulette pareille, voilà comment je m&amp;rsquo;y serai pris pour résoudre le problème.&lt;/p&gt;
&lt;p&gt;#trollface&lt;/p&gt;
&lt;p&gt;Imaginons donc qu&amp;rsquo;en vérifiant la configuration de Cilium, voici ce que vous avez trouvé :&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="l"&gt;kubectl -n cilium exec -it cilium-aaaaa -- cilium status&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="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="nt"&gt;Host Routing&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Legacy&lt;/span&gt;&lt;span class="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;Masquerading&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;IPTables [IPv4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Enabled, IPv6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Disabled]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="l"&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;Damned!&lt;/p&gt;
&lt;p&gt;A y regarder de plus près, les containers cilium-agent dans vos pods cilium prennent beaucoup de CPU et de RAM et commencent à engorger vos workers&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;C&amp;rsquo;est la cata&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="pourquoi-ça-"&gt;Pourquoi ça ?
&lt;/h2&gt;&lt;p&gt;Les premières implémentations du &lt;em&gt;réseau imaginaire&lt;/em&gt; de Kubernetes (les fameux CNI plugins) utilisaient &lt;code&gt;iptables&lt;/code&gt;. Or, on sait depuis très longtemps, notamment dans le cas d&amp;rsquo;usage de Kubernetes, qu&amp;rsquo;&lt;code&gt;iptables&lt;/code&gt; est assez mauvais pour gérer de grandes quantités de règles.&lt;/p&gt;
&lt;p&gt;Dans le cas particulier de Kubernetes, le nombre de règles à tendance à augmenter de manière exponentielle avec la taille du cluster et le CPU consommé pour router des paquets réseaux avec&amp;hellip;&lt;/p&gt;
&lt;p&gt;À un moment donné, parcourir la liste des règles prend tout le CPU d&amp;rsquo;un nœud donné et le rend non réactif.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Allo patron ? On est mal.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;C&amp;rsquo;est une des raisons pour lesquelles j&amp;rsquo;aime beaucoup cilium comme CNI plugin, les développeurs font partie des premiers à avoir misé sur &lt;a class="link" href="https://ebpf.io/" target="_blank" rel="noopener"
&gt;eBPF&lt;/a&gt; comme remplacement d&amp;rsquo;iptables (même s&amp;rsquo;il existe d&amp;rsquo;autres implémentations / d&amp;rsquo;autres technos qui règles ce problème).&lt;/p&gt;
&lt;h2 id="comment-en-est-on-arrivé-là-"&gt;Comment en est on arrivé là ?
&lt;/h2&gt;&lt;p&gt;A vrai dire, j&amp;rsquo;ai juste lu la doc et fait confiance&amp;hellip;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We introduced eBPF-based host-routing in Cilium 1.9 to fully bypass iptables and the upper host stack, and to achieve a faster network namespace switch compared to regular veth device operation. &lt;strong&gt;This option is automatically enabled if your kernel supports it&lt;/strong&gt;. To validate whether your installation is running with eBPF host-routing, run cilium status in any of the Cilium pods and look for the line reporting the status for &amp;ldquo;Host Routing&amp;rdquo; which should state &amp;ldquo;BPF&amp;rdquo;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Grosso modo, &lt;a class="link" href="https://docs.cilium.io/en/stable/operations/system_requirements/" target="_blank" rel="noopener"
&gt;selon la doc officielle, si on dispose du kernel correct et des modules qui vont bien&lt;/a&gt;, l&amp;rsquo;installation de cilium est censée activer automatiquement le mode eBPF&amp;hellip; Sauf qu&amp;rsquo;on voit bien un peu plus haut que ce n&amp;rsquo;est pas le cas, malgré un kernel récent (6.2).&lt;/p&gt;
&lt;p&gt;En creusant un peu plus les logs, voilà ce qu&amp;rsquo;on peut trouver :&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 -n cilium logs cilium-7b5cp
&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="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;BPF host routing requires enable-bpf-masquerade. Falling back to legacy host routing (enable-host-legacy-routing=true).&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;subsys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;daemon
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Bon&amp;hellip; visiblement, il manque une option dans la chart Helm.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-diff" data-lang="diff"&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; kubeProxyReplacement: strict
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ bpf:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ masquerade: 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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Problem solved, fin de l&amp;rsquo;article ?&lt;/p&gt;
&lt;h2 id="problème"&gt;Problème
&lt;/h2&gt;&lt;p&gt;Alors oui, forcément, on peut modifier la chart comme un⋅e bourrin⋅e et fin de l&amp;rsquo;histoire.&lt;/p&gt;
&lt;p&gt;Mais admettons qu&amp;rsquo;on ait pas envie de couper le trafic de production&amp;hellip; Comment on fait ?&lt;/p&gt;
&lt;p&gt;La solution la plus clean qui me vient en tête est la suivante :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on &lt;code&gt;drain&lt;/code&gt; un node&lt;/li&gt;
&lt;li&gt;on lui change sa configuration&lt;/li&gt;
&lt;li&gt;on l&amp;rsquo;&lt;code&gt;uncordon&lt;/code&gt; pour lui remettre un peu de trafic dessus&lt;/li&gt;
&lt;li&gt;on vérifie que la nouvelle configuration fonctionne
ET&lt;/li&gt;
&lt;li&gt;on vérifie que les nodes peuvent se parler entre eux entre ceux en &lt;code&gt;iptables&lt;/code&gt; et ceux en &lt;code&gt;eBPF&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Bah oui, ça serait dommage d&amp;rsquo;avoir la moitié du cluster qui peut pas communiquer avec l&amp;rsquo;autre moitié&amp;hellip;&lt;/p&gt;
&lt;h2 id="vérifier-la-connectivité"&gt;Vérifier la connectivité
&lt;/h2&gt;&lt;p&gt;Ce qui est cool avec cilium (je vous ai déjà dit que j&amp;rsquo;aime cilium ?), c&amp;rsquo;est qu&amp;rsquo;on a déjà du tooling pour tout tester.&lt;/p&gt;
&lt;p&gt;La CLI cilium dispose d&amp;rsquo;une sous commande &lt;code&gt;cilium connectivity test&lt;/code&gt; qui lance des dizaines de tests internes/externes pour tester que tout est OK.&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;➜ ~ cilium -n cilium connectivity &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ℹ️ Monitor aggregation detected, will skip some flow validation steps
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✨ &lt;span class="o"&gt;[&lt;/span&gt;node&lt;span class="o"&gt;]&lt;/span&gt; Deploying echo-same-node service...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✨ &lt;span class="o"&gt;[&lt;/span&gt;node&lt;span class="o"&gt;]&lt;/span&gt; Deploying DNS &lt;span class="nb"&gt;test&lt;/span&gt; server configmap...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✨ &lt;span class="o"&gt;[&lt;/span&gt;node&lt;span class="o"&gt;]&lt;/span&gt; Deploying same-node deployment...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✨ &lt;span class="o"&gt;[&lt;/span&gt;node&lt;span class="o"&gt;]&lt;/span&gt; Deploying client deployment...
&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="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;✅ All &lt;span class="m"&gt;32&lt;/span&gt; tests &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="m"&gt;265&lt;/span&gt; actions&lt;span class="o"&gt;)&lt;/span&gt; successful, &lt;span class="m"&gt;2&lt;/span&gt; tests skipped, &lt;span class="m"&gt;0&lt;/span&gt; scenarios skipped.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Il existe aussi une commande &lt;code&gt;cilium-health&lt;/code&gt;, embarquée dans le container cilium-agent, qui permet de remonter des statistiques périodiques de latences entre tous les nodes du cluster. Utile !&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 -n cilium &lt;span class="nb"&gt;exec&lt;/span&gt; -it cilium-ccccc -c cilium-agent -- cilium-health status
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Probe time: 2023-09-12T14:47:03Z
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Nodes:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; node-02 &lt;span class="o"&gt;(&lt;/span&gt;localhost&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Host connectivity to 172.31.0.152:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ICMP to stack: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;860.424µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HTTP to agent: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;110.142µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Endpoint connectivity to 10.0.2.56:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ICMP to stack: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;783.861µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HTTP to agent: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;256.419µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; node-01:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Host connectivity to 172.31.0.151:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ICMP to stack: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;813.324µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HTTP to agent: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;553.445µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Endpoint connectivity to 10.0.1.53:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ICMP to stack: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;865.976µs
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; HTTP to agent: OK, &lt;span class="nv"&gt;RTT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.440655ms
&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="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et enfin, on peut juste regarder la commande &lt;code&gt;cilium status&lt;/code&gt; qui nous dit qui est &amp;ldquo;reachable&amp;rdquo; (là encore, dans le container cilium-agent)&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 -n cilium &lt;span class="nb"&gt;exec&lt;/span&gt; -ti cilium-ddddd -- cilium status
&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="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Host Routing: Legacy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Masquerading: IPTables &lt;span class="o"&gt;[&lt;/span&gt;IPv4: Enabled, IPv6: Disabled&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 class="o"&gt;[&lt;/span&gt;...&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Cluster health: 5/5 reachable &lt;span class="o"&gt;(&lt;/span&gt;2023-09-14T12:01:51Z&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="changer-la-configuration-dun-node"&gt;Changer la configuration d&amp;rsquo;un node
&lt;/h2&gt;&lt;p&gt;On a de la chance, car, depuis la version 1.13 de cilium (la dernière en date est la 1.14), il est possible d&amp;rsquo;appliquer des configurations différentes pour un sous ensemble de nodes (documentation officielle du &lt;a class="link" href="https://docs.cilium.io/en/stable/configuration/per-node-config/#per-node-configuration" target="_blank" rel="noopener"
&gt;Per-node configuration&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Note : avant ça, c&amp;rsquo;était quand même possible, &lt;em&gt;de manière temporaire&lt;/em&gt;, en éditant la ConfigMap de cilium à la main, puis en redémarrant les pods concernés pour prise en compte.&lt;/p&gt;
&lt;p&gt;Il existe maintenant une CRD pour le faire, qui s&amp;rsquo;appelle &lt;strong&gt;CiliumNodeConfig&lt;/strong&gt;. Les seules choses qu&amp;rsquo;on a à faire, c&amp;rsquo;est ajouter un label sur un node (io.cilium.enable-ebpf: &amp;ldquo;true&amp;rdquo;) et ajouter le manifest suivant sur notre 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;cat &amp;gt; cilium-fix.yaml &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: cilium.io/v2alpha1
&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: CiliumNodeConfig
&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; namespace: cilium
&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: cilium-switch-from-iptables-ebpf
&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; nodeSelector:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; matchLabels:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; io.cilium.enable-ebpf: &amp;#34;true&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; defaults:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; enable-bpf-masquerade: &amp;#34;true&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;EOF&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;kubectl apply -f cilium-fix.yaml
&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 label node node-05 --overwrite &lt;span class="s1"&gt;&amp;#39;io.cilium.enable-ebpf=true&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Si jamais on est en production, le plus propre est donc de &lt;code&gt;kubectl drain&lt;/code&gt; le Node préalablement.&lt;/p&gt;
&lt;p&gt;Par curiosité, j&amp;rsquo;ai quand même essayé de le faire &amp;ldquo;à chaud&amp;rdquo;, pour le fun.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai déployé un daemonset contenant &lt;a class="link" href="https://github.com/zwindler/vhelloworld" target="_blank" rel="noopener"
&gt;une app en V(lang) qui répond simplement le nom du container dans une page web&lt;/a&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="l"&gt;cat &amp;gt; vhelloworld-daemonset.yaml &amp;lt;&amp;lt; EOF&lt;/span&gt;&lt;span class="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;DaemonSet&lt;/span&gt;&lt;span class="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;vhelloworld-daemonset&lt;/span&gt;&lt;span class="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;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;vhelloworld&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;vhelloworld&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;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;vhelloworld&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;zwindler/vhelloworld: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;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;8081&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;imagePullPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Always&lt;/span&gt;&lt;span class="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;EOF&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&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="l"&gt;kubectl apply -f vhelloworld-daemonset.yaml&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 get pods -o wide
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vhelloworld-daemonset-q4h44 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 3m31s 10.0.4.87 nodekube-05 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vhelloworld-daemonset-w97pv 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 3m31s 10.0.3.85 nodekube-04 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Puis j&amp;rsquo;ai créé des containers contenant un shell et &lt;code&gt;curl&lt;/code&gt; pour vérifier périodiquement et depuis plusieurs nodes que les containers sont bien accessibles :&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 run -it --image curlimages/curl:latest curler -- /bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;If you don&lt;span class="err"&gt;&amp;#39;&lt;/span&gt;t see a &lt;span class="nb"&gt;command&lt;/span&gt; prompt, try pressing enter.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~ $ curl http://10.0.4.87:8081
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-g2zdw
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;~ $ curl http://10.0.3.85:8081
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-65k2n
&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="k"&gt;while&lt;/span&gt; true&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; date
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl http://10.0.4.87:8081
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl http://10.0.3.85:8081
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fois le label appliqué sur un node, son pod cilium tué (via un &lt;code&gt;kubectl delete&lt;/code&gt;), les pods sont restés accessibles&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;Fri Sep &lt;span class="m"&gt;15&lt;/span&gt; 14:05:34 UTC &lt;span class="m"&gt;2023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-g2zdw
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-65k2n
&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;Fri Sep &lt;span class="m"&gt;15&lt;/span&gt; 14:05:35 UTC &lt;span class="m"&gt;2023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-g2zdw
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-65k2n
&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;Fri Sep &lt;span class="m"&gt;15&lt;/span&gt; 14:05:36 UTC &lt;span class="m"&gt;2023&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-g2zdw
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hello from vhelloworld-daemonset-65k2n
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A un moment donné, cilium a redémarré, remarqué la présence de pods existants, et pris le relais avec eBPF !&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;evel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Rewrote endpoint BPF program&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;containerID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;8b7be1b032 &lt;span class="nv"&gt;datapathPolicyRevision&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;desiredPolicyRevision&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="nv"&gt;endpointID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1018&lt;/span&gt; &lt;span class="nv"&gt;identity&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;4773&lt;/span&gt; &lt;span class="nv"&gt;ipv4&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10.0.3.85 &lt;span class="nv"&gt;ipv6&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;k8sPodName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;default/vhelloworld-daemonset-w97pv &lt;span class="nv"&gt;subsys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;endpoint
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;info &lt;span class="nv"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Restored endpoint&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;endpointID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1018&lt;/span&gt; &lt;span class="nv"&gt;ipAddr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;[10.0.3.85 ]&amp;#34;&lt;/span&gt; &lt;span class="nv"&gt;subsys&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;endpoint
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="ca-fonctionne-mais-est-ce-que-cest-mieux-en-termes-de-consommation-de-ressources-"&gt;Ca fonctionne, mais est ce que c&amp;rsquo;est mieux en termes de consommation de ressources ?
&lt;/h2&gt;&lt;p&gt;Ca, c&amp;rsquo;était &lt;strong&gt;la&lt;/strong&gt; bonne nouvelle. Je m&amp;rsquo;attendais à des gains car le cluster était vraiment pas loin de souffrir.&lt;/p&gt;
&lt;p&gt;Les containers cilium prenaient 10% des CPU de mes nodes, et plusieurs Go de RAM en mode &lt;code&gt;iptables&lt;/code&gt;. Ca aurait rapidement pu monter bien plus haut, si j&amp;rsquo;avais agrandi le cluster au-delà des 50 nodes / 2000 pods.&lt;/p&gt;
&lt;p&gt;Le passage au mode eBPF a permis de retrouver des niveaux totalement indolores (1% de CPU par node, 1-2% de RAM) par rapport aux configurations de mes machines.&lt;/p&gt;
&lt;p&gt;Pas mal, hein ?&lt;/p&gt;
&lt;h2 id="bonus"&gt;Bonus
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://isovalent.com/blog/post/tutorial-migrating-to-cilium-part-1/" target="_blank" rel="noopener"
&gt;Un tutoriel bien velu pour migrer totalement de CNI à chaud (de flannel à cilium)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>k3s et cilium rapide et facile</title><link>https://blog.zwindler.fr/2023/09/01/k3s-et-cilium-rapide-et-facile/</link><pubDate>Fri, 01 Sep 2023 10:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2023/09/01/k3s-et-cilium-rapide-et-facile/</guid><description>&lt;img src="https://blog.zwindler.fr/2023/09/k3s_cilium.webp" alt="Featured image of post k3s et cilium rapide et facile" /&gt;&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;&lt;a class="link" href="https://k3s.io/" target="_blank" rel="noopener"
&gt;K3s&lt;/a&gt; est une distribution de Kubernetes éditée par Rancher (et certifiée par la CNCF) que je trouve super pratique car légère et supportant plusieurs plateformes (en particulier ARM):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;x86_64&lt;/li&gt;
&lt;li&gt;armhf&lt;/li&gt;
&lt;li&gt;arm64/aarch64&lt;/li&gt;
&lt;li&gt;s390x&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je l&amp;rsquo;ai déjà utilisée par le passé (&lt;a class="link" href="https://blog.zwindler.fr/2019/03/21/deployer-en-5-minutes-un-cluster-kubernetes-sur-arm-avec-k3s-et-ansible/" target="_blank" rel="noopener"
&gt;ici&lt;/a&gt;) pour faire un cluster kubernetes avec des vieux Raspberry Pi. A l&amp;rsquo;époque, on pouvait même faire tourner les nodes &amp;ldquo;workers&amp;rdquo; sur des RPi 1 (je ne sais pas si c&amp;rsquo;est toujours possible).&lt;/p&gt;
&lt;p&gt;La particularité de k3s est que tout est intégré dans un seul binaire. Nécessairement, des choix techniques ont été fais pour rendre l&amp;rsquo;installation et l&amp;rsquo;utilisation la plus légère possible. Parmi ces choix techniques, il y a l&amp;rsquo;utilisation de &lt;a class="link" href="https://github.com/flannel-io/flannel" target="_blank" rel="noopener"
&gt;flannel comme &amp;ldquo;CNI plugin&amp;rdquo;&lt;/a&gt; (pour faire simple, le réseau virtuel qui permet aux containers de parler en eux).&lt;/p&gt;
&lt;p&gt;Je ne suis vraiment PAS fan de flannel qui m&amp;rsquo;a posé beaucoup de soucis en production, qui ne supporte pas les &amp;ldquo;Network Policies&amp;rdquo;, qui a pendant plusieurs années utilisé comme backend etcd2 alors que ce logiciel était marqué comme obsolète (deprecated) depuis plusieurs années, &amp;hellip;&lt;/p&gt;
&lt;p&gt;A l&amp;rsquo;inverse, je suis très fan de &lt;a class="link" href="https://cilium.io/" target="_blank" rel="noopener"
&gt;cilium&lt;/a&gt;, qui a beaucoup de fonctionnalités sympa grâce à l&amp;rsquo;usage de eBPF en termes de performance et de sécurité et a pas mal de traction dans l&amp;rsquo;écosystème.&lt;/p&gt;
&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;p&gt;Dans ce tutoriel, je pars du principe que vous avez installé un serveur avec Ubuntu 22.04.&lt;/p&gt;
&lt;p&gt;Cilium est très friand d&amp;rsquo;eBPF, dont il tire la plupart de ces fonctionnalités. Et comme eBPF est quelque chose d&amp;rsquo;assez récent dans le kernel Linux, le mieux est de mettre à jour notre serveur et de lui mettre un kernel plus récent :&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;$ sudo apt update -y
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo apt upgrade -y
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo apt install curl linux-image-generic-hwe-22.04 -y
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fois que c&amp;rsquo;est fait, on reboot le serveur pour la prise en compte du nouveau kernel.&lt;/p&gt;
&lt;h2 id="installation-de-k3s"&gt;Installation de k3s
&lt;/h2&gt;&lt;p&gt;On installe donc &lt;a class="link" href="https://docs.k3s.io/installation" target="_blank" rel="noopener"
&gt;k3s&lt;/a&gt;, en remplaçant flannel par cilium (voir documentation &lt;a class="link" href="https://docs.k3s.io/installation/network-options" target="_blank" rel="noopener"
&gt;Installation - Network options&lt;/a&gt;). La doc conseille de retirer le support des network-policy. Je me demande comment c&amp;rsquo;est géré puisque flannel n&amp;rsquo;est pas censé les supporter :thinking_face:.&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 -sfL https://get.k3s.io &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;INSTALL_K3S_EXEC&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;--flannel-backend=none --disable &amp;#34;traefik&amp;#34;&amp;#39;&lt;/span&gt; sh -s - --disable-network-policy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On vérifie que k3s est démarré et fonctionne :&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;$ systemctl status k3s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;● k3s.service - Lightweight Kubernetes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Loaded: loaded &lt;span class="o"&gt;(&lt;/span&gt;/etc/systemd/system/k3s.service&lt;span class="p"&gt;;&lt;/span&gt; enabled&lt;span class="p"&gt;;&lt;/span&gt; vendor preset: enabled&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Active: active &lt;span class="o"&gt;(&lt;/span&gt;running&lt;span class="o"&gt;)&lt;/span&gt; since Fri 2023-09-01 12:32:55 UTC&lt;span class="p"&gt;;&lt;/span&gt; 1min 13s ago
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Docs: https://k3s.io
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Process: &lt;span class="m"&gt;1724&lt;/span&gt; &lt;span class="nv"&gt;ExecStartPre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/bin/sh -xc ! /usr/bin/systemctl is-enabled --quiet nm-cloud-setup.service &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;exited, &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0/SUCCESS&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Process: &lt;span class="m"&gt;1726&lt;/span&gt; &lt;span class="nv"&gt;ExecStartPre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/sbin/modprobe br_netfilter &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;exited, &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0/SUCCESS&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Process: &lt;span class="m"&gt;1727&lt;/span&gt; &lt;span class="nv"&gt;ExecStartPre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/sbin/modprobe overlay &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;exited, &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0/SUCCESS&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 class="o"&gt;[&lt;/span&gt;...&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;Par défaut, le &amp;ldquo;kubeconfig&amp;rdquo; d&amp;rsquo;administration est déposé dans le fichier &lt;code&gt;/etc/rancher/k3s/k3s.yaml&lt;/code&gt;, dont le propriétaire est root. Il existe un flag &lt;code&gt;--write-kubeconfig-mode&lt;/code&gt; dans k3s pour modifier les droits de ce fichier mais cela le rendrait &amp;ldquo;world readable&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Pour éviter de faire ça, on récupère le kube/config pour notre utilisateur &amp;ldquo;ubuntu&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Comme le binaire k3s embarque tout, dont kubectl, on va aussi devoir lui dire de ne pas utiliser ce fichier en forçant la variable d&amp;rsquo;environnement $KUBECONFIG.&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;$ mkdir ~/.kube
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo cp /etc/rancher/k3s/k3s.yaml ~/.kube/config
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ sudo chown ubuntu:ubuntu ~/.kube/config
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ chmod &lt;span class="m"&gt;600&lt;/span&gt; ~/.kube/config
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;export&lt;/span&gt; &lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;~/.kube/config
&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;export KUBECONFIG=~/.kube/config&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt; ~/.bashrc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;source &amp;lt;(kubectl completion bash)&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt; ~/.bashrc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;alias k=kubectl&amp;#39;&lt;/span&gt; &amp;gt;&amp;gt; ~/.bashrc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nb"&gt;source&lt;/span&gt; ~/.bashrc
&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 nodes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS ROLES AGE VERSION
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube01 NotReady control-plane,master 70s v1.27.4+k3s1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ici, kube01 est marqué NOTREADY. C&amp;rsquo;est normal car on a pas de CNI plugin (donc pas de réseau interne).&lt;/p&gt;
&lt;h2 id="ajout-dun-second-node"&gt;Ajout d&amp;rsquo;un second node
&lt;/h2&gt;&lt;p&gt;Sur le premier node, récupérez le token pour que le second puisse rejoindre le 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;cat /var/lib/rancher/k3s/server/node-token
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fois que c&amp;rsquo;est fait, connectez vous sur le node à rajouter au cluster, installez le kernel &lt;code&gt;hwe&lt;/code&gt;, rebootez&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sudo apt update
sudo apt upgrade
sudo apt install curl linux-image-generic-hwe-22.04 -y
sudo reboot
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Une fois que le node est à jour et prêt à rejoindre le cluster, lancez la commande suivante :&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;IP_MASTER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;IP.DE.VOTRE.MASTER
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;K3STOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;le:token:du:master
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -sfL https://get.k3s.io &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nv"&gt;K3S_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;IP_MASTER&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;:6443 &lt;span class="nv"&gt;K3S_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;K3STOKEN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; sh -
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Au bout de quelques secondes, votre node va apparaître dans la liste des nodes.&lt;/p&gt;
&lt;h2 id="cni-plugin--ingresscontroller"&gt;CNI plugin + ingressController
&lt;/h2&gt;&lt;p&gt;Nos nodes sont toujours en &amp;ldquo;Not Ready&amp;rdquo;. On installe helm et on ajoute le CNI plugin :&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 https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 &lt;span class="p"&gt;|&lt;/span&gt; bash
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ &lt;span class="nv"&gt;CILIUM_VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1.14.2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm repo add cilium https://helm.cilium.io/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ helm upgrade --install cilium cilium/cilium --version&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CILIUM_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&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; --set global.tag&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;v&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CILIUM_VERSION&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; --set global.containerRuntime.integration&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;containerd&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; --set global.containerRuntime.socketPath&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/var/run/k3s/containerd/containerd.sock&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; --set global.kubeProxyReplacement&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;strict&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; --set global.bpf.masquerade&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;true&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; --set ingressController.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&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; --set ingressController.default&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&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; --namespace cilium &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --create-namespace
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note: pour le fun, j&amp;rsquo;ai aussi ajouté le support de l&amp;rsquo;ingressController de cilium. Comme ça c&amp;rsquo;est fait :D.&lt;/p&gt;
&lt;p&gt;Au bout de quelques secondes, notre cluster devrait devenir opérationnel. On a aussi metric server (pour les stats de base).&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 STATUS RESTARTS AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system cilium-operator-86dbc96dd6-hlgtt 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 56s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system cilium-operator-86dbc96dd6-h7sq6 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 56s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system cilium-pxrrz 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 56s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system cilium-psjr4 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 56s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system svclb-cilium-ingress-c27a13c6-x7tld 2/2 Running &lt;span class="m"&gt;0&lt;/span&gt; 32s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system local-path-provisioner-957fdf8bc-5w9n9 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 6m9s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system coredns-77ccd57875-zlvpc 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 6m9s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system svclb-cilium-ingress-c27a13c6-8dbf6 2/2 Running &lt;span class="m"&gt;0&lt;/span&gt; 34s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system metrics-server-648b5df564-xr29p 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 6m9s
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Voilà pour ce très court article sur k3s et cilium.&lt;/p&gt;
&lt;p&gt;Ce setup, rapide à installer sur une install fraîche est ma base pour beaucoup d&amp;rsquo;expérimentations avec Kubernetes.&lt;/p&gt;</description></item></channel></rss>