<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Système on Zwindler's Reflection</title><link>https://blog.zwindler.fr/categories/systeme/</link><description>Recent content in Système on Zwindler's Reflection</description><generator>Hugo -- gohugo.io</generator><language>fr-fr</language><copyright>Licensed under CC BY-SA 4.0</copyright><lastBuildDate>Tue, 28 Apr 2026 10:00:00 +0200</lastBuildDate><atom:link href="https://blog.zwindler.fr/categories/systeme/index.xml" rel="self" type="application/rss+xml"/><item><title>UserNamespaces dans Kubernetes : la feature géniale qui sert (presque) à rien</title><link>https://blog.zwindler.fr/2026/04/28/kubernetes-usernamespaces/</link><pubDate>Tue, 28 Apr 2026 10:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2026/04/28/kubernetes-usernamespaces/</guid><description>&lt;img src="https://blog.zwindler.fr/2026/04/usernamespaces.webp" alt="Featured image of post UserNamespaces dans Kubernetes : la feature géniale qui sert (presque) à rien" /&gt;&lt;h2 id="linfographie-qui-ma-trigger"&gt;L&amp;rsquo;infographie qui m&amp;rsquo;a trigger
&lt;/h2&gt;&lt;p&gt;Depuis quelques jours, les infographies se suivent (et se ressemblent) sur Linkedin. Kubernetes 1.36 est sorti et une des features qui fait le plus parler, c&amp;rsquo;est la sortie en GA des &lt;strong&gt;UserNamespaces&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est un sujet que je suis depuis 2018 (talk &lt;a class="link" href="https://blog.zwindler.fr/2018/05/03/recap-du-premier-jour-de-kubecon-europe-2018/" target="_blank" rel="noopener"
&gt;The Route to rootless container&lt;/a&gt; à la kubecon EU de 2018) donc je peux dire que je suis content de voir l&amp;rsquo;aboutissement de ce long chemin. Cependant, je suis &amp;ldquo;profondément choqué&amp;rdquo; de voir la façon dont c&amp;rsquo;est présenté sur LinkedIn, visiblement par des gens qui n&amp;rsquo;ont aucune idée de comment ça fonctionne (et qui probablement, s&amp;rsquo;en fichent).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Kubernetes just made root safer. Just add &lt;code&gt;hostUsers: false&lt;/code&gt; to your Pod spec.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Le visuel : un roi tout-puissant &amp;ldquo;inside the container&amp;rdquo; et un mendiant impuissant &amp;ldquo;outside on the host&amp;rdquo;. La promesse : &amp;ldquo;No Host Access. No Privilege Escalation. No Lateral Movement. No Node Takeover.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est accrocheur.&lt;/p&gt;
&lt;p&gt;Mais c&amp;rsquo;est surtout hyper grave de le présenter comme ça, parce que ça occulte des pans entiers de sécurité applicative et opérationnelle. Vendre &lt;code&gt;hostUsers: false&lt;/code&gt; comme le remède universel au problème du &amp;ldquo;root dans les containers&amp;rdquo;, c&amp;rsquo;est une simplification dramatique qui va pousser des équipes à ignorer les vraies &lt;strong&gt;priorités&lt;/strong&gt; de sécurité.&lt;/p&gt;
&lt;h2 id="ce-que-les-usernamespaces-font-réellement-sans-bullshit"&gt;Ce que les UserNamespaces font réellement, sans bullshit
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Le threat model : le container escape&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Avant tout, de quoi parle-t-on exactement ? Un &lt;strong&gt;container escape&lt;/strong&gt; (évasion de container), c&amp;rsquo;est quand un attaquant réussit à sortir de son container et à accéder directement au kernel ou au système de fichiers de l&amp;rsquo;hôte, en bypassant complètement les mécanismes d&amp;rsquo;isolation habituels.&lt;/p&gt;
&lt;p&gt;Ce type de vulnérabilité est &lt;strong&gt;rare&lt;/strong&gt;, mais des exemples bien réels existent :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2019-5736" target="_blank" rel="noopener"
&gt;CVE-2019-5736&lt;/a&gt;&lt;/strong&gt; (runc) : écriture dans &lt;code&gt;/proc/self/exe&lt;/code&gt; du process hôte depuis le container&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2022-0492" target="_blank" rel="noopener"
&gt;CVE-2022-0492&lt;/a&gt;&lt;/strong&gt; (cgroups v1) : escape via &lt;code&gt;unshare&lt;/code&gt; dans certaines configurations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2024-21626" target="_blank" rel="noopener"
&gt;CVE-2024-21626&lt;/a&gt;&lt;/strong&gt; (runc, &amp;ldquo;Leaky Vessels&amp;rdquo;) : fuite de file descriptor vers le répertoire de travail de l&amp;rsquo;hôte&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;S&amp;rsquo;il y a une faille de ce type sur votre Node ET qu&amp;rsquo;un process est compromis ET s&amp;rsquo;il tourne en root dans le container ET qu&amp;rsquo;il n&amp;rsquo;a pas les UserNamespaces, l&amp;rsquo;attaquant obtient &lt;strong&gt;root sur l&amp;rsquo;hôte&lt;/strong&gt;, c&amp;rsquo;est &lt;strong&gt;game over&lt;/strong&gt;. Accès à tous les fichiers du Node, à tous les secrets montés par les autres pods, possibilité d&amp;rsquo;installer un rootkit ou d&amp;rsquo;exfiltrer les données de tous les tenants présents sur le Node.&lt;/p&gt;
&lt;p&gt;Ca reste possible, mais ça fait beaucoup de &amp;ldquo;si&amp;rdquo;. Quoiqu&amp;rsquo;il en soit, c&amp;rsquo;est exactement ce scénario que les UserNamespaces adressent. Ils introduisent un &lt;strong&gt;mapping d&amp;rsquo;UID&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;L&amp;rsquo;UID 0 dans le container est mappé vers un UID non-privilégié sur l&amp;rsquo;hôte (ex: 100000, propre à chaque pod)&lt;/li&gt;
&lt;li&gt;Si un attaquant réussit à s&amp;rsquo;échapper du container via un exploit kernel, il se retrouve &lt;strong&gt;&lt;code&gt;nobody&lt;/code&gt;&lt;/strong&gt; sur le node, l&amp;rsquo;escape réussit, mais l&amp;rsquo;impact post-escape est dramatiquement réduit&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C&amp;rsquo;est le scénario &amp;ldquo;Breakouts Lose Impact&amp;rdquo; de l&amp;rsquo;infographie, et là-dessus, &lt;strong&gt;l&amp;rsquo;infographie dit vrai&lt;/strong&gt;. C&amp;rsquo;est le vrai apport de la feature.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cas particulier : le multi-tenant même en non-root&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Même sans container root, les UserNamespaces apportent aussi quelque chose dans un contexte &lt;strong&gt;vraiment multi-tenant&lt;/strong&gt; (plusieurs clients différents sur le même cluster). Sans UserNamespaces, si deux pods de clients différents tournent tous les deux avec &lt;code&gt;runAsUser: 1000&lt;/code&gt;, ils partagent le même UID 1000 sur le node. En cas d&amp;rsquo;escape sur l&amp;rsquo;un, l&amp;rsquo;attaquant peut accéder aux fichiers de l&amp;rsquo;autre pod qui ont le même propriétaire. Le mapping UID de UserNamespaces, en donnant un offset unique à chaque pod, isole les UID entre pods même à valeur identique dans le container.&lt;/p&gt;
&lt;p&gt;Pour les clusters internes où vous contrôlez tous les workloads, ce scénario est théorique. Pour une plateforme SaaS multi-tenant ou un service de build public, c&amp;rsquo;est une vraie ligne de défense.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Prérequis techniques&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pour pouvoir bénéficier de cette feature, il y a quelques prérequis, mais la majorité des clusters à jour devraient pouvoir se qualifier.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Kernel Linux ≥ 5.19&lt;/li&gt;
&lt;li&gt;Runtime compatible (containerd ≥ 1.7, CRI-O ≥ 1.25)&lt;/li&gt;
&lt;li&gt;Support des &lt;em&gt;idmapped mounts&lt;/em&gt; pour les volumes persistants (XFS, ext4, mais pas NFS dans tous les cas par exemple)&lt;/li&gt;
&lt;li&gt;Kubernetes ≥ 1.33 (Beta), ≥ 1.36 (GA)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="ce-que-linfographie-exagère-et-ce-quelle-oublie"&gt;Ce que l&amp;rsquo;infographie exagère (et ce qu&amp;rsquo;elle oublie)
&lt;/h2&gt;&lt;p&gt;L&amp;rsquo;infographie a donc raison sur un point précis : UserNamespaces réduit l&amp;rsquo;impact d&amp;rsquo;un container escape réussi. C&amp;rsquo;est réel. Le problème, c&amp;rsquo;est qu&amp;rsquo;elle vend la feature comme solution universelle au &amp;ldquo;root dans les containers&amp;rdquo;, et là c&amp;rsquo;est n&amp;rsquo;importe quoi.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. L&amp;rsquo;isolation de l&amp;rsquo;UID n&amp;rsquo;est pas une isolation des privilèges applicatifs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;infographie promet &amp;ldquo;No Lateral Movement&amp;rdquo;. C&amp;rsquo;est faux (et archi faux).&lt;/p&gt;
&lt;p&gt;Un container root avec &lt;code&gt;hostUsers: false&lt;/code&gt; peut toujours lire le &lt;strong&gt;ServiceAccount Token&lt;/strong&gt; monté dans &lt;code&gt;/var/run/secrets/kubernetes.io/serviceaccount/token&lt;/code&gt;. Si ce token a des permissions RBAC étendues (ce qui arrive, on en reparlera peut être dans un autre article prochainement), l&amp;rsquo;attaquant peut appeler l&amp;rsquo;API Server, énumérer les ressources du cluster, et se déplacer latéralement, le tout sans jamais toucher le Node hôte.&lt;/p&gt;
&lt;p&gt;Le mapping d&amp;rsquo;UID protège l&amp;rsquo;hôte. Il ne protège pas le cluster.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. Un container root reste root dans le container&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&amp;ldquo;Install Anything ✅&amp;rdquo; — c&amp;rsquo;est littéralement écrit dans l&amp;rsquo;infographie, présenté comme un avantage 😖.&lt;/p&gt;
&lt;p&gt;Dans un container root (même avec UserNS), un attaquant qui prend la main peut :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Installer &lt;code&gt;nmap&lt;/code&gt;, &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;nc&lt;/code&gt; pour scanner le réseau interne&lt;/li&gt;
&lt;li&gt;Modifier les fichiers de l&amp;rsquo;application, les binaires, les configurations&lt;/li&gt;
&lt;li&gt;Lire tous les fichiers montés en volume&lt;/li&gt;
&lt;li&gt;Persister dans le container entre les redémarrages si le filesystem est writable&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Les UserNamespaces ne retirent aucun de ces vecteurs d&amp;rsquo;attaque. Pouvoir ajouter des logiciels, c&amp;rsquo;est la garantie d&amp;rsquo;un mouvement latéral rapide (là encore).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. C&amp;rsquo;est pas si facile, surtout pour le sto&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Activer &lt;code&gt;hostUsers: false&lt;/code&gt; casse le stockage existant dans la plupart des cas.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;UID 0 du container est mappé sur l&amp;rsquo;UID 100000+ sur l&amp;rsquo;hôte (chaque container a son propre &amp;ldquo;offset&amp;rdquo;). Si un volume persistant (NFS, EBS, Ceph RBD) appartient à l&amp;rsquo;UID 1000, le container root ne peut ni lire ni écrire dessus. Le résultat : des &lt;code&gt;Permission Denied&lt;/code&gt; contre intuitifs, potentiellement complexes à diagnostiquer car l&amp;rsquo;application n&amp;rsquo;a probablement pas été conçue, si elle est root, pour ne pas avoir accès à ses propres fichiers.&lt;/p&gt;
&lt;p&gt;La solution technique existe (&lt;em&gt;idmapped mounts&lt;/em&gt;), mais elle nécessite un kernel récent et un filesystem compatible. Voir la &lt;a class="link" href="https://www.kernel.org/doc/html/latest/filesystems/idmappings.html" target="_blank" rel="noopener"
&gt;documentation officielle des idmapped mounts&lt;/a&gt; pour les détails.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;4. Idem, mais pour le réseau&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;hostUsers: false&lt;/code&gt; est incompatible avec &lt;code&gt;hostNetwork: true&lt;/code&gt;. C&amp;rsquo;est un détail, mais il piège les workloads réseau (agents de monitoring, CNI plugins, etc.).&lt;/p&gt;
&lt;p&gt;Note : cela dit, avoir des containers en hostNetwork est &lt;strong&gt;un autre souci de sécurité&lt;/strong&gt;, donc bon&amp;hellip;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="comparaison-sans-bullshit--userns-vs-les-alternatives-existantes"&gt;Comparaison sans bullshit : UserNS vs les alternatives existantes
&lt;/h2&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;Vecteur d&amp;rsquo;attaque&lt;/th&gt;
&lt;th style="text-align: center"&gt;UserNS (root inside)&lt;/th&gt;
&lt;th style="text-align: center"&gt;Non-root (UID 1000)&lt;/th&gt;
&lt;th style="text-align: center"&gt;Distroless / Scratch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Impact post-escape si container escape réussi&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Nobody sur l&amp;rsquo;hôte&lt;/td&gt;
&lt;td style="text-align: center"&gt;⚠️ UID 1000 sur l&amp;rsquo;hôte&lt;/td&gt;
&lt;td style="text-align: center"&gt;⚠️ UID 1000 sur l&amp;rsquo;hôte&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Isolation UID entre pods (multi-tenant)&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Offset unique par pod&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ UID partagé sur le node&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ UID partagé sur le node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Installation de malwares dans le container&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Trivial&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Possible&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Quasi impossible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Périmètre d&amp;rsquo;écriture dans le FS éphémère du container&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Tout le FS&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Répertoire de l&amp;rsquo;app&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Quasi impossible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Mouvement latéral via SA Token&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Possible&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Possible&lt;/td&gt;
&lt;td style="text-align: center"&gt;⚠️ Potentiellement difficile&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Complexité opérationnelle&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Parfois élevée&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Souvent quasi nulle&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Souvent faible&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;Compatibilité stockage existant&lt;/td&gt;
&lt;td style="text-align: center"&gt;❌ Parfois problématique&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Standard&lt;/td&gt;
&lt;td style="text-align: center"&gt;✅ Standard&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;La lecture de la table révèle la vraie nature d&amp;rsquo;UserNamespaces, il excelle sur &lt;strong&gt;exactement deux lignes&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;l&amp;rsquo;impact post-escape&lt;/li&gt;
&lt;li&gt;l&amp;rsquo;isolation UID en multi-tenant.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sur tout le reste, non-root + distroless fait mieux, ou aussi bien, sans la complexité opérationnelle. Et ce sont ces &amp;ldquo;tout le reste&amp;rdquo; (périmètre d&amp;rsquo;écriture dans le FS, installation de malwares, mouvement latéral via SA Token) qui représentent la grande majorité des vecteurs d&amp;rsquo;attaque réels, bien plus fréquents qu&amp;rsquo;un container escape. On en reparlera dans la section &lt;a class="link" href="#o%c3%b9-investir-son-budget-s%c3%a9curit%c3%a9" &gt;Où investir son budget sécurité&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="les-vrais-cas-dusage"&gt;Les vrais cas d&amp;rsquo;usage
&lt;/h2&gt;&lt;p&gt;Ce serait malhonnête de tout rejeter. Il existe trois scénarios où les UserNamespaces ne sont pas une option &amp;ldquo;fainéante&amp;rdquo; mais une &lt;em&gt;nécessité&lt;/em&gt; technique (et encore, ça se discute).&lt;/p&gt;
&lt;h3 id="1-build-as-a-service-buildah-rootless-podman"&gt;1. Build-as-a-Service (Buildah, rootless Podman)
&lt;/h3&gt;&lt;p&gt;Pour construire une image Docker, le moteur de build doit pouvoir faire des &lt;code&gt;chown&lt;/code&gt;, &lt;code&gt;chmod&lt;/code&gt; et &lt;code&gt;mknod&lt;/code&gt;. Ces opérations nécessitent &lt;code&gt;CAP_CHOWN&lt;/code&gt; et &lt;code&gt;CAP_FOWNER&lt;/code&gt;. Avant les UserNamespaces, la solution était de lancer le pod en &lt;code&gt;--privileged&lt;/code&gt;, ce qui est évidemment une porte ouverte sur l&amp;rsquo;hôte.&lt;/p&gt;
&lt;p&gt;Avec &lt;code&gt;hostUsers: false&lt;/code&gt;, le moteur de build a l&amp;rsquo;illusion d&amp;rsquo;être root pour manipuler ses fichiers, mais il est incapable d&amp;rsquo;impacter l&amp;rsquo;hôte. C&amp;rsquo;est le seul cas où &amp;ldquo;root inside&amp;rdquo; est une contrainte technique et non de la dette.&lt;/p&gt;
&lt;p&gt;Note : &lt;a class="link" href="https://github.com/GoogleContainerTools/kaniko" target="_blank" rel="noopener"
&gt;Kaniko&lt;/a&gt;, longtemps la référence pour le build in-cluster, est archivé depuis juin 2025 et ne reçoit plus de mises à jour de sécurité. Buildah ou rootless Podman sont les alternatives actives.&lt;/p&gt;
&lt;p&gt;Mon avis : ça peut être éventuellement utile pour les plateformes CI/CD mutualisées (GitLab Runners, Tekton) qui refusent les pods privilégiés. Mais si l&amp;rsquo;isolation est critique (plateforme publique, multi-tenant agressif), les microVMs (Kata Containers, Firecracker) offrent une garantie bien supérieure pour un overhead devenu raisonnable.&lt;/p&gt;
&lt;h3 id="2-multi-tenancy-hostile-plateformes-de-code-utilisateur"&gt;2. Multi-tenancy hostile (plateformes de code utilisateur)
&lt;/h3&gt;&lt;p&gt;Si votre métier est de faire tourner du code fourni par des inconnus (PaaS, éditeur de code en ligne, CI/CD publique), vous savez d&amp;rsquo;avance que l&amp;rsquo;utilisateur &lt;em&gt;va&lt;/em&gt; essayer d&amp;rsquo;escalader ses privilèges. Dans ce contexte, l&amp;rsquo;UserNS est une barrière supplémentaire contre les 0-day kernel.&lt;/p&gt;
&lt;p&gt;Mon avis : honnêtement, si l&amp;rsquo;environnement est vraiment &lt;strong&gt;hostile&lt;/strong&gt;, l&amp;rsquo;UserNS seul n&amp;rsquo;est pas suffisant. Les microVMs (Kata Containers, Firecracker) offrent une isolation matérielle réelle et sont le choix correct pour ce cas. L&amp;rsquo;UserNS peut être un complément, pas un substitut.&lt;/p&gt;
&lt;h3 id="3-legacy-hard-coded-postfix-dovecot-bind"&gt;3. Legacy &amp;ldquo;hard-coded&amp;rdquo; (Postfix, Dovecot, BIND)
&lt;/h3&gt;&lt;p&gt;Certains vieux démons UNIX démarrent en root pour ouvrir un port &amp;lt; 1024 ou lire des fichiers de config sensibles, puis &amp;ldquo;drop&amp;rdquo; leurs privilèges via &lt;code&gt;setuid()&lt;/code&gt;. Ce mécanisme échoue dans un container non-root classique.&lt;/p&gt;
&lt;p&gt;Les UserNamespaces permettent à ces processus de croire qu&amp;rsquo;ils peuvent faire leurs appels système de gestion d&amp;rsquo;identité, car ils sont root dans leur namespace.&lt;/p&gt;
&lt;p&gt;Voici un exemple concret écrit par un collègue (thanks Louis 😘) :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;v1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Pod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postfix&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hostUsers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# mapping UID : root dans le container → nobody sur l&amp;#39;hôte&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# autorisé en PSS Restricted *uniquement* grâce à hostUsers: false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;fsGroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;103&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# GID postfix&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postfix&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;postfix:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# idem, cf. https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#integration-with-pod-security-admission-checks&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;allowPrivilegeEscalation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;readOnlyRootFilesystem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;seccompProfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;RuntimeDefault&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ALL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# d&amp;#39;abord, on drop tout&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;add&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SETUID &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# on ajoute SETUID mais le drop de privilèges via setuid() &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# est fait par postfix lui-même au démarrage &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;SETGID &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# idem pour les groupes&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;CHOWN &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# chown sur les queues au démarrage&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FOWNER &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# opérations sur fichiers sans être propriétaire&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;FSETID &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# conserver le setuid bit après écriture&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;DAC_OVERRIDE &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# OBLIGATOIRE : root dans UserNS n&amp;#39;est pas &amp;#34;vrai&amp;#34; root,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# les vérifications DAC ne sont pas contournées automatiquement&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Ce manifest illustre plusieurs choses importantes.&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;abord, c&amp;rsquo;est très pénible de rendre une application legacy secure avec les UserNS, et il faut faire des compromis, notamment sur les capabilities (on est loin de la feature magique qui sécurise les apps root).&lt;/p&gt;
&lt;p&gt;Ensuite, on remarque des trucs rigolos. Normalement, la policy &lt;code&gt;Restricted&lt;/code&gt; (Pod Security Standard) interdit &lt;code&gt;runAsNonRoot: false&lt;/code&gt;. Kubernetes fait une exception quand &lt;code&gt;hostUsers: false&lt;/code&gt; est présent. C&amp;rsquo;est documenté &lt;a class="link" href="https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/#integration-with-pod-security-admission-checks" target="_blank" rel="noopener"
&gt;ici&lt;/a&gt;. Sans UserNamespaces, ce pod serait rejeté par l&amp;rsquo;admission controller.&lt;/p&gt;
&lt;p&gt;De plus, on doit ajouter la &lt;strong&gt;capacité &lt;code&gt;DAC_OVERRIDE&lt;/code&gt;&lt;/strong&gt;, ce qui est contre-intuitif. root dans un UserNS n&amp;rsquo;est pas un vrai root du point de vue du kernel pour les vérifications DAC (Discretionary Access Control). Quand Postfix essaie de faire &lt;code&gt;set-permissions&lt;/code&gt; pour &lt;code&gt;chown&lt;/code&gt; ses queues, le kernel vérifie quand même les permissions ; et les refuse si &lt;code&gt;DAC_OVERRIDE&lt;/code&gt; n&amp;rsquo;est pas là. C&amp;rsquo;est exactement le genre de surprise opérationnelle invisible jusqu&amp;rsquo;au premier déploiement en production.&lt;/p&gt;
&lt;p&gt;On notera malgré tout qu&amp;rsquo;on a pu garder &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt; et &lt;code&gt;allowPrivilegeEscalation: false&lt;/code&gt; ; le legacy ne justifie pas de tout sacrifier.&lt;/p&gt;
&lt;p&gt;Mon avis : c&amp;rsquo;est le seul cas d&amp;rsquo;usage où l&amp;rsquo;UserNS est vraiment acceptable. Pas de code tiers non maîtrisé, pas de plateforme hostile, juste du legacy bien identifié &lt;strong&gt;avec un plan de migration&lt;/strong&gt; ultérieur. Les deux autres cas sont &amp;ldquo;acceptables sous conditions&amp;rdquo;, le legacy reste le plus propre des trois.&lt;/p&gt;
&lt;h2 id="quelques-contre-arguments"&gt;Quelques contre-arguments
&lt;/h2&gt;&lt;p&gt;Je vous vois arriver avec quelques contre arguments, donc pour gagner du temps à tout le monde, je vais faire les questions et les réponses :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;C&amp;rsquo;est de la défense en profondeur.&amp;rdquo;&lt;/strong&gt;
&amp;ldquo;Vrai, mais&amp;rdquo;&amp;hellip; La défense en profondeur suppose qu&amp;rsquo;on a déjà posé les couches de base. Si vous n&amp;rsquo;avez pas encore migré vos images vers un user non-root, mettre de l&amp;rsquo;énergie sur l&amp;rsquo;UserNS est un non-sens. Et une fois en non-root, le gain marginal de l&amp;rsquo;UserNS est négligeable face à la complexité qu&amp;rsquo;il introduit.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;On ne contrôle pas les images tierces.&amp;rdquo;&lt;/strong&gt;
Argument un peu faible, à mon avis : si vous avez une image blackbox de votre éditeur propriétaire, qui est codée pour tourner en root, il y a de fortes chances :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;soit qu&amp;rsquo;elle en ait réellement besoin pour fonctionner (typiquement, le cas pour certains outils de sécurité propriétaires)&lt;/li&gt;
&lt;li&gt;soit qu&amp;rsquo;elle gère mal le mapping d&amp;rsquo;UID (cf. le problème de stockage).
L&amp;rsquo;UserNS n&amp;rsquo;est pas une baguette magique qui rend n&amp;rsquo;importe quelle image tierce compatible et sécurisée.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;C&amp;rsquo;est un garde-fou contre les erreurs humaines.&amp;rdquo;&lt;/strong&gt;
Il est aussi facile d&amp;rsquo;oublier &lt;code&gt;hostUsers: false&lt;/code&gt; que d&amp;rsquo;oublier &lt;code&gt;runAsNonRoot: true&lt;/code&gt;. La vraie solution centralisée, ce sont les &lt;strong&gt;Pod Security Standards&lt;/strong&gt; ou un Admission Controller (Kyverno, OPA) qui rejettent purement et simplement les pods root. C&amp;rsquo;est plus simple, plus fiable, et ça ne casse pas le stockage.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;On en a besoin pour la compliance SOC2/PCI-DSS/&amp;hellip;&amp;rdquo;&lt;/strong&gt;
Si votre compliance exige une isolation stricte entre tenants, l&amp;rsquo;UserNS sera probablement jugé insuffisant par vos auditeurs. Les VMs ou microVMs restent le standard. Utiliser l&amp;rsquo;UserNS pour la compliance, c&amp;rsquo;est choisir l&amp;rsquo;outil le plus complexe à maintenir pour un résultat discutable.&lt;/p&gt;
&lt;h2 id="où-investir-son-budget-sécurité-"&gt;Où investir son budget sécurité ?
&lt;/h2&gt;&lt;p&gt;Si on met de côté le marketing, voici où l&amp;rsquo;effort paye vraiment, du plus impactant au plus niche :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Priorité 1 — Images non-root + &lt;code&gt;nobody&lt;/code&gt; (UID 65534)&lt;/strong&gt;
Passer les images en non-root, idéalement vers l&amp;rsquo;utilisateur &lt;code&gt;nobody&lt;/code&gt; (le moins privilégié du système). Si une application est compromise sous &lt;code&gt;nobody&lt;/code&gt;, l&amp;rsquo;attaquant ne peut pratiquement rien faire, même sur le filesystem du container. À combiner avec &lt;code&gt;readOnlyRootFilesystem: true&lt;/code&gt; et &lt;code&gt;capabilities: drop: [&amp;quot;ALL&amp;quot;]&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsNonRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;runAsUser&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;65534&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# nobody&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;seccompProfile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;RuntimeDefault&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-app:distroless&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;securityContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;allowPrivilegeEscalation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;capabilities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;drop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ALL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;readOnlyRootFilesystem&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Priorité 2 — Pod Security Standards (PSS) en mode &lt;code&gt;Baseline&lt;/code&gt; ou &lt;code&gt;Restricted&lt;/code&gt;&lt;/strong&gt;
Bloquer le root et les privilèges sans rien casser au niveau infra. Ça nécessite d&amp;rsquo;avoir déjà fait le point n°1, mais c&amp;rsquo;est gratuit, standard, et ça s&amp;rsquo;applique à tout le cluster via un label de namespace (et ça peut s&amp;rsquo;overrider par namespace si nécessaire). Plus de risque d&amp;rsquo;oubli possible. C&amp;rsquo;est déjà configuré par défaut sur plusieurs types de Kubernetes (je pense à Talos, mais ce n&amp;rsquo;est pas le seul).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Priorité 3 — MicroVMs (Kata Containers, Firecracker)&lt;/strong&gt;
Pour les workloads vraiment non-fiables. Isolation matérielle réelle, overhead devenu raisonnable sur les générations récentes.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Priorité 4 — UserNamespaces&lt;/strong&gt;
When all else fail. Uniquement pour les cas légitimes identifiés ci-dessus (build, legacy, multi-tenancy hostile). C&amp;rsquo;est vraiment littéralement la &lt;strong&gt;dernière&lt;/strong&gt; chose à faire.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Les UserNamespaces dans Kubernetes 1.36 sont l&amp;rsquo;aboutissement d&amp;rsquo;un chantier qui aura pris &amp;ldquo;officiellement&amp;rdquo; cinq ans (KEP-127 date de 2021) et dont on parle quasiment depuis la genèse de Kubernetes. Pour les plateformes de build mutualisées et les SaaS multi-tenants qui font tourner du code utilisateur, c&amp;rsquo;est une brique potentiellement intéressante (notamment pour éviter qu&amp;rsquo;une app d&amp;rsquo;un client lise les apps d&amp;rsquo;un autre en cas de container escape sans élévation de privilège).&lt;/p&gt;
&lt;p&gt;Pour le reste (c&amp;rsquo;est-à-dire 99% des clusters de production) ce n&amp;rsquo;est pas là que commence la sécurité container — et c&amp;rsquo;est précisément le problème avec ce genre d&amp;rsquo;infographie.&lt;/p&gt;
&lt;p&gt;Les infographies LinkedIn qui vendent une sécurité sans effort sont dangereuses : &amp;ldquo;gardez votre image root de 800 Mo pleine d&amp;rsquo;outils, ajoutez juste &lt;code&gt;hostUsers: false&lt;/code&gt;, et vous êtes protégés&amp;rdquo;. C&amp;rsquo;est exactement &lt;strong&gt;l&amp;rsquo;inverse de la bonne démarche&lt;/strong&gt;. La vraie sécurité container se construit dans le Dockerfile, pas dans le PodSpec.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Si vous activez les UserNamespaces pour sécuriser une application dont vous possédez le code source, vous avez probablement raté une étape dans votre cycle de développement sécurisé.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="références"&gt;Références
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/kubernetes/enhancements/blob/master/keps/sig-node/127-user-namespaces/README.md" target="_blank" rel="noopener"
&gt;KEP-127 : Support for User Namespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/" target="_blank" rel="noopener"
&gt;Kubernetes docs — User Namespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://kubernetes.io/docs/concepts/security/pod-security-standards/" target="_blank" rel="noopener"
&gt;Pod Security Standards&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2019-5736" target="_blank" rel="noopener"
&gt;CVE-2019-5736&lt;/a&gt; — runc : écriture dans &lt;code&gt;/proc/self/exe&lt;/code&gt; du process hôte depuis le container&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2022-0492" target="_blank" rel="noopener"
&gt;CVE-2022-0492&lt;/a&gt; — cgroups v1 : escape via &lt;code&gt;unshare&lt;/code&gt;, UserNS aide mais &lt;code&gt;runAsNonRoot&lt;/code&gt; aussi&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://nvd.nist.gov/vuln/detail/CVE-2024-21626" target="_blank" rel="noopener"
&gt;CVE-2024-21626&lt;/a&gt; — runc &amp;ldquo;Leaky Vessels&amp;rdquo; : fuite de file descriptor vers le répertoire de travail de l&amp;rsquo;hôte&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/GoogleContainerTools/distroless" target="_blank" rel="noopener"
&gt;Distroless containers — GoogleContainerTools&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Installation d'un Alpine Linux en mode 'headless' sur Raspberry Pi</title><link>https://blog.zwindler.fr/2025/12/23/alpine-linux-raspberry-pi-headless/</link><pubDate>Tue, 23 Dec 2025 08:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/12/23/alpine-linux-raspberry-pi-headless/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/12/rpizero.webp" alt="Featured image of post Installation d'un Alpine Linux en mode 'headless' sur Raspberry Pi" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Pour un article qui sortira peut-être sur papier (je croise les doigts), j&amp;rsquo;avais besoin de tester différents types d&amp;rsquo;OS à installer sur un Raspberry Pi Zero 2 W.&lt;/p&gt;
&lt;p&gt;Comme je ne connaissais ni Alpine, ni les spécificités du Pi Zero 2 W, je suis tombé sur pas mal de trucs que je ne connaissais pas.&lt;/p&gt;
&lt;p&gt;Même si au final, l&amp;rsquo;article papier ne traite pas d&amp;rsquo;installation d&amp;rsquo;Alpine Linux (ni même ne l&amp;rsquo;utilise), je me suis dit que c&amp;rsquo;était dommage de ne pas publier cette expérience Alpine + RPi Zero, donc la voici.&lt;/p&gt;
&lt;h2 id="pourquoi-alpine-linux-"&gt;Pourquoi Alpine Linux ?
&lt;/h2&gt;&lt;p&gt;Principalement pour sa légèreté et sa faible consommation de ressources. Sur un nano-ordinateur avec seulement 512 Mo de RAM, chaque mégaoctet compte. Alpine Linux est connu pour être minimaliste et aussi un peu controversé (notamment dans le monde Docker) pour le remplacement de la glibc par musl (ce qui provoque parfois des surprises).&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;objectif de cet article est d&amp;rsquo;avoir une installation headless (sans écran ni clavier) accessible en SSH via WiFi, avec une RAM optimisée au maximum.&lt;/p&gt;
&lt;p&gt;On va voir plein de détails techniques intéressants, notamment sur la gestion de la RAM du GPU et la configuration du swap avec zswap !&lt;/p&gt;
&lt;h2 id="téléchargement-de-limage-alpine-linux-pour-raspberry-pi"&gt;Téléchargement de l&amp;rsquo;image Alpine Linux pour Raspberry Pi
&lt;/h2&gt;&lt;p&gt;La première chose à faire est de télécharger l&amp;rsquo;image, avant de pouvoir la copier sur une carte microSD qui sera insérée dans le rpi avant boot.&lt;/p&gt;
&lt;p&gt;Coup de bol, Alpine Linux propose des images spécifiques pour Raspberry Pi, on ne va pas avoir besoin d&amp;rsquo;ajouter à la main le firmware des RPi.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/alpine-pi.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;On va récupérer la dernière version disponible depuis le &lt;a class="link" href="https://www.alpinelinux.org/downloads/" target="_blank" rel="noopener"
&gt;site officiel&lt;/a&gt;. Exemple :&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;wget https://dl-cdn.alpinelinux.org/alpine/v3.23/releases/aarch64/alpine-rpi-3.23.2-aarch64.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Cette image est spécialement compilée pour l&amp;rsquo;architecture ARM64 (aarch64) des Raspberry Pi récents (donc mon RPi Zero 2 W).&lt;/p&gt;
&lt;h2 id="configuration-headless-avec-le-fichier-overlay"&gt;Configuration headless avec le fichier overlay
&lt;/h2&gt;&lt;p&gt;Une fois l&amp;rsquo;image flashée sur votre carte microSD (avec Raspberry Pi Imager par exemple), on va préparer le système pour un accès headless.&lt;/p&gt;
&lt;p&gt;Alpine Linux supporte ce qu&amp;rsquo;on appelle des &amp;ldquo;overlay files&amp;rdquo; qui permettent de préconfigurer le système avant le premier boot. C&amp;rsquo;est documenté dans la &lt;a class="link" href="https://wiki.alpinelinux.org/wiki/Installation_on_a_headless_host#Headless_bootstrap_overlay_file" target="_blank" rel="noopener"
&gt;documentation officielle sur l&amp;rsquo;installation headless&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Montez le contenu de votre carte microSD sur votre machine et déposez le fichier overlay à la racine de cette carte microSD :&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;wget https://github.com/macmpi/alpine-linux-headless-bootstrap/raw/469ee440e7d394cca0976c78f357e7a0e1c82cc4/headless.apkovl.tar.gz
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note : c&amp;rsquo;est assez &lt;strong&gt;flippant&lt;/strong&gt; de monter un fichier aussi sensible provenant d&amp;rsquo;un repo github tiers (github.com/macmpi), mais Alpine Linux se veut rassurant en nous disant :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note: The author of above repo macmpi also maintains a number of packages for Alpine Linux.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Je suis pas beaucoup plus rassuré&amp;hellip; mais bon, c&amp;rsquo;est &amp;ldquo;officiel&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Ce fichier &lt;code&gt;headless.apkovl.tar.gz&lt;/code&gt; configure Alpine pour :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Booter en mode DHCP&lt;/li&gt;
&lt;li&gt;Activer SSH automatiquement&lt;/li&gt;
&lt;li&gt;Permettre la connexion root sans mot de passe (attention à ne pas laisser ça comme ça trop longtemps !)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pour que le Raspberry Pi se connecte automatiquement à votre réseau WiFi, créez un fichier &lt;code&gt;wpa_supplicant.conf&lt;/code&gt; à la racine de la microSD :&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; wpa_supplicant.conf &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;country=FR
&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;network={
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; key_mgmt=WPA-PSK
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; ssid=&amp;#34;mySSID&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; psk=&amp;#34;myPassPhrase&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;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/alpine-root-headless.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Remplacez évidemment &lt;code&gt;mySSID&lt;/code&gt; et &lt;code&gt;myPassPhrase&lt;/code&gt; par vos informations WiFi.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; : Le projet alpine-linux-headless-bootstrap propose d&amp;rsquo;autres options de configuration très utiles, comme la définition d&amp;rsquo;un mot de passe root, d&amp;rsquo;une adresse IP fixe, etc. Plus d&amp;rsquo;infos dans &lt;a class="link" href="https://github.com/macmpi/alpine-linux-headless-bootstrap/?tab=readme-ov-file#extra-configuration" target="_blank" rel="noopener"
&gt;leur README&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="premier-boot-et-installation-permanente"&gt;Premier boot et installation permanente
&lt;/h2&gt;&lt;p&gt;Une fois le premier boot effectué, connectez-vous en SSH :&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;ssh root@&amp;lt;ip_du_raspberry&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/alpine-headless-boot.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Attention&lt;/strong&gt; : Par défaut, le mot de passe root est vide ! Mais on n&amp;rsquo;a pas besoin de changer ça si on réalise &lt;strong&gt;immédiatement&lt;/strong&gt; l&amp;rsquo;installation qui suit.&lt;/p&gt;
&lt;p&gt;À ce stade, Alpine tourne entièrement en RAM (image &amp;ldquo;live&amp;rdquo;). Pour l&amp;rsquo;installer définitivement, nous allons utiliser l&amp;rsquo;utilitaire &lt;code&gt;setup-alpine&lt;/code&gt; dont les options sont décrites dans &lt;a class="link" href="https://wiki.alpinelinux.org/wiki/Installation#Base_configuration" target="_blank" rel="noopener"
&gt;wiki.alpinelinux.org/wiki/Installation&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;setup-alpine
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Voici un petit extrait des informations demandées (somme tout assez classiques pour une install d&amp;rsquo;OS) :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/alpine-setup-1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/alpine-setup-2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Suivez les instructions, puis rebootez :&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;reboot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="optimisation-de-la-ram-gpu"&gt;Optimisation de la RAM GPU
&lt;/h2&gt;&lt;p&gt;Par défaut sur un Raspberry Pi, une (grande) partie de la RAM est allouée au GPU pour le traitement graphique. Sur un serveur headless, ça ne sert strictement à rien.&lt;/p&gt;
&lt;p&gt;Vérifions d&amp;rsquo;abord la RAM disponible avant optimisation :&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;free -m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; total used free shared buff/cache available
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Mem: &lt;span class="m"&gt;417&lt;/span&gt; &lt;span class="m"&gt;37&lt;/span&gt; &lt;span class="m"&gt;325&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;55&lt;/span&gt; &lt;span class="m"&gt;369&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Swap: &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Seulement 417 Mo disponibles sur les 512 Mo théoriques. Voyons comment récupérer ces précieux mégaoctets.&lt;/p&gt;
&lt;p&gt;Idéalement, on va vouloir mettre l&amp;rsquo;option &lt;code&gt;gpu_mem=16&lt;/code&gt; dans &lt;code&gt;/boot/config.txt&lt;/code&gt; pour n&amp;rsquo;allouer que 16 Mo au GPU (on ne peut pas mettre moins, et il faut des multiples de 16).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mais attention&lt;/strong&gt;, il y a un piège !&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/12/nouvelle_regle.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Lorsque vous réglez &lt;code&gt;gpu_mem&lt;/code&gt; à 16 Mo, le bootloader du Raspberry Pi tente de charger des fichiers de firmware allégés :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;start_cd.elf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;fixup_cd.dat&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sauf que ces fichiers ne sont pas présents sur votre partition de boot. Si on met juste &lt;code&gt;gpu_mem=16&lt;/code&gt; dans &lt;code&gt;/boot/config.txt&lt;/code&gt; et qu&amp;rsquo;on reboot, le Pi refusera de démarrer (LED ACT clignotant généralement 4 fois).&lt;/p&gt;
&lt;h3 id="solutions-pour-minimiser-la-ram-gpu"&gt;Solutions pour minimiser la RAM GPU
&lt;/h3&gt;&lt;p&gt;Vous avez deux options :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 1&lt;/strong&gt; : Mettre &lt;code&gt;gpu_mem=32&lt;/code&gt; dans &lt;code&gt;/boot/config.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est la solution de facilité (qui a dit fainéant ?), il n&amp;rsquo;y a rien à faire. Vous récupérez quand même 32 Mo de RAM.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Option 2&lt;/strong&gt; : Télécharger les fichiers de firmware allégés (celle que je préfère)&lt;/p&gt;
&lt;p&gt;On n&amp;rsquo;est pas à quelques Mo près sur la carte microSD, ce qui n&amp;rsquo;est pas le cas de la RAM, où chaque Mo compte.&lt;/p&gt;
&lt;p&gt;Si vous voulez vraiment descendre à 16 Mo, récupérez les fichiers manquants :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /boot
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget https://raw.githubusercontent.com/raspberrypi/firmware/master/boot/start_cd.elf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;wget https://raw.githubusercontent.com/raspberrypi/firmware/master/boot/fixup_cd.dat
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Puis modifiez &lt;code&gt;/boot/config.txt&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ini" data-lang="ini"&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;&lt;span class="na"&gt;gpu_mem&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;16&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;start_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;start_cd.elf &lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="na"&gt;fixup_file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;fixup_cd.dat&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Après reboot, vérifiez le gain :&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;free -m
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; total used free shared buff/cache available
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Mem: &lt;span class="m"&gt;465&lt;/span&gt; &lt;span class="m"&gt;37&lt;/span&gt; &lt;span class="m"&gt;393&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;35&lt;/span&gt; &lt;span class="m"&gt;416&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Swap: &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On passe de 417 Mo à 465 Mo disponibles ! Soit presque 50 Mo de récupérés, ce qui représente environ 10% de RAM supplémentaire. Sur un système aussi contraint, c&amp;rsquo;est significatif.&lt;/p&gt;
&lt;h2 id="configuration-du-swap-avec-zswap"&gt;Configuration du swap avec zswap
&lt;/h2&gt;&lt;p&gt;Sur un système avec seulement 512 Mo de RAM, il est plus que judicieux de configurer du swap. Or, Alpine Linux n&amp;rsquo;en configure pas par défaut. Je vais ici en configurer un avec &lt;a class="link" href="https://docs.kernel.org/admin-guide/mm/zswap.html" target="_blank" rel="noopener"
&gt;zswap&lt;/a&gt;, qui compresse les pages de mémoire avant de les écrire sur le disque (ce qui limite la taille des accès à la carte microSD au passage).&lt;/p&gt;
&lt;h3 id="création-du-swapfile"&gt;Création du swapfile
&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;&lt;span class="c1"&gt;# Créer un swapfile de 2 Go&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;fallocate -l 2G /swapfile
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod &lt;span class="m"&gt;600&lt;/span&gt; /swapfile
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkswap /swapfile
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;swapon /swapfile
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="configuration-persistante"&gt;Configuration persistante
&lt;/h3&gt;&lt;p&gt;Pour rendre le swapfile et zswap persistants au reboot, créez &lt;code&gt;/etc/fstab&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;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/swapfile none swap sw 0 0&amp;#34;&lt;/span&gt; &amp;gt;&amp;gt; /etc/fstab
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Créez un script de démarrage pour Alpine :&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; /etc/local.d/swap.start &lt;span class="s"&gt;&amp;lt;&amp;lt; &amp;#39;EOF&amp;#39;
&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/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;swapon /swapfile
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod +x /etc/local.d/swap.start
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rc-update add &lt;span class="nb"&gt;local&lt;/span&gt; default
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Enfin, éditez &lt;code&gt;/boot/cmdline.txt&lt;/code&gt; et ajoutez à la fin (sur une seule ligne) :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;zswap.enabled=1 zswap.compressor=lz4 zswap.max_pool_percent=20 zswap.accept_threshold_percent=85 zswap.shrinker_enabled=Y
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Quelques explications sur ces paramètres :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;compressor=lz4&lt;/code&gt; : utilise lz4, rapide et efficace pour la compression&lt;/li&gt;
&lt;li&gt;&lt;code&gt;max_pool_percent=20&lt;/code&gt; : zswap n&amp;rsquo;utilisera pas plus de 20% de la RAM (~93 Mo) comme cache de compression avant d&amp;rsquo;écrire sur le swapfile disque&lt;/li&gt;
&lt;li&gt;&lt;code&gt;accept_threshold_percent=85&lt;/code&gt; : n&amp;rsquo;accepte de compresser que si le taux de compression attendu est d&amp;rsquo;au moins 85%&lt;/li&gt;
&lt;li&gt;&lt;code&gt;shrinker_enabled=Y&lt;/code&gt; : permet de libérer automatiquement la mémoire compressée si nécessaire&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le principe : zswap agit comme un cache rapide en RAM (compressé) devant le swapfile de 2 Go sur disque. Les pages sont d&amp;rsquo;abord compressées dans zswap, puis évacuées vers le disque si nécessaire.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Pas vraiment de conclusion à cet article, en réalité.&lt;/p&gt;
&lt;p&gt;Je me suis pris pas mal de &amp;ldquo;murs&amp;rdquo;, ne sachant pas utiliser Alpine (au delà des usages que j&amp;rsquo;en avais fait avec Docker), et j&amp;rsquo;ai découvert quelques subtilités liées au firmware du Raspberry Pi Zero avec lequel je fais joujou depuis quelques semaines maintenant.&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;où cet article ;-)&lt;/p&gt;
&lt;p&gt;Bon hack !&lt;/p&gt;</description></item><item><title>SLO, SLI, Error Budget et Critical User Journey expliqués simplement (et pourquoi ce ne sont pas des SLA !) (en plusieurs prompts)</title><link>https://blog.zwindler.fr/2025/06/17/slo-sli-error-budget-critical-user-journey-expliques-simplement/</link><pubDate>Tue, 17 Jun 2025 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/06/17/slo-sli-error-budget-critical-user-journey-expliques-simplement/</guid><description>&lt;img src="https://blog.zwindler.fr/talks/2022-sre-sre-partout/binaries/sre_sre_partout.webp" alt="Featured image of post SLO, SLI, Error Budget et Critical User Journey expliqués simplement (et pourquoi ce ne sont pas des SLA !) (en plusieurs prompts)" /&gt;&lt;p&gt;&lt;strong&gt;NOTE IMPORTANTE :&lt;/strong&gt; cet article a été généré par un LLM. Ceci va à l&amp;rsquo;encontre de règles que je me suis fixé pour ce blog (cf l&amp;rsquo;&lt;a class="link" href="https://blog.zwindler.fr/ai-manifesto/" target="_blank" rel="noopener"
&gt;AI Manifesto&lt;/a&gt;).&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Je pars du principe que si je ne prends pas la peine d’écrire moi-même le contenu de ce blog, vous ne devriez pas prendre la peine de le lire.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Je l&amp;rsquo;ai fait dans le cadre d&amp;rsquo;une expérience qui est décrite dans l&amp;rsquo;article suivant :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/06/17/reflexions-blogging-technique-ere-llms/" &gt;Réflexions sur le blogging technique à l&amp;rsquo;ère des LLMs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cependant, je ne vous interdit pas de lire cet article ci (les informations qu&amp;rsquo;il contient sur les SLOs sont correctes), je veux juste que vous le fassiez en connaissance de cause ;-P.&lt;/p&gt;
&lt;h2 id="introduction--quand-les-devs-découvrent-le-sre"&gt;Introduction : quand les devs découvrent le SRE
&lt;/h2&gt;&lt;p&gt;Suite à plusieurs discussions récentes avec des collègues développeurs, je me suis rendu compte que les concepts SRE comme les SLO, SLI et Error Budget restaient flous pour beaucoup d&amp;rsquo;entre eux. Pourtant, ces notions sont de plus en plus utilisées dans nos équipes, souvent sans qu&amp;rsquo;on prenne le temps de bien les expliquer.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est donc l&amp;rsquo;occasion parfaite pour revenir aux fondamentaux et expliquer ces concepts tels qu&amp;rsquo;ils ont été conçus à l&amp;rsquo;origine par Google. Car oui, il faut le rappeler : le SRE (Site Reliability Engineering) et tous les concepts associés ont été inventés par Google, plus précisément par Ben Treynor Sloss en 2003, bien avant que DevOps ne devienne populaire !&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;idée de Google était simple : et si on demandait à des ingénieurs logiciel de concevoir une équipe d&amp;rsquo;exploitation ? De cette approche sont nés des concepts révolutionnaires pour mesurer et gérer la fiabilité des services.&lt;/p&gt;
&lt;p&gt;Fun fact : j&amp;rsquo;avais déjà abordé ces sujets dans &lt;a class="link" href="https://blog.zwindler.fr/talks/2022-sre-sre-partout/index.html" &gt;mon talk sur le SRE en 2022&lt;/a&gt;, mais je me dis qu&amp;rsquo;un article dédié ne fait pas de mal pour clarifier les choses :-).&lt;/p&gt;
&lt;h2 id="sla-vs-slo--ne-mélangeons-pas-tout-"&gt;SLA vs SLO : ne mélangeons pas tout !
&lt;/h2&gt;&lt;p&gt;Avant de rentrer dans le vif du sujet, petit aparté important : &lt;strong&gt;ne confondez pas SLA et SLO&lt;/strong&gt; !&lt;/p&gt;
&lt;p&gt;Le SLA (Service Level Agreement), c&amp;rsquo;est un contrat, souvent avec des pénalités financières si pas respecté. Genre &amp;ldquo;si le service est en panne plus de X heures dans le mois, on vous rembourse Y€&amp;rdquo;. Le SLO (Service Level Objective), c&amp;rsquo;est un objectif &lt;strong&gt;interne&lt;/strong&gt; que vous vous fixez pour la fiabilité de votre service.&lt;/p&gt;
&lt;p&gt;La différence est importante : les SLA sont souvent moins stricts que les SLO pour avoir une marge de manœuvre. Si votre SLA c&amp;rsquo;est 99,9% de disponibilité, votre SLO interne sera peut-être à 99,95%.&lt;/p&gt;
&lt;p&gt;Bon, maintenant qu&amp;rsquo;on a éclairci ça, rentrons dans le détail !&lt;/p&gt;
&lt;h2 id="critical-user-journey--commencer-par-ce-qui-compte-vraiment"&gt;Critical User Journey : commencer par ce qui compte vraiment
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;Est-ce que le &lt;strong&gt;client&lt;/strong&gt; est content d&amp;rsquo;utiliser le service ?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;C&amp;rsquo;est LA question fondamentale. Et pour y répondre, il faut d&amp;rsquo;abord identifier les &lt;strong&gt;Critical User Journey&lt;/strong&gt; (CUJ), autrement dit les parcours utilisateurs critiques.&lt;/p&gt;
&lt;p&gt;Mais attention, quand on parle d&amp;rsquo;&lt;strong&gt;utilisateur&lt;/strong&gt; dans le contexte SRE, ce n&amp;rsquo;est pas forcément l&amp;rsquo;utilisateur final ! Pour un service backend, l&amp;rsquo;utilisateur peut être le frontend qui fait les appels API. Pour une plateforme de CI/CD, ce sont les développeurs qui veulent livrer une nouvelle version. Pour une base de données, ce sont les applications qui l&amp;rsquo;interrogent.&lt;/p&gt;
&lt;p&gt;Concrètement, ça veut dire quoi ? Prenons l&amp;rsquo;exemple d&amp;rsquo;une plateforme e-commerce. Les CUJ pourraient être : un utilisateur peut rechercher et consulter un produit, il peut ajouter un produit au panier et passer commande, il peut se connecter à son compte.&lt;/p&gt;
&lt;p&gt;On ne va pas définir des SLO pour toutes les fonctionnalités (la page &amp;ldquo;À propos&amp;rdquo; de votre site, on s&amp;rsquo;en fiche un peu), mais se concentrer sur celles qui, si elles tombent en panne, vont vraiment énerver vos utilisateurs.&lt;/p&gt;
&lt;p&gt;Et c&amp;rsquo;est là que ça devient intéressant : définir les CUJ, c&amp;rsquo;est souvent un exercice qui doit impliquer le business, pas seulement les équipes techniques. C&amp;rsquo;est eux qui savent ce qui rapporte de l&amp;rsquo;argent !&lt;/p&gt;
&lt;h2 id="sli--mesurer-ce-qui-compte"&gt;SLI : mesurer ce qui compte
&lt;/h2&gt;&lt;p&gt;Une fois qu&amp;rsquo;on a identifié nos CUJ, il faut les &lt;strong&gt;mesurer&lt;/strong&gt;. C&amp;rsquo;est là qu&amp;rsquo;interviennent les &lt;strong&gt;SLI (Service Level Indicators)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Un SLI, c&amp;rsquo;est simplement une métrique qui indique si votre service fonctionne bien du point de vue de l&amp;rsquo;utilisateur. Les plus classiques sont la disponibilité (pourcentage de requêtes qui réussissent), la latence (temps de réponse du service), le débit (nombre de requêtes traitées par seconde) et la qualité (pourcentage de réponses correctes, sans erreurs de données).&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;idée clé ici, c&amp;rsquo;est de mesurer depuis le point de vue de l&amp;rsquo;utilisateur, pas depuis vos serveurs. Peu importe que votre CPU soit à 10% si l&amp;rsquo;utilisateur voit des erreurs 500 !&lt;/p&gt;
&lt;h3 id="quelques-conseils-pour-choisir-vos-sli"&gt;Quelques conseils pour choisir vos SLI
&lt;/h3&gt;&lt;p&gt;Pour vous aider à concevoir des SLI fiables, vous pouvez vous inspirer des méthodes &lt;strong&gt;USE&lt;/strong&gt; et &lt;strong&gt;RED&lt;/strong&gt;. USE (Utilization, Saturation, Errors) se concentre sur les ressources systèmes, tandis que RED (Rate, Errors, Duration) se concentre sur les requêtes. Ces frameworks vous donnent un bon point de départ pour identifier les métriques qui comptent vraiment.&lt;/p&gt;
&lt;p&gt;Mais attention, un microservice ne doit pas avoir trop de SLI ! Trois ou quatre SLI bien choisies et qui ont un sens métier valent mieux qu&amp;rsquo;une dizaine de métriques que personne ne regarde. D&amp;rsquo;ailleurs, seuls les membres de l&amp;rsquo;équipe qui fournissent le service peuvent vraiment savoir quelles sont les métriques pertinentes. On ne peut pas imposer des SLI génériques à toute une entreprise !&lt;/p&gt;
&lt;p&gt;Exemple concret pour notre CUJ &amp;ldquo;recherche de produit&amp;rdquo; : SLI disponibilité pourrait être &lt;code&gt;(requêtes HTTP 200 sur /search) / (total requêtes sur /search) * 100&lt;/code&gt;, et SLI latence &lt;code&gt;95% des requêtes sur /search répondent en moins de X ms&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="slo--se-fixer-des-objectifs-réalistes"&gt;SLO : se fixer des objectifs réalistes
&lt;/h2&gt;&lt;p&gt;Maintenant qu&amp;rsquo;on sait &lt;strong&gt;quoi&lt;/strong&gt; mesurer, il faut se fixer des &lt;strong&gt;objectifs&lt;/strong&gt;. C&amp;rsquo;est le rôle des &lt;strong&gt;SLO (Service Level Objectives)&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Un SLO, c&amp;rsquo;est tout simplement une valeur cible pour vos SLI sur une période donnée. Par exemple : &amp;ldquo;99,9% des requêtes de recherche doivent réussir sur une période de 30 jours&amp;rdquo; ou &amp;ldquo;95% des pages de recherche doivent s&amp;rsquo;afficher en moins de 500ms sur une période de 7 jours&amp;rdquo;.&lt;/p&gt;
&lt;h3 id="quelques-conseils-pour-bien-définir-vos-slo"&gt;Quelques conseils pour bien définir vos SLO
&lt;/h3&gt;&lt;p&gt;Commencez par mesurer l&amp;rsquo;existant. Inutile de viser 99,99% si votre service actuel est à 98%. Regardez vos métriques historiques et fixez-vous des objectifs atteignables mais ambitieux.&lt;/p&gt;
&lt;p&gt;Pensez S.M.A.R.T. : vos SLO doivent être Spécifiques, Mesurables, Atteignables, Réalistes et Temporellement définis. Comme tout bon objectif !&lt;/p&gt;
&lt;p&gt;Et n&amp;rsquo;oubliez pas que 100% c&amp;rsquo;est mal ! Comme le dit si bien Ben Treynor Sloss (le papa du SRE chez Google) :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;100% is the &lt;strong&gt;wrong&lt;/strong&gt; reliability target for basically everything&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Plus on veut de &amp;ldquo;9&amp;rdquo;, plus ça coûte cher exponentiellement. Et au-delà d&amp;rsquo;un certain seuil, les utilisateurs ne voient même plus la différence ! Prenez l&amp;rsquo;exemple d&amp;rsquo;un site web qui ne charge pas sur un smartphone : au-delà d&amp;rsquo;un certain niveau de disponibilité, l&amp;rsquo;utilisateur ne saura pas dire si c&amp;rsquo;est son smartphone qui a un problème, son navigateur, le réseau 5G ou bien le site web qui rencontre un incident. Si recharger la page une fois de temps en temps suffit et que les utilisateurs ne sont pas plus impactés que ça, inutile d&amp;rsquo;investir dans plus de fiabilité.&lt;/p&gt;
&lt;p&gt;Pour vous aider à calculer ces pourcentages et temps d&amp;rsquo;indisponibilité, vous pouvez utiliser &lt;a class="link" href="https://uptime.is/" target="_blank" rel="noopener"
&gt;uptime.is&lt;/a&gt; qui fait les conversions pour vous.&lt;/p&gt;
&lt;h2 id="error-budget--retourner-le-problème"&gt;Error Budget : retourner le problème
&lt;/h2&gt;&lt;p&gt;Et là, c&amp;rsquo;est le moment où ça devient vraiment malin. Au lieu de raisonner en &amp;ldquo;disponibilité&amp;rdquo;, les équipes SRE raisonnent en &lt;strong&gt;Error Budget&lt;/strong&gt; (budget d&amp;rsquo;erreur).&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est un simple changement de perspective : service accessible 99,9% = service &lt;strong&gt;inaccessible&lt;/strong&gt; 0,1% du temps. Sur 30 jours, ça fait environ 43 minutes d&amp;rsquo;indisponibilité &amp;ldquo;autorisée&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Cette approche change complètement la donne ! Au lieu de voir les pannes comme des échecs, on les voit comme un &lt;strong&gt;budget à dépenser intelligemment&lt;/strong&gt;.&lt;/p&gt;
&lt;h3 id="comment-utiliser-son-error-budget-"&gt;Comment utiliser son Error Budget ?
&lt;/h3&gt;&lt;p&gt;Contre-intuitivement&amp;hellip; &lt;strong&gt;IL FAUT L&amp;rsquo;UTILISER&lt;/strong&gt; !&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/talks/2022-sre-sre-partout/binaries/simpsons.png"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Si votre SLO est respecté (utilisateurs contents), vous pouvez &amp;ldquo;dépenser&amp;rdquo; votre error budget pour faire des déploiements plus risqués, tester des nouvelles fonctionnalités en prod, faire du chaos engineering, ou réaliser des maintenances disruptives.&lt;/p&gt;
&lt;p&gt;À l&amp;rsquo;inverse, si vous &amp;ldquo;cramez&amp;rdquo; votre error budget (SLO pas atteint), alors là, stop : on arrête tout ce qui n&amp;rsquo;améliore pas la fiabilité du service !&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est un formidable outil de priorisation entre les équipes produit (qui veulent des nouvelles features) et les équipes ops (qui veulent de la stabilité). Le fameux mur de la confusion :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/talks/2022-sre-sre-partout/binaries/mur_de_la_confusion.png"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="exemple-concret--une-api-de-recommendation"&gt;Exemple concret : une API de recommendation
&lt;/h2&gt;&lt;p&gt;Bon, assez de théorie, prenons un exemple concret. Imaginons qu&amp;rsquo;on ait une API de recommandation de produits.&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;abord, on définit le CUJ : &amp;ldquo;Un utilisateur doit pouvoir récupérer des recommandations personnalisées en moins de 1 seconde&amp;rdquo;. Ensuite, on choisit les SLI : disponibilité &lt;code&gt;(réponses HTTP 200) / (total requêtes) * 100&lt;/code&gt; et latence &lt;code&gt;P95 du temps de réponse&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Pour les SLO, on pourrait avoir : &amp;ldquo;99,5% des requêtes sur l&amp;rsquo;API de recommandation doivent réussir sur 30 jours&amp;rdquo; et &amp;ldquo;95% des requêtes doivent répondre en moins de 800ms sur 7 jours&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Enfin, on calcule l&amp;rsquo;Error Budget : 99,5% de disponibilité = 0,5% d&amp;rsquo;indisponibilité autorisée, soit environ 3,6 heures d&amp;rsquo;indisponibilité &amp;ldquo;budgetées&amp;rdquo; sur 30 jours.&lt;/p&gt;
&lt;p&gt;Simple, non ?&lt;/p&gt;
&lt;h2 id="les-pièges-à-éviter"&gt;Les pièges à éviter
&lt;/h2&gt;&lt;p&gt;Le premier piège, c&amp;rsquo;est de vouloir mettre des SLO partout. N&amp;rsquo;essayez pas ! Commencez par 2-3 SLO sur vos CUJ les plus critiques. Vous pourrez étendre ensuite.&lt;/p&gt;
&lt;p&gt;Le deuxième piège, c&amp;rsquo;est de fixer des SLO trop stricts. Si vous mettez la barre trop haut, vous allez passer votre temps en &amp;ldquo;SLO violation&amp;rdquo; et personne ne prendra plus ça au sérieux.&lt;/p&gt;
&lt;p&gt;Enfin, le troisième piège, c&amp;rsquo;est d&amp;rsquo;oublier l&amp;rsquo;aspect organisationnel. Les Error Budgets ne marchent que si toute l&amp;rsquo;organisation (business inclus) adhère au principe. Sinon, vous aurez beau être en SLO violation, on vous demandera quand même de déployer la nouvelle feature&amp;hellip;&lt;/p&gt;
&lt;h2 id="comment-commencer-"&gt;Comment commencer ?
&lt;/h2&gt;&lt;p&gt;Si vous n&amp;rsquo;avez jamais fait de SLO, voici un plan d&amp;rsquo;action simple.&lt;/p&gt;
&lt;p&gt;Identifiez d&amp;rsquo;abord 1-2 CUJ critiques (avec le business !). Regardez ensuite vos métriques actuelles sur ces parcours. Définissez alors des SLO réalistes mais un peu ambitieux. Mettez en place l&amp;rsquo;alerting quand vous êtes en train de consommer votre error budget. Et enfin, itérez ! Les SLO ne sont pas gravés dans le marbre.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;espère que cet article vous aura donné envie de creuser ces concepts ! Les SLO/SLI/Error Budget ne sont pas juste des buzzwords, c&amp;rsquo;est vraiment un changement de paradigme dans la façon d&amp;rsquo;appréhender la fiabilité.&lt;/p&gt;
&lt;p&gt;Et le plus beau, c&amp;rsquo;est que ça marche autant pour une startup avec 3 développeurs que pour une GAFAM avec 10000 ingénieurs. L&amp;rsquo;important, c&amp;rsquo;est de commencer simple et d&amp;rsquo;itérer.&lt;/p&gt;
&lt;p&gt;Pour aller plus loin, je vous recommande chaudement le &lt;a class="link" href="https://sre.google/sre-book/service-level-objectives/" target="_blank" rel="noopener"
&gt;SRE Book de Google&lt;/a&gt; (gratuit !) et leur &lt;a class="link" href="https://cloud.google.com/blog/products/management-tools/practical-guide-to-setting-slos" target="_blank" rel="noopener"
&gt;guide pratique pour définir des SLO&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Et si vous voulez approfondir le sujet SRE en général, n&amp;rsquo;hésitez pas à jeter un œil aux &lt;a class="link" href="https://blog.zwindler.fr/talks/2022-sre-sre-partout/index.html" &gt;slides de mon talk de 2022&lt;/a&gt; ;-).&lt;/p&gt;
&lt;p&gt;Bon monitoring !&lt;/p&gt;</description></item><item><title>Tutoriel Proxmox VE 8 - SDN en mode VXLAN avec des machines sur Internet</title><link>https://blog.zwindler.fr/2025/03/30/tutoriel-sdn-vxlan-proxmoxve-8/</link><pubDate>Sun, 30 Mar 2025 12:30:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/03/30/tutoriel-sdn-vxlan-proxmoxve-8/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/03/vxlan2.webp" alt="Featured image of post Tutoriel Proxmox VE 8 - SDN en mode VXLAN avec des machines sur Internet" /&gt;&lt;h2 id="préambule"&gt;Préambule
&lt;/h2&gt;&lt;p&gt;Ce tutoriel est un genre de &lt;em&gt;hors série&lt;/em&gt; dans ma suite d&amp;rsquo;articles sur Proxmox VE 8.&lt;/p&gt;
&lt;p&gt;Dans la &lt;a class="link" href="https://blog.zwindler.fr/2025/03/25/deploiement-d-un-cluster-proxmox-ve-8-part-3/" &gt;troisième partie qui est sortie il y a quelques jours&lt;/a&gt;, on s&amp;rsquo;est arrêté sur un cluster de machines distantes sur Internet.&lt;/p&gt;
&lt;p&gt;Elles ne sont pas dans le même LAN, et pour tout dire, elles sont même distantes d&amp;rsquo;environ 100ms (et ça marche très bien).&lt;/p&gt;
&lt;p&gt;A la fin de l&amp;rsquo;article, ça fonctionne, on a bien un réseau unique sur nos deux machines, avec un IPAM et un DHCP fonctionnel des deux côtés. Mais pour autant, les machines virtuelles qui sont sur des hôtes différents ne peuvent pas se parler directement et c&amp;rsquo;est quand même un peu dommage de ne pas avoir été au bout de l&amp;rsquo;exercice.&lt;/p&gt;
&lt;p&gt;Plutôt que de réécrire cette partie, j&amp;rsquo;ai décidé de faire un petit aparté où je termine le travaille et teste la fonction VXLAN du SDN de proxmox VE, parce que OUI, ça peut fonctionner jusqu&amp;rsquo;au bout.&lt;/p&gt;
&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;p&gt;Je pars du principe que vous avez déjà deux (ou plus) machines au sein d&amp;rsquo;un même cluster Proxmox VE 8.&lt;/p&gt;
&lt;p&gt;Si ce n&amp;rsquo;est pas le cas, vous êtes partis pour la lecture de ma suite d&amp;rsquo;articles (en cours d&amp;rsquo;écriture) sur le sujet (have fun!) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/01/27/deploiement-d-un-cluster-proxmox-ve-8-part-1" &gt;Déploiement d&amp;rsquo;un cluster Proxmox VE 8 sur des serveurs dédiés (1/4)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ces machines ne sont pas dans le même LAN (sinon pas forcément besoin de s&amp;rsquo;embêter avec du VXLAN, le &lt;a class="link" href="https://blog.zwindler.fr/2025/03/25/deploiement-d-un-cluster-proxmox-ve-8-part-3/" &gt;SDN Simple peut suffire, comme on a fait dans la partie 3&lt;/a&gt;)&lt;/p&gt;
&lt;h2 id="problème"&gt;Problème
&lt;/h2&gt;&lt;p&gt;Ce qu&amp;rsquo;on veut faire, c&amp;rsquo;est créer un SDN qui passe par Internet, au point que les machines virtuelles croient être sur le même LAN.&lt;/p&gt;
&lt;p&gt;On ne peut pas faire ça avec le SDN Simple, les VMs d&amp;rsquo;un hôte ne peuvent pas contacter les VMs d&amp;rsquo;un autre hôte distant sur Internet, même en étant dans le même SDN Simple et avec le même plan d&amp;rsquo;adressage.&lt;/p&gt;
&lt;p&gt;Mais si on lit un peu plus loin dans la &lt;a class="link" href="https://pve.proxmox.com/pve-docs/chapter-pvesdn.html" target="_blank" rel="noopener"
&gt;doc de PVE sur le SDN&lt;/a&gt;, on voit que c&amp;rsquo;est précisément le usecase mis en avant pour l&amp;rsquo;utilisation des SDN de type VXLAN :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The VXLAN plugin establishes a tunnel (overlay) on top of an existing network (underlay). This encapsulates layer 2 Ethernet frames within layer 4 UDP datagrams&amp;hellip;
[&amp;hellip;]
You can, for example, create a VXLAN overlay network on top of public internet, appearing to the VMs as if they share the same local Layer 2 network&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Petit souci cependant, en vrai, il ne vaut mieux pas le faire, car les trames ne sont pas chiffrées, et sur Internet, ce n&amp;rsquo;est vraiment pas la meilleure idée du monde&amp;hellip;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Warning VXLAN on its own does does not provide any encryption. When joining multiple sites via VXLAN, make sure to establish a secure connection between the site, for example by using a site-to-site VPN.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;En alternative, on aurait pu partir directement sur la version la plus complète / complexe du SDN de Proxmox VE : les &lt;strong&gt;EVPN Zones&lt;/strong&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The EVPN zone creates a routable Layer 3 network, capable of spanning across multiple clusters. This is achieved by establishing a VPN and utilizing BGP as the routing protocol.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Mais je n&amp;rsquo;ai pas envie de tremper avec BGP ici et je veux rester sur un truc simple. Cependant, c&amp;rsquo;est probablement l&amp;rsquo;option la plus &amp;ldquo;propre&amp;rdquo; pour un setup de production.&lt;/p&gt;
&lt;h2 id="vxlan-donc-mais-avec-un-vpn"&gt;VXLAN donc, mais avec un VPN
&lt;/h2&gt;&lt;p&gt;Je ne vais pas me prendre la tête, on va monter un petit VPN avec wireguard à la mano. Il y a des tonnes d&amp;rsquo;outils de VPN sur le marché et des tonnes d&amp;rsquo;outils pour se faciliter la vie. Si vous voulez plus d&amp;rsquo;info sur Wireguard, je suis sûr que vous en trouverez plein, je mets juste ici le strict minimum pour que ça &amp;ldquo;juste marche&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Sur les deux serveurs, installez wireguard, qui devraient être dans les dépôts debian 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;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt install -y wireguard
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Générez un couple de clé &lt;strong&gt;sur chaque machine&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;wg genkey &lt;span class="p"&gt;|&lt;/span&gt; tee privatekey &lt;span class="p"&gt;|&lt;/span&gt; wg pubkey &amp;gt; publickey
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Petite gymnastique mentale, on va écrire un fichier de configuration avec pour chaque serveur, la clé privée du serveur (fichier &lt;code&gt;privatekey&lt;/code&gt;) sur lequel on est, mais la clé publique (fichier &lt;code&gt;publickey&lt;/code&gt;) &lt;strong&gt;du serveur distant&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;(Quand on y réfléchit, c&amp;rsquo;est évidement, mais je préfère insister)&lt;/p&gt;
&lt;p&gt;Et il faut évidemment changer les adresses et les interfaces réseaux en fonction du serveur sur lequel on est de manière à ne pas avoir de conflit.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;vi /etc/wireguard/wg0.conf
[Interface]
Address = 10.10.30.1/24
ListenPort = 51820
PrivateKey = &amp;lt;clé privée du serveur local&amp;gt;
PostUp = iptables -A FORWARD -i wg0 -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o &amp;lt;nom d&amp;#39;interface réseau&amp;gt; -j MASQUERADE
PostDown = iptables -D FORWARD -i wg0 -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o &amp;lt;nom d&amp;#39;interface réseau&amp;gt; -j MASQUERADE
[Peer]
PublicKey = &amp;lt;clé publique du serveur distant&amp;gt;
Endpoint = &amp;lt;IP publique du serveur distant&amp;gt;:51820
AllowedIPs = 10.10.30.2/32
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Note : vous pouvez aussi ajouter plus de nodes dans le VPN, il suffit d&amp;rsquo;ajouter plus de &amp;ldquo;Peer&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;On termine en démarrant le VPN et en vérifiant que les nodes se parlent entre eux :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl &lt;span class="nb"&gt;enable&lt;/span&gt; wg-quick@wg0 --now
&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;ping 10.10.30.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ping 10.10.30.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="configuration-du-sdn"&gt;Configuration du SDN
&lt;/h2&gt;&lt;p&gt;Normalement, si vous avez suivi les articles précédents, vous avez déjà les prérequis, mais pour ceux qui ne l&amp;rsquo;auraient pas fait, je remets que dans le fichier /etc/network/interfaces, il est nécessaire d&amp;rsquo;ajouter en fin de fichier la ligne suivante (suivi d&amp;rsquo;un petit &lt;code&gt;systemctl restart networking&lt;/code&gt;)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;source /etc/network/interfaces.d/*
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Ensuite, dans le menu Datacenter, ouvrir le sous menu SDN.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/sdn1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Dans le menu SDN/Zones, ajouter une zone de type &amp;ldquo;VXLan&amp;rdquo;. Deux points importants ici :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;on doit renseigner la liste de tous les &amp;ldquo;peers&amp;rdquo;, ici les IPs de nos VPNs donc 10.10.30.1 et 10.10.30.2&lt;/li&gt;
&lt;li&gt;on va devoir adapter la MTU. Par défaut, une trame, c&amp;rsquo;est 1500. Sauf que wireguard pour son encapsulation a diminué la MTU à 1420 et que l&amp;rsquo;encapsulation VXLAN en prend 50. On doit donc mettre 1370 pour éviter au mieux de la fragmentation, au pire, des paquets dropés&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/vxlan2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Si vous venez de lire l&amp;rsquo;article précédent, vous allez remarquer qu&amp;rsquo;ici, il n&amp;rsquo;y a pas de &amp;ldquo;coche&amp;rdquo; pour activer le DHCP sur la zone. C&amp;rsquo;est malheureusement parce que ce n&amp;rsquo;est pas disponible pour l&amp;rsquo;instant.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Currently only Simple Zones have support for automatic DHCP&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Source : &lt;a class="link" href="https://pve.proxmox.com/pve-docs/chapter-pvesdn.html#pvesdn_config_dhcp" target="_blank" rel="noopener"
&gt;pve.proxmox.com/pve-docs/chapter-pvesdn.html#pvesdn_config_dhcp&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/hulk_sad.gif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Une fois que vous avez validé, allez dans le sous menu SDN/VNets, cliquez sur le bouton &amp;ldquo;Create&amp;rdquo; pour créer un réseau virtuel, puis le sélectionner une fois créé pour créer un sous réseau.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/vxlan4.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/vxlan5.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Une fois de plus, n&amp;rsquo;oubliez d&amp;rsquo;aller voir la partie DHCP, pour déclarer le range pour notre futur DHCP.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/vxlan6.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Une fois qu&amp;rsquo;on a bien tout validé, on voit qu&amp;rsquo;à côté de toutes ces nouvelles choses qu&amp;rsquo;on vient de créer, il y a une icône avec deux flèches jaunes et un statut à &amp;ldquo;New&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Pour déployer ces modifications, on doit retourner dans le menu SDN du début, et cliquer sur Apply. Deux lignes &amp;ldquo;vxlan1&amp;rdquo; (une pour chaque serveur, en vrai) devraient apparaitre.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/vxlan7.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="cest-tout-"&gt;C&amp;rsquo;est tout ?
&lt;/h2&gt;&lt;p&gt;Et oui, c&amp;rsquo;est tout, franchement, j&amp;rsquo;aurai vraiment dû insister un peu avant de poster mon article précédent&amp;hellip;&lt;/p&gt;
&lt;p&gt;Pour s&amp;rsquo;en convaincre, il suffit de pop deux containers LXC (sans DHCP 🥲), un sur chaque serveur, et de voir s&amp;rsquo;ils se pingent.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/vxlan-test.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;It works!&lt;/p&gt;
&lt;p&gt;Bon, ben&amp;hellip; il ne me reste plus qu&amp;rsquo;à migrer toutes mes machines existantes sur le vnet VXLAN pour avoir enfin un cluster où les VMs se voient entre elles !!&lt;/p&gt;</description></item><item><title>Déploiement d'un cluster Proxmox VE 8 sur des serveurs dédiés (3/4)</title><link>https://blog.zwindler.fr/2025/03/25/deploiement-d-un-cluster-proxmox-ve-8-part-3/</link><pubDate>Tue, 25 Mar 2025 12:30:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/03/25/deploiement-d-un-cluster-proxmox-ve-8-part-3/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/03/sdn2.webp" alt="Featured image of post Déploiement d'un cluster Proxmox VE 8 sur des serveurs dédiés (3/4)" /&gt;&lt;p&gt;Note - cet article fait partie d&amp;rsquo;une suite d&amp;rsquo;articles :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/01/27/deploiement-d-un-cluster-proxmox-ve-8-part-1" &gt;Déploiement d&amp;rsquo;un cluster Proxmox VE 8 sur des serveurs dédiés (1/4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/02/17/deploiement-d-un-cluster-proxmox-ve-8-part-2/" &gt;Déploiement d&amp;rsquo;un cluster Proxmox VE 8 sur des serveurs dédiés (2/4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/03/25/deploiement-d-un-cluster-proxmox-ve-8-part-3/" &gt;Déploiement d&amp;rsquo;un cluster Proxmox VE 8 sur des serveurs dédiés (3/4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/03/30/tutoriel-sdn-vxlan-proxmoxve-8/" target="_blank" rel="noopener"
&gt;Hors série - SDN en mode VXLAN avec des machines sur Internet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Et &lt;em&gt;au moins&lt;/em&gt; un autre à suivre&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="on-reprend-de-là-où-on-en-était--premiers-pas-dans-lui"&gt;On reprend de là où on en était : premiers pas dans l&amp;rsquo;UI
&lt;/h2&gt;&lt;p&gt;Dans le tutoriel précédent (que je vous invite à lire, si ça n&amp;rsquo;a pas été fait, sinon ça ne sera pas clair), on a configuré le stockage, le réseau et on a déployé notre première machine virtuelle pour nous assurer que tout fonctionne. Mais il reste encore pas mal de choses à explorer, notamment la mise en cluster, le SDN de proxmox VE, le firewall intégré, les sauvegardes, le monitoring&amp;hellip;&lt;/p&gt;
&lt;p&gt;On reprend donc de là où on en était, et on va installer un autre serveur (difficile de faire un cluster avec une seule machine).&lt;/p&gt;
&lt;p&gt;Ce serveur a été réinstallé avec les mêmes procédures que le serveur dont je vous parle dans les parties 1 et 2, à ceci près que nous n&amp;rsquo;avons pas besoin de créer le bridge (vmbr0) car nous allons utiliser le SDN !!&lt;/p&gt;
&lt;p&gt;Idéalement, il faut que toutes les machines du cluster aient les mêmes pools de storage puisque je le rappelle, cette configuration est censée être commune à tous les serveurs, car gérée au niveau &amp;ldquo;Datacenter&amp;rdquo; de notre cluster (même si on peut configurer des exceptions).&lt;/p&gt;
&lt;h2 id="création-du-cluster"&gt;Création du cluster
&lt;/h2&gt;&lt;p&gt;La mise en cluster de machines Proxmox VE est un sujet que j&amp;rsquo;ai abondamment traité sur le blog, au point qu&amp;rsquo;il est d&amp;rsquo;ailleurs surtout connu de certains d&amp;rsquo;entre vous surtout pour ces articles ;-) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2019/07/22/proxmox-en-5-min-ansible-tinc/" &gt;Un cluster Proxmox VE en 5 minutes avec Ansible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2019/08/20/cluster-proxmox-ve-v6-cette-fois-ci/" &gt;Cluster Proxmox VE, v6 cette fois-ci !&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2019/10/11/un-cluster-proxmox-ve-avec-seulement-2-machines/" &gt;Un cluster Proxmox VE avec seulement 2 machines !&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2017/09/19/tutoriel-demonter-proprement-cluster-proxmox-ve/" &gt;[Tutoriel] Démonter proprement un cluster Proxmox VE&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Je ne vais donc pas y passer trop de temps. Sachez juste que :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;c&amp;rsquo;est plus facile à faire depuis la version 6 parce qu&amp;rsquo;il n&amp;rsquo;y a plus besoin de monter un VPN entre les serveurs (corosync v3)&lt;/li&gt;
&lt;li&gt;ça peut se faire en ligne de commande&lt;/li&gt;
&lt;li&gt;dans tous les cas, il faut se loguer en root &lt;strong&gt;avec mot de passe&lt;/strong&gt; lors de la mise en cluster&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Très brièvement donc, on va sur le premier serveur, dans la &lt;strong&gt;Server View&lt;/strong&gt; en haut à gauche, on sélectionne notre &lt;strong&gt;Datacenter&lt;/strong&gt;, puis dans le menu &lt;strong&gt;Cluster&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/create-cluster-1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;On clique sur le bouton &lt;strong&gt;Create Cluster&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/create-cluster-2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Note : ici, c&amp;rsquo;est simple, je n&amp;rsquo;ai que ma carte réseau, donc il n&amp;rsquo;y a pas de doute sur l&amp;rsquo;interface à utiliser. Cependant, dans le cas d&amp;rsquo;un serveur avec plus d&amp;rsquo;interfaces et plus de bridges (ou des VPNs comme on a dû le faire jusqu&amp;rsquo;en PVE 6), il faut bien choisir quelle interface sert au clustering.&lt;/p&gt;
&lt;p&gt;On valide le formulaire :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/create-cluster-3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;À partir de ce moment-là, le serveur n&amp;rsquo;est plus en &amp;ldquo;standalone&amp;rdquo; et dispose de services systemd supplémentaires.&lt;/p&gt;
&lt;p&gt;On a notamment un dossier /etc/pve qui est synchronisé entre tous les nodes (el famoso &lt;a class="link" href="https://pve.proxmox.com/wiki/Proxmox_Cluster_File_System_%28pmxcfs%29" target="_blank" rel="noopener"
&gt;Proxmox Cluster File System&lt;/a&gt;) avec la configuration de chaque node et les fichiers de configuration des machines virtuelles. On a aussi le logiciel corosync, qui va régulièrement contacter tous les nodes du cluster pour s&amp;rsquo;assurer que tout le monde est bien vivant / accessible.&lt;/p&gt;
&lt;h2 id="rejoindre-le-cluster"&gt;Rejoindre le cluster
&lt;/h2&gt;&lt;p&gt;Si on retourne voir le menu &amp;ldquo;Cluster&amp;rdquo;, on a maintenant des informations qui s&amp;rsquo;affichent sur notre cluster, à savoir son nombre de nœuds (ici un seul, normal). Cliquer sur le bouton &lt;strong&gt;Join information&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Une popup s&amp;rsquo;ouvre avec plusieurs informations à copier-coller qui nous seront demandées dans le menu de l&amp;rsquo;autre serveur, celui qui va rejoindre le cluster :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/create-cluster-4.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;On se connecte donc sur l&amp;rsquo;interface graphique de notre second serveur Proxmox VE (mon Atom dans l&amp;rsquo;exemple), on va dans le même menu, mais au lieu de créer un nouveau cluster, on clique sur le bouton &lt;strong&gt;Join Cluster&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/join-cluster.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Petit coup de flip normalement, une fois que le node va rejoindre le cluster, vous allez perdre la main sur l&amp;rsquo;interface web et avoir un message d&amp;rsquo;erreur &amp;ldquo;401&amp;rdquo;. C&amp;rsquo;est &amp;ldquo;normal&amp;rdquo;, quand le serveur rentre dans le cluster, des services sont redémarrés et on passe d&amp;rsquo;un mode standalone à un mode clusterisé&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/join-cluster2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Pas de panique, un F5 devrait résoudre l&amp;rsquo;affaire (ou alors, connectez-vous au &amp;ldquo;premier&amp;rdquo; serveur du cluster).&lt;/p&gt;
&lt;p&gt;On a maintenant un beau cluster :)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/pve-cluster.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="split-brain"&gt;Split brain
&lt;/h2&gt;&lt;p&gt;Notez par contre que dans mon exemple, on n&amp;rsquo;a que 2 nodes, ce qui est plus que déconseillé quand on fait un cluster Linux (en vrai, un cluster en général). Si jamais le lien réseau tombe entre nos deux machines, elles n&amp;rsquo;auront aucun moyen de savoir si c&amp;rsquo;est un problème réseau (&lt;em&gt;que se apelerio split brain&lt;/em&gt;) ou une panne d&amp;rsquo;un des nodes.&lt;/p&gt;
&lt;p&gt;Pour éviter de tout péter en redémarrant des VMs des deux côtés (le cas le plus catastrophique dans un split brain), lorsque le quorum ne sera plus atteint, Proxmox VE passe les nodes qui n&amp;rsquo;ont pas de majorité en (quasi) &amp;ldquo;lecture seule&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Et au cas où ce n&amp;rsquo;est pas clair, le quorum c&amp;rsquo;est la moitié des nodes +1, donc ici : 2/2+1 = 2&amp;hellip; donc dès qu&amp;rsquo;on a plus un des deux nodes, on a plus le quorum.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Simple, basique.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Du coup, si jamais on coupe un node ou la communication entre les deux, vos VMs encore actives continueront à fonctionner (j&amp;rsquo;ai débattu de ça sur LinkedIn il y a quelques jours).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Par contre&lt;/strong&gt;, il sera impossible de modifier la configuration du cluster, démarrer ou créer des VMs, etc.&lt;/p&gt;
&lt;p&gt;La seule chose qu&amp;rsquo;on peut éventuellement faire qui ne soit pas &amp;ldquo;read only&amp;rdquo;, c&amp;rsquo;est arrêter des VMs, car ça n&amp;rsquo;induit aucun risque pour la cohérence des données. Mais vous ne pourrez pas les rallumer, vous êtes prévenus&amp;hellip;&lt;/p&gt;
&lt;p&gt;Pour la petite histoire, le débat sur LinkedIn, c&amp;rsquo;était &amp;ldquo;est-ce que ProxmoxVE coupe toutes les VMs en cas de split brain&amp;rdquo; et la réponse est évidemment &lt;strong&gt;non&lt;/strong&gt;. Par contre, la plupart des actions sont bien bloquées.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/quorum-ko1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/quorum-ko2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Dans tous les cas&lt;/strong&gt;, pour éviter ça, le mieux est d&amp;rsquo;avoir un nombre impair de nodes pour que le split brain ne puisse jamais arriver, ou éventuellement d&amp;rsquo;ajouter un vote complémentaire à l&amp;rsquo;aide d&amp;rsquo;une instance servant juste d&amp;rsquo;arbitre comme c&amp;rsquo;est expliqué dans mon vieil article de 2019 ou dans la doc officielle :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2019/10/11/un-cluster-proxmox-ve-avec-seulement-2-machines/" target="_blank" rel="noopener"
&gt;Un cluster Proxmox VE avec seulement 2 machines !&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://pve.proxmox.com/wiki/Cluster_Manager#_corosync_external_vote_support" target="_blank" rel="noopener"
&gt;Proxmox Wiki - Corosync External Vote Support&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="configuration-du-sdn"&gt;Configuration du SDN
&lt;/h2&gt;&lt;p&gt;Bon, c&amp;rsquo;est cool, on a un cluster de deux machines. Mais dans la configuration actuelle, la VM qu&amp;rsquo;on a créée dans la partie précédente ne peut pas migrer facilement d&amp;rsquo;un serveur à l&amp;rsquo;autre. En effet, on l&amp;rsquo;a attachée sur un bridge Linux (vmbr0) qui a son propre plan d&amp;rsquo;adressage réseau.&lt;/p&gt;
&lt;p&gt;En cas de migration, il faudrait potentiellement démarrer la machine, changer l&amp;rsquo;adresse IP, revoir les iptables / règles de firewalling. C&amp;rsquo;est ce que je faisais jusqu&amp;rsquo;à présent, et dans le cas d&amp;rsquo;un PRA pour une infra de lab et un blog perso, c&amp;rsquo;est &amp;ldquo;OK&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Une grosse nouveauté de la version 8.1, est l&amp;rsquo;activation d&amp;rsquo;un module &amp;ldquo;SDN&amp;rdquo; (Software Defined Network) qui est présent en test par les développeurs et quelques early adopters depuis la version 6.X, qui va nous permettre de gérer ça de manière centralisée depuis l&amp;rsquo;interface. Si vous voulez aller bouquiner la doc officielle, c&amp;rsquo;est ici :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://pve.proxmox.com/pve-docs/chapter-pvesdn.html" target="_blank" rel="noopener"
&gt;pve.proxmox.com/pve-docs/chapter-pvesdn.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le seul prérequis qui va nous manquer est dnsmasq pour le DHCP, et la doc nous dit de lancer les commandes suivantes sur nos machines :&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;apt update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt install dnsmasq
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# disable default instance&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl disable --now dnsmasq
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Et dans le fichier /etc/network/interfaces, il est aussi nécessaire d&amp;rsquo;ajouter en fin de fichier la ligne (suivi d&amp;rsquo;un petit &lt;code&gt;systemctl restart networking&lt;/code&gt;)&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;source /etc/network/interfaces.d/*
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Dans le menu Datacenter, ouvrir le sous menu SDN. On remarque qu&amp;rsquo;il existe déjà des SDNs de déclarés, mais il s&amp;rsquo;agit en fait des réseaux par défaut qu&amp;rsquo;on a déclaré dans les articles précédents :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/sdn1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Dans le menu SDN/Zones, ajouter une zone de type &amp;ldquo;Simple&amp;rdquo;. Il existe des zones plus complexes qui seront utiles dans des contextes de production avec des LANs non triviaux (VLAN, VXLan) mais dans notre cas, la Simple est suffisante.&lt;/p&gt;
&lt;p&gt;Note : ne pas oublier de cocher la case &lt;strong&gt;advanced&lt;/strong&gt; pour pouvoir activer le DHCP. &lt;strong&gt;OUI on va enfin avoir du DHCP pour nos machines virtuelles et nos containers&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/sdn2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Une fois que vous avez validé, allez dans le sous menu SDN/VNets, cliquez sur le bouton &amp;ldquo;Create&amp;rdquo; pour créer un réseau virtuel, puis le sélectionner une fois créé pour créer un sous réseau.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/sdn3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/sdn4.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Une fois de plus, n&amp;rsquo;oubliez d&amp;rsquo;aller voir la partie DHCP, pour déclarer le range pour notre futur DHCP.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/sdn5.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Une fois qu&amp;rsquo;on a bien tout validé, on voit qu&amp;rsquo;à côté de toutes ces nouvelles choses qu&amp;rsquo;on vient de créer, il y a une icône avec deux flèches jaunes et un statut à &amp;ldquo;New&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/sdn6.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Pour déployer ces modifications, on doit retourner dans le menu SDN du début, et cliquer sur Apply. Deux lignes &amp;ldquo;sdn1&amp;rdquo; (une pour chaque serveur, en vrai) devraient apparaitre.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/sdn7.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Fait rigolo, le SDN de Proxmox VE va rajouter des règles iptables qui devraient vous dire quelque chose si vous avez déroulé les précédents articles :&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;#version:5
auto vnet1
iface vnet1
address 10.10.20.1/24
post-up iptables -t nat -A POSTROUTING -s &amp;#39;10.10.20.0/24&amp;#39; -o vmbr0 -j SNAT --to-source 203.0.113.1
post-down iptables -t nat -D POSTROUTING -s &amp;#39;10.10.20.0/24&amp;#39; -o vmbr0 -j SNAT --to-source 203.0.113.1
post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone 1
post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone 1
bridge_ports none
bridge_stp off
bridge_fd 0
ip-forward on
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Et si maintenant, je recrée une autre machine virtuelle, mais que cette fois-ci au lieu de &lt;strong&gt;vmbr0&lt;/strong&gt;, je choisis &lt;strong&gt;vnet1&lt;/strong&gt;, je n&amp;rsquo;ai pas besoin de saisir l&amp;rsquo;adresse IP (puisque j&amp;rsquo;ai le SDN qui sert le DHCP) et elle a accès à Internet sans configuration supplémentaire.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/sdn8.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/03/sdn9.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Avec cette configuration cependant (SDN en mode &lt;strong&gt;Simple Zone&lt;/strong&gt;), il ne sera toujours pas possible de faire communiquer les machines virtuelles entre elles. Ça sera possible avec des configurations plus complexes :&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can, for example, create a VXLAN overlay network on top of public internet, appearing to the VMs as if they share the same local Layer 2 network&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://pve.proxmox.com/pve-docs/chapter-pvesdn.html#pvesdn_zone_plugin_vxlan" target="_blank" rel="noopener"
&gt;VXLan Zones&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://pve.proxmox.com/pve-docs/chapter-pvesdn.html#pvesdn_zone_plugin_evpn" target="_blank" rel="noopener"
&gt;EVPN Zones&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note : j&amp;rsquo;ai exploré les VXLAN depuis, et &lt;a class="link" href="https://blog.zwindler.fr/2025/03/30/tutoriel-sdn-vxlan-proxmoxve-8" &gt;j&amp;rsquo;ai écrit un article complémentaire ici&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Je n&amp;rsquo;ai toujours pas terminé cette série, car j&amp;rsquo;ai encore des choses à dire sur la réplication SDN et le firewalling de Proxmox VE, les sauvegardes, le monitoring, mais une fois de plus, je dépasse déjà les 12000 signes. Il est donc temps de publier l&amp;rsquo;article et de commencer à écrire le suivant.&lt;/p&gt;
&lt;p&gt;Et en attendant, have fun :)&lt;/p&gt;</description></item><item><title>Déploiement d'un cluster Proxmox VE 8 sur des serveurs dédiés (2/3)</title><link>https://blog.zwindler.fr/2025/02/17/deploiement-d-un-cluster-proxmox-ve-8-part-2/</link><pubDate>Mon, 17 Feb 2025 10:30:00 +0200</pubDate><guid>https://blog.zwindler.fr/2025/02/17/deploiement-d-un-cluster-proxmox-ve-8-part-2/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/02/proxmox8.webp" alt="Featured image of post Déploiement d'un cluster Proxmox VE 8 sur des serveurs dédiés (2/3)" /&gt;&lt;p&gt;Note - cet article fait partie d&amp;rsquo;une suite d&amp;rsquo;articles :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/01/27/deploiement-d-un-cluster-proxmox-ve-8-part-1" &gt;Déploiement d&amp;rsquo;un cluster Proxmox VE 8 sur des serveurs dédiés (1/4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/02/17/deploiement-d-un-cluster-proxmox-ve-8-part-2/" &gt;Déploiement d&amp;rsquo;un cluster Proxmox VE 8 sur des serveurs dédiés (2/4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/03/25/deploiement-d-un-cluster-proxmox-ve-8-part-3/" &gt;Déploiement d&amp;rsquo;un cluster Proxmox VE 8 sur des serveurs dédiés (3/4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/03/30/tutoriel-sdn-vxlan-proxmoxve-8/" target="_blank" rel="noopener"
&gt;Hors série - SDN en mode VXLAN avec des machines sur Internet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Et &lt;em&gt;au moins&lt;/em&gt; un autre à suivre&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="on-reprend-de-là-où-on-en-était--premiers-pas-dans-lui"&gt;On reprend de là où on en était : premiers pas dans l&amp;rsquo;UI
&lt;/h2&gt;&lt;p&gt;Dans le tutoriel précédent (que je vous invite à lire, si ça n&amp;rsquo;a pas été fait, sinon ça ne sera pas clair), on a choisi un serveur physique dédié chez un provider type OVHCloud ou Hetzner. On a ensuite installé Proxmox VE dans la dernière version (8.x), configuré quelques options de sécurité de base, ajouté des utilisateurs et des groupes.&lt;/p&gt;
&lt;p&gt;On peut maintenant essayer de se connecter à l&amp;rsquo;UI. La première chose qui devrait vous frapper (💥 aïe !) c&amp;rsquo;est que la page d&amp;rsquo;administration est en HTTPS avec un certificat autosigné. On va commencer par régler ça.&lt;/p&gt;
&lt;p&gt;Un truc assez frustrant avec Proxmox VE (mais c&amp;rsquo;est probablement pour de &amp;ldquo;bonnes raisons&amp;rdquo; que j&amp;rsquo;ignore) c&amp;rsquo;est que certaines opérations ne sont pas possibles à réaliser dans l&amp;rsquo;UI si vous vous connectez avec le compte admin zwindler@pve que nous avons créé dans le blog post précédent.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est typiquement vrai pour les mises à jour, la mise en cluster et la configuration et&amp;hellip; pour &lt;strong&gt;la configuration des certificats&lt;/strong&gt; (liste non exhaustive). Ici, on va donc devoir pour l&amp;rsquo;instant se connecter en root@pam et non pas en zwindler@pve&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/auth-pam.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Notez bien qu&amp;rsquo;il y a (pour l&amp;rsquo;instant) 2 &amp;ldquo;realms&amp;rdquo;, Linux PAM (pour root) et Proxmox VE Auth (pour l&amp;rsquo;admin).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/auth-pve.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Dans la barre de droite, on retrouve notre serveur, qui est pour l&amp;rsquo;instant tout seul dans son datacenter (on y reviendra plus tard). Il y a plusieurs vues dans l&amp;rsquo;UI de Proxmox VE, certaines choses sont difficiles à trouver quand on n&amp;rsquo;a pas ça en tête. Ici, je suis dans la &amp;ldquo;server view&amp;rdquo;, probablement la plus classique si vous venez du monde VMware. Dans ce menu, vous trouverez vos hyperviseurs et on pourra interagir avec chacun d&amp;rsquo;entre eux, comment ils sont configurés.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/pve-first-login.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Une fois notre serveur sélectionné, on a donc le menu principal qui affiche de nombreux menus. Sélectionner &lt;strong&gt;System / Certificates&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/pve-certs-01.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;On a de la chance, Proxmox VE est livré avec un module pour commander tout seul des certificats &lt;a class="link" href="https://letsencrypt.org/fr/" target="_blank" rel="noopener"
&gt;Let&amp;rsquo;s encrypt&lt;/a&gt;. Si vous avez votre propre certificat ça fonctionne aussi (bouton &lt;strong&gt;Upload Custom Certificate&lt;/strong&gt;), bien entendu, mais je vais partir du principe que vous n&amp;rsquo;en avez pas, comme moi.&lt;/p&gt;
&lt;p&gt;Dans la partie ACME, cliquer sur &lt;strong&gt;Add ACME Account&lt;/strong&gt; pour enregistrer votre email et accepter les Terms Of Service (TOS) de Let&amp;rsquo;s Encrypt.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/pve-certs-02.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;À partir de là, ajouter le FQDN de notre serveur en cliquant sur le bouton &lt;strong&gt;Add&lt;/strong&gt; sous ACME. Le bouton était grisé tant que nous n&amp;rsquo;avions pas ajouté de compte.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/pve-certs-03.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Maintenant, on peut enfin commander le certificat en cliquant sur &lt;strong&gt;Order Certificates Now&lt;/strong&gt;. Si tout se passe bien, le certificat sera correctement livré par Let&amp;rsquo;s Encrypt, et le serveur pve-proxy (qui sert le frontend de l&amp;rsquo;UI de Proxmox VE) devrait redémarrer, et votre page web se rafraîchir, avec un bon certificat cette fois-ci :).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/pve-certs-04.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="stockage--zfs"&gt;Stockage / ZFS
&lt;/h2&gt;&lt;p&gt;Si vous lisez souvent mon blog et que je vous dis que je suis un fan inconditionnel de ZFS, vous ne serez pas surpris.&lt;/p&gt;
&lt;p&gt;Et si vous avez bien lu l&amp;rsquo;article précédent, vous vous rappellerez que nous avons réduit la partition par défaut du template OVHcloud en LVM pour pouvoir faire une belle partition ZFS. Sauf que si on va dans la vue stockage, elle n&amp;rsquo;est malheureusement pas visible.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/hulk_sad.gif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Petite subtilité de l&amp;rsquo;UI, le menu pour ajouter du stockage se retrouve au niveau du Datacenter et non de la configuration du serveur lui-même. En théorie, la gestion du stockage doit être identique sur tous les serveurs d&amp;rsquo;un même DC dans Proxmox VE, même si on peut passer outre ce principe et mettre en place des exceptions depuis de nombreuses versions déjà.&lt;/p&gt;
&lt;p&gt;Dans &lt;strong&gt;Datacenter / Storage&lt;/strong&gt;, cliquez sur &lt;strong&gt;Add&lt;/strong&gt;, puis ZFS dans le menu déroulant qui s&amp;rsquo;affiche :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/storage-01.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/storage-02.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Sur les stockages de type ZFS, on ne peut stocker que les &amp;ldquo;Disk images&amp;rdquo; et les &amp;ldquo;Containers&amp;rdquo;, ce qui ne va pas nous arranger si nous voulons aussi stocker des ISOs, des templates ou des backups.&lt;/p&gt;
&lt;p&gt;On pourra se contenter de les stocker sur le stockage par défaut &amp;ldquo;local&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Mais on peut aussi &amp;ldquo;tricher&amp;rdquo; en se connectant sur le serveur en SSH, et en créant un dossier (/tank/data dans mon exemple), puis en créant un nouveau stockage de type Directory qui n&amp;rsquo;a pas de restriction sur les types de données.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/storage-03.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="parlons-un-peu-de-zfs"&gt;Parlons un peu de ZFS
&lt;/h2&gt;&lt;p&gt;Je me suis pris la tête un nombre incalculable de fois avec des gens qui persistent raconter n&amp;rsquo;importe quoi sur ZFS, même après leur avoir expliqué.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;C&amp;rsquo;est faux. Et archifaux. Euh&amp;hellip;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;non, il n&amp;rsquo;y a pas besoin d&amp;rsquo;avoir de la RAM ECC pour stocker des choses sur ZFS. D&amp;rsquo;abord, c&amp;rsquo;est une préconisation (un peu zélée probablement) faite par des gens qui ont conçu un filesystem extrêmement robuste. De très nombreuses prods fonctionnent avec ZFS sans RAM ECC. Ensuite, c&amp;rsquo;est surtout vrai si vous faites de la déduplication, car la perte d&amp;rsquo;un bloc peut avoir des conséquences catastrophiques dans ce genre de cas&amp;hellip;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;non, ZFS ne nécessite pas 1 Go de RAM par To de disque, là encore, c&amp;rsquo;est uniquement si vous activez la déduplication (pour stocker les tables de correspondance entre les hashes et les blocs).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En revanche, ce qui est exact, c&amp;rsquo;est que par défaut, ZFS va essayer d&amp;rsquo;utiliser 50% de la RAM disponible sur votre serveur et de s&amp;rsquo;en servir comme cache. Dans beaucoup de logiciels d&amp;rsquo;administration, y compris Proxmox VE, cette valeur va être beaucoup trop haute pour nous, car la RAM est une ressource précieuse sur un hyperviseur.&lt;/p&gt;
&lt;p&gt;Ça ne veut pas dire que cette fonctionnalité est inutile. Avoir du cache quand on a de la RAM qui ne sert à rien, c&amp;rsquo;est toujours bien. Donc, on va réduire cette valeur pour éviter des conflits de ressources entre l&amp;rsquo;optimisation des performances de notre stockage et la quantité de VMs qu&amp;rsquo;on peut héberger.&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;# Restrict to 512MB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="m"&gt;536870912&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sudo tee -a /sys/module/zfs/parameters/zfs_arc_max
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Restrict to 4GB&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="m"&gt;4294967296&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; sudo tee -a /sys/module/zfs/parameters/zfs_arc_max
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note : cette commande sera à persister d&amp;rsquo;une manière ou d&amp;rsquo;une autre&lt;/p&gt;
&lt;h2 id="du-réseau"&gt;Du réseau
&lt;/h2&gt;&lt;p&gt;Bon&amp;hellip; j&amp;rsquo;ai essayé de repousser au maximum le moment où on arrive au réseau (parce que je déteste ça) mais à un moment, il va bien falloir s&amp;rsquo;y mettre&amp;hellip;&lt;/p&gt;
&lt;p&gt;Par défaut, notre serveur Proxmox VE est installé avec un bridge Linux qui va nous permettre de partager du réseau entre l&amp;rsquo;interface physique de notre serveur et nos machines virtuelles.&lt;/p&gt;
&lt;p&gt;Ce setup fonctionne bien sur votre réseau local avec un DHCP et votre propre LAN IPv4 local ou si vous disposez d&amp;rsquo;une plage d&amp;rsquo;IP (que ce soit v6 ou v4) et de quoi les affecter aux machines virtuelles.&lt;/p&gt;
&lt;p&gt;Malheureusement dans mon cas, je n&amp;rsquo;ai qu&amp;rsquo;une IPv4 (je pourrais activer l&amp;rsquo;IPv6 mais j&amp;rsquo;aurais d&amp;rsquo;autres problématiques et je préfère rester simple ici). Si je crée des VMs et que je les affecte sur le bridge, elles ne récupèreront pas d&amp;rsquo;IP et n&amp;rsquo;auront pas accès à Internet.&lt;/p&gt;
&lt;p&gt;Il existe plusieurs façons de connecter les machines virtuelles au réseau extérieur. Si vous avez du temps, je vous conseille de lire avec attention le Wiki de Proxmox VE qui a une très bonne page qui liste les différentes possibilités.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://pve.proxmox.com/wiki/Network_Configuration" target="_blank" rel="noopener"
&gt;pve.proxmox.com/wiki/Network_Configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Dans les précédents articles sur &lt;a class="link" href="https://blog.zwindler.fr/2017/07/11/deploiment-de-proxmox-ve-5-sur-un-serveur-dedie-part-1" &gt;Proxmox VE 5&lt;/a&gt; puis &lt;a class="link" href="https://blog.zwindler.fr/2020/03/02/deploiement-de-proxmox-ve-6-pfsense-sur-un-serveur-dedie/" &gt;Proxmox VE 6&lt;/a&gt;, on avait fait des trucs compliqués à base de plusieurs bridges, de DMZ, de firewalling pfSense, et d&amp;rsquo;un script iptables de l&amp;rsquo;enfer.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2017/07/proxmox-install_simple-infra-map.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Rien que d&amp;rsquo;y penser, je suis déjà épuisé.&lt;/p&gt;
&lt;p&gt;Heureusement, il existe depuis plusieurs versions déjà un firewall intégré à Proxmox VE, ainsi qu&amp;rsquo;un SDN que je n&amp;rsquo;ai jamais pris le temps de découvrir et je pense que c&amp;rsquo;est le bon tuto pour le faire :).&lt;/p&gt;
&lt;p&gt;On va donc se référer à la documentation officielle, et choisir le mode &amp;ldquo;Masquerading (NAT) with iptables&amp;rdquo;, mais avec un setup plus simple par rapport à précédemment.&lt;/p&gt;
&lt;h2 id="danger-zone"&gt;Danger zone
&lt;/h2&gt;&lt;p&gt;La configuration de l&amp;rsquo;interface réseau peut en théorie se faire directement depuis l&amp;rsquo;UI plutôt qu&amp;rsquo;en allant modifier les fichiers de configuration (/etc/network/interfaces). Dans la vue du serveur, allez dans le menu &lt;strong&gt;System / Network&lt;/strong&gt; :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/pve-network-1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Ici, on voit que je ne vous ai pas raconté de carabistouilles, et qu&amp;rsquo;on a bien un bridge avec notre interface physique (ici enp1s0).&lt;/p&gt;
&lt;p&gt;(On a même une plage IPv6, oh là là 🙈 je n&amp;rsquo;ai aucune excuse&amp;hellip; BREEEF, on va faire comme si on n&amp;rsquo;avait pas vu.)&lt;/p&gt;
&lt;p&gt;On arrive maintenant au moment rigolo où on peut se couper la chique assez facilement. Je l&amp;rsquo;ai fait assez souvent et passer en rescue&amp;hellip;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;fun fun fun fun&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;L&amp;rsquo;idée ici, c&amp;rsquo;est donc de retirer l&amp;rsquo;interface du bridge, de configurer le réseau directement dessus, et de donner un réseau local pour nos VMs sur le bridge, tout en ajoutant les règles iptables pour faire le masquerading.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/pve-network-2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/pve-network-3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Tant qu&amp;rsquo;on ne cliquera pas sur le bouton &lt;strong&gt;Apply Configuration&lt;/strong&gt;, on ne &amp;ldquo;risque&amp;rdquo; rien.&lt;/p&gt;
&lt;p&gt;Le problème de l&amp;rsquo;UI est que nous n&amp;rsquo;allons pouvoir modifier QUE les IP, les gateway et les interfaces sur le bridge ou non. Sauf que pour réaliser le masquerading et que nos machines virtuelles accèdent à Internet, nous devons ajouter les fameuses règles iptables dont je vous parle juste avant et on ne peut pas le faire depuis l&amp;rsquo;UI.&lt;/p&gt;
&lt;p&gt;Il va falloir rajouter les cinq dernières lignes dans la configuration du vmbr0&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;cat /etc/network/interfaces
&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;auto vmbr0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;iface vmbr0 inet static
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; address 10.10.10.1/24
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bridge-ports none
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bridge-stp off
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bridge-fd &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;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# à ajouter pour que le masquerading fonctionne&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; post-up &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &amp;gt; /proc/sys/net/ipv4/ip_forward
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; post-up iptables -t nat -A POSTROUTING -s &lt;span class="s1"&gt;&amp;#39;10.10.10.0/24&amp;#39;&lt;/span&gt; -o enp1s0 -j MASQUERADE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; post-down iptables -t nat -D POSTROUTING -s &lt;span class="s1"&gt;&amp;#39;10.10.10.0/24&amp;#39;&lt;/span&gt; -o enp1s0 -j MASQUERADE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; post-up iptables -t raw -I PREROUTING -i fwbr+ -j CT --zone &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; post-down iptables -t raw -D PREROUTING -i fwbr+ -j CT --zone &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Attention&lt;/strong&gt; : le point le plus important est de bien vérifier que l&amp;rsquo;interface de sortie (-o) a bien le bon nom. Ici, mon interface physique connectée à Internet est &lt;strong&gt;enp1s0&lt;/strong&gt;, mais si la vôtre a un autre nom, il faut adapter.&lt;/p&gt;
&lt;p&gt;De mon point de vue, l&amp;rsquo;UI n&amp;rsquo;est tout de même pas complètement inutile, car elle nous permet de faire le changement le plus délicat (échange des IP) avec le diff visuel et le bouton &amp;ldquo;apply config&amp;rdquo;. Les scripts post-up ont peu de chances de nous couper l&amp;rsquo;accès.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/pve-network-4.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="premier-test"&gt;Premier test
&lt;/h2&gt;&lt;p&gt;On est loin d&amp;rsquo;avoir fini (ça ne rentrera pas dans cet article, de toute façon, donc autant brûler les étapes&amp;hellip;). Mais je suis sûr que vous êtes impatients de lancer une VM et on devrait avoir quelque chose de fonctionnel, bien qu&amp;rsquo;incomplet.&lt;/p&gt;
&lt;p&gt;On va donc faire un petit test rapide de notre setup pour vérifier qu&amp;rsquo;on a bien un hyperviseur fonctionnel &lt;em&gt;a minima&lt;/em&gt; (c&amp;rsquo;est-à-dire sans clustering, sans SDN, sans firewall, sans monitoring, sans sauvegarde&amp;hellip; sans rien, en fait).&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai commencé à faire joujou avec &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=talos" target="_blank" rel="noopener"
&gt;Talos Linux&lt;/a&gt; comme distribution Linux pour Kubernetes. Pour faire simple, je vais donc lancer l&amp;rsquo;install d&amp;rsquo;un Node Talos. Si elle arrive à s&amp;rsquo;enrôler dans l&amp;rsquo;interface Omni, ça veut dire que j&amp;rsquo;ai un hyperviseur fonctionnel, réseau inclus.&lt;/p&gt;
&lt;p&gt;Une fois l&amp;rsquo;ISO généré sur le site de mon Omni (le control plane SaaS de Sidero Labs, l&amp;rsquo;éditeur de Talos Linux), on peut le pousser sur notre Proxmox VE.&lt;/p&gt;
&lt;p&gt;Pour le faire, depuis notre serveur (donc pas au niveau Datacenter), on sélectionne un pool de stockage qui accepte les images ISO (si vous vous souvenez bien, notre pool ZFS ne le supporte pas par défaut), on choisit &lt;strong&gt;ISO Images&lt;/strong&gt;, et on clique sur &lt;strong&gt;Upload&lt;/strong&gt; pour téléverser l&amp;rsquo;ISO (comme on dit en bon français).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/iso.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Une fois uploadé, on peut maintenant cliquer sur le bouton bleu en haut à droite &lt;strong&gt;Create VM&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/proxmox-create-01.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;À partir de là, le wizard de création de VM devrait vous prendre la main. Ça va être un peu verbeux, mais tous les menus sont utiles, quand on commence à bien connaître Proxmox VE. Je vous donne quand même le minimum :&lt;/p&gt;
&lt;p&gt;Dans le premier menu, on ne va pas choisir sur quel nœud on installe la VM puisque pour l&amp;rsquo;instant, on n&amp;rsquo;en a qu&amp;rsquo;un&amp;hellip; En revanche, on va devoir lui donner un ID (100 par défaut) unique pour tout le cluster. Idéalement, on lui donne aussi un petit nom. Moi, j&amp;rsquo;ai opté pour talos02 (car j&amp;rsquo;ai déjà un talos01 sur un autre serveur, vous avez vu comme je suis original ?).&lt;/p&gt;
&lt;p&gt;Enfin, j&amp;rsquo;ai coché la case &amp;ldquo;Start at boot&amp;rdquo;, c&amp;rsquo;est le genre de truc relou quand on a oublié de le mettre et que l&amp;rsquo;hyperviseur redémarre (ça arrive)&amp;hellip;&lt;/p&gt;
&lt;p&gt;On peut donner des tags à nos VMs, elles auront de jolies pastilles de couleurs pour les distinguer :)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/create-vm-1.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Dans le second menu (OS), on doit indiquer deux choses. D&amp;rsquo;abord, qu&amp;rsquo;on veut utiliser l&amp;rsquo;ISO qu&amp;rsquo;on vient de récupérer / uploader pour booter notre machine (il faut choisir le bon pool de stockage, par défaut, c&amp;rsquo;est &amp;ldquo;local&amp;rdquo; qui est sélectionné). On doit aussi donner le type d&amp;rsquo;OS pour des histoires de compatibilité de pilotes pour les périphériques virtuels. Ça fait belle lurette qu&amp;rsquo;on n&amp;rsquo;a plus trop besoin de ça, &amp;ldquo;6.X - 2.6&amp;rdquo; fonctionne pour tous les Linux récents (la sortie du kernel 2.6, c&amp;rsquo;est 2003 !).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/create-vm-2.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Je saute System, dans Disks, on ajoute un disque sur le bon pool (donc pas local, mais bien celui en ZFS). Pour ma VM Talos 32 Go, c&amp;rsquo;est largement suffisant&amp;hellip; À noter qu&amp;rsquo;il existe beaucoup d&amp;rsquo;options importantes ici pour tout ce qui est &amp;ldquo;optimisation&amp;rdquo; des lectures / écritures, mais on est largement au-delà du scope de cet article.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/create-vm-3.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Je saute CPU et memory, l&amp;rsquo;important, c&amp;rsquo;est de surtout lui donner au moins 2 cores et 2 Go de RAM (au moins). On peut là aussi optimiser les performances CPU en activant les bons flags, mais là encore, on n&amp;rsquo;est plus dans le but de l&amp;rsquo;article donc je n&amp;rsquo;insiste pas.&lt;/p&gt;
&lt;p&gt;Dans la partie network, on s&amp;rsquo;assure juste qu&amp;rsquo;on est bien branché sur le bon bridge (normalement si vous avez suivi l&amp;rsquo;article, on en a qu&amp;rsquo;un, &lt;strong&gt;vmbr0&lt;/strong&gt;, donc on ne peut pas se tromper).&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/create-vm-4.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Et on vérifie que tout est OK. Protip, pour gagner cinq secondes dans votre vie, cliquez sur &amp;ldquo;Start after created&amp;rdquo;, ce qui permettra de démarrer la VM au plus vite après sa création.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/create-vm-5.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="its-alive"&gt;It&amp;rsquo;s alive?
&lt;/h2&gt;&lt;p&gt;À ce stade, la VM devrait automatiquement booter sur le CD Talos et l&amp;rsquo;OS devrait s&amp;rsquo;installer sur le disque. Une fois prête, la machine va essayer de s&amp;rsquo;enrôler sur mon control plane Omni.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Et là, c&amp;rsquo;est le drame&amp;hellip;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Car oui, je n&amp;rsquo;ai pas configuré de DHCP ni de SDN. Il faut donc aller configurer à la main la carte réseau virtuelle (ce qui n&amp;rsquo;est pas foufou), en appuyant sur F3 dans la console.&lt;/p&gt;
&lt;p&gt;(Et en subissant un peu de QWERTY, mais c&amp;rsquo;est à ça qu&amp;rsquo;on reconnaît un bon sysadmin normalement : il sait faire du QWERTY dans les consoles)&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/talos02-config.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Une fois le réseau configuré, la machine devrait quasiment instantanément commencer à se configurer&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/talos02-booted.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&amp;hellip; et à s&amp;rsquo;enrôler dans Omni :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/omni.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Victoire !!&lt;/p&gt;
&lt;h2 id="enfin-bon"&gt;Enfin bon&amp;hellip;
&lt;/h2&gt;&lt;p&gt;Enfin bon&amp;hellip; oui, on a une VM fonctionnelle. Mais j&amp;rsquo;ambitionne qu&amp;rsquo;on aille quand même un peu plus loin avant de vraiment crier victoire.&lt;/p&gt;
&lt;p&gt;Comme je l&amp;rsquo;ai dit plus haut, on a dû configurer le réseau de notre VM à la main, et on n&amp;rsquo;a encore ni clustering, ni firewalling, ni SDN, ni monitoring, ni sauvegarde&amp;hellip; Mais on chatouille allègrement les 16 000 signes pour ce blog post, il est donc temps de raccrocher pour se dire &amp;ldquo;à la prochaine fois&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Et en attendant, have fun :)&lt;/p&gt;</description></item><item><title>Déploiement d'un cluster Proxmox VE 8 sur des serveurs dédiés (1/2)</title><link>https://blog.zwindler.fr/2025/01/27/deploiement-d-un-cluster-proxmox-ve-8-part-1/</link><pubDate>Mon, 27 Jan 2025 20:00:00 +0000</pubDate><guid>https://blog.zwindler.fr/2025/01/27/deploiement-d-un-cluster-proxmox-ve-8-part-1/</guid><description>&lt;img src="https://blog.zwindler.fr/2025/02/proxmox8.webp" alt="Featured image of post Déploiement d'un cluster Proxmox VE 8 sur des serveurs dédiés (1/2)" /&gt;&lt;p&gt;Note - cet article fait partie d&amp;rsquo;une suite d&amp;rsquo;articles :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/01/27/deploiement-d-un-cluster-proxmox-ve-8-part-1" &gt;Déploiement d&amp;rsquo;un cluster Proxmox VE 8 sur des serveurs dédiés (1/4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/02/17/deploiement-d-un-cluster-proxmox-ve-8-part-2/" &gt;Déploiement d&amp;rsquo;un cluster Proxmox VE 8 sur des serveurs dédiés (2/4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/03/25/deploiement-d-un-cluster-proxmox-ve-8-part-3/" &gt;Déploiement d&amp;rsquo;un cluster Proxmox VE 8 sur des serveurs dédiés (3/4)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2025/03/30/tutoriel-sdn-vxlan-proxmoxve-8/" target="_blank" rel="noopener"
&gt;Hors série - SDN en mode VXLAN avec des machines sur Internet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Et &lt;em&gt;au moins&lt;/em&gt; un autre à suivre&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Ceux qui suivent un peu le &amp;ldquo;lore&amp;rdquo; de ce blog savent que je suis un peu connu dans la communauté des bidouilleurs du dimanche pour avoir rédigé (ou co-rédigé) plusieurs tutos sur Proxmox VE. Je pense en particulier à deux suites d&amp;rsquo;articles sur &lt;a class="link" href="https://blog.zwindler.fr/2017/07/11/deploiment-de-proxmox-ve-5-sur-un-serveur-dedie-part-1/" target="_blank" rel="noopener"
&gt;Proxmox VE 5&lt;/a&gt;, puis &lt;a class="link" href="https://blog.zwindler.fr/2020/03/02/deploiement-de-proxmox-ve-6-pfsense-sur-un-serveur-dedie/" target="_blank" rel="noopener"
&gt;Proxmox VE 6&lt;/a&gt;, pour créer de zéro un cluster de virtualisation avec Proxmox VE (et pfSense à l&amp;rsquo;époque).&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est une nouvelle itération de ces tutos, basée cette fois-ci sur Proxmox VE 8, avec pas mal de choses qui changent et de nouvelles approches.&lt;/p&gt;
&lt;p&gt;Le but ultime de cette suite d&amp;rsquo;articles est de disposer d&amp;rsquo;une plateforme de virtualisation avec Proxmox VE. Cette plateforme sera composée de plusieurs machines (idéalement 3, mais 2 ça fonctionne aussi), sécurisée, supervisée, hautement disponible, sauvegardée.&lt;/p&gt;
&lt;p&gt;Il faudra suivre plusieurs articles pour y parvenir.&lt;/p&gt;
&lt;h2 id="postulats-de-départ"&gt;Postulats de départ
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;1-&lt;/strong&gt; Je pars du principe que vous n&amp;rsquo;avez pas de machines pour le faire à la maison et que vous n&amp;rsquo;avez pas envie ou les moyens d&amp;rsquo;investir dans un homelab (&lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=homelab" &gt;même si ça peut être très rigolo, ça demande de l&amp;rsquo;investissement&lt;/a&gt;). Cependant, si vous avez des machines à la maison, une grande partie de l&amp;rsquo;article reste valide, une fois les machines installées.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2-&lt;/strong&gt; Comme Proxmox VE est une plateforme de virtualisation, je pars aussi du principe que vous voulez disposer de machines &lt;strong&gt;dédiées&lt;/strong&gt; (physique) et pas de machines virtuelles, pour justement pouvoir virtualiser des OS entiers sans trop de problèmes (même si on a toujours l&amp;rsquo;option &amp;ldquo;nested virt&amp;rdquo;, c&amp;rsquo;est moins glop).&lt;/p&gt;
&lt;p&gt;Note : si vous n&amp;rsquo;avez pas besoin de virtualisation (exemple : pas de Windows ou d&amp;rsquo;OS un peu particulier), Proxmox VE est une super plateforme de containérisation, notamment grâce à LXC, qui permet de créer des containers Linux avec un OS entier, que vous pourrez gérer exactement comme une VM mais avec des performances bien meilleures (et quelques limitations dues au partage de kernel, mais c&amp;rsquo;est souvent suffisant). Vous pouvez aller lire &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=lxc" &gt;mes articles sur LXC&lt;/a&gt; si ça vous intéresse.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3-&lt;/strong&gt; J&amp;rsquo;aimerais attirer votre attention sur le fait que de nombreuses actions auraient pu être scriptées ou gérées via de &amp;ldquo;l&amp;rsquo;infrastructure as code&amp;rdquo;. C&amp;rsquo;est plus dans l&amp;rsquo;air du temps, car plus robuste et plus fiable. Cependant, l&amp;rsquo;intérêt de cet article est de faire &amp;ldquo;à la main&amp;rdquo; pour comprendre ce qu&amp;rsquo;on fait, pas à pas, pas juste vous fournir une infrastructure &amp;ldquo;clé en main&amp;rdquo; que vous ne saurez pas gérer au premier pépin.&lt;/p&gt;
&lt;p&gt;Si vous cherchez des méthodes plus industrielles pour déployer des clusters de virtualisation, vous en trouverez sûrement plein sur Internet, de la part d&amp;rsquo;autres blogueurs (certains français, certains sont même des copains).&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai d&amp;rsquo;ailleurs moi-même plusieurs fois fait l&amp;rsquo;exercice avec Ansible ou &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=rudder" &gt;Rudder&lt;/a&gt; par le passé, par exemple ici (note : ce code est obsolète) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.zwindler.fr/2019/07/22/proxmox-en-5-min-ansible-tinc/" &gt;Un cluster Proxmox VE en 5 minutes avec Ansible&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/zwindler/ansible-proxmoxve/tree/master" target="_blank" rel="noopener"
&gt;github.com/zwindler/ansible-proxmoxve&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Maintenant que l&amp;rsquo;objectif et le contexte est clair pour tout le monde, on peut commencer :)&lt;/p&gt;
&lt;h2 id="choix-des-machines"&gt;Choix des machines
&lt;/h2&gt;&lt;p&gt;Comme on a dit qu&amp;rsquo;on n&amp;rsquo;auto-héberge pas, il faut trouver un hébergeur qui propose des machines physiques. Idéalement, quelque chose de pas trop cher, ça serait bien pour commencer.&lt;/p&gt;
&lt;p&gt;On va donc plutôt viser à trouver des machines peu onéreuses MAIS avec le support des instructions VT-x / AMD-v, ce qui est à peu près tous les CPUs qui fonctionnent encore aujourd&amp;rsquo;hui SAUF les Atom et les machines ARM type Raspberry.&lt;/p&gt;
&lt;p&gt;Grosso modo, ça nous laisse les Kimsufi chez OVHCloud, les dédibox chez Scaleway et les machines chez Hetzner.&lt;/p&gt;
&lt;p&gt;Chez Hetzner pour 45€, vous avez un i5 gen13, 64 Go de RAM et 2 SSD NVMe.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/hetzner.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Chez OVHcloud, avec un peu de chance, vous pourrez tomber sur des machines autour des 15 - 20€ par mois avec du matériel plus ancien, mais largement suffisant pour ce qu&amp;rsquo;on va en faire.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/ovhcloud.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Chez Scaleway, l&amp;rsquo;offre d&amp;rsquo;entrée de gamme est centrée sur les Atom Avoton ou de très vieux Xeon. L&amp;rsquo;Avoton C2750 reste une option valable, car c&amp;rsquo;est un octo-cores qui supporte le VT-x (contrairement au C2350, attention !). Mais bon, c&amp;rsquo;est du matoss de 2013 💀&amp;hellip;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/scaleway.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Pour les besoins de cet article, j&amp;rsquo;ai optimisé les coûts et je suis parti sur le serveur OVHcloud que je présente juste au-dessus (KS-GAME-LE), malheureusement uniquement disponible au Canada :(. Mais 8 threads et 16 Go de RAM pour 12€ TTC par mois, ce n&amp;rsquo;est vraiment pas cher payé.&lt;/p&gt;
&lt;p&gt;Deux points noirs :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;le stockage ridiculement petit pour de la virtualisation (seulement 240 Go, un seul disque)&lt;/li&gt;
&lt;li&gt;la latence - presque 100 ms depuis chez moi, contre 11 ms pour mon autre machine chez Scaleway&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;64 bytes from proxmox.example.org (203.0.113.78): icmp_seq=1 ttl=53 time=98.0 ms
64 bytes from proxmox.example.org (203.0.113.78): icmp_seq=2 ttl=53 time=97.5 ms
64 bytes from proxmox.example.org (203.0.113.78): icmp_seq=3 ttl=53 time=97.6 ms
64 bytes from proxmox.example.org (203.0.113.78): icmp_seq=4 ttl=53 time=97.7 ms
64 bytes from proxmox.example.org (203.0.113.78): icmp_seq=5 ttl=53 time=97.7 ms
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="installation-de-los"&gt;Installation de l&amp;rsquo;OS
&lt;/h2&gt;&lt;p&gt;Une fois commandé, le serveur est relativement rapidement disponible dans votre &amp;ldquo;manager OVH&amp;rdquo;. On a un menu relativement intuitif pour installer directement Proxmox VE 8, quasiment à jour (les images fournies sont reconstruites très régulièrement avec toutes les mises à jour).&lt;/p&gt;
&lt;p&gt;Pour lancer l&amp;rsquo;installation, on clique sur les &amp;ldquo;&amp;hellip;&amp;rdquo; dans la partie Système d&amp;rsquo;exploitation (OS), puis on sélectionne l&amp;rsquo;install depuis un template OVH&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/install01.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/install02.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;On déroule la liste des types d&amp;rsquo;OS, on sélectionne Virtualisation, puis proxmox VE 8 (tant qu&amp;rsquo;à faire). J&amp;rsquo;ai aussi coché la case &amp;ldquo;Personnaliser la configuration des partitions&amp;rdquo;, parce que par défaut, l&amp;rsquo;installation d&amp;rsquo;OVH utilise du stockage de type &amp;ldquo;LVM thick&amp;rdquo; pour la majorité de l&amp;rsquo;espace disque, alors qu&amp;rsquo;on va utiliser les fonctionnalités avancées de ZFS pour la suite :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/install03.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/install04.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Avant de valider l&amp;rsquo;installation, n&amp;rsquo;oubliez pas de modifier son nom (Custom hostname) pour éviter d&amp;rsquo;avoir besoin de le faire post install, puis d&amp;rsquo;ajouter une clé publique SSH :&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/install05.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/install06.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;h2 id="premières-tâches-post-installation--upgrade-et-reboot"&gt;Premières tâches post-installation : upgrade et reboot
&lt;/h2&gt;&lt;p&gt;Il y a toute une petite série de trucs à faire quand on vient d&amp;rsquo;installer une machine, en particulier sur Proxmox VE, et on va essayer de se retenir de &amp;ldquo;vite vite&amp;rdquo; se connecter à l&amp;rsquo;interface graphique.&lt;/p&gt;
&lt;p&gt;De toute façon, on ne pourra pas s&amp;rsquo;y connecter, à l&amp;rsquo;interface graphique de Proxmox VE&amp;hellip;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ah bon ???&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Oui, car si on peut normalement se connecter avec l&amp;rsquo;utilisateur &lt;code&gt;root&lt;/code&gt; de notre Linux, on ne va pas pouvoir dans le cas d&amp;rsquo;OVHcloud, tout simplement parce qu&amp;rsquo;on n&amp;rsquo;a PAS le mot de passe root. Heureusement qu&amp;rsquo;on a mis une clé SSH ;)&lt;/p&gt;
&lt;p&gt;On y reviendra plus tard, on a plus urgent à faire.&lt;/p&gt;
&lt;p&gt;La première chose que moi, j&amp;rsquo;ai faite, c&amp;rsquo;est d&amp;rsquo;ajouter tout de suite un record DNS de type A pour que l&amp;rsquo;IP de ma machine corresponde à un FQDN. Pour cet article, j&amp;rsquo;y ferai référence de la manière suivante :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;hostname: myPVEhost&lt;/li&gt;
&lt;li&gt;FQDN: proxmox.example.org&lt;/li&gt;
&lt;li&gt;IP: 203.0.113.159&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/dns_ovh.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;On se connecte donc sur l&amp;rsquo;IP de notre machine fraichement installée en SSH (root@IPmachine) et on va en profiter pour faire un peu de ménage.&lt;/p&gt;
&lt;p&gt;La première chose à faire est de mettre à jour le serveur, idéalement suivi d&amp;rsquo;un reboot :&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;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt upgrade -y &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; reboot
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fois le serveur revenu à la vie, on se reconnecte et active d&amp;rsquo;une manière ou d&amp;rsquo;une autre un système de mise à jour automatiques. Le plus simple c&amp;rsquo;est d&amp;rsquo;utiliser &lt;code&gt;unattended-upgrades&lt;/code&gt; s&amp;rsquo;il n&amp;rsquo;est pas installé par défaut (il est possible qu&amp;rsquo;il y soit déjà) mais il existe des outils plus perfectionnés.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt install unattended-upgrades
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="cest-pour-la-sécurité"&gt;C&amp;rsquo;est pour la sécurité
&lt;/h2&gt;&lt;p&gt;Une fois que c&amp;rsquo;est fait, je vous conseille d&amp;rsquo;installer tout de suite CrowdSec. Ici, je me suis simplement basé sur la documentation officielle de crowdsec, pour ajouter le moteur de détection, le composant de remédiation (ici via &lt;code&gt;iptables&lt;/code&gt;) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://doc.crowdsec.net/u/getting_started/installation/linux" target="_blank" rel="noopener"
&gt;CrowdSec.net - Installation Linux&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -s https://install.crowdsec.net &lt;span class="p"&gt;|&lt;/span&gt; sudo sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt install crowdsec
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# vérifier que le service crowdsec fonctionne&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl status crowdsec
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;● crowdsec.service - Crowdsec agent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Loaded: loaded &lt;span class="o"&gt;(&lt;/span&gt;/lib/systemd/system/crowdsec.service&lt;span class="p"&gt;;&lt;/span&gt; enabled&lt;span class="p"&gt;;&lt;/span&gt; preset: enabled&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Active: active &lt;span class="o"&gt;(&lt;/span&gt;running&lt;span class="o"&gt;)&lt;/span&gt; since Sun 2025-01-26 21:04:26 UTC&lt;span class="p"&gt;;&lt;/span&gt; 1min 56s ago
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Process: &lt;span class="m"&gt;73830&lt;/span&gt; &lt;span class="nv"&gt;ExecStartPre&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/usr/bin/crowdsec -c /etc/crowdsec/config.yaml -t -error &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;exited, &lt;span class="nv"&gt;status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0/SUCCESS&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Main PID: &lt;span class="m"&gt;73862&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;crowdsec&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Tasks: &lt;span class="m"&gt;13&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;limit: 19059&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Memory: 33.1M
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; CPU: 2.571s
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; CGroup: /system.slice/crowdsec.service
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─73862 /usr/bin/crowdsec -c /etc/crowdsec/config.yaml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─73877 journalctl --follow -n &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;_SYSTEMD_UNIT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ssh.service
&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;Jan &lt;span class="m"&gt;26&lt;/span&gt; 21:04:23 myPVEhost systemd&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;]&lt;/span&gt;: Starting crowdsec.service - Crowdsec agent...
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Jan &lt;span class="m"&gt;26&lt;/span&gt; 21:04:26 myPVEhost systemd&lt;span class="o"&gt;[&lt;/span&gt;1&lt;span class="o"&gt;]&lt;/span&gt;: Started crowdsec.service - Crowdsec agent.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# installer le moteur de remédiation&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;apt install crowdsec-firewall-bouncer
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Une fois que Crowdsec est opérationnel, on peut lui ajouter la collection &amp;ldquo;fulljackz/proxmox&amp;rdquo;, qu&amp;rsquo;il va falloir modifier (merci le super article de Julien Louis sur son blog slash-root.fr), qui va écouter les logs de la webUI pour détecter tout brute force et bannir ceux qui tenteraient de le faire :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://app.crowdsec.net/hub/author/fulljackz/collections/proxmox" target="_blank" rel="noopener"
&gt;https://app.crowdsec.net/hub/author/fulljackz/collections/proxmox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://slash-root.fr/crowdsec-proteger-lauthentification-proxmox/" target="_blank" rel="noopener"
&gt;slash-root.fr - CrowdSec : Protéger l’authentification Proxmox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cscli collections install fulljackz/proxmox
&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;INFO Enabled fulljackz/proxmox
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;INFO Run &lt;span class="s1"&gt;&amp;#39;sudo systemctl reload crowdsec&amp;#39;&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; the new configuration to be effective.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Le pattern écouté par le parser proxmox n&amp;rsquo;est pas/plus bon avec les nouvelles versions&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;Jan 26 21:23:14 myPVEhost pvedaemon[1250]: authentication failure; rhost=::ffff:203.0.113.159.78 user=coucou@pve msg=no such user (&amp;#39;coucou@pve&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;On voit ici que Julien a rajouté le préfixe &amp;ldquo;::ffff:&amp;rdquo; dans la variable &lt;strong&gt;PVE_AUTH_FAIL&lt;/strong&gt; du fichier &lt;code&gt;/etc/crowdsec/parsers/s01-parse/proxmox-logs.yaml&lt;/code&gt;, mais quand je vois les cas de tests prévus dans la collection, j&amp;rsquo;ai l&amp;rsquo;impression que ce n&amp;rsquo;est pas nécessaire&amp;hellip;&lt;/p&gt;
&lt;p&gt;Dans tous les cas, on va créer un fichier &lt;code&gt;/etc/crowdsec/acquis.d/proxmox.yaml&lt;/code&gt; pour que CrowdSec puisse commencer à analyser nos logs :&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 -p /etc/crowdsec/acquis.d/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; /etc/crowdsec/acquis.d/proxmox.yaml &lt;span class="s"&gt;&amp;lt;&amp;lt;EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;journalctl_filter:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; - _SYSTEMD_UNIT=pvedaemon.service
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;labels:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt; type: syslog
&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 termine par recharger crowdsec et on regarde &lt;del&gt;le monde brûler&lt;/del&gt; les scripts kiddies se faire bloquer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;systemctl reload crowdsec
&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;cscli alerts list
&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;│ ID │ value │ reason │ country │ as │ decisions │ created_at │
&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="m"&gt;2&lt;/span&gt; │ Ip:203.0.113.222 │ crowdsecurity/ssh-slow-bf │ IR │ &lt;span class="m"&gt;202468&lt;/span&gt; Gloubi Bo ulgua Co. &lt;span class="o"&gt;(&lt;/span&gt; Private Joint Stock&lt;span class="o"&gt;)&lt;/span&gt; │ ban:1 │ 2025-01-26 22:31:15.366187524 +0000 UTC │
&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;h2 id="nagging-popup"&gt;Nagging popup
&lt;/h2&gt;&lt;p&gt;Dans la liste des petits trucs pénibles livrés de base avec Proxmox VE, il y avait avant le fait que Proxmox VE avait les dépôts apt entreprise sur toutes les installations, et il était nécessaire de préalablement les désactiver à la main avant de pouvoir faire les mises à jour.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;était super pénible et les développeurs de Proxmox VE ont entendu (un peu) leur communauté et retiré cette obligation&amp;hellip; pour la remplacer par une popup.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/nagging_popup.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;Alors, une popup, je veux bien ! C&amp;rsquo;est important de récompenser le travail des développeurs et de les soutenir. Mais est-ce que c&amp;rsquo;est vraiment nécessaire de le faire &lt;strong&gt;à chaque login&lt;/strong&gt; ET &lt;strong&gt;à chaque fois qu&amp;rsquo;on rafraîchit la liste des packages à mettre à jour&lt;/strong&gt; ?&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est bien entendu une question rhétorique.&lt;/p&gt;
&lt;p&gt;Évidemment, de nombreuses personnes ont trouvé plusieurs méthodes pour désactiver le code JS responsable. Big up à &lt;a class="link" href="https://blog.zwindler.fr/authors/fabio/" target="_blank" rel="noopener"
&gt;fabio&lt;/a&gt; pour sa version qui fonctionne bien.&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;sed -Ezi.bak &lt;span class="s2"&gt;&amp;#34;s/(function\(orig_cmd\) \{)/\1\n\torig_cmd\(\);\n\treturn;/g&amp;#34;&lt;/span&gt; /usr/share/javascript/proxmox-widget-toolkit/proxmoxlib.js &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; systemctl restart pveproxy.service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Note : si comme moi vous vous êtes déjà connecté une fois, il faudra aussi nettoyer le cache sinon la popup restera&amp;hellip;&lt;/p&gt;
&lt;h2 id="création-de-groupes-et-dutilisateurs-pour-linterface-graphique"&gt;Création de groupes et d&amp;rsquo;utilisateurs pour l&amp;rsquo;interface graphique
&lt;/h2&gt;&lt;blockquote&gt;
&lt;p&gt;Ok ok, on a fait plein de trucs, est-ce qu&amp;rsquo;on peut se connecter maintenant ???&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Mais ouiiii, on peut. De toute façon, j&amp;rsquo;en suis déjà à presque 15000 signes, cet article est beaucoup trop long, il faut abréger.&lt;/p&gt;
&lt;p&gt;Dernière étape dans cet article, on va créer un administrateur pour se connecter à l&amp;rsquo;UI et un utilisateur qui nous servira plus tard pour le monitoring.&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;# vous n&amp;#39;êtes pas **vraiment** obligé d&amp;#39;appeler l&amp;#39;admin &amp;#34;zwindler&amp;#34; vous savez ?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pveum group add admin -comment &lt;span class="s2"&gt;&amp;#34;System Administrators&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pveum acl modify / -group admin -role Administrator
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pveum useradd zwindler@pve
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pveum usermod zwindler@pve -group admin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pveum passwd zwindler@pve
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# notre utilisateur de monitoring&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pveum groupadd monitoring -comment &lt;span class="s1"&gt;&amp;#39;Monitoring group&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pveum aclmod / -group monitoring -role PVEAuditor
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pveum useradd pve_exporter@pve
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pveum usermod pve_exporter@pve -group monitoring
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pveum passwd pve_exporter@pve
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Allez, c&amp;rsquo;est bon, vous avez assez patienté, vous pouvez vous connecter à votre Proxmox VE ! Rendez-vous sur :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;https://@IPmachine:8006
ou&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://proxmox.example.org:8006" target="_blank" rel="noopener"
&gt;https://proxmox.example.org:8006&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Note : pour l&amp;rsquo;instant, le certificat est autosigné et le navigateur va nous remonter une erreur. On peut accepter d&amp;rsquo;ignorer le problème pour l&amp;rsquo;instant.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/pve_login.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;N&amp;rsquo;oubliez pas de changer le &amp;ldquo;realm&amp;rdquo; de PAM (les utilisateurs unix, qu&amp;rsquo;on n&amp;rsquo;utilise pas ici) à &amp;ldquo;Proxmox&amp;rdquo; pour pouvoir utiliser l&amp;rsquo;administrateur qu&amp;rsquo;on vient de créer.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://blog.zwindler.fr/2025/02/proxmox8.avif"
loading="lazy"
&gt;&lt;/p&gt;
&lt;p&gt;À bientôt pour la suite. Et en attendant, have fun!&lt;/p&gt;</description></item><item><title>Sauvegarder des dashboards Grafana dans Kubernetes, en s’amusant (j’explique les RBAC, Job et les CronJob)</title><link>https://blog.zwindler.fr/2024/10/01/sauvegarder-dashboards-grafana/</link><pubDate>Tue, 01 Oct 2024 18:00:00 +0200</pubDate><guid>https://blog.zwindler.fr/2024/10/01/sauvegarder-dashboards-grafana/</guid><description>&lt;img src="https://blog.zwindler.fr/2020/01/20200102_084825-2.webp" alt="Featured image of post Sauvegarder des dashboards Grafana dans Kubernetes, en s’amusant (j’explique les RBAC, Job et les CronJob)" /&gt;&lt;p&gt;Note : Pour les allergiques à Kubernetes, cet article peut être vu comme une entrée en la matière, car je vais progressivement explorer certains concepts, de manière très progressive.&lt;/p&gt;
&lt;p&gt;Note 2 : par convention dans cet article, j&amp;rsquo;ai essayé de nommer tous les objets Kubernetes avec une majuscule à tous les mots (exemple : &lt;strong&gt;ServiceAccount&lt;/strong&gt;) pour que ce soit plus clair.&lt;/p&gt;
&lt;h2 id="grafana"&gt;Grafana
&lt;/h2&gt;&lt;p&gt;Je ne vous ferai pas l&amp;rsquo;affront de vous présenter Grafana, l&amp;rsquo;outil de visualisation open source de la société éponyme. C&amp;rsquo;est un outil que j&amp;rsquo;utilise depuis un moment et qui est quasiment incontournable dans toutes les solutions de monitoring modernes (ou non ? xD).&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;ailleurs, &lt;a class="link" href="https://blog.zwindler.fr/recherche/?keyword=grafana" target="_blank" rel="noopener"
&gt;j&amp;rsquo;ai écrit déjà quelques articles sur Grafana au fil des années&lt;/a&gt;, c&amp;rsquo;est un outil aussi simple qu&amp;rsquo;irremplaçable (jusqu&amp;rsquo;à ce qu&amp;rsquo;on trouve mieux, bien entendu).&lt;/p&gt;
&lt;p&gt;Si vous avez déjà utilisé au moins une fois Grafana, vous savez donc que les visualisations sont organisées comme suit :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;des dossiers&lt;/li&gt;
&lt;li&gt;dans lesquels on range des dashboards&lt;/li&gt;
&lt;li&gt;qui contiennent des panels (nos visualisations)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On peut donc créer des dashboards à gogo en fonction du besoin, les cloner, les modifier, les importer depuis un magasin sur grafana.com (assez inutilisable maintenant, mais bon) et en afficher le code JSON.&lt;/p&gt;
&lt;p&gt;Et ça, c&amp;rsquo;est super cool, parce que si mon Grafana brûle (mes machines persos ne sont pas hébergées dans les conditions les plus pros possibles&amp;hellip; 😱), je n&amp;rsquo;ai pas envie de tout perdre. Mais aller régulièrement copier / coller le code JSON de chaque dashboard, c&amp;rsquo;est pénible.&lt;/p&gt;
&lt;h2 id="because-im-api"&gt;Because I&amp;rsquo;m API
&lt;/h2&gt;&lt;p&gt;Un truc qui est cool avec Grafana, c&amp;rsquo;est que comme tout logiciel un peu pro qui se respecte, il dispose d&amp;rsquo;une API. Je ne vais pas juger de sa qualité globale d&amp;rsquo;API (vous connaissez peut-être bien mieux que moi), mais elle a le mérite d&amp;rsquo;exister.&lt;/p&gt;
&lt;p&gt;Récemment, je me suis donc demandé quelle quantité d&amp;rsquo;effort, il faudrait déployer pour sauvegarder de manière automatisée des dashboards Grafana avec l&amp;rsquo;API et Kubernetes (oui hein, vous n&amp;rsquo;allez pas y couper).&lt;/p&gt;
&lt;p&gt;Partons donc du principe que j&amp;rsquo;ai un cluster Kubernetes sur lequel &lt;a class="link" href="https://github.com/grafana/helm-charts/tree/main/charts/grafana" target="_blank" rel="noopener"
&gt;j&amp;rsquo;ai déployé la chart Helm officielle de Grafana&lt;/a&gt; (rappel, Helm, c&amp;rsquo;est à la fois un package manager et un moyen de déployer des applications dans Kubernetes).&lt;/p&gt;
&lt;p&gt;À partir de là, de quoi j&amp;rsquo;ai besoin ?&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Je suis le roi des fainéants, je ne veux pas avoir à aller créer les accès dans Grafana moi même (via 3 clics clics). Il faut donc que j&amp;rsquo;automatise la création d&amp;rsquo;un token pour communiquer avec l&amp;rsquo;API&lt;/li&gt;
&lt;li&gt;Une fois que j&amp;rsquo;ai mon token, je veux créer une tâche périodique qui va se connecter à l&amp;rsquo;API, lister les dashboards disponibles et les sauvegarder sur un bucket s3 chez Scaleway (il y a 75 Go gratuit dans le free-tier, ça suffira largement)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="se-préparer-à-créer-un-token"&gt;Se préparer à créer un token
&lt;/h2&gt;&lt;p&gt;Bon, là en apparence, on se retrouve face à un problème d&amp;rsquo;œuf / poule. Comment je fais pour me connecter à l&amp;rsquo;API si je n&amp;rsquo;ai pas un accès à l&amp;rsquo;API ?&lt;/p&gt;
&lt;p&gt;Heureusement, on va tricher&amp;hellip; grâce à Kubernetes 😏&lt;/p&gt;
&lt;p&gt;Quand on déploie Grafana dans Kubernetes, la chart officielle va créer un &lt;strong&gt;Secret&lt;/strong&gt; (au sens Kubernetes du terme) qui contient le login / password du compte local administrateur de Grafana.&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;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="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;creationTimestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2024-10-01T13:11:48Z&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;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;grafana&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resourceVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1234567&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;deadbeef-dead-cafe-baba-123456789012&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;admin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;YWRtaW4=&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;TmV2ZXJHb25uYUdpdmVZb3VVcAo=&lt;/span&gt;&lt;span class="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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Opaque&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;S&amp;rsquo;il est déconseillé de l&amp;rsquo;utiliser au jour le jour (mieux vaut utiliser des comptes nominatifs, idéalement via un &lt;a class="link" href="https://en.wikipedia.org/wiki/Identity_provider" target="_blank" rel="noopener"
&gt;Identity Provider&lt;/a&gt;), c&amp;rsquo;est super pratique ici !&lt;/p&gt;
&lt;p&gt;Il suffit de créer un &lt;strong&gt;Job&lt;/strong&gt; Kubernetes avec suffisamment de droits pour lire ce &lt;strong&gt;Secret&lt;/strong&gt;, qui va se connecter (une seule fois) sur l&amp;rsquo;API avec le compte admin, créer un token, le récupérer, et le stocker dans un autre &lt;strong&gt;Secret&lt;/strong&gt; !&lt;/p&gt;
&lt;p&gt;Sans rentrer à fond dans les détails de comment fonctionne le RBAC (Role Based Access Control) dans Kubernetes, voilà à quoi ça ressemble :&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="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;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="c"&gt;# le compte de service (ServiceAccount) qui va être utilisé par le Job et qui a besoin de droits particuliers&lt;/span&gt;&lt;span class="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;ServiceAccount&lt;/span&gt;&lt;span class="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;grafana-backup-sa&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;rbac.authorization.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="c"&gt;# le rôle, c&amp;#39;est la politique de sécurité qui va nous permettre de faire des trucs&lt;/span&gt;&lt;span class="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;Role&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="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;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;grafana&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;grafana-backup-role&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# ici je n&amp;#39;autorise qu&amp;#39;à lire le secret contenant l&amp;#39;admin/password de grafana, pas plus&lt;/span&gt;&lt;span class="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;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;secrets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resourceNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;grafana&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# ici, je vais autoriser le fait de modifier un secret qui s&amp;#39;appelle grafana-backup&lt;/span&gt;&lt;span class="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;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;secrets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resourceNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;grafana-backup&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;update&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;patch&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# ici, je vais autoriser le fait de créer des secrets&lt;/span&gt;&lt;span class="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;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;secrets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;create&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# ici, je lie simplement ServiceAccount à un Role pour lui donner les droits dont j&amp;#39;ai besoin&lt;/span&gt;&lt;span class="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;rbac.authorization.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;RoleBinding&lt;/span&gt;&lt;span class="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;grafana-backup-rolebinding&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&lt;/span&gt;&lt;span class="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;subjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="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;ServiceAccount&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;grafana-backup-sa&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&lt;/span&gt;&lt;span class="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;roleRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;Role&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;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;grafana-backup-role&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;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;rbac.authorization.k8s.io&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;Note : vous aurez remarqué que je n&amp;rsquo;ai pas autorisé la création ET la modification du &lt;strong&gt;Secret&lt;/strong&gt; grafana-backup en une seule étape, mais en deux. Ceci est dû à une limitation de l&amp;rsquo;API de Kubernetes, qui ne permet pas de limiter la création d&amp;rsquo;un objet via son &amp;ldquo;ressource name&amp;rdquo;. Je vous laisse aller lire la doc officielle si ça vous intéresse.&lt;/p&gt;
&lt;p&gt;Sachez que je me suis un peu &amp;ldquo;embêté&amp;rdquo; à gérer la politique de droits et qu&amp;rsquo;on aurait pu faire bien plus simple. Mais c&amp;rsquo;est pour respecter &lt;a class="link" href="https://fr.wikipedia.org/wiki/Principe_de_moindre_privil%C3%A8ge" target="_blank" rel="noopener"
&gt;le principe en sécurité informatique du moindre privilège&lt;/a&gt; (c&amp;rsquo;est important).&lt;/p&gt;
&lt;h2 id="bon-on-va-le-créer-ce-token-"&gt;Bon&amp;hellip; on va le créer, ce token ??
&lt;/h2&gt;&lt;p&gt;Maintenant qu&amp;rsquo;on a géré les aspects de sécurité (ça limitera les possibilités d&amp;rsquo;un attaquant qui réussirait à prendre la main sur notre &lt;strong&gt;Pod&lt;/strong&gt; / container), on peut créer la tâche qui va créer le token.&lt;/p&gt;
&lt;p&gt;Ici, il n&amp;rsquo;y a pas de raison de laisser tourner un container ad vitam une fois que le token est créé, on peut l&amp;rsquo;éteindre. Pour ça, il existe un objet dans Kubernetes qui s&amp;rsquo;appelle le &lt;strong&gt;Job&lt;/strong&gt; (avec une gestion d&amp;rsquo;erreur et de retry intégré). Voilà à quoi il va ressembler :&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="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;batch/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;Job&lt;/span&gt;&lt;span class="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;grafana-backup-api-token-job&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&lt;/span&gt;&lt;span class="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;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;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="c"&gt;# on lui donne bien le bon ServiceAccount sinon on a fait tout ce RBAC pour rien T_T&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serviceAccountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana-backup-sa &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana-create-api-token&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# une petite astuce pour de la bidouille, merci Jérôme :-*&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nixery.dev/shell/curl/jq/kubectl:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# on &amp;#34;monte&amp;#34; le Secret Kubernetes comme des fichiers dans notre container&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;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;grafana-secret-volume&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/etc/grafana-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;readOnly&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/bin/sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- -&lt;span class="l"&gt;c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Comme on a monté le secret dans notre container, c&amp;#39;est super facile de récupérer
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # l&amp;#39;utilisateur et le mot de passe admin sans avoir à le stocker nulle part (surtout pas sur github)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; GRAFANA_ADMIN_USER=$(cat /etc/grafana-secret/admin)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; GRAFANA_ADMIN_PASS=$(cat /etc/grafana-secret/password)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # On peut maintenant faire une requête HTTP via curl sur l&amp;#39;API de Grafana
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; GRAFANA_API_TOKEN=$(curl -s -X POST http://@IPgrafana:80/api/auth/keys \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -u &amp;#34;$GRAFANA_ADMIN_USER:$GRAFANA_ADMIN_PASS&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -H &amp;#34;Content-Type: application/json&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; -d &amp;#39;{
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;name&amp;#34;: &amp;#34;Kubernetes Backup Token&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;role&amp;#34;: &amp;#34;Admin&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; &amp;#34;secondsToLive&amp;#34;: 31536000
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; }&amp;#39; | jq -r &amp;#39;.key&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # On termine par stocker le token dans le nouveau Secret grafana-backup
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; kubectl create secret generic grafana-backup \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --from-literal=GRAFANA_API_TOKEN=$GRAFANA_API_TOKEN \
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; --dry-run=client -o yaml | kubectl apply -f -&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restartPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;OnFailure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;grafana-secret-volume&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secretName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&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;Normalement, avec ce bon script code BASH, j&amp;rsquo;ai réconcilié les amateurs de bash avec Kubernetes.&lt;/p&gt;
&lt;p&gt;Comment ça, &amp;ldquo;non&amp;rdquo; ?&lt;/p&gt;
&lt;p&gt;Il ne me reste plus qu&amp;rsquo;à appliquer le manifest YAML sur mon cluster Kubernetes et le &lt;strong&gt;Secret&lt;/strong&gt; grafana-backup contenant la clé d&amp;rsquo;API devrait apparaitre sur notre cluster :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;kubectl apply -f job.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="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;kubectl get secrets&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;NAME TYPE DATA AGE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;grafana-backup Opaque 1 1m&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;kubectl describe secret grafana-backup&lt;/span&gt;&lt;span class="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;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;grafana-backup&lt;/span&gt;&lt;span class="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;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;grafana&lt;/span&gt;&lt;span class="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;Labels&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;none&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Annotations&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;none&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Opaque&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;Data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;====&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;GRAFANA_API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;104&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bytes&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;Première victoire :)&lt;/p&gt;
&lt;h2 id="rbac-again"&gt;RBAC, again
&lt;/h2&gt;&lt;p&gt;On peut s&amp;rsquo;attaquer à la deuxième partie du problème : comment récupérer puis sauvegarder les JSON ?&lt;/p&gt;
&lt;p&gt;Ben, avec une nouvelle couche de moindres privilèges, pardi !!&lt;/p&gt;
&lt;p&gt;Vous connaissez la musique maintenant. On crée un &lt;strong&gt;ServiceAccount&lt;/strong&gt;, on ne lui donne le droit qu&amp;rsquo;à lire les &lt;strong&gt;Secrets&lt;/strong&gt; qui nous intéressent via un &lt;strong&gt;Role&lt;/strong&gt; / &lt;strong&gt;RoleBinding&lt;/strong&gt; pour ne pas faciliter le travail d&amp;rsquo;un attaquant qui aurait compromis mon &lt;strong&gt;Pod&lt;/strong&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;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;ServiceAccount&lt;/span&gt;&lt;span class="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;grafana-backup-cron-sa&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;rbac.authorization.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;Role&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="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;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;grafana&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;grafana-backup-cron-role&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- &lt;span class="nt"&gt;apiGroups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;secrets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resourceNames&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;grafana-backup&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;grafana-backup-s3-credentials&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;verbs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;get&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;rbac.authorization.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;RoleBinding&lt;/span&gt;&lt;span class="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;grafana-backup-cron-rolebinding&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&lt;/span&gt;&lt;span class="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;subjects&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="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;ServiceAccount&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;grafana-backup-cron-sa&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&lt;/span&gt;&lt;span class="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;roleRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;Role&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;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;grafana-backup-cron-role&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;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;rbac.authorization.k8s.io&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;Petite subtilité, ici, je vais aussi devoir créer un &lt;strong&gt;Secret&lt;/strong&gt; supplémentaire pour que mon container puisse s&amp;rsquo;authentifier sur le bucket S3 sur lequel je vais déposer mes JSONs. Je ne rentre pas dans les détails, on sort du cadre de l&amp;rsquo;article, vous pouvez aller l&lt;a class="link" href="https://www.scaleway.com/en/docs/storage/object/api-cli/object-storage-aws-cli/" target="_blank" rel="noopener"
&gt;ire la doc officielle de scaleway qui est plutôt bien faite (Using Object Storage with the AWS-CLI)&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Voilà à quoi ressemble le &lt;strong&gt;Secret&lt;/strong&gt; grafana-backup-s3-credentials :&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;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="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;grafana-backup-s3-credentials&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&lt;/span&gt;&lt;span class="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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Opaque&lt;/span&gt;&lt;span class="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;stringData&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# le Secret se compose de 2 entrées, qui seront montés comme des fichiers textes &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# dans le container ; pratique pour des fichiers de config avec des secrets !!&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [default]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; aws_access_key_id=SCxxxxxxxxxxxxxxxxxx
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; aws_secret_access_key=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [default]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; region = fr-par
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; output = json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; services = scw-fr-par
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; [services scw-fr-par]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; s3 =
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; endpoint_url = https://s3.fr-par.scw.cloud&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;Et pour finir, on veut donc créer une tâche planifiée, qui va s&amp;rsquo;occuper de réaliser notre sauvegarde régulièrement. Dans Kubernetes, il s&amp;rsquo;agit de l&amp;rsquo;objet &lt;strong&gt;CronJob&lt;/strong&gt;, qui va lancer périodiquement des &lt;strong&gt;Job&lt;/strong&gt; (le même qu&amp;rsquo;on a vu précédemment).&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="nn"&gt;---&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;apiVersion&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;batch/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;CronJob&lt;/span&gt;&lt;span class="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;grafana-backup-cronjob&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana&lt;/span&gt;&lt;span class="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;schedule&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0 */2 * * *&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;jobTemplate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;spec&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;template&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;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;serviceAccountName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana-backup-cron-sa&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;grafana-backup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nixery.dev/shell/curl/jq/awscli:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumeMounts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;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;grafana-backup-s3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/.aws/credentials&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;subPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;credentials&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;grafana-backup-s3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mountPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/.aws/config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;subPath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;config&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restartPolicy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;OnFailure&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GRAFANA_API_TOKEN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;valueFrom&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secretKeyRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;grafana-backup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GRAFANA_API_TOKEN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/bin/sh&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- -&lt;span class="l"&gt;c&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="p"&gt;|&lt;/span&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Ici, on récupère un gros JSON listant les dashboards et on utilise jq pour extraire l&amp;#39;identifiant unique &amp;#34;uid&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; dashboards=$(curl -s -H &amp;#34;Authorization: Bearer $GRAFANA_API_TOKEN&amp;#34; &amp;#39;http://@IPgrafana:80/api/search?query=&amp;amp;type=dash-db&amp;#39; | jq -r &amp;#39;.[] | .uid&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # On boucle ensuite sur la liste des uid
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; for uid in $dashboards; do
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # On récupère le JSON qu&amp;#39;on copie en local
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; dashboard_json=$(curl -s -H &amp;#34;Authorization: Bearer $GRAFANA_API_TOKEN&amp;#34; http://@IPgrafana:80/api/dashboards/uid/$uid)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; echo &amp;#34;$dashboard_json&amp;#34; &amp;gt; /tmp/dashboard-$uid.json
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; # Puis on utilise aws-cli pour uploader le fichier sur notre bucket s3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; aws s3 cp /tmp/dashboard-$uid.json s3://grafana-backup/grafana-backups/dashboard-$uid.json --profile default
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; done&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;grafana-backup-s3&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secretName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;secret-grafana-backup-credentials&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;Toutes les deux heures, nos dashboards seront tous sauvegardés sur notre bucket chez Scaleway. Si la fonction de versioning a été activé sur le bucket, on pourra même garder un historique des versions.&lt;/p&gt;
&lt;p&gt;Deuxième victoire :)&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Ok, écrire ces scripts et les manifests qui vont avec a surement pris un peu plus de temps que d&amp;rsquo;aller manuellement créer un token dans l&amp;rsquo;UI de Grafana, puis de le donner en dur dans un gros script bash.&lt;/p&gt;
&lt;p&gt;Cependant, il y a quand même quelques différences avec cette approche :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A part pour les crédentials du bucket S3 (et encore on aurait pu utiliser minIO dans Kubernetes), on a manipulé aucun identifiant. Toute cette gestion des &lt;strong&gt;Secrets&lt;/strong&gt; est déléguée à Kubernetes, loin de l&amp;rsquo;admin (avec les limitations que ça peut avoir). On n&amp;rsquo;aura pas de leaks sur github de secrets git-és par erreur et si on gère son RBAC correctement, seuls les &lt;strong&gt;Pods&lt;/strong&gt; qui sont habilités à lire ses &lt;strong&gt;Secrets&lt;/strong&gt; pourront le faire.&lt;/li&gt;
&lt;li&gt;Tout est géré &amp;ldquo;as-code&amp;rdquo; et automatisé. Le faire une fois à la main va plus vite. Le faire 100 fois, car on a 100 clusters identiques, c&amp;rsquo;est quand même dommage.&lt;/li&gt;
&lt;li&gt;On profite des fonctions de Kubernetes de gestion des erreurs et de retries des Jobs et des Cronjobs. C&amp;rsquo;est du code (bash ou autre) en moins à gérer si on veut fiabiliser l&amp;rsquo;outil dans une utilisation plus &amp;ldquo;industrielle&amp;rdquo;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Que je vous aie convaincu ou pas, au-delà de l&amp;rsquo;exemple, le but était surtout d&amp;rsquo;introduire quelques concepts de Kubernetes (le RBAC et les &lt;strong&gt;Jobs&lt;/strong&gt;, comment on construit des manifests, &amp;hellip;) et j&amp;rsquo;espère que ça vous a plu.&lt;/p&gt;
&lt;h2 id="bonus"&gt;Bonus
&lt;/h2&gt;&lt;p&gt;Si vous trouvez ces scripts trop limités pour vos besoins, vous pouvez aller voir les outils suivants, plus poussés. Attention cependant, certains ne sont pas/plus maintenu, et je n&amp;rsquo;ai pas audité le code. A vos risques et périls donc :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://staffordwilliams.com/blog/2021/02/14/grafana-in-kubernetes/" target="_blank" rel="noopener"
&gt;https://staffordwilliams.com/blog/2021/02/14/grafana-in-kubernetes/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/ysde/grafana-backup-tool" target="_blank" rel="noopener"
&gt;https://github.com/ysde/grafana-backup-tool&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/grafana-wizzy/wizzy" target="_blank" rel="noopener"
&gt;https://github.com/grafana-wizzy/wizzy&lt;/a&gt; (archivé)&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/esnet/gdg" target="_blank" rel="noopener"
&gt;https://github.com/esnet/gdg&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>