<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Clever Cloud on Zwindler's Reflection</title><link>https://blog.zwindler.fr/tags/clever-cloud/</link><description>Recent content in Clever Cloud on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>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/clever-cloud/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>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>Créer un cluster Kubernetes sur Clever Cloud avec les apps Linux</title><link>https://blog.zwindler.fr/2025/07/20/kubernetes-sur-clever-cloud-linux/</link><pubDate>Sat, 19 Jul 2025 13:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/07/20/kubernetes-sur-clever-cloud-linux/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/07/k8s-clever.webp" alt="Featured image of post Créer un cluster Kubernetes sur Clever Cloud avec les apps Linux" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Après avoir testé les &lt;a class="link" href="https://blog.zwindler.fr/2025/07/07/je-reessaye-les-sites-statiques-chez-clever/" &gt;apps statiques chez Clever Cloud&lt;/a&gt;, j&amp;rsquo;ai voulu pousser plus loin l&amp;rsquo;exploration des nouvelles fonctionnalités.&lt;/p&gt;
&lt;p&gt;Clever Cloud a en effet lancé récemment un nouveau type d&amp;rsquo;application : les &lt;strong&gt;apps Linux&lt;/strong&gt;. Ces dernières permettent de déployer des applications génériques sur une machine Linux, sans runtime prédéfini. Un peu comme avoir sa propre VM, mais managée par Clever Cloud.&lt;/p&gt;
&lt;p&gt;Du coup, pour &amp;ldquo;rigoler&amp;rdquo; (et par pure curiosité technique), j&amp;rsquo;ai décidé de créer un &lt;strong&gt;control plane Kubernetes&lt;/strong&gt; complet sur ce type d&amp;rsquo;instance. Pourquoi ?&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;abord parce que je parle tout le temps de Kubernetes. Et ensuite, pour faire un petit clin d&amp;rsquo;œil aux copains de chez Clever Cloud : ils annoncent depuis des mois leur offre Kubernetes managée qui devrait sortir (soon™).&lt;/p&gt;
&lt;p&gt;Et en poussant les runtimes Linux dans leurs retranchements, ça m&amp;rsquo;a permis de découvrir pas mal de fonctionnalités que (j&amp;rsquo;avoue) je ne connaissais pas chez Clever.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Disclaimer&lt;/strong&gt; : ce projet est purement éducatif et expérimental. Ne l&amp;rsquo;utilisez pas, même pour un usage personnel !&lt;/p&gt;
&lt;h2 id="le-projet--k8s-on-clever-linux"&gt;Le projet : k8s-on-clever-linux
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai créé un dépôt GitHub dédié à ce projet : &lt;a class="link" href="https://github.com/zwindler/k8s-on-clever-linux" target="_blank" rel="noopener"
&gt;k8s-on-clever-linux&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le principe est simple : déployer tous les composants du control plane Kubernetes (etcd, kube-apiserver, kube-controller-manager, kube-scheduler) comme des processus sur une app Linux Clever Cloud.&lt;/p&gt;
&lt;p&gt;Le projet s&amp;rsquo;inspire fortement de mon tutoriel &lt;a class="link" href="https://github.com/zwindler/demystifions-kubernetes" target="_blank" rel="noopener"
&gt;demystifions-kubernetes&lt;/a&gt;. Et d&amp;rsquo;ailleurs, pour tester le &lt;em&gt;proof of concept&lt;/em&gt;, c&amp;rsquo;est ce que j&amp;rsquo;ai bêtement déroulé, pour valider la faisabilité.&lt;/p&gt;
&lt;p&gt;Cependant, dans le cas de Clever, plutôt que de lancer tous les composants un par un en déroulant un script bash (ce qui est fait dans github.com/zwindler/demystifions-kubernetes), j&amp;rsquo;ai fait un poil plus rusé&amp;hellip;&lt;/p&gt;
&lt;h2 id="découverte-de-mise"&gt;Découverte de Mise.
&lt;/h2&gt;&lt;p&gt;Mais d&amp;rsquo;abord un peu de contexte :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Clever Cloud platform provides a multi-runtime environment, including many tools to deploy and run your applications. The Linux runtime is a versatile solution to build and deploy any kind of application. The Mise package manager helps you to install and manage any supported dependencies Clever Cloud doesn’t provide by default such as Dart, Gleam, Zig for example.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.clever-cloud.com/developers/doc/applications/linux/" target="_blank" rel="noopener"
&gt;Documentation officielle des Linux runtimes chez Clever Cloud&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Déjà, ça parle d&amp;rsquo;un truc qui s&amp;rsquo;appelle &lt;strong&gt;Mise package manager&lt;/strong&gt;. C&amp;rsquo;est quoi ça ?&lt;/p&gt;
&lt;p&gt;Il s&amp;rsquo;agit d&amp;rsquo;un projet de &lt;em&gt;Jeff Dickey&lt;/em&gt; (&lt;a class="link" href="https://jdx.dev/" target="_blank" rel="noopener"
&gt;jdx.dev&lt;/a&gt;). Sur nos apps Linux, ça va nous permettre d&amp;rsquo;installer une partie des dépendances d&amp;rsquo;une part, et d&amp;rsquo;ordonnancer le lancement de commandes pour &lt;em&gt;build&lt;/em&gt;, puis pour &lt;em&gt;run&lt;/em&gt; notre projet dans l&amp;rsquo;instance Linux&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://mise.jdx.dev/dev-tools/" target="_blank" rel="noopener"
&gt;mise.jdx.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="découpage-de-lapplication"&gt;Découpage de l&amp;rsquo;application
&lt;/h2&gt;&lt;p&gt;Maintenant qu&amp;rsquo;on en sait plus, voilà comment j&amp;rsquo;ai découpé l&amp;rsquo;application avec Mise :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Initialisation (&lt;code&gt;mise.toml&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Même si tous les prérequis ne sont pas tous disponibles dans Mise, je peux quand même déléguer le téléchargement d&amp;rsquo;une partie d&amp;rsquo;entre eux, ce qui va alléger le script de téléchargement des dépendances par rapport à &lt;strong&gt;démystifions kubernetes&lt;/strong&gt;. C&amp;rsquo;est toujours bon à prendre :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;tools&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="nx"&gt;cfssl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;latest&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;etcd&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;latest&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;kubectl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;latest&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;helm&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;latest&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Phase de build (&lt;code&gt;.mise-tasks/build&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Télécharge tous les binaires Kubernetes&lt;/li&gt;
&lt;li&gt;Génère les certificats pour l&amp;rsquo;authentification des composants&lt;/li&gt;
&lt;li&gt;Configure les paramètres statiques&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mise en cache&lt;/strong&gt; : cette phase n&amp;rsquo;est exécutée que lors des changements de code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;De cette manière, en cas de reboot de l&amp;rsquo;application, on ne perd pas les certificats et on gagne un peu de temps pour le redéploiement.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Phase de run (&lt;code&gt;.mise-tasks/run&lt;/code&gt;)&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lancer tous les composants de notre control plane Kubernetes&lt;/li&gt;
&lt;li&gt;Génère un bootstrap token pour ajouter des Nodes&lt;/li&gt;
&lt;li&gt;Lance un serveur HTTP qui écoute sur le port 8080 et ne sert&amp;hellip; rien.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pourquoi un serveur HTTP ? Eh bien tout simplement parce que&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Linux runtime only requires a CC_RUN_COMMAND to execute, with a working web application listening on 0.0.0.0:8080.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Si je ne mets rien en écoute sur le port 8080, Clever va logguer que mon app est en panne (alors que non).&lt;/p&gt;
&lt;h2 id="focus-sur-mise-pour-installer-les-dépendances"&gt;Focus sur Mise pour installer les dépendances
&lt;/h2&gt;&lt;p&gt;Concrêtement, comment ça se présente quand on lance une app dans Clever Cloud avec un mise.toml ?&lt;/p&gt;
&lt;p&gt;Ben tout simplement comme ça :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2025-07-20T15:54:04.304Z mise etcd@3.6.2 install
2025-07-20T15:54:05.302Z mise etcd@3.6.2 download etcd-v3.6.2-linux-amd64.tar.gz
2025-07-20T15:54:06.403Z mise etcd@3.6.2 checksum etcd-v3.6.2-linux-amd64.tar.gz
2025-07-20T15:54:06.422Z mise etcd@3.6.2 extract etcd-v3.6.2-linux-amd64.tar.gz
2025-07-20T15:54:06.705Z mise etcd@3.6.2 ✓ installed
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;La liste des tools disponibles avec mise est ici :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://mise.jdx.dev/registry.html" target="_blank" rel="noopener"
&gt;mise.jdx.dev/registry&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et on peut remarquer que les tools viennent de différentes sources telles qu&amp;rsquo;aqua, pipx, ubi, asdf&amp;hellip; Ça me donne envie de tester ça pour les dépendances qui me manquent donc attendez-vous à un prochain article sur le sujet :).&lt;/p&gt;
&lt;h2 id="mais-ils-sont-où-les-binaires-du-control-plane-de-kubernetes-"&gt;Mais&amp;hellip; ils sont où les binaires du control plane de Kubernetes ?
&lt;/h2&gt;&lt;p&gt;Initialement, j&amp;rsquo;avais mis tout ça dans la phase de &lt;code&gt;run&lt;/code&gt;. Sauf que David m&amp;rsquo;a proposé de découvrir une autre fonctionnalité de Clever Cloud que je n&amp;rsquo;avais jamais utilisée : les Workers.&lt;/p&gt;
&lt;p&gt;Les &lt;strong&gt;Workers&lt;/strong&gt; sont des processus en arrière-plan qui tournent en parallèle de votre application principale.&lt;/p&gt;
&lt;p&gt;Pour notre cluster Kubernetes, c&amp;rsquo;est &lt;strong&gt;parfait&lt;/strong&gt; ! Plutôt que de lancer tous les composants dans un script bash, chaque composant du control plane devient un worker dédié :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Worker 0&lt;/strong&gt;: &lt;code&gt;./run-scripts/start-etcd.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Worker 1&lt;/strong&gt;: &lt;code&gt;./run-scripts/start-kube-apiserver.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Worker 2&lt;/strong&gt;: &lt;code&gt;./run-scripts/start-kube-controller-manager.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Worker 3&lt;/strong&gt;: &lt;code&gt;./run-scripts/start-kube-scheduler.sh&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Worker 4&lt;/strong&gt;: &lt;code&gt;./run-scripts/post-boot.sh&lt;/code&gt; - bootstrap tokens, RBAC, worker scripts&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avec en bonus la possibilité de configurer le comportement de redémarrage :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;CC_WORKER_RESTART=on-failure&lt;/code&gt; (par défaut) : redémarre seulement en cas d&amp;rsquo;erreur&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CC_WORKER_RESTART=always&lt;/code&gt; : redémarre toujours&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CC_WORKER_RESTART_DELAY=1&lt;/code&gt; : délai en secondes avant redémarrage&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="installation-et-configuration"&gt;Installation et configuration
&lt;/h2&gt;&lt;p&gt;Bon, clairement, cette partie n&amp;rsquo;est pas la plus user-friendly du tutoriel. Si vous voulez plus de détails sur ce qui est fait et pourquoi, vous pouvez aller jeter un œil directement sur le dépôt &lt;a class="link" href="https://github.com/zwindler/k8s-on-clever-linux" target="_blank" rel="noopener"
&gt;github.com/zwindler/k8s-on-clever-linux&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Les grandes étapes sont :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Créer l&amp;rsquo;app Linux &lt;code&gt;clever create --type linux --github zwindler/k8s-on-clever-linux&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configuration de la redirection TCP&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;Linux runtime only requires a CC_RUN_COMMAND to execute, with a working web application listening on 0.0.0.0:8080.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Pour rappel, Clever Cloud s&amp;rsquo;attend à ce qu&amp;rsquo;on expose une app en HTTP non sécurisé sur le port 8080. Sauf que moi, je veux exposer l&amp;rsquo;API server Kubernetes en HTTPS. On va donc activer la &amp;ldquo;redirection TCP&amp;rdquo; : Clever Cloud attribue un port aléatoire (dans les 5XXX) qui redirige vers le port 4040 de votre application (et donc je dois dire à l&amp;rsquo;API server de ne pas écouter sur le 6443, mais 4040) :&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 tcp-redirs add --namespace default
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Successfully added tcp redirection on port: &lt;span class="m"&gt;5131&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;Configuration du domaine&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une fois le port TCP connu, on peut configurer notre domaine.&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 domain add k8soncleverlinux.domain.org
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Dans mon cas, l&amp;rsquo;API server sera accessible sur le domaine &lt;code&gt;k8soncleverlinux.zwindler.fr&lt;/code&gt; et sur le port &lt;code&gt;5131&lt;/code&gt;. Comme ces deux valeurs seront différentes pour vous, j&amp;rsquo;ai vite codé un script qui permet de prendre les valeurs par défaut pour moi, mais vous permettre de les écraser si vous spécifiez des valeurs sur VOTRE app Clever Cloud :&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 env &lt;span class="nb"&gt;set&lt;/span&gt; K8S_DOMAIN k8soncleverlinux.zwindler.fr
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clever env &lt;span class="nb"&gt;set&lt;/span&gt; K8S_TCP_PORT &lt;span class="m"&gt;5131&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;Je suis vraiment sympa.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Configuration des workers : pour ça j&amp;rsquo;ai fait un script &lt;code&gt;./setup-clever-workers.sh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce script configure 5 workers dont j&amp;rsquo;ai parlé plus haut et qui sont gérés par systemd.&lt;/p&gt;
&lt;h2 id="déploiement-et-test"&gt;Déploiement et test
&lt;/h2&gt;&lt;p&gt;Si tout s&amp;rsquo;est bien passé, l&amp;rsquo;app devrait correctement se lancer, Mise devrait lancer la phase de &lt;code&gt;build&lt;/code&gt; (surtout télécharger les dépendances qui manquent), puis la phase de &lt;code&gt;run&lt;/code&gt; (les dépendances connues de Mise, et les workers une fois que &lt;code&gt;run&lt;/code&gt; se termine) :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2025-07-19T13:10:45.130Z Successfully deployed in 0 minutes and 18 seconds
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Les workers en tâche de fond devraient démarrer tous les composants de notre control plane Kubernetes. Au bout de quelques secondes (1 minute max), on devrait avoir un control plane fonctionnel, et tout un tas de certificats et autres secrets.&lt;/p&gt;
&lt;p&gt;Mais comment le tester ? Via SSH sur l&amp;rsquo;instance Clever Cloud ! Car oui, on peut SSH sur cette instance :)&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 ssh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Last login: Fri Jul &lt;span class="m"&gt;18&lt;/span&gt; 20:17:00 UTC &lt;span class="m"&gt;2025&lt;/span&gt; on pts/0
&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 version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Client Version: v1.33.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Kustomize Version: v5.6.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Server Version: v1.33.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;🎉 &lt;strong&gt;Ça marche !&lt;/strong&gt; On a bien un control plane Kubernetes qui tourne sur Clever Cloud !&lt;/p&gt;
&lt;p&gt;Et si vous avez bien travaillé (c&amp;rsquo;est à dire correctement configuré votre domaine et le port), on peut même l&amp;rsquo;utiliser depuis l&amp;rsquo;extérieur en copiant le fichier &lt;code&gt;admin.conf&lt;/code&gt; localement et en modifiant l&amp;rsquo;&lt;code&gt;endpoint&lt;/code&gt; dans le YAML :&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 cluster-info
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Kubernetes control plane is running at https://k8soncleverlinux.zwindler.fr:5131
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="ajout-dun-worker-node"&gt;Ajout d&amp;rsquo;un worker node
&lt;/h2&gt;&lt;p&gt;Deuxième hic dans mon histoire. Un control plane sans worker, c&amp;rsquo;est pas terrible.&lt;/p&gt;
&lt;p&gt;Et je ne peux pas utiliser l&amp;rsquo;app de type Linux de chez Clever car je n&amp;rsquo;ai pas assez de privilèges pour changer les options kernel (routage) et installer &lt;strong&gt;containerd&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Mais je n&amp;rsquo;allais pas en rester là. Dans mon projet &amp;ldquo;Démystifions Kubernetes&amp;rdquo;, j&amp;rsquo;installe un worker directement sur la machine du control plane et j&amp;rsquo;utilise le &lt;code&gt;admin.conf&lt;/code&gt; (cluster-admin) pour enrôler l&amp;rsquo;hôte courant en tant que Node.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est cracra, mais quand je suis en conf et que j&amp;rsquo;ai 20 minutes pour speedrun, c&amp;rsquo;est acceptable.&lt;/p&gt;
&lt;p&gt;Ici, j&amp;rsquo;ai fait l&amp;rsquo;effort d&amp;rsquo;aller regarder comment faire pour enrôler des Nodes avec un bootstrap token et c&amp;rsquo;est assez intéressant.&lt;/p&gt;
&lt;p&gt;La doc officielle de kube est plutôt bien faite et je ferai peut-être un article à part sur le sujet, c&amp;rsquo;est assez facile en fait, avec un flag à ajouter dans l&amp;rsquo;API server, quelques RBAC et un secret bien spécifique à générer.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/" target="_blank" rel="noopener"
&gt;https://kubernetes.io/docs/reference/access-authn-authz/bootstrap-tokens/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je vous épargne les détails, le projet va générer automatiquement un script pour ajouter des nodes externes :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;2025-07-19T13:51:47.848Z ✓ Worker setup script generated: setup-worker-node.sh
2025-07-19T13:51:47.849Z 📋 Next steps:
2025-07-19T13:51:47.849Z 1. Copy setup-worker-node.sh to your external worker node
2025-07-19T13:51:47.849Z 2. (Optional) Edit NODE_NAME_OVERRIDE and NODE_IP_OVERRIDE if auto-detection is incorrect
2025-07-19T13:51:47.849Z 3. Ensure your worker node has sudo privileges and internet access
2025-07-19T13:51:47.849Z 4. Run the script on your worker node: sudo ./setup-worker-node.sh
2025-07-19T13:51:47.849Z 5. Check the node joined with: kubectl get nodes
&lt;/code&gt;&lt;/pre&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;# Script généré automatiquement&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#!/bin/bash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# External Worker Node Setup Script for Kubernetes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;API_SERVER_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://k8soncleverlinux.zwindler.fr:5131&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;BOOTSTRAP_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;xxxxxx.xxxxxxxxxxxxxxxx&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ... (configuration complète et certificats/secrets inclus)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Il suffit de copier ce script sur une VM Linux quelconque et de l&amp;rsquo;exécuter. Le script :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installe containerd, kubelet, kube-proxy&lt;/li&gt;
&lt;li&gt;Configure automatiquement l&amp;rsquo;authentification via bootstrap token&lt;/li&gt;
&lt;li&gt;Démarre les services&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;✓ Worker node setup completed!
&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;coucou1 NotReady &amp;lt;none&amp;gt; 13m v1.33.3
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le node apparaît en &amp;ldquo;NotReady&amp;rdquo; car il manque le plugin CNI. Flannel fonctionne parfaitement :&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://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&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;coucou1 Ready &amp;lt;none&amp;gt; 30m v1.33.3
&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 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-flannel kube-flannel-ds-b65jg 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 14m
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le &lt;strong&gt;cluster est fonctionnel 😎😎😎 !&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;On a maintenant un cluster Kubernetes complet avec control plane sur Clever Cloud et worker externe&amp;hellip; Avant la sortie du kube managé de Clever.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/07/Trollface.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="limitations-et-évolutions-possibles"&gt;Limitations et évolutions possibles
&lt;/h2&gt;&lt;p&gt;Le worker externe n&amp;rsquo;étant pas dans le réseau Clever Cloud, l&amp;rsquo;API server ne peut pas communiquer avec le kubelet (dans le sens api-server -&amp;gt; kubelet uniquement). Cela limite les fonctionnalités comme &lt;code&gt;kubectl logs&lt;/code&gt;, &lt;code&gt;kubectl exec&lt;/code&gt;, etc. Une solution possible serait d&amp;rsquo;utiliser les &lt;a class="link" href="https://www.clever-cloud.com/developers/doc/develop/network-groups/" target="_blank" rel="noopener"
&gt;Network Groups&lt;/a&gt; de Clever Cloud pour créer un réseau privé sécurisé.&lt;/p&gt;
&lt;p&gt;Si ce projet continue de m&amp;rsquo;amuser, plusieurs améliorations sont envisageables :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;exploration des Network Groups (dont je viens de parler)&lt;/li&gt;
&lt;li&gt;intégration de &lt;a class="link" href="https://github.com/k3s-io/kine" target="_blank" rel="noopener"
&gt;Kine&lt;/a&gt;. Cela permettrait d&amp;rsquo;utiliser les bases de données existantes de Clever Cloud (PostgreSQL, MySQL) comme DB pour Kubernetes, ce qui permettrait éventuellement de rendre cette install &amp;ldquo;HA&amp;rdquo; ce qui serait aussi débile que rigolo&lt;/li&gt;
&lt;li&gt;création des packages aqua/ubi manquants (les binaires de kubernetes et &lt;code&gt;cfssljson&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai commencé ce projet comme une blague et j&amp;rsquo;ai finalement mis le doigt dans plein de petites fonctionnalités cools de chez Clever Cloud et dont j&amp;rsquo;ignorais l&amp;rsquo;existence (ou alors, que j&amp;rsquo;avais vu passer mais pas creusé).&lt;/p&gt;
&lt;p&gt;Même si ça m&amp;rsquo;a pris quelques soirées, c&amp;rsquo;était super intéressant. La première itération a été très vite fonctionnelle et je me suis pris au jeu des améliorations successives.&lt;/p&gt;
&lt;p&gt;Le code complet est disponible sur &lt;a class="link" href="https://github.com/zwindler/k8s-on-clever-linux" target="_blank" rel="noopener"
&gt;GitHub&lt;/a&gt; si vous voulez reproduire l&amp;rsquo;expérience ou contribuer !&lt;/p&gt;
&lt;p&gt;Et vous, quels projets fous avez-vous envie de tester avec les apps &lt;strong&gt;Linux&lt;/strong&gt; ? 😄 Moi, j&amp;rsquo;attends mon accès au k8s managé de Clever Cloud maintenant 😝 (comment ça, je suis un gros &lt;em&gt;forceur&lt;/em&gt; ???)&lt;/p&gt;</description></item><item><title>Je réessaye les sites statiques (Bloggrify) chez Clever Cloud</title><link>https://blog.zwindler.fr/2025/07/07/je-reessaye-les-sites-statiques-chez-clever/</link><pubDate>Mon, 07 Jul 2025 09:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/07/07/je-reessaye-les-sites-statiques-chez-clever/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/07/9zdbra.webp" alt="Featured image of post Je réessaye les sites statiques (Bloggrify) chez Clever Cloud" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Pour celles et ceux qui suivent mes péripéties d&amp;rsquo;hébergement de blog, vous savez que j&amp;rsquo;ai une relation compliquée (le fameux &amp;ldquo;it&amp;rsquo;s complicated&amp;rdquo; sur Facebook) avec &lt;a class="link" href="https://www.clever-cloud.com/fr/" target="_blank" rel="noopener"
&gt;Clever Cloud&lt;/a&gt;. J&amp;rsquo;ai déjà écrit à ce sujet dans deux articles précédents :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2023/12/30/its-migration-day-again/" &gt;Migration du blog sur Clever Cloud&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2024/01/29/planifier-les-posts-clever-cloud/" &gt;Planifier les posts avec Clever Cloud&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour finalement abandonner Clever Cloud et revenir sur une VM IONOS que je gère moi-même (voir &lt;a class="link" href="https://blog.zwindler.fr/2025/01/15/ca-bouge-encore-sur-le-blog/" &gt;Ça bouge encore sur le blog&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Mais depuis, Clever Cloud a ajouté les &lt;strong&gt;apps statiques&lt;/strong&gt; à son catalogue. Plus besoin de passer par un runtime Apache + PHP, ce qui était effectivement un peu overkill pour un site Hugo statique (et pas compatible avec l&amp;rsquo;offre Pico, donc fallait prendre au minimum Nano à ~7€ par mois).&lt;/p&gt;
&lt;p&gt;Du coup, je me suis dit : pourquoi ne pas retenter l&amp;rsquo;expérience ?&lt;/p&gt;
&lt;p&gt;Comme Julien Wittouck m&amp;rsquo;a devancé de 3 semaines et a fait un article sur l&amp;rsquo;hébergement de sites statiques avec Hugo via ce nouveau type d&amp;rsquo;app chez Clever Cloud, &lt;a class="link" href="https://codeka.io/2025/06/05/d%C3%A9ployer-des-applications-statiques-sur-clever-cloud/" target="_blank" rel="noopener"
&gt;vous pouvez aller lire son post ici&lt;/a&gt;, je n&amp;rsquo;ai pas envie de faire la même chose.&lt;/p&gt;
&lt;p&gt;Pour ce test, j&amp;rsquo;ai choisi d&amp;rsquo;utiliser un autre site que celui-ci : &lt;strong&gt;50ndk.zwindler.fr&lt;/strong&gt;, qui me sert pour faire la promotion de mon livre.&lt;/p&gt;
&lt;p&gt;Il est actuellement hébergé sur GitHub Pages et utilise le moteur &lt;a class="link" href="https://bloggrify.io/" target="_blank" rel="noopener"
&gt;Bloggrify&lt;/a&gt; (un des projets d&amp;rsquo;&lt;a class="link" href="https://eventuallycoding.com/" target="_blank" rel="noopener"
&gt;Hugo Lassiège&lt;/a&gt;, quelqu&amp;rsquo;un que j&amp;rsquo;apprécie énormément dans l&amp;rsquo;écosystème tech français).&lt;/p&gt;
&lt;p&gt;Le principe est globalement le même qu&amp;rsquo;avec Hugo, à quelques petites différences près. C&amp;rsquo;est un site statique généré tout pareil, mais c&amp;rsquo;est Nuxt sous le capot et ça va nous être utile. Il y a quelques petites différences aussi depuis mon dernier test de 2023 que je ne manquerai pas de souligner.&lt;/p&gt;
&lt;p&gt;Les sources du site sont disponibles ici si vous êtes curieux / curieuse :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/zwindler/50ndk" target="_blank" rel="noopener"
&gt;https://github.com/zwindler/50ndk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;p&gt;On pourrait aller créer l&amp;rsquo;application dans l&amp;rsquo;UI. Normalement, les &amp;ldquo;tuiles&amp;rdquo; Static, Linux et VLang sont disponibles à tout le monde depuis le 4 juillet. J&amp;rsquo;ai un petit accès anticipé (merci David) mais je n&amp;rsquo;en ai pas beaucoup profité 🙃 (occupé avec le livre). Grosso modo c&amp;rsquo;est comme les autres types d&amp;rsquo;Apps chez Clever, vous ne serez pas perdus.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/06/cleverl-nouvelles-tuiles.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Bon, en WebUI c&amp;rsquo;est bien mais c&amp;rsquo;est plus rigolo de le faire en CLI.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;clever login
Opening https://console.clever-cloud.com/cli-oauth?cli_version=3.0.2&amp;amp;cli_token=xxxxxxxxxxxxx in your browser to log you in…
Login successful as Denis GERMAIN &amp;lt;denis@domain.org&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/06/clever-login.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Euh, je vois 3.0.2 dans l&amp;rsquo;URL ??? Je ne suis pas à jour. Visiblement j&amp;rsquo;avais installé la CLI clever avec npm (je ne m&amp;rsquo;en souviens pas). Plus simple, &lt;a class="link" href="https://www.clever-cloud.com/developers/doc/cli/install/" target="_blank" rel="noopener"
&gt;il y a aussi des dépôts .deb (ou brew, autre selon votre distribution)&lt;/a&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;clever version
3.0.2
╭─────────────────────────────────────────╮
│ │
│ Update available 3.0.2 → 3.13.1 │
│ Run npm i -g clever-tools to update │
│ │
╰─────────────────────────────────────────╯
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Un petit &lt;code&gt;npm i -g clever-tools&lt;/code&gt; et ça va mieux :)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ clever version
3.13.1
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="configuration-de-lapp"&gt;Configuration de l&amp;rsquo;app
&lt;/h2&gt;&lt;p&gt;Un petit &lt;code&gt;clever create&lt;/code&gt; avec le type de l&amp;rsquo;app et c&amp;rsquo;est parti !&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;denis@coucou % clever create --type static
✓ Application 50ndk successfully created!
Type ⬢ Static
ID app_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
Org ID user_yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
Name 50ndk
Zone par
Next steps:
! Commit your changes first:
$ git add .
$ git commit -m &amp;#34;My changes&amp;#34;
→ Run clever deploy to deploy your application
→ Manage your application at: https://console.clever-cloud.com/goto/app_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note : si votre app est hébergée sur GitHub, vous pouvez rajouter aussi &lt;code&gt;--github OWNER/REPO&lt;/code&gt; à la ligne de commande précédente. J&amp;rsquo;en parle plus tard, mais lisez tout avant de le faire.&lt;/p&gt;
&lt;p&gt;Comme fin 2023, je vais avoir besoin de 2 machines différentes. Une qui va construire mon site statique, une qui va le servir. Et du coup, là comme je n&amp;rsquo;ai pas Apache, je peux avoir accès à une pico et économiser quelques euros à la fin de l&amp;rsquo;année :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ clever scale --build-flavor M
App rescaled successfully
$ clever scale --flavor pico
App rescaled successfully
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans mon test précédent avec Hugo+Clever, j&amp;rsquo;avais besoin de spécifier dans quel dossier le contenu statique doit être servi (et aussi quel dossier la machine qui construit doit partager avec la machine qui sert).&lt;/p&gt;
&lt;p&gt;Dans le cas de Bloggrify, tout est dans .output/public. MAIS comme c&amp;rsquo;est du Nuxt, David m&amp;rsquo;a dit que je n&amp;rsquo;en avais pas besoin parce que Clever détecte automatiquement que c&amp;rsquo;est du Nuxt grâce au fichier &lt;code&gt;nuxt.config.ts&lt;/code&gt; à la racine du projet.&lt;/p&gt;
&lt;p&gt;Inutile donc de spécifier les variables CC_WEBROOT, CC_OVERRIDE_BUILDCACHE (pour pointer sur &lt;code&gt;.output/public&lt;/code&gt;) CC_PRE_BUILD_HOOK et CC_BUILD_COMMAND (les commandes &lt;code&gt;npm&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;Plutôt une bonne surprise :).&lt;/p&gt;
&lt;h2 id="déploiement-de-lapplication"&gt;Déploiement de l&amp;rsquo;application
&lt;/h2&gt;&lt;p&gt;À partir de là, on peut essayer de faire un premier déploiement à la main. Pour rappel, soit on envoie un commit sur le remote spécial créé par Clever (cf le message lors de la commande &lt;code&gt;clever create&lt;/code&gt;), mais je vais préférer utiliser la commande &lt;code&gt;clever deploy&lt;/code&gt;, déjà disponible en 2023 lors de mon premier test, car je l&amp;rsquo;utiliserai pour automatiser le workflow plus tard.&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;dgermain@dgermain-mac 50ndk % clever deploy
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;🚀 Deploying 50ndk
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Application ID app_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Org ID user_yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy
&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;🔀 Git information
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ! App is brand new, no commits on remote yet
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Local commit aaaaaa &lt;span class="o"&gt;[&lt;/span&gt;will be deployed&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;La première partie du processus va donc lancer une machine de taille M dans le but d&amp;rsquo;accélérer un peu le temps de construction :&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;🔄 Deployment progress
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Pushing &lt;span class="nb"&gt;source&lt;/span&gt; code to Clever Cloud…
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ✓ Code pushed to Clever Cloud
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Waiting &lt;span class="k"&gt;for&lt;/span&gt; deployment to start…
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ✓ Deployment started &lt;span class="o"&gt;(&lt;/span&gt;deployment_zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → Waiting &lt;span class="k"&gt;for&lt;/span&gt; application logs…
&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;Clever va détecter tout seul que j&amp;rsquo;ai du Nuxt, lancer un &lt;code&gt;npm install&lt;/code&gt; pour installer les prérequis pour Bloggrify, puis le &lt;code&gt;npm run generate&lt;/code&gt; pour générer le code HTML statique.&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;2025-07-07T10:23:47.847Z Deploying commit ID defc0be02d44ace246127e11a17d4f359262ccfb
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:23:47.847Z Nuxt.js configuration file detected
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:23:47.847Z Running build command: npm i &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; npm run generate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; mv .output/public cc_static_autobuilt
&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;2025-07-07T10:24:48.809Z &lt;span class="o"&gt;[&lt;/span&gt;nitro&lt;span class="o"&gt;]&lt;/span&gt; ℹ Initializing prerenderer
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:52.533Z &lt;span class="o"&gt;[&lt;/span&gt;nitro&lt;span class="o"&gt;]&lt;/span&gt; ℹ Prerendering &lt;span class="m"&gt;8&lt;/span&gt; initial routes with crawler
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:52.598Z &lt;span class="o"&gt;[&lt;/span&gt;nitro&lt;span class="o"&gt;]&lt;/span&gt; ├─ /robots.txt &lt;span class="o"&gt;(&lt;/span&gt;17ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:52.841Z &lt;span class="o"&gt;[&lt;/span&gt;nitro&lt;span class="o"&gt;]&lt;/span&gt; ├─ /200.html &lt;span class="o"&gt;(&lt;/span&gt;296ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:52.842Z &lt;span class="o"&gt;[&lt;/span&gt;nitro&lt;span class="o"&gt;]&lt;/span&gt; ├─ /404.html &lt;span class="o"&gt;(&lt;/span&gt;297ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:52.964Z &lt;span class="o"&gt;[&lt;/span&gt;nitro&lt;span class="o"&gt;]&lt;/span&gt; ├─ /api/search &lt;span class="o"&gt;(&lt;/span&gt;415ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:52.965Z &lt;span class="o"&gt;[&lt;/span&gt;nitro&lt;span class="o"&gt;]&lt;/span&gt; ├─ /sitemap.xml &lt;span class="o"&gt;(&lt;/span&gt;417ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:52.965Z &lt;span class="o"&gt;[&lt;/span&gt;nitro&lt;span class="o"&gt;]&lt;/span&gt; ├─ /rss.xml &lt;span class="o"&gt;(&lt;/span&gt;416ms&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:53.105Z &lt;span class="o"&gt;[&lt;/span&gt;nitro&lt;span class="o"&gt;]&lt;/span&gt; ├─ / &lt;span class="o"&gt;(&lt;/span&gt;564ms&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;Une fois le generate terminé, la machine de construction génère un artefact contenant notre site, et passe la main à la machine qui va servir le trafic :&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;2025-07-07T10:24:55.843Z ✔ You can now deploy .output/public to any static hosting!
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:56.137Z Creating build cache archive…
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:56.197Z build cache archive successfully created
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:56.197Z No cron to setup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:56.213Z Uploading application build cache archive… file is 21M before compression.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:56.899Z 2025-07-07T10:24:56.899385Z INFO multipart_upload_lib::uploader: Uploaded &lt;span class="nv"&gt;part&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&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;2025-07-07T10:24:57.041Z 2025-07-07T10:24:57.041879Z INFO multipart_upload_lib::uploader: Completed the multipart upload
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:57.350Z Done uploading build cache archive
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:24:57.350Z Build succeeded in &lt;span class="m"&gt;1&lt;/span&gt; minute and &lt;span class="m"&gt;14&lt;/span&gt; seconds
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;11 secondes plus tard, le site de promotion de mon livre est déployé sur Clever Cloud&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;2025-07-07T10:25:28.425Z Serving static website from /cc_static_autobuilt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:25:28.425Z Launching &lt;span class="s1"&gt;&amp;#39;static-web-server&amp;#39;&lt;/span&gt; on port &lt;span class="m"&gt;8080&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:25:28.425Z No cron to setup
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2025-07-07T10:25:28.425Z Successfully deployed in &lt;span class="m"&gt;0&lt;/span&gt; minutes and &lt;span class="m"&gt;10&lt;/span&gt; seconds
&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;✓ Access your application: https://app-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx.cleverapps.io
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;→ Manage your application: https://console.clever-cloud.com/goto/app_xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
&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/07/preview.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="ajouter-un-domaine"&gt;Ajouter un domaine
&lt;/h2&gt;&lt;p&gt;Comme chez tous les PaaS, il est évidemment possible d&amp;rsquo;ajouter un domaine de manière à ce que les requêtes entrantes n&amp;rsquo;aient pas besoin de se faire sur l&amp;rsquo;URL en cleverapps.io&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;% clever domain add 50ndk.zwindler.fr
Your domain has been successfully saved
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Une fois le FQDN associé, il est possible d&amp;rsquo;ajouter un CNAME chez votre gestionnaire de nom de domaine :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/07/cname.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="automatiser-le-clever-deploy"&gt;Automatiser le &lt;code&gt;clever deploy&lt;/code&gt;
&lt;/h2&gt;&lt;p&gt;Imaginons que je sois un fainéant ou que je n&amp;rsquo;aie pas envie d&amp;rsquo;installer clever CLI sur tous les postes où je travaille. Admettons que j&amp;rsquo;aie envie qu&amp;rsquo;une nouvelle version de mon site soit automatiquement déployée dès que je pousse un commit sur GitHub.&lt;/p&gt;
&lt;h3 id="deploy-to-clever-cloud"&gt;deploy-to-clever-cloud
&lt;/h3&gt;&lt;p&gt;Dans l&amp;rsquo;article &lt;a class="link" href="https://blog.zwindler.fr/2024/01/29/planifier-les-posts-clever-cloud" &gt;Planifier les posts de mon blog Hugo sur Clever Cloud&lt;/a&gt;, j&amp;rsquo;avais exploré la piste d&amp;rsquo;un Cron pour redéployer régulièrement mon site, notamment pour que les posts publiés dans le futur soient postés &amp;ldquo;au bon moment&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;avais aussi trouvé une GitHub Action tierce de quelqu&amp;rsquo;un (&lt;a class="link" href="https://github.com/47ng" target="_blank" rel="noopener"
&gt;47ng&lt;/a&gt;) qui a l&amp;rsquo;air très bien mais que je ne connais pas :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/marketplace/actions/deploy-to-clever-cloud" target="_blank" rel="noopener"
&gt;https://github.com/marketplace/actions/deploy-to-clever-cloud&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;J&amp;rsquo;ai testé, elle fonctionne, mais j&amp;rsquo;étais moyen chaud à l&amp;rsquo;époque de déléguer mes creds à Github (les fameuses variables CLEVER_TOKEN et CLEVER_SECRET affichées lors du &lt;code&gt;clever login&lt;/code&gt;), qui en retour les aurait injecté à cette &amp;ldquo;action&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Et ben ça tombe bien parce qu&amp;rsquo;il y a deux nouvelles pour répondre à ces problématiques.&lt;/p&gt;
&lt;h3 id="preview-par-pr"&gt;preview par PR
&lt;/h3&gt;&lt;p&gt;La première, c&amp;rsquo;est qu&amp;rsquo;il existe plusieurs façons d&amp;rsquo;automatiser les déploiements avec Clever Cloud. Je ne sais pas si elle existait à l&amp;rsquo;époque, mais en tout cas je l&amp;rsquo;ai trouvée aujourd&amp;rsquo;hui ;-P.&lt;/p&gt;
&lt;p&gt;On peut faire des environnements de preview par PR (depuis 2024) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.clever-cloud.com/developers/doc/ci-cd/github/" target="_blank" rel="noopener"
&gt;https://www.clever-cloud.com/developers/doc/ci-cd/github/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/marketplace/actions/clever-cloud-review-app-on-prs" target="_blank" rel="noopener"
&gt;https://github.com/marketplace/actions/clever-cloud-review-app-on-prs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je ne vais pas dans cet article aborder cette action qui permet de déployer des apps en préview &lt;strong&gt;par PR&lt;/strong&gt; car je n&amp;rsquo;en ai pas du tout le besoin pour ce site secondaire. Cela dit, si jamais j&amp;rsquo;ai une grosse mise à jour avec breaking changes, ça pourra valoir le coup de jeter un œil.&lt;/p&gt;
&lt;h3 id="créer-des-tokens-de-ci"&gt;Créer des tokens de CI
&lt;/h3&gt;&lt;p&gt;La seconde, c&amp;rsquo;est qu&amp;rsquo;il est possible de créer des tokens (là encore, je ne sais plus si c&amp;rsquo;était possible en 2023), qu&amp;rsquo;il sera possible de révoquer en cas de leak.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.clever-cloud.com/developers/api/howto/#request-the-api" target="_blank" rel="noopener"
&gt;https://www.clever-cloud.com/developers/api/howto/#request-the-api&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Et avec ces tokens, on peut se faire sa propre CI :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.clever-cloud.com/developers/doc/ci-cd/custom-scripts/" target="_blank" rel="noopener"
&gt;https://www.clever-cloud.com/developers/doc/ci-cd/custom-scripts/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note : j&amp;rsquo;ai cependant l&amp;rsquo;impression qu&amp;rsquo;il n&amp;rsquo;y a pas encore de scope sur le token. Donc en termes de sécu, c&amp;rsquo;est mieux car je peux les révoquer, mais s&amp;rsquo;il y a un leak, on a accès à tout mon compte, pour l&amp;rsquo;instant (sauf si j&amp;rsquo;ai mal compris).&lt;/p&gt;
&lt;h3 id="déploiements-depuis-github-ou-gitlab"&gt;Déploiements depuis GitHub (ou GitLab)
&lt;/h3&gt;&lt;p&gt;Mais le plus simple ici, c&amp;rsquo;est d&amp;rsquo;utiliser une fonctionnalité de Clever qui existe depuis &amp;ldquo;toujours&amp;rdquo; mais dont j&amp;rsquo;ignorais l&amp;rsquo;existance : les déploiements via Github.&lt;/p&gt;
&lt;p&gt;Si vous voulez utiliser cette fonctionnalité, il faudra par contre autoriser Clever Cloud à lister vos dépôts préalablement (ce qui peut être un problème pour vous), et vous ne pourrez plus pousser des commits avec &lt;code&gt;clever deploy&lt;/code&gt; ou &lt;code&gt;git push&lt;/code&gt; sur le git remote de clever (vous prendrez une erreur 401).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Clever Cloud provides a GitHub integration to deploy any repository hosted on GitHub to Clever Cloud&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On configure ça grâce au &lt;code&gt;--github&lt;/code&gt; de tout à l&amp;rsquo;heure !&lt;/p&gt;
&lt;p&gt;A partir du moment où c&amp;rsquo;est fait, il n&amp;rsquo;y a rien à faire&amp;hellip; le rebuild sera déclenché dès que je pousserai un nouveau commit sur ma branche Github :)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;commit 788465fb980c09e597872a3b510ac44fdaa27d48 (HEAD -&amp;gt; main, origin/main, origin/HEAD)
Author: Denis GERMAIN &amp;lt;denis@example.com&amp;gt;
Date: Fri Jul 4 16:31:11 2025 +0200
test commit
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/07/test-commit.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/07/link.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/07/authorize.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Note : on peut spécifier quelle branche on veut envoyer en prod dans les paramètres de l&amp;rsquo;application :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/07/github-main.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Clever Cloud a énormément bougé ces derniers mois, avec beaucoup de nouvelles apps, de nouvelles fonctionnalités (la plus grosse étant Materia, ainsi que tout le travail autour de l&amp;rsquo;IA).&lt;/p&gt;
&lt;p&gt;Les static apps (et Linux, et V) pourraient presque paraître anecdotiques, mais couplées aux autres améliorations (tokens, Grafana managé avec les stats), ce n&amp;rsquo;est pas le même produit qu&amp;rsquo;en 2023.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est cool à voir :)&lt;/p&gt;</description></item><item><title>Planifier les posts de mon blog Hugo sur Clever Cloud</title><link>https://blog.zwindler.fr/2024/01/29/planifier-les-posts-clever-cloud/</link><pubDate>Mon, 29 Jan 2024 06:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2024/01/29/planifier-les-posts-clever-cloud/</guid><description>&lt;img src="https://blog.zwindler.fr/2022/05/clever-trott.webp" alt="Featured image of post Planifier les posts de mon blog Hugo sur Clever Cloud" /&gt;&lt;h2 id="suite-de-la-migration"&gt;Suite de la migration
&lt;/h2&gt;&lt;p&gt;Fin décembre, &lt;a class="link" href="https://blog.zwindler.fr/2023/12/30/its-migration-day-again" &gt;j&amp;rsquo;ai migré ce blog sur Clever Cloud&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Cependant, David Legrand m&amp;rsquo;a fait remarquer que je n&amp;rsquo;avais plus de méthode pour planifier mes posts.&lt;/p&gt;
&lt;p&gt;Depuis quelque temps, je poste à flux tendu donc ça ne m&amp;rsquo;a pas encore gêné (j&amp;rsquo;écris un post, je le rends public dans la foulée) mais c&amp;rsquo;est vrai qu&amp;rsquo;il me manque cette possibilité par rapport à précédent mon hébergement (une VM).&lt;/p&gt;
&lt;p&gt;Cependant, il existe une solution sur Clever Cloud pour le faire. David en a d&amp;rsquo;ailleurs écrit un petit article sur son blog perso, adapté pour &amp;ldquo;Astro&amp;rdquo; son générateur de site statique.&lt;/p&gt;
&lt;p&gt;Je vais donc faire pareil pour mon propre blog avec Hugo.&lt;/p&gt;
&lt;h2 id="adaptations"&gt;Adaptations
&lt;/h2&gt;&lt;p&gt;Repartons donc d&amp;rsquo;où j&amp;rsquo;en étais au post précédent. Nous avons un blog avec Hugo qui se déploie quand on fait des &lt;code&gt;clever deploy&lt;/code&gt; (ou des commits sur le &lt;em&gt;git remote&lt;/em&gt; de clever).&lt;/p&gt;
&lt;p&gt;La première chose qu&amp;rsquo;on va devoir faire, c&amp;rsquo;est récupérer un token. En effet, c&amp;rsquo;est notre blog qui va déclencher de lui même les redéploiements, on va donc devoir lui donner de quoi s&amp;rsquo;authentifier.&lt;/p&gt;
&lt;p&gt;Si vous n&amp;rsquo;avez pas de token sous la main, on peut en retrouver un en faisant &lt;code&gt;clever login&lt;/code&gt; depuis un terminal, comme je l&amp;rsquo;expliquais dans l&amp;rsquo;article précédent.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/12/clever-login.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Et on va rajouter le TOKEN dans la liste des variables d&amp;rsquo;environnement de notre app :&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 env &lt;span class="nb"&gt;set&lt;/span&gt; CC_SECRET &lt;span class="s2"&gt;&amp;#34;aaaaaaaaaaaaaaaaaaaaaaaaaa&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ clever env &lt;span class="nb"&gt;set&lt;/span&gt; CC_TOKEN &lt;span class="s2"&gt;&amp;#34;aaaaaaaaaaaaaaaaaaaaaaaaaa&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;A partir de là on va créer plusieurs fichiers. D&amp;rsquo;abord, un script qui va déclencher ce fameux redéploiement :&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; clever_rebuild.sh &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;#!/bin/bash -l
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;clever link ${APP_ID}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;clever restart --quiet --without-cache
&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;chmod +x clever_rebuild.sh
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ensuite, je vais créer un fichier qui sera lu par clever-cloud et qui va déclencher ce redéploiement aux heures où je publie mes posts planifiés (le matin, un peu avant 9h) :&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 clevercloud
&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;[&amp;#34;00 08 * * * $ROOT/clever_rebuild.sh&amp;#34;]&amp;#39;&lt;/span&gt; &amp;gt; clevercloud/cron.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Attention aux petites blaguounettes de timezone. Comme tous sysadmins qui se respectent, les gens de clever utilisent le temps UTC sur leurs serveurs (moi aussi). Cependant, ça nécessite une petite gymnastique intellectuelle si jamais vous n&amp;rsquo;utilisez pas aussi le temps UTC dans votre tête pour planifier les posts ;-P.&lt;/p&gt;
&lt;p&gt;Si tout se passe bien, vous verrez qu&amp;rsquo;il remarque qu&amp;rsquo;il y a un cron dans les logs :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;2024-01-25T14:05:21+01:00 Importing cronjobs
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;2024-01-25T14:05:21+01:00 Starting crons setup
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;2024-01-25T14:06:05+01:00 (CRON) INFO (running with inotify support)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;2024-01-25T14:06:05+01:00 (CRON) INFO (RANDOM_DELAY will be scaled with factor 89% if used.)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;2024-01-25T14:06:05+01:00 (CRON) STARTUP (1.7.0)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;[...]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tout dernier point que j&amp;rsquo;ai éludé jusqu&amp;rsquo;à présent, il est possible de redémarrer une application à partir d&amp;rsquo;un cache (pour que ça aille plus vite). Cependant, il est nécessaire d&amp;rsquo;indiquer à clever cloud quoi mettre dans ce cache. On va donc ajouter &amp;ldquo;/clevercloud/cron.json:/clever_rebuild.sh&amp;rdquo; à la variable existante :&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;# précédemment CC_OVERRIDE_BUILDCACHE &amp;#34;/public&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ clever env &lt;span class="nb"&gt;set&lt;/span&gt; CC_OVERRIDE_BUILDCACHE &lt;span class="s2"&gt;&amp;#34;/public:/clevercloud/cron.json:/clever_rebuild.sh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="fail-to-hack-the-template"&gt;(Fail to) hack the template
&lt;/h2&gt;&lt;p&gt;Si Hugo a bien un flag pour permettre de &amp;ldquo;builder&amp;rdquo; les posts dont la date de sortie est postérieure à la date actuelle (&lt;code&gt;buildFuture&lt;/code&gt;), mon thème &amp;ldquo;Stack&amp;rdquo; les affiche par défaut dans la liste des derniers posts ainsi que dans le flux RSS.&lt;/p&gt;
&lt;p&gt;La solution la plus simple à ce problème est de laisser les paramètres par défaut, et d&amp;rsquo;attendre que la date de publication soit dépassée (idéalement juste avant le passage d&amp;rsquo;un cron) et le tour est joué.&lt;/p&gt;
&lt;p&gt;Mais David a eu une autre approche qui m&amp;rsquo;a bien plu : ajouter une condition dans le template pour que le post ne soit pas visible dans la liste des posts, mais quand même publié.&lt;/p&gt;
&lt;p&gt;Avantage : on peut consulter l&amp;rsquo;article à paraître, si on a le lien. Il se rajoute à la liste des articles de la homepage de lui-même lors du prochain rebuild, une fois la date dépassée.&lt;/p&gt;
&lt;p&gt;Dans mon thème, la page d&amp;rsquo;accueil qui liste les posts &lt;a class="link" href="https://github.com/CaiJimmy/hugo-theme-stack/blob/master/layouts/index.html" target="_blank" rel="noopener"
&gt;est définie ici dans le code&lt;/a&gt; :&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; {{ define &amp;#34;main&amp;#34; }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ $pages := where .Site.RegularPages &amp;#34;Type&amp;#34; &amp;#34;in&amp;#34; .Site.Params.mainSections }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ $notHidden := where .Site.RegularPages &amp;#34;Params.hidden&amp;#34; &amp;#34;!=&amp;#34; true }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ {{ $notFuture := where .Site.RegularPages &amp;#34;.Date&amp;#34; &amp;#34;le&amp;#34; now }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gi"&gt;+ {{ $filtered := ($pages | intersect $notHidden | intersect $notFuture) }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gd"&gt;- {{ $filtered := ($pages | intersect $notHidden) }}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ $pag := .Paginate ($filtered) }}
&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; &amp;lt;section class=&amp;#34;article-list&amp;#34;&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ range $index, $element := $pag.Pages }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ partial &amp;#34;article-list/default&amp;#34; . }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ end }}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;/section&amp;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; {{- partial &amp;#34;pagination.html&amp;#34; . -}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{- partial &amp;#34;footer/footer&amp;#34; . -}}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {{ end }}
&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;En théorie, ça fonctionne. Sauf que je n&amp;rsquo;ai pas réussi à le faire marcher avec le thème &amp;ldquo;Stack&amp;rdquo; que j&amp;rsquo;utilise sur le site (j&amp;rsquo;ai réussi avec un thème &amp;ldquo;blank&amp;rdquo;).&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est peut-être lié à &lt;a class="link" href="https://github.com/gohugoio/hugo/issues/11916#issuecomment-1910462590" target="_blank" rel="noopener"
&gt;un problème d&amp;rsquo;appel multiple à la fonction &lt;code&gt;.Paginate()&lt;/code&gt;&lt;/a&gt;, qui &amp;ldquo;cache&amp;rdquo; la pagination la première fois qu&amp;rsquo;elle est lancée.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;If you call .Paginator or .Paginate multiple times on the same page, you should ensure all the calls are identical. Once either .Paginator or .Paginate is called while generating a page, its result is cached, and any subsequent similar call will reuse the cached result. This means that any such calls which do not match the first one will not behave as written.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://gohugo.io/templates/pagination/" target="_blank" rel="noopener"
&gt;https://gohugo.io/templates/pagination/&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Même en partant du principe que j&amp;rsquo;arrive à le faire marcher, reste encore la problématique du flux RSS, comme j&amp;rsquo;ai pu le remarquer avec Seboss666 ;-)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2024/01/seboss666.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai donc laissé tomber cette idée. De toute façon, je n&amp;rsquo;avais jamais vraiment eu besoin jusqu&amp;rsquo;à présent puisque je n&amp;rsquo;y avais pas pensé ;-P.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai donc, comme avant, mes posts &amp;ldquo;planifiés&amp;rdquo; qui sont visible le matin de leur publication et c&amp;rsquo;est déjà bien comme ça (pour l&amp;rsquo;instant).&lt;/p&gt;
&lt;p&gt;Have fun !&lt;/p&gt;</description></item><item><title>Migration du blog sur Clever Cloud</title><link>https://blog.zwindler.fr/2023/12/30/its-migration-day-again/</link><pubDate>Sat, 30 Dec 2023 16:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2023/12/30/its-migration-day-again/</guid><description>&lt;img src="https://blog.zwindler.fr/2022/05/clever-trott.webp" alt="Featured image of post Migration du blog sur Clever Cloud" /&gt;&lt;h2 id="its-groundhog-migration-day-again"&gt;It’s &lt;del&gt;groundhog&lt;/del&gt; migration day, again
&lt;/h2&gt;&lt;p&gt;Tous les 6 mois environ, une mouche me pique.&lt;/p&gt;
&lt;p&gt;Après avoir migré un nombre incalculable de fois mon blog wordpress à droite / à gauche, puis &lt;a class="link" href="https://blog.zwindler.fr/2019/06/10/comment-migrer-de-wordpress-a-hugo/" &gt;migré sur Hugo&lt;/a&gt;, autohébergé, utilisé du managé chez &lt;a class="link" href="https://vercel.com/" target="_blank" rel="noopener"
&gt;vercel&lt;/a&gt;, chez &lt;a class="link" href="https://froggit.fr/" target="_blank" rel="noopener"
&gt;froggit&lt;/a&gt;, autohébergé encore&amp;hellip;&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai décidé (un peu sur un coup de tête) de migrer le blog chez les copain⋅es de chez Clever Cloud.&lt;/p&gt;
&lt;p&gt;Et je vous montre comment j&amp;rsquo;ai fait.&lt;/p&gt;
&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;p&gt;Je pars du principe que vous avez comme moi un dépôt git / site hugo déjà fonctionnel. Et je ne vais pas non plus détailler la procédure pour activer un compte chez &lt;a class="link" href="https://www.clever-cloud.com/" target="_blank" rel="noopener"
&gt;Clever Cloud&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Je commence par installer la CLI, les &amp;ldquo;clever tools&amp;rdquo;. Pour les installer, il nous faut &lt;code&gt;npm&lt;/code&gt;. J&amp;rsquo;utilise rarement &lt;code&gt;npm&lt;/code&gt;, mais quand je le fais, j&amp;rsquo;utilise un autre tool, &lt;code&gt;volta&lt;/code&gt; (&lt;a class="link" href="https://volta.sh/" target="_blank" rel="noopener"
&gt;volta.sh&lt;/a&gt;), qui va me permettre de choisir la version dont j&amp;rsquo;ai besoin&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/12/idontalways.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ curl https://get.volta.sh | bash
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Une fois installé, je peux télécharger la version qui m&amp;rsquo;intéresse de npm (18 par exemple, la version minimum pour installer les clever tools)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ volta install node@20
success: installed and set node@20.10.0 (with npm@10.2.3) as default
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Je peux donc maintenant installer les clever tools :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;npm i -g clever-tools
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Puis me loguer depuis mon terminal&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ clever login
Opening https://console.clever-cloud.com/cli-oauth?cli_version=3.0.2&amp;amp;cli_token=xxxxxxxxxxxxxxxxx in your browser to log you in…
Login successful as [unspecified name] &amp;lt;xxx.yyy@example.org&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/12/clever-login.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Si jamais comme indiqué sur l&amp;rsquo;écran de login, vous avez besoin d&amp;rsquo;un token + secret (pour de la CI par exemple), sachez qu&amp;rsquo;ils sont stockés dans un fichier &lt;code&gt;~/.config/clever-cloud/clever-tools.json&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2024/01/clever-token.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Denier prérequis, je vais ajouter une &lt;a class="link" href="https://console.clever-cloud.com/users/me/ssh-keys" target="_blank" rel="noopener"
&gt;clé SSH dans ma console clever&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/12/add-ssh.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="création-de-lapplication"&gt;Création de l&amp;rsquo;application
&lt;/h2&gt;&lt;p&gt;A partir de maintenant, je suis capable d’interagir avec clever cloud en CLI. Je vais commencer par créer l&amp;rsquo;application qui va héberger mon site statique :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ clever create -t static-apache blog-zwindler
Your application has been successfully created!
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Clever Cloud offre la possibilité de dissocier la taille des instances pour la partie run et la partie build. Je vais donc créer l&amp;rsquo;instance la plus petite possible pour le run (une nano), et une plus pêchue (une M) pour le build, histoire d&amp;rsquo;être plus réactif sur les commits de modifications :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ clever scale --build-flavor M
App rescaled successfully
$ clever scale --flavor nano
App rescaled successfully
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Je peux ensuite configurer quelques variables d&amp;rsquo;environnement (trouvées sur la doc de clever cloud) pour générer mon site statique avec l&amp;rsquo;image &lt;strong&gt;static-apache&lt;/strong&gt; de clever.&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 env &lt;span class="nb"&gt;set&lt;/span&gt; CC_WEBROOT &lt;span class="s2"&gt;&amp;#34;/public&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ clever env &lt;span class="nb"&gt;set&lt;/span&gt; CC_OVERRIDE_BUILDCACHE &lt;span class="s2"&gt;&amp;#34;/public&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ clever env &lt;span class="nb"&gt;set&lt;/span&gt; CC_PRE_BUILD_HOOK &lt;span class="s2"&gt;&amp;#34;bash setup_hugo.sh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ clever env &lt;span class="nb"&gt;set&lt;/span&gt; CC_POST_BUILD_HOOK &lt;span class="s2"&gt;&amp;#34;hugo --minify --gc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Pour finir, on crée le script &lt;code&gt;setup_hugo.sh&lt;/code&gt; à la racine de notre site statique Hugo :&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; setup_hugo.sh &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;HUGO_VERSION=&amp;#34;0.121.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="s"&gt;HUGO_URL=&amp;#34;https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz&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;DEST_BIN=&amp;#34;${HOME}/.local/bin&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;FILENAME=&amp;#34;hugo.tar.gz&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;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;# Download Hugo Extended and place it in a folder in the $PATH
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;curl --create-dirs -s -L -o ${DEST_BIN}/${FILENAME} ${HUGO_URL}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;cd ${DEST_BIN}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;tar xvf ${FILENAME} -C ${DEST_BIN}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;rm ${FILENAME}
&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;On commit les fichiers créés :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git add .
git commit -m &amp;#34;Add clever&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="déployer-notre-code"&gt;Déployer notre code
&lt;/h2&gt;&lt;p&gt;A partir de là, on a notre instance qui est prête à démarrer, dès qu&amp;rsquo;elle aura reçu du code.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/12/app-created.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Ici, clever cloud attend un push sur un dépôt git.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/12/blog-zwindler-clever.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;La méthode la plus &amp;ldquo;simple&amp;rdquo; pour pousser du code sur clever à ce moment-là est donc de créer un dépôt distance (remote) :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git remote add clever git+ssh://git@push-n3-par-clevercloud-customers.services.clever-cloud.com/app_xxxxxxxxxxxxxxxxxxxx.git
git push clever master
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Le &amp;ldquo;problème&amp;rdquo; dans mon cas est que (pour l&amp;rsquo;instant), le nom de la branche n&amp;rsquo;est pas configurable et est forcément &amp;ldquo;master&amp;rdquo;, ce qui ne m&amp;rsquo;arrange pas puisque je bosse habituellement sur &amp;ldquo;main&amp;rdquo;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;remote: Error: You tried to push to a custom branch. This is not allowed.
remote: error: hook declined to update refs/heads/main
To git+ssh://push-n3-par-clevercloud-customers.services.clever-cloud.com/app_xxxxxxxxxxxxxxxxxxxx.git
! [remote rejected] main -&amp;gt; main (hook declined)
error: impossible de pousser des références vers &amp;#39;git+ssh://push-n3-par-clevercloud-customers.services.clever-cloud.com/app_xxxxxxxxxxxxxxxxxxxx.git&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;2 manières de contourner ce problème :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;soit je force la branche master sur le remote clever dans git&lt;/li&gt;
&lt;li&gt;soit j&amp;rsquo;utilise la CLI &lt;code&gt;clever deploy&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans un premier temps, je me contente de feinter git :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git push clever main:master
Énumération des objets: 30, fait.
Décompte des objets: 100% (24/24), fait.
Compression par delta en utilisant jusqu&amp;#39;à 16 fils d&amp;#39;exécution
Compression des objets: 100% (16/16), fait.
Écriture des objets: 100% (16/16), 287.70 Kio | 31.97 Mio/s, fait.
Total 16 (delta 8), réutilisés 0 (delta 0), réutilisés du pack 0
remote: [SUCCESS] The application has successfully been queued for redeploy.
To git+ssh://push-n3-par-clevercloud-customers.services.clever-cloud.com/app_xxxxxxxxxxxxxxxxxxxx.git
54ff42f..960ce62 main -&amp;gt; master
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;On termine par la configuration du nom de domaine :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ clever domain add blog.zwindler.fr
Your domain has been successfully saved
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Il ne nous reste plus qu&amp;rsquo;à mettre à jour le CNAME dans notre DNS (&lt;em&gt;domain.par.clever-cloud.com.&lt;/em&gt; dans mon cas, mais la bonne configuration est visible dans l&amp;rsquo;onglet &lt;strong&gt;Domain names&lt;/strong&gt; de l&amp;rsquo;application)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2023/12/dns.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="et-avec-clever-deploy-"&gt;Et avec &lt;code&gt;clever deploy&lt;/code&gt; ?
&lt;/h2&gt;&lt;p&gt;Eh bien en fait, c&amp;rsquo;est &amp;ldquo;encore plus simple&amp;rdquo;. Il me suffit juste de faire une modif, de commit&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git add .
git commit -m &amp;#34;test clever deploy&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Puis de simplement lancer la commande &lt;code&gt;clever deploy&lt;/code&gt;.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;$ clever deploy
Remote git head commit is c4e3c283585cafff44f07c7d78d860065318cd68
Current deployed commit is c4e3c283585cafff44f07c7d78d860065318cd68
New local commit to push is 0b3536a9f1d61178a99f6238831ccb4197989ddf (from refs/heads/main)
Pushing source code to Clever Cloud…
Your source code has been pushed to Clever Cloud.
Waiting for deployment to start…
Deployment started (deployment_b5f841ff-a233-4bb4-8a16-f618ee69ef28)
Waiting for application logs…
[...]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quelle est donc cette diablerie ?&lt;/p&gt;
&lt;p&gt;En réalité, les informations nécessaires pour pousser le code sont en réalité simplement dans un fichier &lt;code&gt;.clever.json&lt;/code&gt; qui a été créé à la racine lorsqu&amp;rsquo;on a lancé la commande &lt;code&gt;clever create -t static-apache blog-zwindler&lt;/code&gt; au tout début du tuto.&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;apps&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;app_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;app_xxxxxxxxxxxxxxxxxxx&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;org_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;user_yyyyyyyyyyyyyyyyyyyyy&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;deploy_url&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://push-n3-par-clevercloud-customers.services.clever-cloud.com/xxxxxxxxxxxxxxxxxxxx.git&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;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;blog-zwindler&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;alias&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;blog-zwindler&amp;#34;&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;Évidemment, je ne vais pas m&amp;rsquo;amuser à faire un &lt;code&gt;clever login&lt;/code&gt; / &lt;code&gt;clever deploy&lt;/code&gt; à chaque fois que je veux pousser une modif sur mon blog. Et c&amp;rsquo;est là où nos CLEVER_TOKEN / CLEVER_SECRET seront utiles !&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Après quelques heures d&amp;rsquo;usage, je peux dire que &lt;strong&gt;dans le cadre de l&amp;rsquo;hébergement de mon blog&lt;/strong&gt;, j&amp;rsquo;aime bien utiliser Clever Cloud.&lt;/p&gt;
&lt;p&gt;Qu&amp;rsquo;on soit bien clair, c&amp;rsquo;est pour mon cas d&amp;rsquo;usage et comparé à :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Vercel : l&amp;rsquo;expérience utilisateur est incroyable sur Vercel, mais j&amp;rsquo;ai toujours été un peu gêné par le côté &amp;ldquo;boite noire&amp;rdquo; (on a pas accès à beaucoup plus que des variables d&amp;rsquo;environnement). Et je ne parle pas des problématiques de privacy&amp;hellip;&lt;/li&gt;
&lt;li&gt;Deux serveurs physiques avec Proxmox VE en cluster étendu chez One-provider : à l&amp;rsquo;inverse héberger moi-même mon blog sur une machine (en vrai, 2) louée résout les problèmes de privacy et j&amp;rsquo;ai la main sur toute la stack, mais ça me demande de la maintenance pour des perfs minables. Ok, c&amp;rsquo;est pas cher, mais j&amp;rsquo;ai 2 à 3 minutes de rebuild avec le CPU à 100% sur mon Atom de 2010 chez Online&amp;hellip;&lt;/li&gt;
&lt;li&gt;Froggit : c&amp;rsquo;était du Gitlab + Gitlab pages et c&amp;rsquo;était très cool car c&amp;rsquo;est plus flexible que Vercel, mais j&amp;rsquo;avais dû passer un peu de temps à configurer la pipeline et leur faire pousser les murs pour que mon site rentre (les 400 Mo de médias n&amp;rsquo;étaient pas supportés au début et les transferts Gitlab =&amp;gt; Gitlab pages étaient un peu longs).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avec Clever, mes 450 articles (3600 pages générées, 400 Mo de média) sont très rapidement générés, transférés, et enfin mis à disposition (&amp;lt; 1 minute tout compris, plus rapide que les 3 solutions précédentes).&lt;/p&gt;
&lt;p&gt;Le tout avec une disponibilité/fiabilité sans aucun doute bien meilleure que ce que j&amp;rsquo;aurais pu faire moi-même en auto hébergé avec un coût similaire, et en très peu de temps.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai passé beaucoup plus de temps à écrire cet article qu&amp;rsquo;à migrer le blog, puisque la doc est bien écrite, le site est intuitif, et je n&amp;rsquo;ai été bloqué à aucun moment.&lt;/p&gt;
&lt;p&gt;Il me reste encore à optimiser l&amp;rsquo;étape de déploiement, pour qu&amp;rsquo;un commit/push sur mon dépôt git déclenche automatiquement un déploiement côté clever. Objectivement, ça ne devrait pas prendre trop longtemps (github action / gitlab CI)&amp;hellip;&lt;/p&gt;
&lt;p&gt;Et par contre, ça manque d&amp;rsquo;IPv6 natif, screugneugneu !&lt;/p&gt;</description></item><item><title>Et si on testait le Clever Operator pour Kubernetes ?</title><link>https://blog.zwindler.fr/2022/05/17/et-si-on-testait-clever-kubernetes-operator/</link><pubDate>Tue, 17 May 2022 10:00:00 +0000</pubDate><guid>https://blog.zwindler.fr/2022/05/17/et-si-on-testait-clever-kubernetes-operator/</guid><description>&lt;img src="https://blog.zwindler.fr/2022/05/clever-trott.webp" alt="Featured image of post Et si on testait le Clever Operator pour Kubernetes ?" /&gt;&lt;h2 id="clever-operator"&gt;Clever Operator
&lt;/h2&gt;&lt;p&gt;Il y a quelques mois, je suis tombé sur cet article de Clever Cloud qui disait avoir &lt;a class="link" href="https://www.clever-cloud.com/fr/blog/fonctionnalites/2022/03/16/clever-operator/" target="_blank" rel="noopener"
&gt;annonçait la sortie de l&amp;rsquo;Operator pour Kubernetes&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Nous avons commencé à travailler sur le Clever Operator suite aux retours de certains de nos clients utilisant k8s ou Openshift qui n’étaient pas vraiment satisfaits des solutions de gestion de base de données fournies par ces plateformes.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;C&amp;rsquo;est pas un secret, j&amp;rsquo;ai beaucoup râlé sur les gens qui disent que Kubernetes c&amp;rsquo;est compliqué et/ou que ça apporte plus de problèmes que ça n&amp;rsquo;en résout (cf &lt;a class="link" href="https://blog.zwindler.fr/2019/09/03/concerning-kubernetes-combien-de-problemes-ces-stacks-ont-generes/%29" target="_blank" rel="noopener"
&gt;https://blog.zwindler.fr/2019/09/03/concerning-kubernetes-combien-de-problemes-ces-stacks-ont-generes/)&lt;/a&gt;. On en a notamment parle avec Quentin ADAM ;-).&lt;/p&gt;
&lt;p&gt;Mais ce que je ne disais pas clairement à cette époque, c&amp;rsquo;est que Kubernetes, c&amp;rsquo;est quand même plus simple quand il s&amp;rsquo;agit d&amp;rsquo;autonomiser les devs sur des workloads stateless.&lt;/p&gt;
&lt;p&gt;Ou alors du stateful, mais pas trop violent : genre stockage de fichiers (Prometheus par exemple), voire fichiers partagés (avec un FS distribué, partagé entre plusieurs apps ou instances d&amp;rsquo;une même app).&lt;/p&gt;
&lt;p&gt;Par contre, les bases de données, là, on rigole tout de suite beaucoup moins&amp;hellip;&lt;/p&gt;
&lt;p&gt;Attention, je ne dis pas que c&amp;rsquo;est impossible. Plein de gens le font, en prod (moi compris). Mais c&amp;rsquo;est tout de suite un autre niveau de complexité, car il faut gérer le cycle de vie de la base de données, les mises à jours, les pertes de noeuds proprement, etc.&lt;/p&gt;
&lt;p&gt;Pour faciliter ça, il existe les &amp;ldquo;Opérateurs&amp;rdquo; dans Kubernetes, qui ont pour but d&amp;rsquo;ajouter toute une logique fonctionnelle pour gérer ça (comme celui pour &lt;a class="link" href="https://blog.zwindler.fr/2020/09/28/mes-bases-mongodb-dans-kubernetes/" target="_blank" rel="noopener"
&gt;MongoDB dont je parle dans cet article&lt;/a&gt;), mais c&amp;rsquo;est pas encore parfait.&lt;/p&gt;
&lt;p&gt;Ajoutez à cela le fait que DBA, c&amp;rsquo;est un job &amp;ldquo;à part&amp;rdquo;, avec des compétences bien spécifiques et qui nécessite vraiment de savoir ce qu&amp;rsquo;on fait.&lt;/p&gt;
&lt;p&gt;Du coup&amp;hellip; j&amp;rsquo;aurais pu dire (en moins classe) :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Les DBs dans Kube, c&amp;rsquo;est ch**nt. Ca serait cool si quelqu&amp;rsquo;un dont c&amp;rsquo;est l&amp;rsquo;expertise pouvait gérer ça pour moi, en dehors de Kube.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Qui a dit &amp;ldquo;Clever Cloud&amp;rdquo; ?&lt;/p&gt;
&lt;h2 id="clever-operator-1"&gt;Clever Operator
&lt;/h2&gt;&lt;p&gt;Ok, admettons qu&amp;rsquo;on ait un Kubernetes fonctionnel. On va plugger Clever Cloud dessus pour voir ce qu&amp;rsquo;on peut faire. Le billet sur le blog de Clever indique 2 manières de déployer l&amp;rsquo;operator (plot twist, il y en a une 3ème qui est mieux).&lt;/p&gt;
&lt;p&gt;La première consiste à utiliser &lt;a class="link" href="https://operatorhub.io/" target="_blank" rel="noopener"
&gt;operatorhub.io&lt;/a&gt;, un site communautaire pour partager des opérateurs Kubernetes.&lt;/p&gt;
&lt;p&gt;Pour utiliser cette méthode, il faut déployer le &lt;code&gt;operator-lifecycle-manager&lt;/code&gt;, lui-même un opérateur, dans notre kubernetes. J&amp;rsquo;ai essayé et j&amp;rsquo;ai plein de critiques à faire sur ce tool.&lt;/p&gt;
&lt;p&gt;Déjà, la doc nous demande de faire un &lt;code&gt;curl | bash&lt;/code&gt; (déjà une hérésie en soit), ensuite, il se déploie sans prévenir sur mon contexte Kubernetes par défaut. Enfin, c&amp;rsquo;est pas bien clair ce qu&amp;rsquo;il faut faire pour le customiser (notamment pour ajouter nos credentials).&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai essayé 1h, j&amp;rsquo;ai laissé tomber, mais si vous avez l&amp;rsquo;habitude d&amp;rsquo;utiliser Operatorhub.io vous aurez peut être des infos que je n&amp;rsquo;ai pas.&lt;/p&gt;
&lt;p&gt;La seconde méthode proposée est de déployer (ou de builder) soit même le container Docker dans notre cluster Kubernetes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Vous pouvez le built à partir du code source sur Github ou utiliser notre image docker sur Docker Hub.
Puis, configurez le. Ça se résume à paramétrer les variables d’environnement CLEVER_OPERATOR_*. Par exemple, vous devez créer un token pour vous connecter à l’API.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Je suis un poil sceptique sur le &amp;ldquo;ça se résume à&amp;rdquo; 😏.&lt;/p&gt;
&lt;p&gt;Quand on va voir le code du clever operator (&lt;a class="link" href="https://github.com/CleverCloud/clever-operator/tree/main/deployments/kubernetes/v1.21.0" target="_blank" rel="noopener"
&gt;ici&lt;/a&gt;), on se rend tout de suite compte qu&amp;rsquo;il va falloir déployer &lt;strong&gt;beaucoup&lt;/strong&gt; de ressources Kube et pas juste un container (~650 lignes de YAML).&lt;/p&gt;
&lt;p&gt;En même temps c&amp;rsquo;est logique, il y a le fichier du &lt;code&gt;Deployment&lt;/code&gt;, des &lt;code&gt;Roles&lt;/code&gt;, plusieurs définitions de &lt;code&gt;CRDs&lt;/code&gt;, bref&amp;hellip;&lt;/p&gt;
&lt;h2 id="diy"&gt;DIY
&lt;/h2&gt;&lt;p&gt;Edit du 24/05 : depuis la rédaction de l&amp;rsquo;article, j&amp;rsquo;ai proposé une PR à Florentin (le dev de la feature chez Clever) pour ajouter une Helm Chart. Le principe est le même, on va devoir renseigner des valeurs dans values.yaml donc vous pouvez quand même dérouler l&amp;rsquo;article.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;cd deployments/kubernetes/helm
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;helm install clever-operator -n clever-operator --create-namespace -f values.yaml .
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Du coup, j&amp;rsquo;ai déjà spoilé la solution (qui est d&amp;rsquo;ailleurs mise en avant dans le README.md du &lt;a class="link" href="https://github.com/CleverCloud/clever-operator/blob/main/deployments/kubernetes/v1.21.0/10-custom-resource-definition.yaml" target="_blank" rel="noopener"
&gt;dépot Github.com&lt;/a&gt;). On va déployer le YAML à la main.&lt;/p&gt;
&lt;p&gt;On installe les &lt;code&gt;CRDs&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;kubectl apply -f https://raw.githubusercontent.com/CleverCloud/clever-operator/main/deployments/kubernetes/v1.21.0/10-custom-resource-definition.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;customresourcedefinition.apiextensions.k8s.io/postgresqls.api.clever-cloud.com configured
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;customresourcedefinition.apiextensions.k8s.io/redis.api.clever-cloud.com configured
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;customresourcedefinition.apiextensions.k8s.io/mysqls.api.clever-cloud.com configured
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;customresourcedefinition.apiextensions.k8s.io/mongodbs.api.clever-cloud.com configured
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;customresourcedefinition.apiextensions.k8s.io/pulsars.api.clever-cloud.com configured
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;customresourcedefinition.apiextensions.k8s.io/configproviders.api.clever-cloud.com configured
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;customresourcedefinition.apiextensions.k8s.io/elasticsearches.api.clever-cloud.com configured
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;kubectl api-resources --api-group=api.clever-cloud.com
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; NAME SHORTNAMES APIVERSION NAMESPACED KIND
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; mongodbs mo api.clever-cloud.com/v1 true MongoDb
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; mysqls my api.clever-cloud.com/v1 true MySql
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; postgresqls pg api.clever-cloud.com/v1 true PostgreSql
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; pulsars pulse,pul api.clever-cloud.com/v1beta1 true Pulsar
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; redis r api.clever-cloud.com/v1 true Redis
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Si je peux me permettre ce commentaire, je trouve ça audacieux, ces shortnames 😱&amp;hellip; Attention aux collisions avec d&amp;rsquo;autres CRDs 🤔.&lt;/p&gt;
&lt;p&gt;Et ensuite on récupère et on va modifier le fichier du &lt;code&gt;Deployment&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;curl -LO https://raw.githubusercontent.com/CleverCloud/clever-operator/main/deployments/kubernetes/v1.21.0/20-deployment.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="préparer-la-configuration"&gt;Préparer la configuration
&lt;/h2&gt;&lt;p&gt;Bon, à un moment donné, il va bien falloir que je connecte l&amp;rsquo;API clever cloud avec mon operator, notamment en renseignant les fameuses variables d’environnement &lt;code&gt;CLEVER_OPERATOR_*&lt;/code&gt; dont parle l&amp;rsquo;article.&lt;/p&gt;
&lt;p&gt;Si on regarde la tête de notre fichier &lt;code&gt;20-deployment.yaml&lt;/code&gt;, on remarque qu&amp;rsquo;il existe une ressource &lt;code&gt;ConfigMap&lt;/code&gt; prévue pour renseigner les credentials (ligne 100).&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;---
apiVersion: v1
kind: ConfigMap
metadata:
namespace: clever-operator-system
name: clever-operator-configuration
data:
config.toml: |
[api]
token = &amp;#34;&amp;#34;
secret = &amp;#34;&amp;#34;
consumerKey = &amp;#34;&amp;#34;
consumerSecret = &amp;#34;&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Si vous êtes curieux (c&amp;rsquo;est mon cas), ce volume est ensuite appelé dans le &lt;code&gt;Deployment&lt;/code&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; volumes:
- name: config
configMap:
name: clever-operator-configuration
items:
- key: &amp;#34;config.toml&amp;#34;
path: &amp;#34;config.toml&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Puis monté dans la spec du template de container&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; volumeMounts:
- name: config
mountPath: &amp;#34;/etc/clever-operator&amp;#34;
readOnly: true
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note : Idéalement, on voudra éviter de faire ça, dans la vraie vie. Les &lt;code&gt;ConfigMaps&lt;/code&gt; (comme les &lt;code&gt;Secrets&lt;/code&gt;) dans Kubernetes sont lisibles par beaucoup de monde (la seule différence pour les &lt;code&gt;Secrets&lt;/code&gt;, c&amp;rsquo;est que les chaines de caractères sont &lt;em&gt;base64ifiée&lt;/em&gt;. Si si, c&amp;rsquo;est dans le dico, &amp;ldquo;base64ifier&amp;rdquo;) et donc absolument pas adaptés pour la gestion de &lt;em&gt;secrets&lt;/em&gt;.
On pourrait aussi ajouter ces secrets comme variables d&amp;rsquo;environnement dans le &lt;code&gt;Deployment&lt;/code&gt; (comme proposé dans la méthode 2 par l&amp;rsquo;article), mais c&amp;rsquo;est pire (on peut en débattre) encore niveau sécu.
Idéalement, il faudra injecter ces valeurs avec un gestionnaire de secret tier, comme &lt;a class="link" href="https://blog.zwindler.fr/2020/08/31/gerez-vos-secrets-kubernetes-dans-vault" &gt;Vault par exemple&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="mais-où-on-les-trouve-ces-valeurs-à-renseigner-"&gt;Mais où on les trouve, ces valeurs à renseigner ?
&lt;/h2&gt;&lt;p&gt;Alors je n&amp;rsquo;avais jamais utilisé Clever Cloud avant, donc ça n&amp;rsquo;était pas super évident pour moi 😅.&lt;/p&gt;
&lt;p&gt;On va d&amp;rsquo;abord devoir créer une &lt;strong&gt;organisation&lt;/strong&gt; et récupérer son ID (en haut à droite de l&amp;rsquo;interface). Elle a la forme &lt;code&gt;orga_xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Pour la partie &lt;strong&gt;consumerKey&lt;/strong&gt; et &lt;strong&gt;consumerSecret&lt;/strong&gt;, j&amp;rsquo;ai trouvé avec la documentation officielle. Dans mon organisation côté UI de Clever Cloud, on peut cliquer sur &amp;ldquo;Create&amp;hellip; / an oauth consumer&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/05/oauth_consumer.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Pour la partie &lt;strong&gt;token&lt;/strong&gt; et &lt;strong&gt;secret&lt;/strong&gt;, je n&amp;rsquo;ai pas trouvé comment en créer un en console web et je n&amp;rsquo;ai pas trouvé dans la doc.&lt;/p&gt;
&lt;p&gt;En bidouillant un peu (monkey testing), j&amp;rsquo;ai trouvé qu&amp;rsquo;on pouvait en récupérer en utilisant la &lt;a class="link" href="https://www.clever-cloud.com/doc/getting-started/cli/" target="_blank" rel="noopener"
&gt;CLI de clever cloud&lt;/a&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;curl -fsSL https://clever-tools.clever-cloud.com/gpg/cc-nexus-deb.public.gpg.key | gpg --dearmor -o /usr/share/keyrings/cc-nexus-deb.gpg
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;echo &amp;#34;deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/cc-nexus-deb.gpg] https://nexus.clever-cloud.com/repository/deb stable main&amp;#34; | tee -a /etc/apt/sources.list
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sudo apt-get update
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;sudo apt-get install clever-tools
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;clever login
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Opening https://console.clever-cloud.com/cli-oauth?cli_version=2.9.1&amp;amp;cli_token=... in your browser to log you in…
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;We&amp;#39;re still waiting for the login process (in your browser) to be completed…
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;Login successful as Denis GERMAIN &amp;lt;...&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fenêtre de navigateur s&amp;rsquo;ouvre pour se loguer et magie magie, on a un &lt;strong&gt;token&lt;/strong&gt; et un &lt;strong&gt;secret&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;CLEVER_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aaa
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;CLEVER_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;aaa
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Tadaa !&lt;/p&gt;
&lt;h2 id="deployer-lopérateur"&gt;Deployer l&amp;rsquo;opérateur
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;Tout est prêt, allons-y.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On peut apply le manifest et déployer le deployment&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;kubectl apply -f 20-deployment.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;namespace/clever-operator-system created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;serviceaccount/clever-operator created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;clusterrole.rbac.authorization.k8s.io/system:clever-operator created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;clusterrolebinding.rbac.authorization.k8s.io/system:clever-operator created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;poddisruptionbudget.policy/clever-operator created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;networkpolicy.networking.k8s.io/clever-operator created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;configmap/clever-operator-configuration created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;deployment.apps/clever-operator created
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;del&gt;Note: la dernière version de l&amp;rsquo;image Docker du Clever Operator a &amp;ldquo;un bug&amp;rdquo; du à une version trop ancienne de la GLIBC. Florentin de chez Clever est en discussion avec Redhat pour fix ça (`GLIBC_2.29&amp;rsquo; not found).&lt;/del&gt;&lt;/p&gt;
&lt;p&gt;&lt;del&gt;Il &amp;ldquo;suffit&amp;rdquo; de prendre une version plus ancienne de l&amp;rsquo;image et ça remarche, en attendant un fix définitif.&lt;/del&gt; (FIXED)&lt;/p&gt;
&lt;h2 id="on-teste-tout-ça"&gt;On teste tout ça
&lt;/h2&gt;&lt;p&gt;Normalement, là on est bons.&lt;/p&gt;
&lt;p&gt;Dans la liste des bases (des add-ons dans la terminology Clever Cloud) supportées par ce operator, il y a donc :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ElasticSearch&lt;/li&gt;
&lt;li&gt;MongoDb&lt;/li&gt;
&lt;li&gt;MySql&lt;/li&gt;
&lt;li&gt;PostgreSql&lt;/li&gt;
&lt;li&gt;Pulsar&lt;/li&gt;
&lt;li&gt;Redis&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;La documentation des CRDs &lt;a class="link" href="https://github.com/CleverCloud/clever-operator/blob/main/docs/40-custom-resources.md" target="_blank" rel="noopener"
&gt;est disponible ici&lt;/a&gt; et elle est bien faite.&lt;/p&gt;
&lt;p&gt;Je ferai des tests plus poussés (déployer une vraie application) plus tard, mais déjà pour tester, j&amp;rsquo;ai pu lancer un mysql managée;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cat mysql.yml
---
apiVersion: api.clever-cloud.com/v1
kind: MySql
metadata:
namespace: default
name: mysql
spec:
organisation: orga_xxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx
options:
version: 80
encryption: false
instance:
region: par
plan: dev
kubectl apply -f mysql.yml
mysql.api.clever-cloud.com/mysql created
kubectl get my
NAME AGE
mysql 14s
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2022/05/db_clever.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;opérateur a également la gentillesse de nous créer un &lt;code&gt;Secret&lt;/code&gt; avec les variables de connexion. C&amp;rsquo;est donc hyper simple de relier notre app à notre nouvelle DB :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-console" data-lang="console"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;kubectl get secret mysql-secrets -o yaml
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&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="go"&gt;data:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; MYSQL_ADDON_DB: aaa=
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; MYSQL_ADDON_HOST: aaa==
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; MYSQL_ADDON_PASSWORD: aaa=
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; MYSQL_ADDON_PORT: aaa==
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; MYSQL_ADDON_URI: aaa==
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; MYSQL_ADDON_USER: aaa==
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt; MYSQL_ADDON_VERSION: aaa
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="go"&gt;kind: Secret
&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;kubectl run -it mysqlclient --image mysql /bin/bash
root@mysqlclient:/# mysql aaa --host aaa-mysql.services.clever-cloud.com --user aaa -p
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
[...]
mysql&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai été très content de tester cet operator, et surtout de pouvoir échanger avec son développeur, Florentin DUBOIS, qui m&amp;rsquo;a aidé et à qui j&amp;rsquo;ai pu remonter quelques petits défauts de jeunesse de l&amp;rsquo;outil.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;intégration entre Clever et Kubernetes marche vraiment bien, c&amp;rsquo;est fluide. La documentation des &lt;code&gt;CRDs&lt;/code&gt; est bien faite, on est pas perdus. Enfin, la génération à la volée du &lt;code&gt;Secret&lt;/code&gt; contenant les informations de connexion directement dans Kubernetes est vraiment une fonctionnalité bien pratique, puisqu&amp;rsquo;on voudra souvent monter directement les informations qu&amp;rsquo;il contient dans un &lt;code&gt;Pod&lt;/code&gt; du même namespace.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;De mon point de vue, il manque une Chart Helm, pour rentre le déploiement simple et flexible de l&amp;rsquo;opérateur, et sans trop spoiler ça devrait arriver bientôt ;-).&lt;/del&gt; (FIXED)&lt;/p&gt;
&lt;p&gt;A moi le pouvoir tout puissant des bases de données que je n&amp;rsquo;ai pas à gérer !&lt;/p&gt;</description></item></channel></rss>