<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Matteo ZINUTTI</title><link>https://mzinutti.fr/post/</link><description>Recent content in Posts on Matteo ZINUTTI</description><generator>Hugo -- gohugo.io</generator><language>fr-FR</language><lastBuildDate>Mon, 12 Oct 2026 18:43:52 +0200</lastBuildDate><atom:link href="https://mzinutti.fr/post/index.xml" rel="self" type="application/rss+xml"/><item><title>Passer un GPU NVIDIA dans un cluster Kubernetes</title><link>https://mzinutti.fr/posts/gpu-passthrough-talos/</link><pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate><guid>https://mzinutti.fr/posts/gpu-passthrough-talos/</guid><description>&lt;img src="https://mzinutti.fr/posts/gpu-passthrough-talos/images/cover.png" alt="Featured image of post Passer un GPU NVIDIA dans un cluster Kubernetes" /&gt;&lt;!--
Requêtes visées : "gpu passthrough proxmox kubernetes", "talos nvidia gpu",
"proxmox hardware mapping opentofu".
Type dominant : how-to, avec une section d'explication (IOMMU, DMA) qui justifie
le ballooning et la vérification des groupes.
--&gt;

 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Série &amp;ldquo;Un cluster Kubernetes avec GPU pour faire de l&amp;rsquo;inférence LLM&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a class="link" href="https://mzinutti.fr/posts/talos-cluster-proxmox/" target="_blank" rel="noopener"
 &gt;Déployer un cluster Kubernetes immuable avec Talos sur Proxmox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Passer un GPU NVIDIA dans un cluster Kubernetes&lt;/strong&gt; (cet article)&lt;/li&gt;
&lt;li&gt;Piloter un cluster Kubernetes en GitOps avec Flux (En cours d&amp;rsquo;écriture)&lt;/li&gt;
&lt;li&gt;Inférence LLM auto-hébergée sur Kubernetes (En cours d&amp;rsquo;écriture)&lt;/li&gt;
&lt;/ol&gt;

 &lt;/blockquote&gt;
&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Le cluster Talos déployé dans la &lt;a class="link" href="https://mzinutti.fr/posts/talos-cluster-proxmox/" target="_blank" rel="noopener"
 &gt;partie 1&lt;/a&gt; est opérationnel, mais le nœud &lt;code&gt;talos-gpu-01&lt;/code&gt; ne voit pour l&amp;rsquo;instant aucune carte graphique. Dans cet article, je détaille comment j&amp;rsquo;ai rendu une GeForce RTX 3050 utilisable par les pods Kubernetes, en partant de l&amp;rsquo;hôte Proxmox. Le travail se découpe en quatre étapes : détacher la carte de l&amp;rsquo;hôte, l&amp;rsquo;attacher à la VM avec OpenTofu, charger les modules NVIDIA dans Talos, puis exposer le GPU au scheduler Kubernetes.&lt;/p&gt;
&lt;figure class="figure-themed"&gt;
 &lt;img class="figure-themed-light" src="https://mzinutti.fr/posts/gpu-passthrough-talos/images/gpu-path.png" alt="Schéma des quatre étapes : sur l'hôte Proxmox, la RTX 3050 est liée à vfio-pci et isolée par l'IOMMU ; OpenTofu l'attache à la VM talos-gpu-01 par un hardware mapping ; Talos charge les modules NVIDIA des extensions ; le GPU Operator publie la ressource nvidia.com/gpu sur le nœud, qu'un pod demande et reçoit via CDI" width="800px"&gt;
 &lt;img class="figure-themed-dark" src="https://mzinutti.fr/posts/gpu-passthrough-talos/images/gpu-path-dark.png" alt="Schéma des quatre étapes : sur l'hôte Proxmox, la RTX 3050 est liée à vfio-pci et isolée par l'IOMMU ; OpenTofu l'attache à la VM talos-gpu-01 par un hardware mapping ; Talos charge les modules NVIDIA des extensions ; le GPU Operator publie la ressource nvidia.com/gpu sur le nœud, qu'un pod demande et reçoit via CDI" width="800px"&gt;
 &lt;figcaption&gt;&lt;h4&gt;Le chemin du GPU, de l&amp;#39;hôte Proxmox au pod Kubernetes&lt;/h4&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Avant de passer aux commandes, j&amp;rsquo;explique ce que fait le passthrough, d&amp;rsquo;où viennent la plupart des contraintes rencontrées ensuite, comme la désactivation du ballooning ou la vérification des groupes IOMMU.&lt;/p&gt;
&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;L&amp;rsquo;objectif final de cette série est de faire tourner de l&amp;rsquo;inférence LLM en local dans mon homelab, ce qui demande un GPU accessible depuis le cluster. Pour donner une carte graphique à une VM, il existe deux grandes approches : la partager entre plusieurs VMs avec du vGPU, ou la dédier entièrement à une seule VM avec du PCI passthrough.&lt;/p&gt;
&lt;p&gt;Dans mon cas, le choix était vite fait. NVIDIA ne propose le vGPU que sur ses gammes datacenter et professionnelles, il n&amp;rsquo;est pas disponible sur une GeForce. Il reste donc le passthrough, avec une conséquence directe : la carte est &lt;strong&gt;dédiée&lt;/strong&gt; à la VM &lt;code&gt;talos-gpu-01&lt;/code&gt;, l&amp;rsquo;hôte Proxmox n&amp;rsquo;y a plus accès et aucune autre VM ne peut la partager.&lt;/p&gt;
&lt;p&gt;Le matériel utilisé est une GeForce RTX 3050 que j&amp;rsquo;avais sous la main.&lt;/p&gt;
&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;Un CPU et une carte mère supportant l&amp;rsquo;IOMMU (VT-d chez Intel, AMD-Vi chez AMD), activée dans le BIOS.&lt;/li&gt;
&lt;li&gt;Le cluster Talos de la &lt;a class="link" href="https://mzinutti.fr/posts/talos-cluster-proxmox/" target="_blank" rel="noopener"
 &gt;partie 1&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Un GPU qui n&amp;rsquo;est pas utilisé pour l&amp;rsquo;affichage de l&amp;rsquo;hôte. Dans mon cas, l&amp;rsquo;hôte Proxmox tourne sans écran et le GPU intégré du processeur est désactivé dans le BIOS, la RTX 3050 est donc la seule carte graphique de la machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="comprendre-le-passthrough"&gt;Comprendre le passthrough
&lt;/h2&gt;&lt;p&gt;Un périphérique PCIe comme un GPU ne passe pas par le CPU pour lire ou écrire en mémoire. Il utilise le DMA (&lt;em&gt;Direct Memory Access&lt;/em&gt;) pour accéder directement à la RAM, ce qui lui permet de transférer de gros volumes de données sans occuper le processeur.&lt;/p&gt;
&lt;p&gt;Dans une VM, ce fonctionnement pose un problème. Les adresses mémoire que voit le système invité sont des adresses physiques invité (GPA, &lt;em&gt;Guest Physical Address&lt;/em&gt;), qui ne correspondent pas aux adresses physiques réelles de l&amp;rsquo;hôte (HPA, &lt;em&gt;Host Physical Address&lt;/em&gt;). Pour les accès du CPU, la traduction GPA vers HPA est faite par le matériel grâce à EPT (&lt;em&gt;Extended Page Tables&lt;/em&gt;) chez Intel ou NPT (&lt;em&gt;Nested Page Tables&lt;/em&gt;) chez AMD, une seconde table de pages que le processeur parcourt en plus de celle de l&amp;rsquo;invité. Un périphérique qui fait du DMA ne passe pas par cette traduction : si le driver de la VM donne une adresse GPA au GPU, la carte écrit à cette adresse dans la RAM de l&amp;rsquo;hôte, qui correspond à tout autre chose.&lt;/p&gt;
&lt;p&gt;C&amp;rsquo;est le rôle de l&amp;rsquo;&lt;strong&gt;IOMMU&lt;/strong&gt; (&lt;em&gt;Input-Output Memory Management Unit&lt;/em&gt;). Elle fait pour les périphériques ce que la MMU (&lt;em&gt;Memory Management Unit&lt;/em&gt;), le composant du processeur qui traduit les adresses virtuelles en adresses physiques, fait pour le CPU : elle traduit les adresses utilisées par le périphérique en adresses physiques réelles, et elle limite le périphérique aux zones mémoire qui lui ont été attribuées. Le GPU passé à la VM ne peut donc écrire que dans la mémoire de cette VM.&lt;/p&gt;
&lt;p&gt;Cette isolation ne se fait pas par périphérique mais par &lt;strong&gt;groupe IOMMU&lt;/strong&gt;. Un groupe rassemble les périphériques que le matériel n&amp;rsquo;est pas capable d&amp;rsquo;isoler les uns des autres, par exemple parce qu&amp;rsquo;ils peuvent communiquer entre eux sans passer par l&amp;rsquo;IOMMU. L&amp;rsquo;unité qu&amp;rsquo;on peut passer à une VM est donc le groupe entier, et il faut vérifier le contenu du groupe de la carte avant de commencer.&lt;/p&gt;
&lt;p&gt;Enfin, côté hôte, la carte ne doit être prise par aucun driver graphique (&lt;code&gt;nouveau&lt;/code&gt; ou &lt;code&gt;nvidia&lt;/code&gt;). C&amp;rsquo;est le driver &lt;code&gt;vfio-pci&lt;/code&gt; qui en prend possession. Il fait partie de &lt;a class="link" href="https://docs.kernel.org/driver-api/vfio.html" target="_blank" rel="noopener"
 &gt;VFIO&lt;/a&gt; (&lt;em&gt;Virtual Function I/O&lt;/em&gt;), le framework du noyau Linux qui donne à un programme hors du noyau, ici QEMU, l&amp;rsquo;accès direct à un périphérique, en s&amp;rsquo;appuyant sur l&amp;rsquo;IOMMU pour limiter ce que le périphérique peut atteindre en mémoire. C&amp;rsquo;est par ce biais que QEMU présente la carte à la VM.&lt;/p&gt;
&lt;figure class="figure-themed"&gt;
 &lt;img class="figure-themed-light" src="https://mzinutti.fr/posts/gpu-passthrough-talos/images/iommu-translation.png" alt="Schéma de la traduction d'adresses dans l'hôte Proxmox : les accès CPU de la VM passent par la MMU et EPT/NPT, les accès DMA du GPU passent par l'IOMMU, les deux arrivent en adresses HPA dans la mémoire épinglée de la VM, et l'IOMMU bloque l'accès au reste de la RAM" width="800px"&gt;
 &lt;img class="figure-themed-dark" src="https://mzinutti.fr/posts/gpu-passthrough-talos/images/iommu-translation-dark.png" alt="Schéma de la traduction d'adresses dans l'hôte Proxmox : les accès CPU de la VM passent par la MMU et EPT/NPT, les accès DMA du GPU passent par l'IOMMU, les deux arrivent en adresses HPA dans la mémoire épinglée de la VM, et l'IOMMU bloque l'accès au reste de la RAM" width="800px"&gt;
 &lt;figcaption&gt;&lt;h4&gt;Traduction d&amp;#39;adresses : accès CPU et accès DMA du GPU&lt;/h4&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id="pourquoi-le-ballooning-doit-être-désactivé"&gt;Pourquoi le ballooning doit être désactivé
&lt;/h3&gt;&lt;p&gt;Le ballooning permet à Proxmox de récupérer à chaud de la RAM inutilisée par une VM, grâce à un driver présent dans l&amp;rsquo;invité qui rend des pages mémoire à l&amp;rsquo;hôte. Avec un périphérique passé via VFIO, ce mécanisme ne peut plus fonctionner : le GPU peut écrire à tout moment dans les pages de la VM sans passer par le CPU, donc l&amp;rsquo;hôte n&amp;rsquo;a aucun moyen de savoir si une page est encore utilisée par la carte. Toute la mémoire de la VM est donc épinglée (&lt;em&gt;pinned&lt;/em&gt;) au démarrage et ne peut plus être rendue à l&amp;rsquo;hôte.&lt;/p&gt;
&lt;p&gt;Dans le provider &lt;code&gt;bpg/proxmox&lt;/code&gt;, c&amp;rsquo;est l&amp;rsquo;argument &lt;code&gt;floating&lt;/code&gt; du bloc &lt;code&gt;memory&lt;/code&gt; qui contrôle le ballooning, et la valeur &lt;code&gt;0&lt;/code&gt; le désactive. Je l&amp;rsquo;ai mis à &lt;code&gt;0&lt;/code&gt; dans la ressource qui crée les nœuds Talos, donc sur les trois nœuds et pas uniquement sur le nœud GPU :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#talos.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;memory&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; dedicated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;memory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; floating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Cela a une conséquence sur le dimensionnement : les 12 GB de RAM du nœud &lt;code&gt;talos-gpu-01&lt;/code&gt; sont réservés en permanence sur l&amp;rsquo;hôte, même quand le nœud ne fait rien.&lt;/p&gt;
&lt;p&gt;Maintenant que le fonctionnement est posé, passons à la préparation de l&amp;rsquo;hôte Proxmox.&lt;/p&gt;
&lt;h2 id="étape-1--préparer-lhôte-proxmox"&gt;Étape 1 : préparer l&amp;rsquo;hôte Proxmox
&lt;/h2&gt;&lt;h3 id="vérifier-liommu-et-les-groupes"&gt;Vérifier l&amp;rsquo;IOMMU et les groupes
&lt;/h3&gt;&lt;p&gt;La première chose à faire est d&amp;rsquo;identifier la carte et le groupe IOMMU auquel elle appartient :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;# La carte et ses fonctions&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;lspci -nn &lt;span class="p"&gt;|&lt;/span&gt; grep -i nvidia
&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;# Le groupe IOMMU auquel elle appartient&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find /sys/kernel/iommu_groups -type l &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s1"&gt;&amp;#39;01:00&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ce qui donne dans mon cas :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA106 [Geforce RTX 3050] [10de:2507] (rev a1)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;01:00.1 Audio device [0403]: NVIDIA Corporation GA106 High Definition Audio Controller [10de:228e] (rev a1)
&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;/sys/kernel/iommu_groups/10/devices/0000:01:00.0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/sys/kernel/iommu_groups/10/devices/0000:01:00.1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La carte expose deux fonctions : &lt;code&gt;01:00.0&lt;/code&gt; pour la partie vidéo et &lt;code&gt;01:00.1&lt;/code&gt; pour le contrôleur audio HDMI. Les deux sont seules dans le groupe IOMMU 10, ce qui veut dire que le groupe peut être passé à la VM tel quel, sans embarquer d&amp;rsquo;autre périphérique de l&amp;rsquo;hôte.&lt;/p&gt;
&lt;p&gt;Si d&amp;rsquo;autres périphériques partageaient ce groupe, par exemple un contrôleur USB, il faudrait les passer eux aussi à la VM. Le patch &lt;em&gt;ACS override&lt;/em&gt; est souvent proposé sur les forums pour découper artificiellement les groupes, mais il demande au noyau d&amp;rsquo;ignorer une isolation que le matériel ne garantit pas, les périphériques du groupe pouvant toujours communiquer entre eux. Le &lt;a class="link" href="https://pve.proxmox.com/wiki/PCI_Passthrough" target="_blank" rel="noopener"
 &gt;wiki Proxmox&lt;/a&gt; le présente d&amp;rsquo;ailleurs comme une dernière option, qui n&amp;rsquo;est pas sans risques.&lt;/p&gt;
&lt;h3 id="attacher-la-carte-à-vfio-pci"&gt;Attacher la carte à vfio-pci
&lt;/h3&gt;&lt;p&gt;J&amp;rsquo;ai configuré l&amp;rsquo;hôte Proxmox pour que &lt;code&gt;vfio-pci&lt;/code&gt; prenne la carte avant tout autre driver. Cette partie n&amp;rsquo;est pas gérée par mon code OpenTofu, je l&amp;rsquo;ai faite directement sur l&amp;rsquo;hôte Proxmox.&lt;/p&gt;
&lt;p&gt;D&amp;rsquo;après la &lt;a class="link" href="https://pve.proxmox.com/pve-docs/chapter-qm.html#qm_pci_passthrough" target="_blank" rel="noopener"
 &gt;documentation Proxmox&lt;/a&gt;, l&amp;rsquo;IOMMU est activée par défaut sur Intel à partir du noyau 6.8, l&amp;rsquo;option &lt;code&gt;intel_iommu=on&lt;/code&gt; que l&amp;rsquo;on trouve dans beaucoup de guides n&amp;rsquo;est donc plus nécessaire. Le seul paramètre utile est &lt;code&gt;iommu=pt&lt;/code&gt;, qui limite la traduction d&amp;rsquo;adresses aux périphériques passés à une VM et peut améliorer les performances des autres.&lt;/p&gt;
&lt;p&gt;Les paramètres du noyau se passent par la ligne de commande que GRUB, le chargeur de démarrage de l&amp;rsquo;hôte, transmet au noyau à chaque boot. Quand l&amp;rsquo;hôte démarre avec GRUB, ce qui est mon cas, elle se définit dans &lt;code&gt;/etc/default/grub&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;#/etc/default/grub&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;quiet iommu=pt&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;GRUB_CMDLINE_LINUX_DEFAULT&lt;/code&gt; s&amp;rsquo;applique aux entrées de démarrage normales, mais pas au mode de récupération. &lt;code&gt;quiet&lt;/code&gt; est présent par défaut sur Debian et Proxmox : il réduit les messages du noyau affichés à l&amp;rsquo;écran pendant le démarrage, sans rien changer à ce qui est enregistré dans les logs.&lt;/p&gt;
&lt;p&gt;Le &lt;code&gt;dmesg&lt;/code&gt; de l&amp;rsquo;hôte confirme que l&amp;rsquo;IOMMU est active, avec le mode passthrough demandé, et que les périphériques sont répartis en groupes IOMMU :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[ 0.284240] iommu: Default domain type: Passthrough (set via kernel command line)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[ 0.329656] pci 0000:00:00.0: Adding to iommu group 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[ 0.329665] pci 0000:00:01.0: Adding to iommu group 1
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les modules VFIO sont chargés au démarrage via &lt;code&gt;/etc/modules&lt;/code&gt; : &lt;code&gt;vfio&lt;/code&gt; pour le cœur du framework, &lt;code&gt;vfio_iommu_type1&lt;/code&gt; pour piloter l&amp;rsquo;IOMMU, et &lt;code&gt;vfio_pci&lt;/code&gt; pour les périphériques PCI :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;#/etc/modules&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vfio
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vfio_iommu_type1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vfio_pci
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Pour que &lt;code&gt;vfio-pci&lt;/code&gt; réserve la carte, je lui donne les identifiants des deux fonctions, la partie vidéo &lt;code&gt;10de:2507&lt;/code&gt; et la partie audio &lt;code&gt;10de:228e&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;#/etc/modprobe.d/vfio.conf&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;options vfio-pci &lt;span class="nv"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;10de:2507,10de:228e &lt;span class="nv"&gt;disable_vga&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;L&amp;rsquo;option &lt;code&gt;disable_vga=1&lt;/code&gt; empêche &lt;code&gt;vfio-pci&lt;/code&gt; d&amp;rsquo;exposer les ressources VGA historiques de la carte à l&amp;rsquo;hôte. En complément, je bloque les drivers graphiques NVIDIA côté hôte pour qu&amp;rsquo;aucun ne prenne la carte au démarrage :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;#/etc/modprobe.d/blacklist.conf&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;blacklist nouveau
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;blacklist nvidia*
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les modifications de &lt;code&gt;/etc/modules&lt;/code&gt; et de &lt;code&gt;/etc/modprobe.d/&lt;/code&gt; ne sont prises en compte qu&amp;rsquo;après régénération de l&amp;rsquo;initramfs (&lt;code&gt;update-initramfs -u -k all&lt;/code&gt;), et celles de &lt;code&gt;/etc/default/grub&lt;/code&gt; après un &lt;code&gt;update-grub&lt;/code&gt;. Après le redémarrage de l&amp;rsquo;hôte, la vérification se fait avec :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;lspci -nnk -s 01:00
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ce qui donne dans mon cas :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA106 [Geforce RTX 3050] [10de:2507] (rev a1)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Subsystem: NVIDIA Corporation GA106 [Geforce RTX 3050] [10de:2507]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Kernel driver in use: vfio-pci
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Kernel modules: nvidiafb, nouveau
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;01:00.1 Audio device [0403]: NVIDIA Corporation GA106 High Definition Audio Controller [10de:228e] (rev a1)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Subsystem: NVIDIA Corporation Device [10de:2507]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Kernel driver in use: vfio-pci
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Kernel modules: snd_hda_intel
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les deux fonctions sont bien prises par &lt;code&gt;vfio-pci&lt;/code&gt;. La ligne &lt;code&gt;Kernel modules&lt;/code&gt; liste les drivers capables de gérer la carte, pas ceux qui sont chargés : &lt;code&gt;nouveau&lt;/code&gt; et &lt;code&gt;nvidiafb&lt;/code&gt; sont bloqués et n&amp;rsquo;apparaissent pas dans &lt;code&gt;lsmod&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote class="alert alert-warning"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;⚠️&lt;/span&gt;
 &lt;span class="alert-title"&gt;Avertissement&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Tant que &lt;code&gt;lspci -nnk&lt;/code&gt; n&amp;rsquo;affiche pas &lt;code&gt;Kernel driver in use: vfio-pci&lt;/code&gt; sur les deux fonctions, il ne sert à rien de continuer. La VM démarrera peut-être, mais la carte ne sera pas utilisable.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;p&gt;L&amp;rsquo;hôte est prêt, la carte peut maintenant être attachée à la VM.&lt;/p&gt;
&lt;h2 id="étape-2--attacher-le-gpu-à-la-vm-avec-opentofu"&gt;Étape 2 : attacher le GPU à la VM avec OpenTofu
&lt;/h2&gt;&lt;h3 id="pourquoi-un-hardware-mapping-et-pas-un-id-pci"&gt;Pourquoi un hardware mapping et pas un ID PCI
&lt;/h3&gt;&lt;p&gt;Le provider &lt;code&gt;bpg/proxmox&lt;/code&gt; propose deux manières de déclarer un périphérique dans le bloc &lt;code&gt;hostpci&lt;/code&gt; d&amp;rsquo;une VM : son ID PCI brut avec l&amp;rsquo;argument &lt;code&gt;id&lt;/code&gt;, ou un &lt;em&gt;hardware mapping&lt;/em&gt; déclaré au niveau du cluster Proxmox avec l&amp;rsquo;argument &lt;code&gt;mapping&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La &lt;a class="link" href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_vm#hostpci" target="_blank" rel="noopener"
 &gt;documentation du provider&lt;/a&gt; précise que &lt;code&gt;id&lt;/code&gt; n&amp;rsquo;est pas compatible avec une authentification par token API et qu&amp;rsquo;il demande le compte root avec son mot de passe. Toute mon IaC s&amp;rsquo;authentifie avec le token de l&amp;rsquo;utilisateur &lt;code&gt;terraform@pve&lt;/code&gt;, je suis donc parti sur le mapping, qui était la seule option sans changer l&amp;rsquo;authentification du provider.&lt;/p&gt;
&lt;p&gt;Le mapping est déclaré avec la ressource &lt;a class="link" href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/hardware_mapping_pci" target="_blank" rel="noopener"
 &gt;proxmox_hardware_mapping_pci&lt;/a&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#hardware_mapping.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;proxmox_hardware_mapping_pci&amp;#34; &amp;#34;this&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; for_each&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;pci_hardware_mappings&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="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;comment&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="n"&gt; map&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;proxmox_node&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;path&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; iommu_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;iommu_group&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; subsystem_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;subsystem_id&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="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les valeurs sont renseignées dans les variables :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#variables.auto.tfvars
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;pci_hardware_mappings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; gpu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; comment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;GeForce RTX 3050 - managed by Terraform&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0000:01:00&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;10de:2507&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; iommu_group&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; subsystem_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;10de:2507&amp;#34;&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le &lt;code&gt;path&lt;/code&gt; est déclaré sans numéro de fonction (&lt;code&gt;0000:01:00&lt;/code&gt; et non &lt;code&gt;0000:01:00.0&lt;/code&gt;), ce qui couvre les deux fonctions de la carte d&amp;rsquo;un coup.&lt;/p&gt;
&lt;p&gt;Les arguments &lt;code&gt;iommu_group&lt;/code&gt; et &lt;code&gt;subsystem_id&lt;/code&gt; sont optionnels pour l&amp;rsquo;API, mais sans eux Proxmox affiche le mapping comme incomplet dans l&amp;rsquo;interface. Je les ai donc renseignés à partir des commandes &lt;code&gt;lspci -nnv -s 0000:01:00&lt;/code&gt; et &lt;code&gt;find /sys/kernel/iommu_groups -type l&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="les-privilèges-proxmox"&gt;Les privilèges Proxmox
&lt;/h3&gt;&lt;p&gt;Le rôle utilisé par OpenTofu doit recevoir trois privilèges supplémentaires :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#users.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Mapping.Audit&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Mapping.Modify&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;Mapping.Use&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mapping.Audit&lt;/strong&gt; : lire les mappings existants.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mapping.Modify&lt;/strong&gt; : créer, modifier et supprimer un mapping.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mapping.Use&lt;/strong&gt; : attacher un mapping à une VM, en plus des droits habituels de modification de la VM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sans &lt;code&gt;Mapping.Use&lt;/code&gt;, le mapping est bien créé mais l&amp;rsquo;attachement à la VM échoue.&lt;/p&gt;
&lt;h3 id="le-bloc-hostpci"&gt;Le bloc hostpci
&lt;/h3&gt;&lt;p&gt;Côté VM, une seule ressource sert les trois nœuds Talos. Le bloc &lt;code&gt;hostpci&lt;/code&gt; est donc dynamique : il est généré à partir de la liste &lt;code&gt;hostpci&lt;/code&gt; de chaque nœud, qui vaut &lt;code&gt;[]&lt;/code&gt; par défaut. Les deux nœuds sans carte n&amp;rsquo;ont ainsi aucun périphérique attaché.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#talos.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;dynamic&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hostpci&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; for_each&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt; { for device in each.value.hostpci : device.device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;device&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="k"&gt;content&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;hostpci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;device&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;proxmox_hardware_mapping_pci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;hostpci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="k"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; pcie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;hostpci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;pcie&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; rombar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;hostpci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;rombar&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; xvga&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;hostpci&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;xvga&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Dans la &lt;a class="link" href="https://mzinutti.fr/posts/talos-cluster-proxmox/" target="_blank" rel="noopener"
 &gt;partie 1&lt;/a&gt;, j&amp;rsquo;avais annoncé une sixième validation sur la variable &lt;code&gt;talos_nodes&lt;/code&gt;. La voici, elle vérifie que chaque &lt;code&gt;hostpci.mapping&lt;/code&gt; correspond bien à un mapping déclaré :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#variables.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;validation&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; condition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;alltrue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;flatten&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;node&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_nodes&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;device&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;hostpci&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="k"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;pci_hardware_mappings&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="k"&gt;device&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;mapping&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; error_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Every Talos node hostpci.mapping must be a key of var.pci_hardware_mappings.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Une faute de frappe dans le nom du mapping est ainsi détectée au &lt;code&gt;plan&lt;/code&gt;, et pas sous la forme d&amp;rsquo;une erreur d&amp;rsquo;API au milieu de l&amp;rsquo;&lt;code&gt;apply&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Une fois le pipeline passé, la configuration de la VM côté Proxmox reflète ce qui a été déclaré :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;qm config &lt;span class="m"&gt;123&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep -E &lt;span class="s1"&gt;&amp;#39;^(hostpci|balloon|memory)&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;balloon: 0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hostpci0: mapping=gpu,pcie=1,rombar=1,x-vga=0
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;memory: 12288
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le périphérique est référencé par le nom du mapping et non par son adresse PCI, et &lt;code&gt;balloon: 0&lt;/code&gt; correspond au &lt;code&gt;floating = 0&lt;/code&gt; du bloc &lt;code&gt;memory&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;La carte est désormais attachée à la VM, il reste à la rendre utilisable par Talos.&lt;/p&gt;
&lt;h2 id="étape-3--les-modules-nvidia-dans-talos"&gt;Étape 3 : les modules NVIDIA dans Talos
&lt;/h2&gt;&lt;h3 id="un-schematic-dédié-au-nœud-gpu"&gt;Un schematic dédié au nœud GPU
&lt;/h3&gt;&lt;p&gt;Comme expliqué dans la &lt;a class="link" href="https://mzinutti.fr/posts/talos-cluster-proxmox/" target="_blank" rel="noopener"
 &gt;partie 1&lt;/a&gt;, les extensions NVIDIA ne sont pas dans l&amp;rsquo;image de base : les nœuds sans carte remontaient des erreurs de chargement des modules à chaque démarrage. Le nœud &lt;code&gt;talos-gpu-01&lt;/code&gt; utilise donc son propre schematic :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#variables.auto.tfvars
&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="n"&gt; hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;talos-gpu-01&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;192.168.1.123&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt; &lt;span class="err"&gt;*&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; disk_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;worker&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; schematic_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;a31d81eceab05afdc2c59b0002701173e117a5fb81bdc1f64062d27655145848&amp;#34;&lt;/span&gt;&lt;span class="c1"&gt; # With NVIDIA GPU support
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; hostpci&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hostpci0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;gpu&amp;#34;&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="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ce schematic ajoute deux extensions à &lt;code&gt;qemu-guest-agent&lt;/code&gt; : &lt;code&gt;nvidia-open-gpu-kernel-modules-production&lt;/code&gt; pour les drivers, et &lt;code&gt;nvidia-container-toolkit-production&lt;/code&gt; pour que le runtime de conteneurs sache exposer la carte aux pods.&lt;/p&gt;
&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;J&amp;rsquo;utilise les drivers &lt;strong&gt;open source&lt;/strong&gt; de NVIDIA. Ils prennent en charge les GPU à partir de l&amp;rsquo;architecture Turing, ce qui inclut la RTX 3050 (Ampere). Sur du matériel plus ancien, il faut passer sur les extensions propriétaires.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;h3 id="charger-les-modules"&gt;Charger les modules
&lt;/h3&gt;&lt;p&gt;Les extensions embarquent les modules noyau mais ne les chargent pas. C&amp;rsquo;est le rôle d&amp;rsquo;un patch de machine config, placé au &lt;strong&gt;niveau nœud&lt;/strong&gt; et non au niveau &lt;code&gt;worker/&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# patches/nodes/talos-gpu-01/30-nvidia-modules.yaml.tftpl&lt;/span&gt;&lt;span class="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;machine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kernel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;modules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;nvidia&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;nvidia_uvm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;nvidia_drm&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;nvidia_modeset&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ce patch utilise le niveau &lt;code&gt;nodes/&lt;/code&gt; présenté dans la partie 1 : placé sous &lt;code&gt;worker/&lt;/code&gt;, il serait aussi appliqué à &lt;code&gt;talos-worker-01&lt;/code&gt;, dont l&amp;rsquo;image ne contient pas les extensions.&lt;/p&gt;
&lt;p&gt;La liste des modules est celle de la &lt;a class="link" href="https://docs.siderolabs.com/talos/v1.13/configure-your-talos-cluster/hardware-and-drivers/nvidia-gpu" target="_blank" rel="noopener"
 &gt;documentation Talos&lt;/a&gt;. Le module &lt;code&gt;nvidia_uvm&lt;/code&gt; est celui qu&amp;rsquo;utilise CUDA pour gérer la mémoire unifiée.&lt;/p&gt;
&lt;h3 id="appliquer-et-vérifier"&gt;Appliquer et vérifier
&lt;/h3&gt;&lt;p&gt;Le schematic et le patch sont déployés par le pipeline GitLab CI décrit dans la partie 1, comme le reste de la configuration des nœuds. Je vérifie ensuite que les extensions sont bien présentes dans l&amp;rsquo;image du nœud :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n 192.168.1.123 get extensions
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ce qui donne le résultat suivant :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NODE NAMESPACE TYPE ID VERSION NAME VERSION
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.123 runtime ExtensionStatus 0 1 nvidia-container-toolkit-production 595.71.05-v1.19.1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.123 runtime ExtensionStatus 1 1 nvidia-open-gpu-kernel-modules-production 595.71.05-v1.13.8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.123 runtime ExtensionStatus 2 1 qemu-guest-agent 11.0.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.123 runtime ExtensionStatus 3 1 schematic a31d81eceab05afdc2c59b0002701173e117a5fb81bdc1f64062d27655145848
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.123 runtime ExtensionStatus modules.dep 1 modules.dep 6.18.42-talos
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les deux extensions NVIDIA sont en version &lt;code&gt;595.71.05&lt;/code&gt;, le numéro du driver, et l&amp;rsquo;ID &lt;code&gt;schematic&lt;/code&gt; est bien celui déclaré pour ce nœud. Les quatre modules du patch doivent ensuite être chargés :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n 192.168.1.123 get loadedkernelmodules &lt;span class="p"&gt;|&lt;/span&gt; grep -i nvidia
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.123 runtime LoadedKernelModule nvidia 2 Live
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.123 runtime LoadedKernelModule nvidia_drm 2 Live
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.123 runtime LoadedKernelModule nvidia_modeset 2 Live
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;192.168.1.123 runtime LoadedKernelModule nvidia_uvm 1 Live
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Enfin, le fichier &lt;code&gt;/proc/driver/nvidia/version&lt;/code&gt; n&amp;rsquo;existe que si le module &lt;code&gt;nvidia&lt;/code&gt; est chargé, et il donne la version exacte du driver :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl -n 192.168.1.123 &lt;span class="nb"&gt;read&lt;/span&gt; /proc/driver/nvidia/version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NVRM version: NVIDIA UNIX Open Kernel Module for x86_64 595.71.05 Release Build (dvs-builder@U22-I3-G08-03-1) Fri Apr 24 06:42:30 UTC 2026
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GCC version: Selected multilib: .;@m64
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La mention &lt;code&gt;Open Kernel Module&lt;/code&gt; confirme que ce sont bien les drivers open source qui tournent.&lt;/p&gt;
&lt;p&gt;Le driver est chargé dans la VM, la dernière étape consiste à le faire connaître à Kubernetes.&lt;/p&gt;
&lt;h2 id="étape-4--exposer-le-gpu-à-kubernetes"&gt;Étape 4 : exposer le GPU à Kubernetes
&lt;/h2&gt;&lt;p&gt;Des modules chargés sur le nœud ne suffisent pas. Le scheduler Kubernetes ne connaît que le CPU et la mémoire, et pour qu&amp;rsquo;il puisse allouer un GPU à un pod, il faut une ressource étendue &lt;code&gt;nvidia.com/gpu&lt;/code&gt; publiée sur le nœud par un &lt;em&gt;device plugin&lt;/em&gt;. Un &lt;a class="link" href="https://kubernetes.io/docs/concepts/extend-kubernetes/compute-storage-net/device-plugins/" target="_blank" rel="noopener"
 &gt;device plugin&lt;/a&gt; est un composant qui tourne sur le nœud, déclare au kubelet les GPU disponibles et leur état, puis choisit quelle carte attribuer à chaque pod qui en demande une.&lt;/p&gt;
&lt;h3 id="nvidia-gpu-operator"&gt;NVIDIA GPU Operator
&lt;/h3&gt;&lt;p&gt;Pour ce faire, j&amp;rsquo;ai utilisé le &lt;a class="link" href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/index.html" target="_blank" rel="noopener"
 &gt;NVIDIA GPU Operator&lt;/a&gt; plutôt que le seul device plugin NVIDIA, pour deux raisons : c&amp;rsquo;est la méthode décrite dans la documentation Talos, et l&amp;rsquo;opérateur déploie aussi le DCGM exporter, qui me permet de récupérer les métriques du GPU dans Prometheus.&lt;/p&gt;
&lt;p&gt;Par défaut, l&amp;rsquo;opérateur installe lui-même les drivers et le container toolkit sur les nœuds, mais sur Talos ces deux composants sont déjà fournis par les extensions système. Il faut donc les désactiver pour éviter les conflits, comme l&amp;rsquo;indique la documentation Talos :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl create namespace gpu-operator
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl label --overwrite namespace gpu-operator pod-security.kubernetes.io/enforce&lt;span class="o"&gt;=&lt;/span&gt;privileged
&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;helm repo add nvidia https://helm.ngc.nvidia.com/nvidia
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm install gpu-operator nvidia/gpu-operator &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace gpu-operator &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --version v26.3.3 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set driver.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set toolkit.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;false&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set hostPaths.driverInstallDir&lt;span class="o"&gt;=&lt;/span&gt;/usr/local &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set dcgmExporter.serviceMonitor.enabled&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;driver.enabled=false&lt;/strong&gt; et &lt;strong&gt;toolkit.enabled=false&lt;/strong&gt; : les drivers et le toolkit viennent des extensions Talos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;hostPaths.driverInstallDir=/usr/local&lt;/strong&gt; : l&amp;rsquo;extension Talos installe les fichiers du driver dans &lt;code&gt;/usr/local&lt;/code&gt;, alors que l&amp;rsquo;opérateur les cherche par défaut dans &lt;code&gt;/run/nvidia/driver&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;dcgmExporter.serviceMonitor.enabled=true&lt;/strong&gt; : crée le &lt;code&gt;ServiceMonitor&lt;/code&gt; qui permet à Prometheus de collecter les métriques du GPU.&lt;/li&gt;
&lt;li&gt;Le namespace doit autoriser les pods privilégiés, car les composants de l&amp;rsquo;opérateur accèdent au matériel du nœud.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;La documentation Talos demande une version du GPU Operator supérieure ou égale à &lt;code&gt;v26.3.1&lt;/code&gt;.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;p&gt;Je n&amp;rsquo;ai pas eu besoin de &lt;code&gt;nodeSelector&lt;/code&gt; pour cibler le nœud GPU. L&amp;rsquo;opérateur déploie d&amp;rsquo;abord &lt;a class="link" href="https://kubernetes-sigs.github.io/node-feature-discovery/" target="_blank" rel="noopener"
 &gt;Node Feature Discovery&lt;/a&gt; sur tous les nœuds, qui détecte les périphériques PCI de chacun. L&amp;rsquo;opérateur ajoute ensuite le label &lt;code&gt;nvidia.com/gpu.present=true&lt;/code&gt; aux nœuds qui ont une carte NVIDIA, et ne déploie ses composants (device plugin, DCGM exporter, validation) que sur ces nœuds :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pods -n gpu-operator -o wide
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-feature-discovery-9ll6h 1/1 Running 0 52d 10.244.0.134 talos-gpu-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-operator-57d75775c8-ls7hf 1/1 Running 0 4d12h 10.244.2.150 talos-controlplane-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-operator-node-feature-discovery-gc-847bb8f7b6-9gmkf 1/1 Running 0 4d12h 10.244.0.231 talos-gpu-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-operator-node-feature-discovery-master-d98f944cd-vhzdw 1/1 Running 0 4d12h 10.244.2.188 talos-controlplane-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-operator-node-feature-discovery-worker-kvvfl 1/1 Running 0 52d 10.244.0.249 talos-gpu-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-operator-node-feature-discovery-worker-ntvzt 1/1 Running 0 52d 10.244.2.39 talos-controlplane-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;gpu-operator-node-feature-discovery-worker-s95fk 1/1 Running 0 52d 10.244.1.208 talos-worker-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia-cuda-validator-gfd6h 0/1 Completed 0 6m42s 10.244.0.109 talos-gpu-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia-dcgm-exporter-rkt88 1/1 Running 0 52d 10.244.0.161 talos-gpu-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia-device-plugin-daemonset-pgnqw 1/1 Running 0 52d 10.244.0.164 talos-gpu-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvidia-operator-validator-xqdd8 1/1 Running 0 52d 10.244.0.50 talos-gpu-01 &amp;lt;none&amp;gt; &amp;lt;none&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Seuls les workers NFD tournent sur les trois nœuds, tous les composants NVIDIA sont sur &lt;code&gt;talos-gpu-01&lt;/code&gt;. Le pod &lt;code&gt;nvidia-cuda-validator&lt;/code&gt; en &lt;code&gt;Completed&lt;/code&gt; est normal : il lance un programme CUDA de test sur la carte, puis s&amp;rsquo;arrête.&lt;/p&gt;
&lt;h3 id="vérifier-lallocation"&gt;Vérifier l&amp;rsquo;allocation
&lt;/h3&gt;&lt;p&gt;La ressource &lt;code&gt;nvidia.com/gpu&lt;/code&gt; doit apparaître dans la capacité du nœud :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get nodes -o json &lt;span class="p"&gt;|&lt;/span&gt; jq &lt;span class="s1"&gt;&amp;#39;.items[] | {name: .metadata.name, gpu: .status.capacity.&amp;#34;nvidia.com/gpu&amp;#34;}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;name&amp;#34;: &amp;#34;talos-controlplane-01&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;gpu&amp;#34;: null
&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;name&amp;#34;: &amp;#34;talos-gpu-01&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;gpu&amp;#34;: &amp;#34;1&amp;#34;
&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;name&amp;#34;: &amp;#34;talos-worker-01&amp;#34;,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;gpu&amp;#34;: null
&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le nœud &lt;code&gt;talos-gpu-01&lt;/code&gt; expose un GPU, les deux autres nœuds n&amp;rsquo;ont pas la ressource. Les labels ajoutés au nœud par le GPU Feature Discovery donnent aussi le détail de la carte vue par Kubernetes :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get node talos-gpu-01 --show-labels &lt;span class="p"&gt;|&lt;/span&gt; tr &lt;span class="s1"&gt;&amp;#39;,&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; grep nvidia.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&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;nvidia.com/cuda.driver-version.full=595.71.05
&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;nvidia.com/cuda.runtime-version.full=13.2
&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;nvidia.com/gpu.count=1
&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;nvidia.com/gpu.family=ampere
&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;nvidia.com/gpu.memory=8192
&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;nvidia.com/gpu.product=NVIDIA-GeForce-RTX-3050
&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le label &lt;code&gt;nvidia.com/cuda.runtime-version.full=13.2&lt;/code&gt; donne la version CUDA maximale supportée par le driver, ce qui sert à choisir l&amp;rsquo;image du pod de test.&lt;/p&gt;
&lt;p&gt;Pour valider toute la chaîne, je lance un pod de test qui exécute &lt;code&gt;nvidia-smi&lt;/code&gt; (&lt;em&gt;NVIDIA System Management Interface&lt;/em&gt;), l&amp;rsquo;outil en ligne de commande livré avec le driver qui affiche les cartes visibles, la version du driver, l&amp;rsquo;occupation mémoire et les processus en cours. Si la commande voit la carte depuis un conteneur, c&amp;rsquo;est que le driver, le runtime et l&amp;rsquo;allocation fonctionnent :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;nvidia-smi&lt;/span&gt;&lt;span class="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;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;Never&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;nvidia-smi&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;nvidia/cuda:13.2.1-base-ubuntu24.04&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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 class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;nvidia-smi&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;resources&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;limits&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nvidia.com/gpu&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le champ &lt;code&gt;nvidia.com/gpu: 1&lt;/code&gt; dans les &lt;code&gt;limits&lt;/code&gt; suffit : le pod demande la ressource publiée par le device plugin, le scheduler ne peut donc le placer que sur &lt;code&gt;talos-gpu-01&lt;/code&gt;, et le GPU lui est réservé tant qu&amp;rsquo;il tourne.&lt;/p&gt;
&lt;p&gt;La documentation Talos ajoute aussi &lt;code&gt;runtimeClassName: nvidia&lt;/code&gt; à son pod de test, pour passer par le runtime NVIDIA que l&amp;rsquo;extension &lt;code&gt;nvidia-container-toolkit&lt;/code&gt; déclare dans containerd. Sur mon cluster, ce n&amp;rsquo;est pas nécessaire, car &lt;a class="link" href="https://github.com/cncf-tags/container-device-interface" target="_blank" rel="noopener"
 &gt;CDI&lt;/a&gt; (&lt;em&gt;Container Device Interface&lt;/em&gt;) est activé dans le GPU Operator :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get clusterpolicy -o &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{.items[0].spec.cdi}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;{&amp;#34;default&amp;#34;:false,&amp;#34;enabled&amp;#34;:true,&amp;#34;nriPluginEnabled&amp;#34;:false}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Avec CDI, le device plugin indique à containerd quels fichiers de périphérique et quelles libs du driver ajouter au conteneur, et containerd les injecte lui-même avec son runtime par défaut. C&amp;rsquo;est aussi de cette manière que le binaire &lt;code&gt;nvidia-smi&lt;/code&gt;, absent de l&amp;rsquo;image &lt;code&gt;nvidia/cuda&lt;/code&gt;, se retrouve dans le conteneur. D&amp;rsquo;après la &lt;a class="link" href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/cdi.html" target="_blank" rel="noopener"
 &gt;documentation NVIDIA sur CDI&lt;/a&gt;, c&amp;rsquo;est le fonctionnement par défaut depuis la version v25.10.0 du GPU Operator, et &lt;code&gt;runtimeClassName: nvidia&lt;/code&gt; ne reste nécessaire que pour les conteneurs qui accèdent aux GPU par la variable d&amp;rsquo;environnement &lt;code&gt;NVIDIA_VISIBLE_DEVICES&lt;/code&gt; sans passer par le device plugin, comme les agents de supervision.&lt;/p&gt;
&lt;p&gt;Une fois le pod terminé, ses logs contiennent la sortie de &lt;code&gt;nvidia-smi&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl logs nvidia-smi
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&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;| NVIDIA-SMI 595.71.05 Driver Version: 595.71.05 CUDA Version: 13.2 |
&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;| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| | | MIG M. |
&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;| 0 NVIDIA GeForce RTX 3050 On | 00000000:01:00.0 Off | N/A |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| 0% 45C P8 7W / 130W | 1MiB / 8192MiB | 0% Default |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| | | N/A |
&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&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;| Processes: |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| GPU GI CI PID Type Process name GPU Memory |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| ID ID Usage |
&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;| No running processes found |
&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le conteneur voit la RTX 3050 avec ses 8 GB de mémoire, le driver &lt;code&gt;595.71.05&lt;/code&gt; fourni par l&amp;rsquo;extension Talos et CUDA 13.2. Toute la chaîne fonctionne, de &lt;code&gt;vfio-pci&lt;/code&gt; sur l&amp;rsquo;hôte Proxmox jusqu&amp;rsquo;au pod.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Le GPU est désormais allouable par le scheduler : le cluster expose une ressource &lt;code&gt;nvidia.com/gpu&lt;/code&gt; sur le nœud &lt;code&gt;talos-gpu-01&lt;/code&gt;, et un pod peut la demander comme il demanderait du CPU ou de la mémoire.&lt;/p&gt;
&lt;p&gt;Il n&amp;rsquo;y a qu&amp;rsquo;une seule ressource &lt;code&gt;nvidia.com/gpu&lt;/code&gt; dans tout le cluster, ce qui veut dire qu&amp;rsquo;un seul pod à la fois peut utiliser la carte. Pour la partager entre plusieurs pods, le GPU Operator propose du &lt;em&gt;time-slicing&lt;/em&gt;, où les pods se partagent la carte à tour de rôle, et NVIDIA propose aussi MPS (&lt;em&gt;Multi-Process Service&lt;/em&gt;), qui fait tourner les processus CUDA de plusieurs pods en même temps sur la carte. Ce sont des pistes que je n&amp;rsquo;ai pas encore mises en place.&lt;/p&gt;
&lt;p&gt;De mon côté, cette partie m&amp;rsquo;a beaucoup appris sur le matériel. Mes connaissances en virtualisation restaient jusque-là à un niveau assez haut, celui de l&amp;rsquo;hyperviseur, et je ne connaissais pas les mécanismes bas niveau sur lesquels repose le passthrough, comme les entrées/sorties des périphériques, le DMA, la MMU ou l&amp;rsquo;IOMMU.&lt;/p&gt;
&lt;p&gt;À ce stade, l&amp;rsquo;infrastructure est entièrement décrite en OpenTofu, mais tout ce qui tourne dans le cluster a été installé avec des &lt;code&gt;helm install&lt;/code&gt; lancés à la main. C&amp;rsquo;est le même problème de dérive de configuration que Talos règle au niveau de l&amp;rsquo;OS, qui se retrouve un étage plus haut. La partie 3 met donc en place du GitOps avec Flux avant d&amp;rsquo;aller plus loin vers l&amp;rsquo;inférence.&lt;/p&gt;
&lt;h2 id="liens-utiles"&gt;Liens utiles
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://pve.proxmox.com/wiki/PCI_Passthrough" target="_blank" rel="noopener"
 &gt;PCI Passthrough - Wiki Proxmox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://pve.proxmox.com/pve-docs/chapter-qm.html#qm_pci_passthrough" target="_blank" rel="noopener"
 &gt;PCI(e) Passthrough - Documentation Proxmox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.siderolabs.com/talos/v1.13/configure-your-talos-cluster/hardware-and-drivers/nvidia-gpu" target="_blank" rel="noopener"
 &gt;NVIDIA GPU sur Talos (drivers OSS)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/latest/index.html" target="_blank" rel="noopener"
 &gt;NVIDIA GPU Operator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/hardware_mapping_pci" target="_blank" rel="noopener"
 &gt;proxmox_hardware_mapping_pci&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Déployer un cluster Kubernetes immuable avec Talos sur Proxmox</title><link>https://mzinutti.fr/posts/talos-cluster-proxmox/</link><pubDate>Fri, 02 Oct 2026 13:33:17 +0000</pubDate><guid>https://mzinutti.fr/posts/talos-cluster-proxmox/</guid><description>&lt;img src="https://mzinutti.fr/posts/talos-cluster-proxmox/images/cover.png" alt="Featured image of post Déployer un cluster Kubernetes immuable avec Talos sur Proxmox" /&gt;
 &lt;blockquote&gt;
 &lt;p&gt;&lt;strong&gt;Série &amp;ldquo;Un cluster Kubernetes avec GPU pour faire de l&amp;rsquo;inférence LLM&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Déployer un cluster Kubernetes immuable avec Talos sur Proxmox&lt;/strong&gt; (cet article)&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://mzinutti.fr/posts/gpu-passthrough-talos/" target="_blank" rel="noopener"
 &gt;Passer un GPU NVIDIA dans un cluster Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Piloter un cluster Kubernetes en GitOps avec Flux (En cours d&amp;rsquo;écriture)&lt;/li&gt;
&lt;li&gt;Inférence LLM auto-hébergée sur Kubernetes (En cours d&amp;rsquo;écriture)&lt;/li&gt;
&lt;/ol&gt;

 &lt;/blockquote&gt;
&lt;!-- &gt; 3. [Piloter un cluster Kubernetes en GitOps avec Flux](https://mzinutti.fr/posts/fluxcd-gitops-cluster/)
&gt; 4. [Inférence LLM auto-hébergée sur Kubernetes](https://mzinutti.fr/posts/llm-inference-homelab/) --&gt;
&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Après avoir déployé un cluster Kubernetes sur &lt;a class="link" href="https://www.proxmox.com/en/products/proxmox-virtual-environment/overview" target="_blank" rel="noopener"
 &gt;Proxmox&lt;/a&gt; avec Kubespray, j&amp;rsquo;ai voulu régler un point qui me gênait : la maintenance de l&amp;rsquo;OS sous le cluster. Dans cet article, je reprends le sujet depuis zéro avec &lt;a class="link" href="https://www.talos.dev/" target="_blank" rel="noopener"
 &gt;Talos Linux&lt;/a&gt;, une distribution immuable dédiée à Kubernetes, que je provisionne entièrement avec &lt;a class="link" href="https://opentofu.org/docs/" target="_blank" rel="noopener"
 &gt;OpenTofu&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Cet article est le premier d&amp;rsquo;une série dont l&amp;rsquo;objectif final est de faire tourner de l&amp;rsquo;inférence LLM en local, sur un GPU passé au cluster. Le but est de me former au MLOps et au déploiement de workloads d&amp;rsquo;IA sur Kubernetes. Le cluster décrit ici en est la base, et il contient donc déjà un nœud prévu pour le GPU.&lt;/p&gt;
&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Mon précédent article détaillait le &lt;a class="link" href="https://mzinutti.fr/posts/k8s-cluster-deployment/" target="_blank" rel="noopener"
 &gt;déploiement d&amp;rsquo;un cluster Kubernetes avec Kubespray&lt;/a&gt;. Le résultat était reproductible, mais l&amp;rsquo;automatisation s&amp;rsquo;arrêtait à l&amp;rsquo;OS : les trois VMs Debian restaient des systèmes classiques, avec leurs mises à jour de paquets, leur accès SSH et une configuration qui pouvait dériver entre deux lancements du playbook.&lt;/p&gt;
&lt;!-- Le dernier déploiement Kubespray affichait `ok=599` sur le seul control plane, soit 599 tâches Ansible pour transformer une Debian générique en nœud Kubernetes. Toute cette configuration est posée sur un système qui continue d'évoluer entre deux runs, et c'est ce que je voulais éviter avec ce nouveau cluster. --&gt;
&lt;p&gt;Avec Talos, l&amp;rsquo;OS n&amp;rsquo;est plus administrable directement : il n&amp;rsquo;y a ni shell, ni SSH, ni gestionnaire de paquets. La seule interface est une API gRPC, et toute la machine est décrite par un fichier de configuration YAML appliqué de manière déclarative. Pour modifier un nœud, je modifie sa configuration et Talos l&amp;rsquo;applique.&lt;/p&gt;
&lt;p&gt;Talos n&amp;rsquo;est pas meilleur que Kubespray dans l&amp;rsquo;absolu. Je perds la possibilité d&amp;rsquo;intervenir à la main sur un nœud, mais en échange je sais qu&amp;rsquo;aucun nœud n&amp;rsquo;a été modifié en dehors du code.&lt;/p&gt;
&lt;h2 id="composants"&gt;Composants
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Composant&lt;/th&gt;
 &lt;th&gt;Version&lt;/th&gt;
 &lt;th&gt;Rôle&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Proxmox&lt;/td&gt;
 &lt;td&gt;9.1&lt;/td&gt;
 &lt;td&gt;Hyperviseur&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Talos Linux&lt;/td&gt;
 &lt;td&gt;1.13.8&lt;/td&gt;
 &lt;td&gt;OS des nœuds&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Kubernetes&lt;/td&gt;
 &lt;td&gt;v1.36.2&lt;/td&gt;
 &lt;td&gt;Orchestrateur&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;OpenTofu&lt;/td&gt;
 &lt;td&gt;1.11.5&lt;/td&gt;
 &lt;td&gt;Création des VMs et configuration Talos&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Provider &lt;code&gt;bpg/proxmox&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;0.114.0&lt;/td&gt;
 &lt;td&gt;Pilotage de Proxmox&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Provider &lt;code&gt;siderolabs/talos&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;0.12&lt;/td&gt;
 &lt;td&gt;Pilotage de Talos&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Composant GitLab CI OpenTofu&lt;/td&gt;
 &lt;td&gt;4.5.0&lt;/td&gt;
 &lt;td&gt;Pipeline de déploiement&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Cilium&lt;/td&gt;
 &lt;td&gt;1.20.2&lt;/td&gt;
 &lt;td&gt;CNI, remplace kube-proxy&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;J&amp;rsquo;ai choisi OpenTofu plutôt que Terraform pour rester cohérent avec ma ligne sur les logiciels open source et leurs licences. Depuis que HashiCorp a passé Terraform sous licence BUSL en 2023, OpenTofu est le fork qui reste sous licence MPL 2.0. Son évolution est aussi davantage guidée par les demandes de fonctionnalités de la communauté, alors que celle de Terraform s&amp;rsquo;oriente vers les offres Enterprise et Cloud de HashiCorp, qui ne m&amp;rsquo;intéressent pas.&lt;/p&gt;
&lt;h2 id="talos-linux"&gt;Talos Linux
&lt;/h2&gt;&lt;p&gt;Talos est une distribution Linux qui ne contient que ce qu&amp;rsquo;il faut pour faire tourner Kubernetes : un noyau, son propre système d&amp;rsquo;init, containerd et les composants du cluster. Quelques-unes de ses propriétés expliquent la plupart des choix faits dans la suite de l&amp;rsquo;article :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Le système de fichiers racine est en lecture seule&lt;/strong&gt;, monté depuis une image squashfs. Les données qui doivent persister (etcd, images de conteneurs) sont écrites sous &lt;code&gt;/var&lt;/code&gt;, sur une partition dédiée. Il n&amp;rsquo;y a donc aucun endroit où déposer un binaire ou modifier un fichier de configuration à la main.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Les mises à jour suivent un schéma A/B.&lt;/strong&gt; La nouvelle version est installée à côté de la version en cours, et si la mise à jour échoue, le nœud peut redémarrer sur l&amp;rsquo;ancienne.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Il n&amp;rsquo;y a pas de systemd.&lt;/strong&gt; Le kubelet, etcd, containerd et l&amp;rsquo;API Talos sont gérés par &lt;code&gt;machined&lt;/code&gt;, et leur configuration vient entièrement de la machine config. Il n&amp;rsquo;est pas possible d&amp;rsquo;ajouter un service ou d&amp;rsquo;en surcharger un.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;L&amp;rsquo;administration passe uniquement par une API gRPC&lt;/strong&gt;, exposée sur le port &lt;code&gt;50000&lt;/code&gt; de chaque nœud avec une authentification TLS mutuelle. Le fichier &lt;code&gt;talosconfig&lt;/code&gt; utilisé par &lt;code&gt;talosctl&lt;/code&gt; contient la CA du cluster et un certificat client, et sans ce fichier, un nœud Talos refuse toute connexion.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Toute la configuration d&amp;rsquo;un nœud tient donc dans un seul fichier YAML, la &lt;strong&gt;machine config&lt;/strong&gt;, qui comporte deux grandes sections : &lt;code&gt;machine.*&lt;/code&gt; pour le nœud lui-même (réseau, disques, kubelet, modules noyau) et &lt;code&gt;cluster.*&lt;/code&gt; pour le cluster Kubernetes (CNI, kube-proxy, manifests additionnels).&lt;/p&gt;
&lt;h3 id="configuration-machine-et-configuration-cluster"&gt;Configuration machine et configuration cluster
&lt;/h3&gt;&lt;p&gt;Ces deux sections sont dans le même fichier, mais elles ne sont pas appliquées de la même manière :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;machine.*&lt;/code&gt; est réconcilié en continu.&lt;/strong&gt; Les contrôleurs de Talos surveillent la configuration et ajustent l&amp;rsquo;état du nœud. Un patch qui modifie &lt;code&gt;machine.network&lt;/code&gt; ou &lt;code&gt;machine.kubelet&lt;/code&gt; est appliqué immédiatement, parfois après le redémarrage du service concerné.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cluster.*&lt;/code&gt; n&amp;rsquo;est lu qu&amp;rsquo;au bootstrap.&lt;/strong&gt; Au moment du &lt;code&gt;talos_machine_bootstrap&lt;/code&gt;, Talos génère des manifests Kubernetes à partir de cette section et les applique au cluster. La section reste ensuite dans la configuration, mais elle n&amp;rsquo;est plus relue.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En pratique, modifier &lt;code&gt;cluster.network.cni.name&lt;/code&gt; sur un cluster déjà démarré ne supprime pas le CNI en place, et Talos n&amp;rsquo;affiche aucune erreur ni aucun avertissement. Les manifests générés à partir de &lt;code&gt;cluster.*&lt;/code&gt; ne sont rejoués, avec suppression de ceux qui ne sont plus générés, que pendant un &lt;code&gt;talosctl upgrade-k8s&lt;/code&gt;.&lt;/p&gt;
&lt;figure class="figure-themed"&gt;
 &lt;img class="figure-themed-light" src="https://mzinutti.fr/posts/talos-cluster-proxmox/images/talos-config-lifecycle.png" alt="Schéma de la machine config : la section machine est réconciliée en continu par les contrôleurs Talos, la section cluster n'est lue qu'au bootstrap pour générer les manifests, rejoués seulement par talosctl upgrade-k8s" width="800px"&gt;
 &lt;img class="figure-themed-dark" src="https://mzinutti.fr/posts/talos-cluster-proxmox/images/talos-config-lifecycle-dark.png" alt="Schéma de la machine config : la section machine est réconciliée en continu par les contrôleurs Talos, la section cluster n'est lue qu'au bootstrap pour générer les manifests, rejoués seulement par talosctl upgrade-k8s" width="800px"&gt;
 &lt;figcaption&gt;&lt;h4&gt;Les deux cycles de vie de la machine config&lt;/h4&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="architecture-cible"&gt;Architecture cible
&lt;/h2&gt;&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Rôle&lt;/th&gt;
 &lt;th&gt;Hostname&lt;/th&gt;
 &lt;th&gt;VM ID&lt;/th&gt;
 &lt;th&gt;IP&lt;/th&gt;
 &lt;th&gt;vCPU&lt;/th&gt;
 &lt;th&gt;RAM&lt;/th&gt;
 &lt;th&gt;Disque&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Control plane&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;talos-controlplane-01&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;121&lt;/td&gt;
 &lt;td&gt;192.168.1.121&lt;/td&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;4 GB&lt;/td&gt;
 &lt;td&gt;50 GB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Worker&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;talos-worker-01&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;122&lt;/td&gt;
 &lt;td&gt;192.168.1.122&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;8 GB&lt;/td&gt;
 &lt;td&gt;100 GB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Worker GPU&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;talos-gpu-01&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;123&lt;/td&gt;
 &lt;td&gt;192.168.1.123&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;12 GB&lt;/td&gt;
 &lt;td&gt;100 GB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;L&amp;rsquo;API Kubernetes ne répond pas sur l&amp;rsquo;IP d&amp;rsquo;un nœud mais sur une &lt;strong&gt;VIP&lt;/strong&gt;, &lt;code&gt;192.168.1.120&lt;/code&gt;. Cette adresse est réservée, et les nœuds commencent donc à &lt;code&gt;.121&lt;/code&gt;.&lt;/p&gt;
&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Il n&amp;rsquo;y a qu&amp;rsquo;un seul control plane, comme dans mon article sur Kubespray, et pour la même raison : c&amp;rsquo;est un homelab. J&amp;rsquo;ai quand même configuré la VIP dès la création, car elle est inscrite dans la configuration de chaque nœud et la mettre en place plus tard obligerait à reconfigurer tout le cluster.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;h3 id="un-nœud-gpu-déclaré-dès-le-départ"&gt;Un nœud GPU déclaré dès le départ
&lt;/h3&gt;&lt;p&gt;Le troisième nœud s&amp;rsquo;appelle &lt;code&gt;talos-gpu-01&lt;/code&gt;, même si aucune carte graphique n&amp;rsquo;est utilisée dans cet article. Il se distingue déjà des deux autres sur trois points :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;12 GB de RAM au lieu de 8.&lt;/strong&gt; Le passthrough PCI impose de désactiver le ballooning, donc cette mémoire sera réservée en permanence sur l&amp;rsquo;hôte. J&amp;rsquo;ai préféré dimensionner le nœud tout de suite en conséquence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Une image Talos différente.&lt;/strong&gt; Les modules NVIDIA sont fournis par des extensions système, qui sont intégrées à l&amp;rsquo;image au moment de sa construction. Ce nœud a donc sa propre image.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un patch de configuration qui ne concerne que lui.&lt;/strong&gt; Charger les modules NVIDIA sur un nœud sans carte produit une erreur à chaque démarrage.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce dernier point a une conséquence sur la façon d&amp;rsquo;organiser la configuration : dès qu&amp;rsquo;un nœud est différent des autres, une configuration par rôle ne suffit plus. Je détaille la solution dans la partie sur les patches.&lt;/p&gt;
&lt;h2 id="provisionnement-avec-opentofu"&gt;Provisionnement avec OpenTofu
&lt;/h2&gt;&lt;h3 id="les-images--talos-image-factory"&gt;Les images : Talos Image Factory
&lt;/h3&gt;&lt;p&gt;Une image Talos ne peut pas être modifiée après coup. Pour ajouter &lt;code&gt;qemu-guest-agent&lt;/code&gt; ou les modules NVIDIA, il faut une image qui les contient déjà, et c&amp;rsquo;est le rôle de l&amp;rsquo;&lt;a class="link" href="https://factory.talos.dev/" target="_blank" rel="noopener"
 &gt;Image Factory&lt;/a&gt; : on lui envoie une liste d&amp;rsquo;extensions, et elle renvoie un &lt;strong&gt;schematic ID&lt;/strong&gt;, un hash qui identifie cette liste. Ce hash permet ensuite de construire l&amp;rsquo;URL de l&amp;rsquo;image pour n&amp;rsquo;importe quelle version de Talos.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Schematic&lt;/th&gt;
 &lt;th&gt;Extensions&lt;/th&gt;
 &lt;th&gt;Nœuds&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ce4c9805...&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;qemu-guest-agent&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;controlplane-01, worker-01&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;a31d81ec...&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;qemu-guest-agent&lt;/code&gt;, &lt;code&gt;nvidia-open-gpu-kernel-modules-production&lt;/code&gt;, &lt;code&gt;nvidia-container-toolkit-production&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;gpu-01&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;L&amp;rsquo;image téléchargée est un disque brut compressé, &lt;code&gt;nocloud-amd64.raw.xz&lt;/code&gt;. Elle change deux choses par rapport à mes déploiements précédents :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;nocloud&lt;/code&gt;&lt;/strong&gt; est la plateforme cible. Avec elle, Talos lit sa configuration réseau initiale sur le lecteur cloud-init fourni par Proxmox, ce qui rend le nœud joignable dès le premier démarrage, avant qu&amp;rsquo;il ait reçu sa machine config.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Il n&amp;rsquo;y a plus de template.&lt;/strong&gt; Mes VMs Debian sont clonées depuis un template construit avec &lt;a class="link" href="https://mzinutti.fr/posts/proxmox-packer-template/" target="_blank" rel="noopener"
 &gt;Packer&lt;/a&gt;, alors que les nœuds Talos importent directement cette image comme disque &lt;code&gt;scsi0&lt;/code&gt;. Je n&amp;rsquo;ai donc plus de template à reconstruire pour ce cluster : pour changer de version de Talos, je modifie une variable.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Le téléchargement utilise un &lt;code&gt;for_each&lt;/code&gt; sur les schematics réellement utilisés, ce qui évite de télécharger deux fois la même image quand plusieurs nœuds partagent le même schematic :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#image.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;locals&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; talos_schematics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;toset&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;node&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_nodes&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="k"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;schematic_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_image_factory_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;proxmox_download_file&amp;#34; &amp;#34;talos_image&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; for_each&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_schematics&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="n"&gt; content_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;iso&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; datastore_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; file_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;talos-${var.talos_version}-${substr(each.key, 0, 8)}-nocloud-amd64.img&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; node_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;proxmox_node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://factory.talos.dev/image/${each.key}/v${var.talos_version}/nocloud-amd64.raw.xz&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; overwrite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; decompression_algorithm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;zst&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;L&amp;rsquo;image est téléchargée au format &lt;code&gt;.raw.xz&lt;/code&gt;, alors que &lt;code&gt;decompression_algorithm&lt;/code&gt; vaut &lt;code&gt;zst&lt;/code&gt;. Le provider n&amp;rsquo;accepte que &lt;code&gt;gz&lt;/code&gt;, &lt;code&gt;lzo&lt;/code&gt;, &lt;code&gt;zst&lt;/code&gt; et &lt;code&gt;bz2&lt;/code&gt;, mais la décompression &lt;code&gt;zst&lt;/code&gt; de Proxmox sait aussi traiter une archive xz. Ce comportement n&amp;rsquo;est pas documenté, je l&amp;rsquo;ai trouvé dans &lt;a class="link" href="https://github.com/orgs/bpg/discussions/1212" target="_blank" rel="noopener"
 &gt;une discussion du provider bpg sur les images compressées en xz&lt;/a&gt;.&lt;/p&gt;
&lt;h3 id="la-topologie-dans-une-variable"&gt;La topologie dans une variable
&lt;/h3&gt;&lt;p&gt;Toute la topologie du cluster est décrite dans une seule variable. Pour ajouter un nœud, il suffit d&amp;rsquo;ajouter un objet à cette liste :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#variables.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;talos_nodes&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; disk_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;controlplane&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Schematic Image Factory. `null` retombe sur var.talos_image_factory_id.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; schematic_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Périphériques PCI passés au nœud (partie 2).
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; hostpci&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;object&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; mapping&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; pcie&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; rombar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; xvga&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;optional&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;validation&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; condition&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="k"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;node&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_nodes&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="k"&gt;node&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_vip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; error_message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;The Talos VIP must not collide with a node IP address.&amp;#34;&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="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;J&amp;rsquo;ai ajouté plus de validations que d&amp;rsquo;habitude sur cette variable, cinq au total :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;role&lt;/code&gt; doit valoir &lt;code&gt;controlplane&lt;/code&gt; ou &lt;code&gt;worker&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;les hostnames doivent être uniques&lt;/li&gt;
&lt;li&gt;les IPs doivent être uniques&lt;/li&gt;
&lt;li&gt;il doit y avoir au moins un &lt;code&gt;controlplane&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;la VIP ne doit pas correspondre à l&amp;rsquo;IP d&amp;rsquo;un nœud&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sans ces validations, chacune de ces erreurs apparaîtrait au milieu d&amp;rsquo;un &lt;code&gt;apply&lt;/code&gt;, sous la forme d&amp;rsquo;une erreur d&amp;rsquo;API Proxmox peu explicite, avec une partie de l&amp;rsquo;infrastructure déjà créée. Avec elles, l&amp;rsquo;erreur est détectée au &lt;code&gt;plan&lt;/code&gt;, avant toute modification. Une sixième validation arrive dans la &lt;a class="link" href="https://mzinutti.fr/posts/gpu-passthrough-talos/" target="_blank" rel="noopener"
 &gt;partie 2&lt;/a&gt;, pour vérifier que chaque &lt;code&gt;hostpci.mapping&lt;/code&gt; correspond bien à un mapping déclaré.&lt;/p&gt;
&lt;h3 id="les-vms"&gt;Les VMs
&lt;/h3&gt;&lt;p&gt;Une seule ressource crée les trois nœuds :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#talos.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;proxmox_virtual_environment_vm&amp;#34; &amp;#34;talos&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; depends_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;proxmox_download_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;proxmox_virtual_environment_pool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;TALOS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; for_each&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt; { for node in var.talos_nodes : node.hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;node&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="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; node_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;proxmox_node&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;split&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="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; machine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;q35&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; bios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ovmf&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;efi_disk&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; datastore_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;proxmox_datastore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;4m&amp;#34;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;memory&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; dedicated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;memory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; floating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;disk&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; datastore_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;proxmox_datastore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; interface&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;scsi0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;disk_size&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;proxmox_download_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_image&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;coalesce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;schematic_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_image_factory_id&lt;/span&gt;&lt;span class="p"&gt;)].&lt;/span&gt;&lt;span class="k"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; discard&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;on&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ssd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; iothread&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;initialization&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; datastore_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;proxmox_datastore&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;ip_config&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;ipv4&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;${each.value.ip}/24&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_gateway&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;dns&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; servers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_nameservers&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Il n&amp;rsquo;y a pas de bloc &lt;code&gt;clone&lt;/code&gt;.&lt;/strong&gt; Le disque système est l&amp;rsquo;image téléchargée plus haut, référencée par son &lt;code&gt;file_id&lt;/code&gt;, et c&amp;rsquo;est ce qui remplace le template.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;machine = &amp;quot;q35&amp;quot;&lt;/code&gt; et &lt;code&gt;bios = &amp;quot;ovmf&amp;quot;&lt;/code&gt; avec un &lt;code&gt;efi_disk&lt;/code&gt;.&lt;/strong&gt; Les VMs démarrent en UEFI, et le firmware OVMF a besoin d&amp;rsquo;un disque EFI pour stocker ses variables.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Le &lt;code&gt;vm_id&lt;/code&gt; est dérivé du dernier octet de l&amp;rsquo;IP.&lt;/strong&gt; &lt;code&gt;192.168.1.123&lt;/code&gt; donne la VM 123, ce qui me permet de faire directement le lien entre l&amp;rsquo;interface Proxmox et le réseau.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;floating = 0&lt;/code&gt;&lt;/strong&gt; désactive le ballooning, ce qui est obligatoire pour le nœud GPU de la partie 2.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Le lecteur cloud-init d&amp;rsquo;un nœud Talos ne sert pas à configurer Talos. Il donne seulement une IP statique au nœud au premier démarrage, pour qu&amp;rsquo;il soit joignable en &lt;strong&gt;mode maintenance&lt;/strong&gt;. Toute la configuration est ensuite appliquée par l&amp;rsquo;API Talos, via le provider &lt;code&gt;siderolabs/talos&lt;/code&gt;.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;p&gt;Les VMs sont prêtes à démarrer, il reste maintenant à leur fournir leur configuration.&lt;/p&gt;
&lt;h2 id="la-configuration-machine"&gt;La configuration machine
&lt;/h2&gt;&lt;h3 id="générer-les-secrets-et-la-configuration-de-base"&gt;Générer les secrets et la configuration de base
&lt;/h3&gt;&lt;p&gt;Le provider &lt;code&gt;siderolabs/talos&lt;/code&gt; enchaîne six ressources, dans cet ordre :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talos_machine_secrets # CA, clés et tokens du cluster
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─&amp;gt; data.talos_client_configuration # le talosconfig de talosctl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─&amp;gt; data.talos_machine_configuration # une config de base, par rôle
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─&amp;gt; talos_machine_configuration_apply # appliquée par nœud, avec les patches
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─&amp;gt; talos_machine_bootstrap # init d&amp;#39;etcd, une seule fois
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─&amp;gt; talos_cluster_kubeconfig # le kubeconfig admin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;talos_machine_secrets&lt;/code&gt; génère tout le matériel cryptographique du cluster : les CA de Kubernetes, de Talos et d&amp;rsquo;etcd, ainsi que les tokens d&amp;rsquo;enrôlement des nœuds. Ces secrets ne sont écrits nulle part sur le disque, ils sont stockés dans le state OpenTofu et n&amp;rsquo;en sortent que par les outputs.&lt;/p&gt;
&lt;p&gt;Le bootstrap et la récupération du kubeconfig n&amp;rsquo;ont pas de &lt;code&gt;for_each&lt;/code&gt;, car ce sont des opérations qui concernent le cluster entier. Elles ciblent un seul control plane, le premier de la liste :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#talos.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;locals&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; talos_controlplanes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt; [for node in var.talos_nodes : node if node.role&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;controlplane&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; talos_first_controlplane&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_controlplanes&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="k"&gt;ip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La ressource &lt;code&gt;talos_machine_configuration_apply&lt;/code&gt; reçoit son argument &lt;code&gt;node&lt;/code&gt; depuis la variable &lt;code&gt;talos_nodes&lt;/code&gt;, et pas depuis un attribut de la VM. OpenTofu ne voit donc aucune dépendance entre les deux, et peut essayer d&amp;rsquo;appliquer la configuration à une IP qui ne répond pas encore. J&amp;rsquo;ai donc ajouté un &lt;code&gt;depends_on&lt;/code&gt; explicite sur les VMs :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#talos.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;talos_machine_configuration_apply&amp;#34; &amp;#34;this&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; depends_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;proxmox_virtual_environment_vm&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; for_each&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt; { for node in var.talos_nodes : node.hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;node&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="n"&gt; client_configuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;talos_machine_secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;client_configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; machine_configuration_input&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_machine_configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;role&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="k"&gt;machine_configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ip&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="n"&gt; config_patches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;patch&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_patch_files&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; node_ip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;ip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; role&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;role&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_vip&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_gateway&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; nameservers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_nameservers&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="les-patches-en-trois-niveaux"&gt;Les patches, en trois niveaux
&lt;/h3&gt;&lt;p&gt;Une liste de patches par rôle ne suffit plus dès qu&amp;rsquo;un seul nœud a du matériel spécifique. J&amp;rsquo;ai donc organisé les patches en trois niveaux, du plus général au plus spécifique :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tofu/patches/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── common/ # tous les nœuds
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── 40-kubelet.yaml.tftpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── controlplane/ # les control planes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── 10-network-vip.yaml.tftpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── 20-cluster-cni-proxy.yaml.tftpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── 30-extra-manifests.yaml.tftpl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── worker/ # les workers
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── nodes/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── talos-gpu-01/ # ce nœud précis
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── 30-nvidia-modules.yaml.tftpl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La liste des patches de chaque nœud est construite avec &lt;code&gt;fileset&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#talos.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;talos_patch_files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;node&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;talos_nodes&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; node.hostname&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;f&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;fileset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${path.module}/patches/common&amp;#34;, &amp;#34;*.yaml.tftpl&amp;#34;) : &amp;#34;${path.module}/patches/common/${f}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;f&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;fileset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${path.module}/patches/${node.role}&amp;#34;, &amp;#34;*.yaml.tftpl&amp;#34;) : &amp;#34;${path.module}/patches/${node.role}/${f}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;f&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;fileset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;${path.module}/patches/nodes/${node.hostname}&amp;#34;, &amp;#34;*.yaml.tftpl&amp;#34;) : &amp;#34;${path.module}/patches/nodes/${node.hostname}/${f}&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ce fonctionnement repose sur trois propriétés de &lt;code&gt;fileset&lt;/code&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Les fichiers sont triés par ordre alphabétique&lt;/strong&gt;, donc le préfixe numérique de chaque fichier fixe l&amp;rsquo;ordre de fusion. Pour une même clé, c&amp;rsquo;est le dernier patch qui l&amp;rsquo;emporte : le niveau nœud écrase le niveau rôle, qui écrase le niveau commun. Je laisse des trous dans la numérotation (10, 20, 30) pour pouvoir intercaler un patch plus tard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Un répertoire absent renvoie un ensemble vide&lt;/strong&gt;, sans erreur. Les trois niveaux sont donc facultatifs, ce qui est pratique puisque Git ne versionne pas les répertoires vides.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ajouter un patch ne demande aucune modification du code.&lt;/strong&gt; Il suffit de déposer un fichier dans le bon répertoire, &lt;code&gt;talos.tf&lt;/code&gt; reste inchangé.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;C&amp;rsquo;est le nœud GPU qui m&amp;rsquo;a amené à créer le niveau &lt;code&gt;nodes/&lt;/code&gt; : un patch NVIDIA placé sous &lt;code&gt;worker/&lt;/code&gt; serait aussi appliqué à &lt;code&gt;talos-worker-01&lt;/code&gt;, dont l&amp;rsquo;image ne contient pas les extensions, et ce nœud échouerait à charger les modules à chaque démarrage.&lt;/p&gt;
&lt;h3 id="la-vip"&gt;La VIP
&lt;/h3&gt;&lt;p&gt;Talos &lt;a class="link" href="https://docs.siderolabs.com/talos/v1.13/networking/advanced/vip" target="_blank" rel="noopener"
 &gt;gère la VIP lui-même&lt;/a&gt;, sans keepalived ni load balancer externe. Les control planes choisissent le nœud qui porte la VIP par une élection dans etcd : un seul la détient à un instant donné, et elle bascule automatiquement sur un autre control plane si ce nœud disparaît.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# patches/controlplane/10-network-vip.yaml.tftpl&lt;/span&gt;&lt;span class="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;machine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interfaces&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;deviceSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;physical&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;dhcp&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;addresses&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;${node_ip}/24&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.0.0.0&lt;/span&gt;&lt;span class="l"&gt;/0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${gateway}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;vip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${vip}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;nameservers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${jsonencode(nameservers)}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;L&amp;rsquo;interface est redéclarée en entier, et pas seulement complétée avec une entrée &lt;code&gt;vip&lt;/code&gt;. La machine config est prioritaire sur la configuration réseau fournie par la plateforme &lt;code&gt;nocloud&lt;/code&gt; : un patch qui ne contiendrait que la VIP ferait disparaître l&amp;rsquo;adressage statique fourni par le lecteur cloud-init, et le nœud deviendrait injoignable.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cluster_endpoint&lt;/code&gt; pointe lui aussi sur la VIP, et pas sur l&amp;rsquo;IP d&amp;rsquo;un nœud. Cette valeur est inscrite dans la configuration de tous les nœuds et dans le kubeconfig : si elle désignait un nœud précis, la perte de ce nœud couperait l&amp;rsquo;accès à l&amp;rsquo;API, et l&amp;rsquo;ajout d&amp;rsquo;un control plane obligerait à reconfigurer tout le cluster pour changer l&amp;rsquo;endpoint.&lt;/p&gt;
&lt;h3 id="cni-et-kube-proxy-désactivés"&gt;CNI et kube-proxy désactivés
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# patches/controlplane/20-cluster-cni-proxy.yaml.tftpl&lt;/span&gt;&lt;span class="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;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cni&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;none&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;disabled&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Par défaut, Talos installe Flannel comme CNI et kube-proxy. Je les remplace tous les deux par Cilium, donc je les désactive.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai choisi &lt;a class="link" href="https://docs.cilium.io/" target="_blank" rel="noopener"
 &gt;Cilium&lt;/a&gt; pour trois raisons : il remplace kube-proxy, il gère les L2 announcements, que j&amp;rsquo;utilise plus tard dans la série pour annoncer les IP des services &lt;code&gt;LoadBalancer&lt;/code&gt; sur mon réseau local, et le tout repose sur eBPF, une technologie sur laquelle je veux m&amp;rsquo;appuyer pour les évolutions de mon homelab.&lt;/p&gt;
&lt;p&gt;Ce choix a deux conséquences :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;les nœuds restent dans l&amp;rsquo;état &lt;code&gt;NotReady&lt;/code&gt; tant qu&amp;rsquo;aucun CNI n&amp;rsquo;est installé, ce qui est normal&lt;/li&gt;
&lt;li&gt;sans kube-proxy, le CNI doit joindre l&amp;rsquo;API server directement, car la ClusterIP du service &lt;code&gt;kubernetes.default&lt;/code&gt; n&amp;rsquo;est pas encore routable au moment où il démarre. C&amp;rsquo;est le rôle de KubePrism, que je présente dans la partie sur Cilium.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce patch est placé sur les control planes car ces manifests sont générés au bootstrap, depuis un control plane. Comme tout ce qui est sous &lt;code&gt;cluster.*&lt;/code&gt;, il n&amp;rsquo;a d&amp;rsquo;effet qu&amp;rsquo;à ce moment-là : appliqué à un cluster déjà démarré, il ne supprime rien.&lt;/p&gt;
&lt;h3 id="certificats-serving-du-kubelet"&gt;Certificats serving du kubelet
&lt;/h3&gt;&lt;p&gt;Par défaut, le kubelet expose son endpoint &lt;code&gt;:10250&lt;/code&gt; avec un certificat auto-signé qu&amp;rsquo;aucune CA du cluster ne reconnaît. D&amp;rsquo;où l&amp;rsquo;option &lt;code&gt;--kubelet-insecure-tls&lt;/code&gt; présente dans la plupart des installations de metrics-server : le client ne vérifie pas le certificat du serveur, donc n&amp;rsquo;importe quelle machine capable de s&amp;rsquo;intercaler sur le réseau peut se faire passer pour un kubelet.&lt;/p&gt;
&lt;p&gt;La première partie de la solution se trouve côté nœud :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# patches/common/40-kubelet.yaml.tftpl&lt;/span&gt;&lt;span class="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;machine&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;kubelet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;extraArgs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rotate-server-certificates&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Avec cette option, le kubelet ne génère plus de certificat auto-signé et envoie une CSR à l&amp;rsquo;API server, avec le signer &lt;code&gt;kubernetes.io/kubelet-serving&lt;/code&gt;. Le problème est que &lt;strong&gt;Kubernetes n&amp;rsquo;approuve jamais automatiquement les CSR de ce signer&lt;/strong&gt;. Le kube-controller-manager sait signer le certificat, mais aucun composant du cluster ne l&amp;rsquo;approuve : les CSR restent en &lt;code&gt;Pending&lt;/code&gt; et le kubelet garde son certificat auto-signé. C&amp;rsquo;est un choix volontaire du projet Kubernetes, qui laisse cette décision à chaque environnement.&lt;/p&gt;
&lt;p&gt;La seconde partie se trouve donc côté cluster :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# patches/controlplane/30-extra-manifests.yaml.tftpl&lt;/span&gt;&lt;span class="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;cluster&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;extraManifests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;https://raw.githubusercontent.com/alex1989hu/kubelet-serving-cert-approver/v0.11.0/deploy/standalone-install.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 class="l"&gt;https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.9.0/components.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;a class="link" href="https://github.com/alex1989hu/kubelet-serving-cert-approver" target="_blank" rel="noopener"
 &gt;kubelet-serving-cert-approver&lt;/a&gt; est un contrôleur qui approuve ces CSR. Il ne fait que les approuver : la signature reste faite par le kube-controller-manager, avec la clé de la CA du cluster. Avant d&amp;rsquo;approuver une demande, il vérifie qu&amp;rsquo;elle correspond bien à celle d&amp;rsquo;un kubelet :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;l&amp;rsquo;organisation du sujet est &lt;code&gt;system:nodes&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;le CN commence par &lt;code&gt;system:node:&lt;/code&gt; et correspond exactement à l&amp;rsquo;utilisateur qui a soumis la CSR&lt;/li&gt;
&lt;li&gt;les usages demandés sont ceux d&amp;rsquo;un certificat serveur&lt;/li&gt;
&lt;li&gt;la demande ne contient pas de SAN de type email ou URI&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;En revanche, il ne vérifie pas que les adresses IP demandées dans les SAN appartiennent réellement au nœud, puisque son ClusterRole ne lui donne aucun accès aux objets &lt;code&gt;Node&lt;/code&gt;. Un nœud déjà enrôlé dans le cluster peut donc obtenir un certificat valide pour l&amp;rsquo;IP d&amp;rsquo;un autre nœud.&lt;/p&gt;
&lt;p&gt;Le gain de sécurité reste réel, mais il est limité : avant, n&amp;rsquo;importe quelle machine sur le réseau pouvait se faire passer pour un kubelet, alors qu&amp;rsquo;il faut maintenant disposer de l&amp;rsquo;identité d&amp;rsquo;un nœud du cluster. Dans mon cas, metrics-server vérifie désormais les certificats des kubelets avec la CA du cluster, sans &lt;code&gt;--kubelet-insecure-tls&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Ces deux manifests doivent rester déployés en permanence, car la rotation des certificats est continue et chaque renouvellement produit une nouvelle CSR à approuver. Les URLs sont fixées sur une version précise, pour qu&amp;rsquo;un re-apply ne récupère pas un contenu différent et pour que Renovate puisse suivre les mises à jour.&lt;/p&gt;
&lt;p&gt;La configuration est complète, je peux passer au déploiement.&lt;/p&gt;
&lt;h2 id="déploiement"&gt;Déploiement
&lt;/h2&gt;&lt;p&gt;Je ne lance pas d&amp;rsquo;apply en local. Le déploiement passe par un pipeline GitLab CI qui utilise le &lt;a class="link" href="https://gitlab.com/components/opentofu" target="_blank" rel="noopener"
 &gt;composant OpenTofu de GitLab&lt;/a&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#.gitlab-ci.yml&lt;/span&gt;&lt;span class="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;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;homelab&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&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;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TF_VAR_proxmox_endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;${proxmox_endpoint}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TF_VAR_proxmox_api_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;${proxmox_api_token}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TF_VAR_proxmox_ssh_private_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;${proxmox_ssh_private_key}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&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;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;# renovate: datasource=gitlab-releases packageName=components/opentofu registryUrl=https://gitlab.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;component&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;$CI_SERVER_FQDN/components/opentofu/validate-plan-apply@4.5.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;inputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;# renovate: datasource=gitlab-releases packageName=components/opentofu registryUrl=https://gitlab.com&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;4.5.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;opentofu_version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1.11.5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;root_dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;tofu/&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&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;stages&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="l"&gt;validate, build, deploy]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&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;workflow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;rules&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;changes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;tofu/**&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;when&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;never&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le template &lt;code&gt;validate-plan-apply&lt;/code&gt; ajoute quatre jobs : &lt;code&gt;fmt&lt;/code&gt; et &lt;code&gt;validate&lt;/code&gt; dans le stage &lt;code&gt;validate&lt;/code&gt;, &lt;code&gt;plan&lt;/code&gt; dans &lt;code&gt;build&lt;/code&gt;, puis &lt;code&gt;apply&lt;/code&gt; dans &lt;code&gt;deploy&lt;/code&gt;. Par défaut, &lt;code&gt;apply&lt;/code&gt; n&amp;rsquo;existe que sur la branche principale et doit être lancé à la main : une merge request s&amp;rsquo;arrête donc au plan, et rien n&amp;rsquo;est appliqué tant que je n&amp;rsquo;ai pas déclenché le job.&lt;/p&gt;
&lt;p&gt;Le reste de la configuration tient en cinq points :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Les jobs tournent sur mon propre runner&lt;/strong&gt;, grâce au tag &lt;code&gt;homelab&lt;/code&gt; : un runner GitLab avec l&amp;rsquo;executor Docker, hébergé sur mon Proxmox. Il est sur le même réseau que l&amp;rsquo;API Proxmox, que les runners partagés de GitLab ne pourraient pas joindre.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Le state est géré par GitLab.&lt;/strong&gt; Le bloc &lt;code&gt;backend &amp;quot;http&amp;quot; {}&lt;/code&gt; de &lt;code&gt;providers.tf&lt;/code&gt; est vide, et c&amp;rsquo;est le composant qui renseigne l&amp;rsquo;adresse du state du projet et les identifiants du job au moment de l&amp;rsquo;exécution.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Les secrets restent dans GitLab.&lt;/strong&gt; Le token de l&amp;rsquo;API Proxmox et la clé SSH sont des variables CI/CD, transmises à OpenTofu par les variables &lt;code&gt;TF_VAR_*&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Le pipeline ne se déclenche que si &lt;code&gt;tofu/&lt;/code&gt; change&lt;/strong&gt;, grâce à la règle &lt;code&gt;workflow&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Les versions sont suivies par &lt;a class="link" href="https://docs.renovatebot.com/" target="_blank" rel="noopener"
 &gt;Renovate&lt;/a&gt;&lt;/strong&gt;, grâce aux commentaires &lt;code&gt;# renovate:&lt;/code&gt; placés au-dessus du composant.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="récupérer-les-accès"&gt;Récupérer les accès
&lt;/h3&gt;&lt;p&gt;Les accès au cluster ne sont écrits nulle part sur le disque, ils sont dans le state, et il faut donc les extraire avec les outputs. Comme le state est stocké dans GitLab, je fais un &lt;code&gt;tofu init&lt;/code&gt; local une seule fois, uniquement pour cette étape. GitLab fournit la commande d&amp;rsquo;init complète dans &lt;strong&gt;Operate &amp;gt; Terraform states&lt;/strong&gt;, avec l&amp;rsquo;action &lt;strong&gt;Copy Terraform init command&lt;/strong&gt;. Je récupère ensuite les deux fichiers :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;tofu output -raw talosconfig &amp;gt; ../talos/config
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tofu output -raw kubeconfig &amp;gt; ../talos/kubeconfig
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le &lt;code&gt;talosconfig&lt;/code&gt; généré par &lt;code&gt;data.talos_client_configuration&lt;/code&gt; contient deux listes : les &lt;code&gt;endpoints&lt;/code&gt;, auxquels &lt;code&gt;talosctl&lt;/code&gt; se connecte réellement et qui ne contiennent que des control planes, et les &lt;code&gt;nodes&lt;/code&gt;, qui sont les cibles par défaut des commandes et qui sont jointes en passant par un endpoint. C&amp;rsquo;est ce qui permet d&amp;rsquo;interroger un worker sans s&amp;rsquo;y connecter directement.&lt;/p&gt;
&lt;p&gt;Par exemple, pour lister les services de &lt;code&gt;talos-worker-01&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl --talosconfig talos/config -e 192.168.1.121 -n 192.168.1.122 services
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;talosctl&lt;/code&gt; ouvre la connexion vers le control plane &lt;code&gt;192.168.1.121&lt;/code&gt;, qui relaie la requête jusqu&amp;rsquo;au worker &lt;code&gt;192.168.1.122&lt;/code&gt;. Sans &lt;code&gt;-e&lt;/code&gt;, ce sont les endpoints du &lt;code&gt;talosconfig&lt;/code&gt; qui sont utilisés, et &lt;code&gt;-n&lt;/code&gt; suffit pour changer de cible.&lt;/p&gt;
&lt;figure class="figure-themed"&gt;
 &lt;img class="figure-themed-light" src="https://mzinutti.fr/posts/talos-cluster-proxmox/images/talosctl-endpoint-node.png" alt="Schéma de talosctl qui se connecte au control plane 192.168.1.121 comme endpoint, qui relaie la requête vers le worker 192.168.1.122 désigné par -n" width="800px"&gt;
 &lt;img class="figure-themed-dark" src="https://mzinutti.fr/posts/talos-cluster-proxmox/images/talosctl-endpoint-node-dark.png" alt="Schéma de talosctl qui se connecte au control plane 192.168.1.121 comme endpoint, qui relaie la requête vers le worker 192.168.1.122 désigné par -n" width="800px"&gt;
 &lt;figcaption&gt;&lt;h4&gt;Endpoint et node avec talosctl&lt;/h4&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id="installer-cilium"&gt;Installer Cilium
&lt;/h3&gt;&lt;p&gt;Le cluster démarre sans CNI, les nœuds sont donc &lt;code&gt;NotReady&lt;/code&gt;. J&amp;rsquo;installe Cilium avec Helm, en remplacement de kube-proxy :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo add cilium https://helm.cilium.io/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm repo update
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm install cilium cilium/cilium &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --version 1.20.2 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --namespace kube-system &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --values cilium-values.yaml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#cilium-values.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;kubeProxyReplacement&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="nt"&gt;k8sServiceHost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;localhost&lt;/span&gt;&lt;span class="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;k8sServicePort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7445&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Talos monte lui-même le cgroup2 et interdit à l&amp;#39;agent de le remonter.&lt;/span&gt;&lt;span class="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;cgroup&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;autoMount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;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;hostRoot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/sys/fs/cgroup&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&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;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;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;ciliumAgent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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&gt;&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;KILL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;NET_ADMIN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;NET_RAW&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;IPC_LOCK&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;SYS_ADMIN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;SYS_RESOURCE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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&gt;&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&gt;&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&gt;&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cleanCiliumState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;NET_ADMIN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;SYS_ADMIN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;SYS_RESOURCE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les valeurs &lt;code&gt;cgroup&lt;/code&gt; et &lt;code&gt;securityContext&lt;/code&gt; sont spécifiques à Talos. Par défaut, le chart monte lui-même le cgroup2 et lance l&amp;rsquo;agent en mode privilégié, ce que Talos ne permet pas. J&amp;rsquo;ai repris la liste de capabilities de la &lt;a class="link" href="https://docs.siderolabs.com/kubernetes-guides/cni/deploying-cilium" target="_blank" rel="noopener"
 &gt;documentation Talos pour Cilium&lt;/a&gt; plutôt que de la reconstruire.&lt;/p&gt;
&lt;p&gt;Il reste le couple &lt;code&gt;k8sServiceHost&lt;/code&gt; / &lt;code&gt;k8sServicePort&lt;/code&gt;. On pourrait pointer Cilium sur la VIP en &lt;code&gt;6443&lt;/code&gt;, puisque c&amp;rsquo;est l&amp;rsquo;endpoint du cluster, mais ce n&amp;rsquo;est pas ce que recommande Talos. Cilium doit joindre l&amp;rsquo;API server avant que le réseau des pods existe, et la VIP dépend de l&amp;rsquo;élection etcd entre les control planes.&lt;/p&gt;
&lt;p&gt;&lt;a class="link" href="https://docs.siderolabs.com/talos/v1.13/configure-your-talos-cluster/system-configuration/kubeprism" target="_blank" rel="noopener"
 &gt;KubePrism&lt;/a&gt; répond à ce problème : c&amp;rsquo;est un load balancer local présent sur chaque nœud, qui écoute sur &lt;code&gt;localhost:7445&lt;/code&gt;. Il choisit automatiquement parmi les endpoints disponibles (l&amp;rsquo;endpoint du cluster et chaque control plane), en écartant ceux qui ne répondent pas et en privilégiant les plus rapides. Il est activé par défaut, et la documentation Talos recommande de le donner comme endpoint aux CNI qui ont besoin de joindre l&amp;rsquo;API server, comme Cilium.&lt;/p&gt;
&lt;figure class="figure-themed"&gt;
 &lt;img class="figure-themed-light" src="https://mzinutti.fr/posts/talos-cluster-proxmox/images/talos-api-access.png" alt="Schéma de l'accès à l'API : sur chaque nœud, l'agent Cilium passe par KubePrism sur localhost:7445, qui joint le kube-apiserver directement ou via la VIP, alors que kubectl passe par la VIP portée par élection etcd" width="800px"&gt;
 &lt;img class="figure-themed-dark" src="https://mzinutti.fr/posts/talos-cluster-proxmox/images/talos-api-access-dark.png" alt="Schéma de l'accès à l'API : sur chaque nœud, l'agent Cilium passe par KubePrism sur localhost:7445, qui joint le kube-apiserver directement ou via la VIP, alors que kubectl passe par la VIP portée par élection etcd" width="800px"&gt;
 &lt;figcaption&gt;&lt;h4&gt;Accès à l&amp;#39;API Kubernetes&lt;/h4&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="vérification"&gt;Vérification
&lt;/h2&gt;&lt;p&gt;Une fois Cilium installé, je vérifie l&amp;rsquo;état du cluster côté Talos avec &lt;code&gt;talosctl health&lt;/code&gt;. J&amp;rsquo;ai retiré de la sortie les lignes de progression, pour ne garder que le résultat de chaque vérification :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talosctl --talosconfig talos/config -n 192.168.1.121 health
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;discovered nodes: [&amp;#34;192.168.1.120&amp;#34; &amp;#34;192.168.1.123&amp;#34; &amp;#34;192.168.1.122&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for etcd to be healthy: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for etcd members to be consistent across nodes: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for etcd members to be control plane nodes: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for apid to be ready: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for all nodes memory sizes: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for all nodes disk sizes: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for no diagnostics: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for kubelet to be healthy: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for all nodes to finish boot sequence: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for all k8s nodes to report: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for all control plane static pods to be running: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for all control plane components to be ready: OK
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for all k8s nodes to report ready: SKIP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for kube-proxy to report ready: SKIP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for coredns to report ready: SKIP
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;waiting for all k8s nodes to report schedulable: OK
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La première adresse listée est la VIP et non &lt;code&gt;192.168.1.121&lt;/code&gt; : le control plane qui la détient l&amp;rsquo;annonce parmi ses adresses, et elle est triée en premier.&lt;/p&gt;
&lt;p&gt;Les trois &lt;code&gt;SKIP&lt;/code&gt; viennent des choix faits dans les patches. &lt;code&gt;talosctl health&lt;/code&gt; ne vérifie pas l&amp;rsquo;état &lt;code&gt;Ready&lt;/code&gt; des nœuds ni CoreDNS quand le CNI est déclaré à &lt;code&gt;none&lt;/code&gt;, puisque ces vérifications dépendent d&amp;rsquo;un CNI que Talos n&amp;rsquo;a pas installé lui-même, et il ignore kube-proxy quand son DaemonSet n&amp;rsquo;existe pas. L&amp;rsquo;état des nœuds se vérifie donc côté Kubernetes :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NAME STATUS ROLES AGE VERSION
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talos-controlplane-01 Ready control-plane 47d v1.36.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talos-gpu-01 Ready &amp;lt;none&amp;gt; 47d v1.36.2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;talos-worker-01 Ready &amp;lt;none&amp;gt; 47d v1.36.2
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les trois nœuds sont &lt;code&gt;Ready&lt;/code&gt;, ce qui confirme que Cilium fonctionne sur chacun d&amp;rsquo;eux.&lt;/p&gt;
&lt;h2 id="pièges-rencontrés"&gt;Pièges rencontrés
&lt;/h2&gt;&lt;h3 id="la-courbe-dapprentissage-de-talos"&gt;La courbe d&amp;rsquo;apprentissage de Talos
&lt;/h3&gt;&lt;p&gt;La prise en main de Talos m&amp;rsquo;a demandé du temps. Sans shell ni SSH, aucun de mes réflexes habituels pour diagnostiquer un nœud ne s&amp;rsquo;applique, et il faut passer par &lt;code&gt;talosctl&lt;/code&gt; pour tout ce qui se faisait avant directement sur la machine. Il faut donc apprendre une nouvelle CLI, de nouveaux concepts et un nouveau nommage.&lt;/p&gt;
&lt;h3 id="un-premier-déploiement-sans-le-provider-talos"&gt;Un premier déploiement sans le provider Talos
&lt;/h3&gt;&lt;p&gt;Mon premier déploiement n&amp;rsquo;utilisait pas le provider &lt;code&gt;siderolabs/talos&lt;/code&gt; : OpenTofu créait les VMs, et je générais puis appliquais les configurations à la main avec &lt;code&gt;talosctl&lt;/code&gt;. Le passage au provider a ramené toute la configuration des nœuds &lt;em&gt;as code&lt;/em&gt;, avec le reste de l&amp;rsquo;infrastructure, et permet donc d&amp;rsquo;avoir un déploiement de bout en bout en un seul run OpenTofu.&lt;/p&gt;
&lt;h3 id="un-seul-schematic-pour-tous-les-nœuds"&gt;Un seul schematic pour tous les nœuds
&lt;/h3&gt;&lt;p&gt;Au départ, tous les nœuds utilisaient le même schematic, celui qui contient les extensions NVIDIA. Les nœuds sans GPU remontaient alors des erreurs liées aux pilotes NVIDIA, puisqu&amp;rsquo;ils embarquaient des modules prévus pour une carte qu&amp;rsquo;ils n&amp;rsquo;ont pas. C&amp;rsquo;est ce qui m&amp;rsquo;a amené à associer à chaque nœud un schematic qui ne contient que les extensions dont il a besoin.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Le cluster est opérationnel, et par rapport à Kubespray, je n&amp;rsquo;ai plus d&amp;rsquo;OS à administrer : pas de mises à jour de paquets, pas de SSH ouvert, et la configuration des nœuds ne peut pas s&amp;rsquo;écarter de ce que décrit le code. Une mise à jour de Talos se résume à changer une version dans une variable, et chaque nœud bascule sur la nouvelle image.&lt;/p&gt;
&lt;figure class="figure-themed"&gt;
 &lt;img class="figure-themed-light" src="https://mzinutti.fr/posts/talos-cluster-proxmox/images/deployment.png" alt="Schéma du déploiement : merge sur main, pipeline GitLab CI (validate, plan, apply manuel), tofu apply qui crée les VMs Proxmox et configure Talos jusqu'au bootstrap d'etcd, puis récupération des accès, installation de Cilium et nœuds Ready" width="800px"&gt;
 &lt;img class="figure-themed-dark" src="https://mzinutti.fr/posts/talos-cluster-proxmox/images/deployment-dark.png" alt="Schéma du déploiement : merge sur main, pipeline GitLab CI (validate, plan, apply manuel), tofu apply qui crée les VMs Proxmox et configure Talos jusqu'au bootstrap d'etcd, puis récupération des accès, installation de Cilium et nœuds Ready" width="800px"&gt;
 &lt;figcaption&gt;&lt;h4&gt;Résumé du workflow de déploiement&lt;/h4&gt;&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Une fois tout en place, j&amp;rsquo;ai surtout été surpris par la facilité et la rapidité avec lesquelles un cluster Kubernetes se monte. Là où Kubespray enchaînait près de 600 tâches Ansible, un seul run du pipeline crée les VMs, applique les configurations et bootstrappe le cluster, et il ne reste plus qu&amp;rsquo;à installer Cilium.&lt;/p&gt;
&lt;p&gt;Le nœud &lt;code&gt;talos-gpu-01&lt;/code&gt; tourne déjà, avec ses 12 GB de RAM réservés et son image qui contient les modules NVIDIA, mais il ne voit encore aucune carte graphique. Faire passer le GPU de l&amp;rsquo;hôte Proxmox jusqu&amp;rsquo;aux pods est le sujet de la &lt;a class="link" href="https://mzinutti.fr/posts/gpu-passthrough-talos/" target="_blank" rel="noopener"
 &gt;partie 2&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="liens-utiles"&gt;Liens utiles
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.siderolabs.com/" target="_blank" rel="noopener"
 &gt;Documentation Talos Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://factory.talos.dev/" target="_blank" rel="noopener"
 &gt;Talos Image Factory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://registry.terraform.io/providers/siderolabs/talos/latest/docs" target="_blank" rel="noopener"
 &gt;Provider Terraform siderolabs/talos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs" target="_blank" rel="noopener"
 &gt;Provider Terraform bpg/proxmox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.siderolabs.com/talos/v1.13/networking/advanced/vip" target="_blank" rel="noopener"
 &gt;VIP partagée des control planes Talos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.siderolabs.com/talos/v1.13/configure-your-talos-cluster/system-configuration/kubeprism" target="_blank" rel="noopener"
 &gt;KubePrism&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.siderolabs.com/kubernetes-guides/cni/deploying-cilium" target="_blank" rel="noopener"
 &gt;Déployer Cilium sur Talos&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/alex1989hu/kubelet-serving-cert-approver" target="_blank" rel="noopener"
 &gt;kubelet-serving-cert-approver&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Garantir la qualité des Helm Charts</title><link>https://mzinutti.fr/posts/helm-chart-quality-pipeline/</link><pubDate>Mon, 11 May 2026 14:00:00 +0000</pubDate><guid>https://mzinutti.fr/posts/helm-chart-quality-pipeline/</guid><description>&lt;img src="https://mzinutti.fr/posts/helm-chart-quality-pipeline/img/helm-flow.svg" alt="Featured image of post Garantir la qualité des Helm Charts" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;En tant que SRE / Platform Engineer, l&amp;rsquo;une de mes responsabilités est de fournir des Helm charts fiables aux équipes de développement. Ces charts constituent la fondation du déploiement de leurs applications sur nos plateformes Kubernetes. Un chart défaillant, ce sont des applications impactées, des heures d&amp;rsquo;investigation, et une confiance érodée sur les composants mis à disposition.&lt;/p&gt;
&lt;p&gt;Le déclencheur de cette réflexion vient d&amp;rsquo;un incident évitable, avec l&amp;rsquo;introduction de &lt;em&gt;breaking changes&lt;/em&gt; non documentés qui ont cassé tous les déploiements utilisant ce chart, et nous a forcés à nous poser une question fondamentale : &lt;strong&gt;comment garantir la qualité des helm charts que nous fournissons ?&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;h3 id="le-problème"&gt;Le problème
&lt;/h3&gt;&lt;p&gt;Gérer des Helm charts manuellement et sans validation automatisée présente plusieurs risques :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Breaking changes non documentés&lt;/strong&gt; : Des modifications incompatibles peuvent être introduites sans que les équipes utilisatrices en soient informées.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Erreurs de syntaxe&lt;/strong&gt; : Des fautes de frappe ou des erreurs YAML peuvent passer inaperçues jusqu&amp;rsquo;au déploiement en production.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Templates invalides&lt;/strong&gt; : Des templates qui ne se rendent pas correctement ou génèrent des manifestes Kubernetes invalides.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Régression fonctionnelle&lt;/strong&gt; : Des modifications qui cassent des fonctionnalités existantes sans être détectées.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Absence de traçabilité&lt;/strong&gt; : Difficulté à comprendre l&amp;rsquo;historique des changements et leur impact.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="lobjectif"&gt;L&amp;rsquo;objectif
&lt;/h3&gt;&lt;p&gt;J&amp;rsquo;avais donc besoin d&amp;rsquo;un cadre capable de valider automatiquement chaque modification avant son intégration dans la branche principale, de signaler explicitement les &lt;em&gt;breaking changes&lt;/em&gt;, de vérifier la conformité Kubernetes des manifestes générés, de tester les comportements attendus et de tracer les évolutions dans un &lt;em&gt;changelog&lt;/em&gt; exploitable.&lt;/p&gt;
&lt;p&gt;Mais il ne s&amp;rsquo;agissait pas seulement d&amp;rsquo;ajouter des outils. La réponse devait aussi être organisationnelle : mieux cadrer la contribution, rendre la collaboration plus lisible, et faire en sorte que la qualité du chart ne repose plus uniquement sur l&amp;rsquo;attention individuelle.&lt;/p&gt;
&lt;h2 id="composants"&gt;Composants
&lt;/h2&gt;&lt;p&gt;Le workflow s&amp;rsquo;articule autour de plusieurs outils et pratiques :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitLab CI/CD&lt;/strong&gt; : Orchestre l&amp;rsquo;ensemble du pipeline de validation et de publication.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Helm &amp;amp; Plugins&lt;/strong&gt; : Gestionnaire de packages pour Kubernetes, avec ses outils natifs de validation et ses plugins.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Git Flow&lt;/strong&gt; : Stratégie de branches et processus de Merge Request.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SemVer&lt;/strong&gt; : Gestion des versions pour communiquer clairement les types de changements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep a Changelog&lt;/strong&gt; : Format standardisé pour documenter tous les changements.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="architecture-du-pipeline"&gt;Architecture du Pipeline
&lt;/h2&gt;&lt;p&gt;Le pipeline GitLab CI est organisé en trois stages principaux, chacun avec un objectif spécifique.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/helm-chart-quality-pipeline/img/helm-flow.svg"
 alt="Pipeline GitLab CI en trois stages : lint avec helm lint, validate avec helm template, helm kubeconform et helm unittest, puis release avec helm package et l&amp;#39;upload vers le registry" width="800px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Vue d&amp;#39;ensemble du pipeline&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="linting"&gt;Linting
&lt;/h3&gt;&lt;p&gt;Le premier niveau de validation dans le pipeline vérifie la syntaxe et la structure globale du chart via la commande &lt;code&gt;helm lint&lt;/code&gt; native à helm. Elle permet d&amp;rsquo;avoir un retour rapide et immédiatement compréhensible, bien qu&amp;rsquo;elle ne couvre pas tous les cas, elle élimine très tôt les erreurs grossières qui ne devraient jamais atteindre les étapes suivantes et évite donc de lancer des validations plus poussées et donc de perdre du temps.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm lint .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;&lt;strong&gt;Ce que Helm lint vérifie :&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Syntaxe YAML correcte&lt;/li&gt;
&lt;li&gt;Structure du chart (Chart.yaml, templates/, etc.)&lt;/li&gt;
&lt;li&gt;Problèmes de formatage&lt;/li&gt;
&lt;li&gt;Warnings sur les bonnes pratiques&lt;/li&gt;
&lt;/ul&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;p&gt;Une fois que l&amp;rsquo;on s&amp;rsquo;est assuré de la bonne structure du chart, on peut passer aux étapes de validation plus spécifiques.&lt;/p&gt;
&lt;h3 id="validation-du-templating"&gt;Validation du templating
&lt;/h3&gt;&lt;p&gt;Pour répondre à la question : est-ce que le chart se rend réellement correctement avec les valeurs fournies ? J&amp;rsquo;ai retenu &lt;code&gt;helm template&lt;/code&gt; qui va permettre d&amp;rsquo;effectuer le rendu des templates, c&amp;rsquo;est souvent à ce niveau que l&amp;rsquo;on découvre des variables manquantes, des conditions mal écrites, ou des erreurs dans les fonctions Go Template.&lt;/p&gt;
&lt;p&gt;Le fonctionnement est simple : Helm interprète les templates et produit les manifestes Kubernetes finaux, sans les appliquer sur un cluster. On obtient donc une vue concrète de ce qui serait déployé, ce qui en fait une étape particulièrement utile pour détecter les erreurs de rendu au plus tôt.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;helm template .&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;&lt;strong&gt;Ce que Helm template vérifie :&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rendu correct des templates Go (variables, fonctions, pipelines)&lt;/li&gt;
&lt;li&gt;Résolution des valeurs et des helpers définis dans &lt;code&gt;_helpers.tpl&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Syntaxe correcte des conditions &lt;code&gt;{{- if }}&lt;/code&gt; et des boucles &lt;code&gt;{{- range }}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Absence de variables non définies ou mal référencées&lt;/li&gt;
&lt;/ul&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;h3 id="conformité-des-schémas"&gt;Conformité des schémas
&lt;/h3&gt;&lt;p&gt;&lt;code&gt;helm template&lt;/code&gt; garantit que le rendu fonctionne, mais pas que le résultat soit acceptable pour Kubernetes. C&amp;rsquo;est précisément pour cette raison que j&amp;rsquo;ai choisi d&amp;rsquo;utiliser &lt;a class="link" href="https://github.com/yannh/kubeconform" target="_blank" rel="noopener"
 &gt;Kubeconform&lt;/a&gt;. Cet outil a été retenu parce qu&amp;rsquo;il est rapide, simple à intégrer dans une CI, et très efficace pour valider les manifestes contre des schémas connus.&lt;/p&gt;
&lt;p&gt;Son principe est de comparer les ressources générées aux schémas Kubernetes attendus. Il ne se contente donc pas de vérifier que le YAML est bien formé : il valide aussi la structure métier du manifeste, les champs autorisés, les types attendus et certaines incompatibilités de version. Avec l&amp;rsquo;ajout du &lt;a class="link" href="https://github.com/datreeio/crds-catalog" target="_blank" rel="noopener"
 &gt;catalogue de CRDs&lt;/a&gt;, il reste utile même dans des environnements Kubernetes enrichis par des opérateurs.&lt;/p&gt;
&lt;p&gt;À l&amp;rsquo;origine, cet outil est un binaire mais un plugin helm a été fait pour pouvoir être intégré directement dans la CLI de helm.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm plugin install https://github.com/jtyr/kubeconform-helm
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;helm kubeconform .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Configuration de Kubeconform (.kubeconform) :&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;schema-location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Default K8s resources schema&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;https://raw.githubusercontent.com/datreeio/CRDs-catalog/main/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json&lt;/span&gt;&lt;span class="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;summary&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="nt"&gt;verbose&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="nt"&gt;ignore-missing-schemas&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 class="c"&gt;# Don&amp;#39;t fail on resources where schema is not found.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;&lt;strong&gt;Ce que kubeconform vérifie :&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Champs invalides ou dépréciés&lt;/li&gt;
&lt;li&gt;Types de données incorrects&lt;/li&gt;
&lt;li&gt;Valeurs manquantes pour les champs requis&lt;/li&gt;
&lt;li&gt;Problèmes de compatibilité avec la version Kubernetes ciblée&lt;/li&gt;
&lt;/ul&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;Exemple de sortie :&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stdin - Secret mariadb-cluster is valid
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stdin - StatefulSet mariadb-cluster is valid
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stdin - Cluster pg-cluster is valid
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;stdin - MongoDBCommunity mongodb-cluster is valid
&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Summary: 17 resources found parsing stdin - Valid: 17, Invalid: 0, Errors: 0, Skipped: 0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="tests-unitaires"&gt;Tests unitaires
&lt;/h3&gt;&lt;p&gt;J&amp;rsquo;ai ajouté &lt;code&gt;Helm Unittest&lt;/code&gt; parce qu&amp;rsquo;au-delà de la syntaxe et de la conformité, il fallait protéger les comportements fonctionnels du chart. C&amp;rsquo;est l&amp;rsquo;outil qui me permet de formaliser des attentes explicites : un nom doit suivre une convention, une ressource doit exister dans un cas donné, une image doit être construite avec telle valeur, etc.&lt;/p&gt;
&lt;p&gt;Le principe est proche d&amp;rsquo;un framework de test classique, mais appliqué au rendu Helm. On décrit un jeu de valeurs d&amp;rsquo;entrée, puis on exprime des assertions sur le manifeste produit. Cette approche est particulièrement utile pour éviter les régressions silencieuses lorsqu&amp;rsquo;un chart évolue et commence à gérer plusieurs variantes de configuration.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;helm plugin install https://github.com/helm-unittest/helm-unittest.git&lt;/span&gt;&lt;span class="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;helm unittest .&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les tests unitaires permettent de vérifier les comportements attendus du chart dans différentes configurations.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemple de test (tests/deployment_test.yaml) :&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;suite&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;test deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;templates&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;deployment.yaml&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;it&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;should create a valid deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;set&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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.tag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;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;asserts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;isKind&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;of&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Deployment&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;matchRegex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;metadata.name&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pattern&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-chart$&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;spec.template.spec.containers[0].image&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nginx:latest&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Ces tests vérifient :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les valeurs par défaut produisent des manifestes valides&lt;/li&gt;
&lt;li&gt;Les différentes configurations fonctionnent correctement&lt;/li&gt;
&lt;li&gt;Les comportements critiques sont préservés (non-régression)&lt;/li&gt;
&lt;/ul&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;h3 id="release-management"&gt;Release Management
&lt;/h3&gt;&lt;p&gt;La publication n&amp;rsquo;est déclenchée que lors de la création d&amp;rsquo;un tag Git sur la branche &lt;code&gt;main&lt;/code&gt;. Ce processus garantit que seules les versions taggées sont publiées, permettant d&amp;rsquo;accumuler plusieurs modifications avant de créer une release.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Point crucial : Le CHANGELOG&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Chaque Merge Request &lt;strong&gt;doit&lt;/strong&gt; inclure une mise à jour du &lt;code&gt;CHANGELOG.md&lt;/code&gt;. Ce fichier suit le format &lt;a class="link" href="https://keepachangelog.com/en/1.0.0/" target="_blank" rel="noopener"
 &gt;Keep a Changelog&lt;/a&gt; et utilise le &lt;a class="link" href="https://semver.org/" target="_blank" rel="noopener"
 &gt;Semantic Versioning&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="gu"&gt;## [1.1.0] - 2026-01-26
&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="gu"&gt;### Added
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Resource limits and requests for all containers
&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="gu"&gt;### Changed
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;-&lt;/span&gt; Updated default memory limit to 512Mi
&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;[1.1.0]: https://gitlab.com/project/compare/1.0.9...1.1.0
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Catégories disponibles :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Added&lt;/strong&gt; : Nouvelles fonctionnalités&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Changed&lt;/strong&gt; : Modifications dans les fonctionnalités existantes&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fixed&lt;/strong&gt; : Corrections de bugs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Removed&lt;/strong&gt; : Fonctionnalités supprimées&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class="alert alert-important"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📌&lt;/span&gt;
 &lt;span class="alert-title"&gt;Important&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Si une modification introduit un breaking change, cela doit être :&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Clairement indiqué dans le CHANGELOG&lt;/li&gt;
&lt;li&gt;Expliqué dans la description de la Merge Request&lt;/li&gt;
&lt;li&gt;Accompagné d&amp;rsquo;un guide de migration si nécessaire&lt;/li&gt;
&lt;/ol&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;h3 id="semantic-versioning"&gt;Semantic Versioning
&lt;/h3&gt;&lt;p&gt;Nous suivons strictement le Semantic Versioning (MAJOR.MINOR.PATCH) :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;MAJOR&lt;/strong&gt; : Breaking changes (incompatibilité avec les versions précédentes)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MINOR&lt;/strong&gt; : Nouvelles fonctionnalités rétrocompatibles&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PATCH&lt;/strong&gt; : Corrections de bugs rétrocompatibles&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Exemple :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;1.0.9&lt;/code&gt; → &lt;code&gt;1.1.0&lt;/code&gt; : Ajout de resource limits (rétrocompatible) → MINOR&lt;/li&gt;
&lt;li&gt;&lt;code&gt;1.1.0&lt;/code&gt; → &lt;code&gt;2.0.0&lt;/code&gt; : Changement de structure du values.yaml (breaking) → MAJOR&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="bénéfices-observés"&gt;Bénéfices Observés
&lt;/h2&gt;&lt;p&gt;Depuis la mise en place de ce workflow, on a pu constater :&lt;/p&gt;
&lt;h3 id="qualité"&gt;Qualité
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Zéro breaking change non documenté&lt;/strong&gt; depuis 6 mois&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Réduction de 90% des erreurs&lt;/strong&gt; de déploiement liées aux charts&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Détection précoce&lt;/strong&gt; des problèmes (avant la revue de code)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="collaboration"&gt;Collaboration
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Processus clair&lt;/strong&gt; pour tous les contributeurs (juniors et seniors)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Revues de code&lt;/strong&gt; plus efficaces (focus sur la logique, pas la syntaxe)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Traçabilité&lt;/strong&gt; complète des changements via le CHANGELOG&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="confiance"&gt;Confiance
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Les équipes de développement ont &lt;strong&gt;confiance&lt;/strong&gt; dans la stabilité des charts&lt;/li&gt;
&lt;li&gt;Les updates sont &lt;strong&gt;moins redoutées&lt;/strong&gt; car le processus est transparent&lt;/li&gt;
&lt;li&gt;Les rollbacks sont &lt;strong&gt;facilités&lt;/strong&gt; par le versioning clair&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="productivité"&gt;Productivité
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Réduction du temps&lt;/strong&gt; de validation manuelle&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automatisation&lt;/strong&gt; de tâches répétitives&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus&lt;/strong&gt; sur la valeur ajoutée plutôt que sur la vérification de syntaxe&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="diagramme-de-séquence"&gt;Diagramme de Séquence
&lt;/h2&gt;&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/helm-chart-quality-pipeline/img/flux-diagram.svg"
 alt="Diagramme de séquence : le pipeline de merge request exécute le lint, le rendu, la validation des schémas et les tests, puis le pipeline de tag package le chart et le publie dans le registry" width="600px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Diagramme de séquence complet&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="points-dattention"&gt;Points d&amp;rsquo;Attention
&lt;/h2&gt;&lt;p&gt;Quelques leçons apprises lors de la mise en place :&lt;/p&gt;
&lt;h3 id="tests-unitaires-complets"&gt;Tests Unitaires Complets
&lt;/h3&gt;&lt;p&gt;Ne testez pas uniquement les cas nominaux. Incluez :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Les configurations par défaut&lt;/li&gt;
&lt;li&gt;Les cas limites&lt;/li&gt;
&lt;li&gt;Les différentes combinaisons de valeurs&lt;/li&gt;
&lt;li&gt;Les scénarios de mise à jour&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="documentation-du-changelog"&gt;Documentation du CHANGELOG
&lt;/h3&gt;&lt;p&gt;Le CHANGELOG est &lt;strong&gt;le contrat&lt;/strong&gt; avec les utilisateurs. Il doit être :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Complet&lt;/strong&gt; : Tous les changements doivent être documentés&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clair&lt;/strong&gt; : Langage compréhensible, pas de jargon technique inutile&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Actionnable&lt;/strong&gt; : Pour les breaking changes, indiquer quoi faire&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="protection-de-la-branche-main"&gt;Protection de la Branche Main
&lt;/h3&gt;&lt;p&gt;La protection de &lt;code&gt;main&lt;/code&gt; doit être stricte :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pas de commit direct&lt;/li&gt;
&lt;li&gt;Pipeline obligatoire&lt;/li&gt;
&lt;li&gt;Approbation obligatoire&lt;/li&gt;
&lt;li&gt;Le CHANGELOG doit être à jour &lt;strong&gt;avant&lt;/strong&gt; le merge&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tags-protégés"&gt;Tags Protégés
&lt;/h3&gt;&lt;p&gt;Restreindre la création de tags aux Maintainers pour éviter les publications accidentelles.&lt;/p&gt;
&lt;h3 id="communication"&gt;Communication
&lt;/h3&gt;&lt;p&gt;Lors d&amp;rsquo;un breaking change, même bien documenté :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Notifier les équipes &lt;strong&gt;en avance&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Fournir un guide de migration&lt;/li&gt;
&lt;li&gt;Offrir du support pendant la transition&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="améliorations-futures"&gt;Améliorations Futures
&lt;/h2&gt;&lt;p&gt;Nous envisageons d&amp;rsquo;ajouter :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Tests de non-régression&lt;/strong&gt; : Comparer les manifestes générés entre versions avec &lt;code&gt;helm diff&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security scanning&lt;/strong&gt; : Intégrer des outils comme Trivy pour détecter les vulnérabilités&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Policy enforcement&lt;/strong&gt; : Utiliser des outils comme OPA ou Kyverno pour valider les policies de sécurité&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitoring des déploiements&lt;/strong&gt; : Suivre l&amp;rsquo;adoption des nouvelles versions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;La mise en place d&amp;rsquo;un pipeline de validation rigoureux pour nos Helm charts a transformé notre manière de travailler. L&amp;rsquo;incident qui nous a poussés à implémenter ce workflow est désormais impossible grâce aux multiples niveaux de validation.&lt;/p&gt;
&lt;p&gt;Ce processus garantit non seulement la qualité technique des charts, mais établit également un contrat clair avec les équipes de développement : elles peuvent faire confiance aux charts que nous fournissons, et lorsqu&amp;rsquo;un breaking change est nécessaire, il est clairement documenté et communiqué.&lt;/p&gt;
&lt;p&gt;Pour une équipe SRE, fournir une plateforme fiable est primordial. Ce workflow nous permet de tenir cette promesse tout en gardant la capacité d&amp;rsquo;évoluer et d&amp;rsquo;améliorer continuellement nos charts.&lt;/p&gt;
&lt;p&gt;Si vous gérez des Helm charts pour une plateforme Kubernetes, je vous encourage vivement à mettre en place un processus similaire. L&amp;rsquo;investissement initial est rapidement rentabilisé par la réduction des incidents et l&amp;rsquo;amélioration de la collaboration.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;strong&gt;Ressources utiles :&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://helm.sh/docs/" target="_blank" rel="noopener"
 &gt;Helm Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/yannh/kubeconform" target="_blank" rel="noopener"
 &gt;Kubeconform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/helm-unittest/helm-unittest" target="_blank" rel="noopener"
 &gt;Helm Unittest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://keepachangelog.com/" target="_blank" rel="noopener"
 &gt;Keep a Changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://semver.org/" target="_blank" rel="noopener"
 &gt;Semantic Versioning&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Déployer un cluster Kubernetes avec Ansible</title><link>https://mzinutti.fr/posts/k8s-cluster-deployment/</link><pubDate>Mon, 24 Nov 2025 10:20:38 +0000</pubDate><guid>https://mzinutti.fr/posts/k8s-cluster-deployment/</guid><description>&lt;img src="https://mzinutti.fr/posts/k8s-cluster-deployment/img/workflow.png" alt="Featured image of post Déployer un cluster Kubernetes avec Ansible" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Dans mon homelab, j’ai souhaité aller plus loin dans l’automatisation en déployant un cluster Kubernetes complet sur Proxmox, en m’appuyant sur des outils d’infrastructure as code et d’automatisation comme Ansible. L’objectif était de pouvoir provisionner rapidement un cluster k8s reproductible, prêt à accueillir des workloads, tout en gardant la maîtrise de chaque étape du déploiement via du code. Ce processus d&amp;rsquo;évolution m&amp;rsquo;a naturellement conduit vers Kubespray.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Après avoir initialement installé un cluster Kubernetes de façon manuelle avec kubeadm, j’ai constaté que le processus était particulièrement fastidieux. Cette expérience m’a convaincu de la nécessité d’automatiser et de standardiser le déploiement, afin de gagner en reproductibilité et en efficacité.&lt;/p&gt;
&lt;p&gt;C’est là que &lt;a class="link" href="https://github.com/kubernetes-sigs/kubespray" target="_blank" rel="noopener"
 &gt;Kubespray&lt;/a&gt; intervient, ce projet open source propose des playbooks Ansible prêts à l’emploi pour installer, configurer et maintenir un cluster Kubernetes avec une approche 100% &lt;em&gt;as code&lt;/em&gt;. Contrairement à kubeadm, il offre un contrôle fin sur les composants du cluster (CRI, CNI, DNS, etc.) tout en restant automatisé et reproductible. Dans cet article, je détaille la démarche suivie, les choix techniques réalisés, et les outils utilisés pour atteindre cet objectif.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-cluster-deployment/img/kubespray_logo.png"
 alt="Logo Kubespray" width="250px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Logo Kubespray&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="prérequis"&gt;Prérequis
&lt;/h2&gt;&lt;p&gt;Avant de commencer le déploiement, il est nécessaire de disposer des ressources suivantes :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt; :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Au moins 24 GB de RAM disponible sur l&amp;rsquo;hyperviseur Proxmox&lt;/li&gt;
&lt;li&gt;150 GB de stockage libre&lt;/li&gt;
&lt;li&gt;Accès réseau avec attribution d&amp;rsquo;IP statiques&lt;/li&gt;
&lt;li&gt;Clé SSH pour l&amp;rsquo;accès aux VMs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="composants"&gt;Composants
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Proxmox 8.X+&lt;/strong&gt; : Fournit l&amp;rsquo;infrastructure pour exécuter et gérer des machines virtuelles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ansible 2.16+&lt;/strong&gt; : Outil d’automatisation utilisé par Kubespray pour déployer et configurer Kubernetes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terraform 1.10+ &amp;amp; Packer 1.12+&lt;/strong&gt; : Pour automatiser la création et la configuration des VMs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="architecture-cible-du-cluster"&gt;Architecture cible du cluster
&lt;/h3&gt;&lt;p&gt;Le cluster sera composé de trois nœuds avec le dimensionnement suivant :&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Rôle&lt;/th&gt;
 &lt;th&gt;Hostname&lt;/th&gt;
 &lt;th&gt;vCPU&lt;/th&gt;
 &lt;th&gt;RAM&lt;/th&gt;
 &lt;th&gt;Stockage&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Control Plane&lt;/td&gt;
 &lt;td&gt;k8s-master-01&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;8 GB&lt;/td&gt;
 &lt;td&gt;50 GB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Worker Node&lt;/td&gt;
 &lt;td&gt;k8s-worker-01&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;8 GB&lt;/td&gt;
 &lt;td&gt;50 GB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Worker Node&lt;/td&gt;
 &lt;td&gt;k8s-worker-02&lt;/td&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;8 GB&lt;/td&gt;
 &lt;td&gt;50 GB&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-cluster-deployment/img/target_architecture.png"
 alt="Architecture cible : GitLab CI applique le code Terraform, avec le state stocké dans GitLab, qui clone le template Packer dans Proxmox pour créer k8s-master-01, k8s-worker-01 et k8s-worker-02" width="700px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Architecture cible&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;hr&gt;
&lt;h2 id="création-des-vms"&gt;Création des VMs
&lt;/h2&gt;&lt;p&gt;Pour mon homelab, j&amp;rsquo;ai opté pour une architecture simple composée de trois VMs : un control plane et deux worker nodes.&lt;/p&gt;
&lt;p&gt;Ce choix s&amp;rsquo;explique par plusieurs raisons :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simplicité pour l&amp;rsquo;apprentissage&lt;/strong&gt; : Avec trois nœuds, le cluster reste simple à gérer tout en permettant d&amp;rsquo;expérimenter la plupart des fonctionnalités Kubernetes (répartition de charge, affinité des pods, tolérance aux pannes, etc.).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tolérance aux pannes des workers&lt;/strong&gt; : Deux workers offrent la possibilité de tester des scénarios de panne d&amp;rsquo;un nœud et d&amp;rsquo;observer le comportement de Kubernetes lors du rescheduling des pods.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Maîtrise des ressources&lt;/strong&gt; : Ce dimensionnement reste raisonnable en termes de ressources consommées (4 vCPU, 8 Go RAM, 50 Go stockage par VM), ce qui est important dans un contexte de homelab.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;&lt;strong&gt;Haute disponibilité&lt;/strong&gt; : Cette architecture comprend un seul control plane. Pour une véritable haute disponibilité en production, il serait nécessaire de déployer au minimum 3 control planes (nombre impair pour le quorum etcd) avec un load balancer devant les API servers ou bien d&amp;rsquo;avoir des instances dédiées pour etcd hors du cluster. Dans le cadre de ce homelab, un seul control plane suffit pour les besoins d&amp;rsquo;apprentissage et d&amp;rsquo;expérimentation.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;p&gt;Une fois le dimensionnement défini, passons à la création des VMs. Pour cela, j&amp;rsquo;ai utilisé Terraform et Packer, ce qui me permet de déployer rapidement mon infrastructure de manière totalement automatisée. J&amp;rsquo;explique en détail cette approche, notamment la création de templates Proxmox avec Packer et leur déploiement avec Terraform, dans &lt;a class="link" href="https://mzinutti.fr/posts/proxmox-packer-template/" target="_blank" rel="noopener"
 &gt;mon article sur les templates Proxmox avec Packer&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;############################
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# VMs for Kubernetes Cluster
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;############################
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;proxmox_virtual_environment_vm&amp;#34; &amp;#34;k8s_cluster&amp;#34;&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; for_each&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; k8s-master&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;K8S Control Plane&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;110&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ip_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;192.168.1.110/24&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;k8s_cluster&amp;#34;, &amp;#34;kube_control_plane&amp;#34;, &amp;#34;etcd&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; k8s-node1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;K8S Worker Node 1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;111&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ip_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;192.168.1.111/24&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;k8s_cluster&amp;#34;, &amp;#34;kube_node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; k8s-node2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;K8S Worker Node 2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;112&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ip_address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;192.168.1.112/24&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;k8s_cluster&amp;#34;, &amp;#34;kube_node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }
&lt;/span&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;description&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;debian12&amp;#34;, &amp;#34;terraform&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;tags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; node_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pve&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;each&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;vm_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; pool_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;PROD&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; reboot_after_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;false&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="p"&gt;...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;clone&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; datastore_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local-lvm&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; node_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pve&amp;#34;&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Après l’application du code Terraform, les trois machines virtuelles sont déployées sur Proxmox. Chaque VM est associée à un ou plusieurs tags parmi : (&lt;code&gt;k8s_cluster&lt;/code&gt;, &lt;code&gt;kube_control_plane&lt;/code&gt;, &lt;code&gt;etcd&lt;/code&gt;, &lt;code&gt;kube_node&lt;/code&gt;). Ces tags faciliteront la génération de l’inventaire pour Kubespray que je détaille plus bas dans l&amp;rsquo;article.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-cluster-deployment/img/vmproxmox.png"
 alt="Les VMs k8s-master, k8s-node1 et k8s-node2 dans Proxmox avec leurs tags debian12, k8s_cluster, etcd, kube_control_plane, kube_node et terraform" width="800px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;VMs dans Proxmox&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h3 id="configuration-réseau-et-accès-ssh"&gt;Configuration réseau et accès SSH
&lt;/h3&gt;&lt;p&gt;Grâce à l’utilisation de Cloud-Init lors du processus de la création des VMs (via le template Packer et l&amp;rsquo;argument &lt;code&gt;initialization&lt;/code&gt; dans la ressource &lt;code&gt;proxmox_virtual_environment_vm&lt;/code&gt; du provider Terraform), la configuration réseau et l’accès SSH sont automatisés :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Adresse IP statique&lt;/strong&gt; : Chaque VM reçoit directement son IP statique lors du déploiement, définie dans la section &lt;code&gt;ip_config&lt;/code&gt; de Terraform. Cela évite toute configuration manuelle post-déploiement.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clé SSH&lt;/strong&gt; : La clé publique SSH est injectée automatiquement dans chaque VM via Cloud-Init, ce qui permet une connexion immédiate sans avoir à copier la clé manuellement.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ce processus permet de gérer toutes les VMs via Ansible dès leur création, sans intervention manuelle supplémentaire. Dans mon cas, je peux donc directement utiliser les playbooks Kubespray.&lt;/p&gt;
&lt;blockquote class="alert alert-warning"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;⚠️&lt;/span&gt;
 &lt;span class="alert-title"&gt;Avertissement&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Dans un environnement de production, il est recommandé de gérer les secrets (tokens API Proxmox, clés SSH, etc.) avec des outils dédiés comme Ansible Vault ou des gestionnaires de secrets externes.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;p&gt;Les ressources sont désormais prêtes et configurées, nous pouvons maintenant passer à la mise en place du cluster Kubernetes avec Kubespray.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="mise-en-place-de-kubespray"&gt;Mise en place de Kubespray
&lt;/h2&gt;&lt;h3 id="cloner-et-configurer-le-dépôt-kubespray"&gt;Cloner et configurer le dépôt Kubespray
&lt;/h3&gt;&lt;p&gt;J&amp;rsquo;ai intégré Kubespray dans mon projet en tant que submodule git. Ceci permet d’inclure un autre repository tout en gardant son historique séparé. Cela facilite la gestion des dépendances et permet de suivre une version précise de Kubespray, la mettre à jour facilement, et avoir les évolutions du projet sans mélanger les codes sources.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;git submodule add https://github.com/kubernetes-sigs/kubespray.git kubespray
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Afin de fixer la version du submodule sur un tag ou un commit spécifique, il faut se rendre dans le répertoire du submodule et checkout sur le commit ou tag désiré dans le projet. Ensuite, il faut retourner dans le répertoire principal du projet, commit les changements et pousser les modifications.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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; kubespray/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout v2.27.0 &lt;span class="c1"&gt;# Version utilisée pour ce déploiement&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; ../
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Pinned submodule to v2.27.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Il est important de fixer la version de Kubespray afin de garantir la reproductibilité des déploiements et d’éviter toute instabilité liée à des changements non maîtrisés dans le projet en amont.&lt;/p&gt;
&lt;p&gt;Une fois le projet Kubespray intégré et configuré dans le dépôt Ansible, on peut passer à la génération de l’inventaire pour Kubespray.&lt;/p&gt;
&lt;h3 id="création-de-linventaire-kubespray"&gt;Création de l’inventaire Kubespray
&lt;/h3&gt;&lt;p&gt;Pour la partie inventaire ansible pour Kubespray, j&amp;rsquo;ai choisi la solution de &lt;a class="link" href="https://docs.ansible.com/ansible/latest/collections/community/general/proxmox_inventory.html" target="_blank" rel="noopener"
 &gt;l&amp;rsquo;inventaire dynamique avec Proxmox&lt;/a&gt;. Ceci permet d&amp;rsquo;avoir un inventaire qui est constamment à jour, car il est chargé sur chaque lancement de playbook, ce qui évite une maintenance manuelle du fichier d&amp;rsquo;inventaire.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;plugin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;community.general.proxmox&lt;/span&gt;&lt;span class="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;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://&amp;lt;proxmox_host&amp;gt;:8006&lt;/span&gt;&lt;span class="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;user&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;&amp;lt;user&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;token_id&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;&amp;lt;token_id&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;token_secret&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;&amp;lt;token_secret&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;validate_certs&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;# Uniquement dans le cadre d&amp;#39;un homelab&lt;/span&gt;&lt;span class="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;want_facts&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="nt"&gt;keyed_groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;proxmox_tags_parsed&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;separator&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;&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="nt"&gt;compose&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ansible_host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;proxmox_agent_interfaces[1][&amp;#34;ip-addresses&amp;#34;][0].split(&amp;#39;/&amp;#39;)[0]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;L&amp;rsquo;inventaire ci-dessus permet de récupérer toutes les VMs dans Proxmox et de les regrouper par tags &lt;code&gt;proxmox_tags_parsed&lt;/code&gt; et de configurer la variable &lt;code&gt;ansible_host&lt;/code&gt; de chaque entrée dans l&amp;rsquo;inventaire avec l&amp;rsquo;IP associée à chaque VM.&lt;/p&gt;
&lt;p&gt;Dans le cas de Kubespray, il faut respecter la nomenclature des tags comme c&amp;rsquo;est écrit dans la &lt;a class="link" href="https://github.com/kubernetes-sigs/kubespray/blob/master/docs/ansible/inventory.md" target="_blank" rel="noopener"
 &gt;documentation&lt;/a&gt;, car c&amp;rsquo;est ce qui va servir à lancer les playbooks sur les bons hôtes.&lt;/p&gt;
&lt;p&gt;Pour générer l&amp;rsquo;inventaire, j&amp;rsquo;utilise la commande ci-dessous, où l&amp;rsquo;option &lt;code&gt;--graph&lt;/code&gt; va permettre de rendre la sortie de la commande un peu plus visuelle afin de bien distinguer les groupes et les hôtes de l&amp;rsquo;inventaire ansible.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;ansible-inventory -i inventories/proxmox.yml --graph
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Et dans mon cas ça donne le résultat suivant :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@all:
&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |--@debian12:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-node1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-master
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-node2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |--@k8s_cluster:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-node1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-master
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-node2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |--@kube_node:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-node1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-node2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |--@terraform:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-node1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-master
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-node2
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |--@etcd:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-master
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |--@kube_control_plane:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |--k8s-master
&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Une fois l&amp;rsquo;inventaire en place, je peux tester la connectivité des VMs via la commande &lt;em&gt;ad hoc&lt;/em&gt; ci-dessous qui utilise le module ansible &lt;code&gt;ping&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;ansible -i inventories/proxmox.yml k8s_cluster -m ping -u &amp;lt;username&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ce qui donne le résultat suivant :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;k8s-node1 | SUCCESS =&amp;gt; {&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;changed&amp;#34;: &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;ping&amp;#34;: &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;k8s-master | SUCCESS =&amp;gt; {&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;changed&amp;#34;: &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;ping&amp;#34;: &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="l"&gt;k8s-node2 | SUCCESS =&amp;gt; {&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;changed&amp;#34;: &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;ping&amp;#34;: &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;pong&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&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Maintenant que je dispose de l&amp;rsquo;inventaire dynamique pour Proxmox et de machines virtuelles accessibles, je vais pouvoir passer à la partie customisation de mon cluster K8s.&lt;/p&gt;
&lt;h3 id="personnalisation-des-variables"&gt;Personnalisation des variables
&lt;/h3&gt;&lt;p&gt;Afin de pouvoir customiser le cluster Kubernetes, il va falloir passer par les &lt;code&gt;group_vars&lt;/code&gt; ansible. Grâce à ces variables, nous allons pouvoir configurer différents aspects du cluster tels que :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Paramétrage Kubernetes (version de kube, réseau interne, DNS,&amp;hellip;)&lt;/li&gt;
&lt;li&gt;Container Runtime Interface (CRI)&lt;/li&gt;
&lt;li&gt;Container Network Interface (CNI)&lt;/li&gt;
&lt;li&gt;Container Storage Interface (CSI)&lt;/li&gt;
&lt;li&gt;Configuration ETCD&lt;/li&gt;
&lt;li&gt;Addons et composants optionnels (ArgoCD, Helm, cert-manager, MetalLB, &amp;hellip;)&lt;/li&gt;
&lt;li&gt;Et bien d&amp;rsquo;autres choses&amp;hellip;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Ces variables sont organisées dans différents fichiers de l&amp;rsquo;inventaire pour une meilleure structuration de la configuration :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;inventories/group_vars/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── etcd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── all.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── etcd.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── k8s_cluster
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── addons.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── all.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── cri-o.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── k8s-cluster.yml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── k8s-net-flannel.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Pour plus d&amp;rsquo;informations à propos de la configuration possible du cluster Kubernetes et des composants disponibles, il faut se référer à la &lt;a class="link" href="https://github.com/kubernetes-sigs/kubespray/tree/master?tab=readme-ov-file#supported-components" target="_blank" rel="noopener"
 &gt;documentation&lt;/a&gt; de Kubespray.&lt;/p&gt;
&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;Dans le cadre de cet article, j&amp;rsquo;utilise principalement les configurations par défaut proposées par Kubespray. L&amp;rsquo;objectif est de montrer le processus de déploiement plutôt que de justifier des choix techniques spécifiques pour chaque composant (CNI, CRI, etc.). Ces choix peuvent être adaptés en fonction des besoins spécifiques de chaque projet.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;p&gt;La configuration cible du cluster Kubernetes a été définie, la prochaine étape va être le déploiement !&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="déploiement-du-cluster-kubernetes"&gt;Déploiement du cluster Kubernetes
&lt;/h2&gt;&lt;p&gt;Avant de lancer le déploiement avec ansible, j&amp;rsquo;ai besoin de configurer mon environnement, pour ce faire, je passe par un &lt;strong&gt;venv&lt;/strong&gt; python afin de fixer la version d&amp;rsquo;ansible ainsi que de mes dépendances dans le but d&amp;rsquo;isoler le tout pour éviter les potentiels conflits avec d&amp;rsquo;autres projets. Le venv va également permettre d&amp;rsquo;avoir un environnement reproductible ce qui est crucial pour exécuter les déploiements de manière identique.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;python3 -m venv venv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;source&lt;/span&gt; venv/bin/activate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install -r kubespray/requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Une fois l&amp;rsquo;environnement prêt, il est enfin temps de passer au déploiement du cluster, en lançant le playbook &lt;code&gt;cluster.yml&lt;/code&gt; et en utilisant l&amp;rsquo;inventaire dynamique ansible pour Proxmox configuré au préalable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;ansible-playbook -i inventories/proxmox.yml -b -u &amp;lt;username&amp;gt; kubespray/cluster.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Une fois le playbook terminé, le cluster Kubernetes est prêt à l’emploi avec la configuration définie plus haut.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;PLAY RECAP *************************************************************************
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8s-master: &lt;span class="nv"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;599&lt;/span&gt; &lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;39&lt;/span&gt; &lt;span class="nv"&gt;unreachable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;skipped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;965&lt;/span&gt; &lt;span class="nv"&gt;rescued&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;ignored&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8s-node1 : &lt;span class="nv"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;421&lt;/span&gt; &lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt; &lt;span class="nv"&gt;unreachable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;skipped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;618&lt;/span&gt; &lt;span class="nv"&gt;rescued&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;ignored&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8s-node2 : &lt;span class="nv"&gt;ok&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt; &lt;span class="nv"&gt;changed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;28&lt;/span&gt; &lt;span class="nv"&gt;unreachable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;failed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;skipped&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;681&lt;/span&gt; &lt;span class="nv"&gt;rescued&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="nv"&gt;ignored&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="vérification-du-cluster"&gt;Vérification du cluster
&lt;/h3&gt;&lt;p&gt;Une fois le déploiement terminé, il est essentiel de vérifier que le cluster Kubernetes est opérationnel et que tous les composants fonctionnent correctement. Pour cela, plusieurs commandes kubectl permettent de valider l&amp;rsquo;état des nœuds et des pods.&lt;/p&gt;
&lt;p&gt;Pour valider l&amp;rsquo;état des nœuds du cluster :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get nodes
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Résultat obtenu :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;NAME STATUS ROLES AGE VERSION
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8s-master Ready control-plane 1d v1.31.4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8s-node1 Ready worker 1d v1.31.4
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;k8s-node2 Ready worker 1d v1.31.4
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Tous les nœuds doivent être dans l&amp;rsquo;état &lt;em&gt;Ready&lt;/em&gt;, ce qui indique qu&amp;rsquo;ils sont prêts à exécuter des workloads.&lt;/p&gt;
&lt;p&gt;Pour s&amp;rsquo;assurer que tous les pods sont en état de fonctionnement, il faut passer par la commande :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubectl get pods -A
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Résultat obtenu :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;NAMESPACE NAME READY STATUS RESTARTS AGE
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;external-dns external-dns-754bcb687f-cqncz 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ingress-nginx ingress-nginx-controller-6d7ff9f49f-2b9dq 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system coredns-d665d669-7h6nf 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system coredns-d665d669-fklwz 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system dns-autoscaler-5cb4578f5f-zkr67 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kube-apiserver-k8s-master 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kube-controller-manager-k8s-master 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kube-flannel-9n6zs 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kube-flannel-m5xtr 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kube-flannel-qchzm 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kube-proxy-54k4k 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kube-proxy-qpb4m 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kube-proxy-zq7ww 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system kube-scheduler-k8s-master 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system nginx-proxy-k8s-node1 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system nginx-proxy-k8s-node2 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system nodelocaldns-l2nc4 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system nodelocaldns-pp8ng 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kube-system nodelocaldns-x522v 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubernetes-dashboard kubernetes-dashboard-8984b9757-2pcmz 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;kubernetes-dashboard kubernetes-metrics-scraper-6d4c5d99f9-z6g6b 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;metallb-system controller-7f649565d4-6dcd8 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;metallb-system speaker-6pj99 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;metallb-system speaker-v44zd 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;metallb-system speaker-w8wzp 1/1 Running &lt;span class="m"&gt;0&lt;/span&gt; 24h
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Tous les pods doivent être dans l&amp;rsquo;état &lt;code&gt;Running&lt;/code&gt;. Cela garantit que les composants du cluster sont correctement déployés et fonctionnels.&lt;/p&gt;
&lt;p&gt;Ces vérifications permettent de valider que le cluster Kubernetes est prêt à accueillir des workloads et que les services essentiels sont opérationnels.&lt;/p&gt;
&lt;h3 id="résultat"&gt;Résultat
&lt;/h3&gt;&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-cluster-deployment/img/k8sfinal.png"
 alt="Composants du cluster Kubernetes déployé : CoreDNS, MetalLB, Flannel, Nginx Ingress Controller et CRI-O" width="600px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Cluster Kubernetes post déploiement&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="opérations-post-déploiement"&gt;Opérations post-déploiement
&lt;/h2&gt;&lt;h3 id="gestion-du-cycle-de-vie"&gt;Gestion du cycle de vie
&lt;/h3&gt;&lt;p&gt;Kubespray ne se limite pas au déploiement initial, il propose également des playbooks pour gérer le cycle de vie complet du cluster :&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Ajout de nœuds&lt;/strong&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;# Pour ajouter un nouveau worker node au cluster&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ansible-playbook -i inventories/proxmox.yml -b -u &amp;lt;username&amp;gt; kubespray/scale.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Mise à jour du cluster&lt;/strong&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;# Pour mettre à jour la version de Kubernetes ou des composants&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ansible-playbook -i inventories/proxmox.yml -b -u &amp;lt;username&amp;gt; kubespray/upgrade-cluster.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Réinitialisation complète&lt;/strong&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;# Pour supprimer complètement le cluster&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ansible-playbook -i inventories/proxmox.yml -b -u &amp;lt;username&amp;gt; kubespray/reset.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;L’automatisation du déploiement d’un cluster Kubernetes avec ansible via le projet Kubespray permet de gagner en fiabilité, en rapidité et en reproductibilité, ce qui est essentiel pour moi dans mon contexte de homelab.&lt;/p&gt;
&lt;p&gt;Grâce à l’inventaire dynamique et aux variables ansible, il est simple d’adapter la configuration du cluster en fonction de mes besoins, tout en gardant la maîtrise sur chaque composant. Cette approche permet également de faciliter la maintenance et l’évolution du cluster k8s, car tout est fait &lt;em&gt;as code&lt;/em&gt; et que kubespray prévoit également des playbooks pour ajouter un nœud dans le cluster (&lt;code&gt;scale.yml&lt;/code&gt;) ou même remettre le cluster à zéro (&lt;code&gt;reset.yml&lt;/code&gt;).&lt;/p&gt;
&lt;h3 id="workflow"&gt;Workflow
&lt;/h3&gt;&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-cluster-deployment/img/global_workflow.png"
 alt="Workflow en quatre étapes : création des VMs, génération de l&amp;#39;inventaire, configuration des variables et déploiement du cluster avec Ansible et Kubespray" width="800px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Résumé du workflow&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="liens-utiles"&gt;Liens utiles
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://kubespray.io/#/" target="_blank" rel="noopener"
 &gt;Documentation Kubespray&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://docs.ansible.com/ansible/latest/collections/community/general/proxmox_inventory.html" target="_blank" rel="noopener"
 &gt;Documentation Inventaire Ansible pour Proxmox&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://github.com/kubernetes-sigs/kubespray/tree/master/inventory/sample/group_vars" target="_blank" rel="noopener"
 &gt;Exemple de group_vars kubespray&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Template pour VM dans Proxmox</title><link>https://mzinutti.fr/posts/proxmox-packer-template/</link><pubDate>Fri, 28 Feb 2025 10:20:38 +0000</pubDate><guid>https://mzinutti.fr/posts/proxmox-packer-template/</guid><description>&lt;img src="https://mzinutti.fr/posts/proxmox-packer-template/images/sequence_diagram.svg" alt="Featured image of post Template pour VM dans Proxmox" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Dans mon homelab, j&amp;rsquo;ai récemment travaillé sur un projet qui m&amp;rsquo;a permis de créer des templates de machines virtuelles (VM) sur Proxmox en utilisant une combinaison d&amp;rsquo;outils tels que Packer, Cloud-Init et Terraform. L&amp;rsquo;objectif était de pouvoir créer des VMs de manière automatisée et répétable, tout en minimisant les interventions manuelles.&lt;/p&gt;
&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;Gérer les VMs et leurs configurations manuellement peut vite devenir une tâche fastidieuse, surtout lorsqu&amp;rsquo;il s&amp;rsquo;agit de déployer plusieurs instances avec des configurations quasiment identiques à chaque fois. J&amp;rsquo;avais donc besoin d&amp;rsquo;une solution pour standardiser tout ce processus et ainsi avoir un template de VM prêt à l&amp;rsquo;emploi afin de l&amp;rsquo;utiliser comme base dans toutes mes VMs.&lt;/p&gt;
&lt;p&gt;Je vais expliquer dans la suite de cet article comment j&amp;rsquo;ai atteint mon objectif grâce à différents outils dans le but d&amp;rsquo;optimiser les déploiements sur mon homelab.&lt;/p&gt;
&lt;h2 id="composants"&gt;Composants
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Proxmox&lt;/strong&gt; : Fournit l&amp;rsquo;infrastructure pour exécuter et gérer des machines virtuelles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Packer&lt;/strong&gt; : Automatise l&amp;rsquo;installation et la configuration de l&amp;rsquo;OS pour produire une image ou un template standardisé.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Terraform&lt;/strong&gt; : Permet de définir, provisionner et gérer des infrastructures via du code déclaratif.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cloud-Init&lt;/strong&gt; : Permet la configuration dynamique des machines au premier démarrage.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="packer"&gt;Packer
&lt;/h2&gt;&lt;p&gt;Packer est un outil communautaire permettant de créer des images de machines identiques pour plusieurs plateformes à partir d&amp;rsquo;une configuration source unique. Pour ce faire, Packer va utiliser ce qui s&amp;rsquo;appelle des &lt;em&gt;&lt;strong&gt;builders&lt;/strong&gt;&lt;/em&gt; qui sont des composants de Packer capables de créer un template/ une image de machine virtuelle sur une plateforme spécifique.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/proxmox-packer-template/images/packer.png"
 alt="Logo HashiCorp Packer" width="400px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Logo Packer&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Dans mon cas, je vais simplement utiliser Packer sur mon environnement Proxmox grâce à son &lt;a class="link" href="https://developer.hashicorp.com/packer/integrations/hashicorp/Proxmox" target="_blank" rel="noopener"
 &gt;builder dédié&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#packer.pkr.hcl
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;packer&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;required_plugins&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.2.2&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;github.com/hashicorp/Proxmox&amp;#34;&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&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="configuration-du-template"&gt;Configuration du template
&lt;/h3&gt;&lt;p&gt;Ce &lt;em&gt;&lt;strong&gt;builder&lt;/strong&gt;&lt;/em&gt; permet de soit cloner une machine virtuelle pour en faire un template (Proxmox-clone) ou bien de créer un template de machine virtuelle depuis un ISO source (Proxmox-iso). Afin de répondre à mon besoin initial, je vais utiliser le builder Proxmox-iso. Pour ce qui est de la configuration, j&amp;rsquo;ai utilisé la &lt;a class="link" href="https://developer.hashicorp.com/packer/integrations/hashicorp/Proxmox/latest/components/builder/iso" target="_blank" rel="noopener"
 &gt;documentation&lt;/a&gt; du &lt;em&gt;&lt;strong&gt;builder&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemple de fichier packer.pkr.hcl :&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;span class="lnt"&gt;52
&lt;/span&gt;&lt;span class="lnt"&gt;53
&lt;/span&gt;&lt;span class="lnt"&gt;54
&lt;/span&gt;&lt;span class="lnt"&gt;55
&lt;/span&gt;&lt;span class="lnt"&gt;56
&lt;/span&gt;&lt;span class="lnt"&gt;57
&lt;/span&gt;&lt;span class="lnt"&gt;58
&lt;/span&gt;&lt;span class="lnt"&gt;59
&lt;/span&gt;&lt;span class="lnt"&gt;60
&lt;/span&gt;&lt;span class="lnt"&gt;61
&lt;/span&gt;&lt;span class="lnt"&gt;62
&lt;/span&gt;&lt;span class="lnt"&gt;63
&lt;/span&gt;&lt;span class="lnt"&gt;64
&lt;/span&gt;&lt;span class="lnt"&gt;65
&lt;/span&gt;&lt;span class="lnt"&gt;66
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#packer.pkr.hcl
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;source&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Proxmox-iso&amp;#34; &amp;#34;debian12&amp;#34;&lt;/span&gt; {&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Proxmox server
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; Proxmox_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;proxmox_api_url&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; node&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pve&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;Proxmox_token_id&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;lt;Proxmox_token&amp;gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; insecure_skip_tls_verify&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # VM Info
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; template_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pckr-debian-12&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vm_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pckr-debian-12&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; template_description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Debian 12.9.0, generated on ${timestamp()}&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;template;debian12&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;TEMPLATE&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; os&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;l26&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; machine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;q35&amp;#34;&lt;/span&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Boot
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;boot_iso&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;scsi&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; iso_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local:iso/debian-12.9.0-amd64-netinst.iso&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; unmount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; iso_checksum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;none&amp;#34;&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="n"&gt; boot_command&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt; [&amp;#34;&amp;lt;esc&amp;gt;&amp;lt;wait&amp;gt;auto console-keymaps-at/keymap&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;fr console-setup/ask_detect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="n"&gt; debconf/frontend&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;noninteractive fb&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="n"&gt; url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;http&lt;/span&gt;&lt;span class="err"&gt;://&lt;/span&gt;{{ &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;HTTPIP&lt;/span&gt; }}&lt;span class="err"&gt;:&lt;/span&gt;{{ &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;HTTPPort&lt;/span&gt; }}&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="k"&gt;preseed&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;cfg&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;enter&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; boot_wait&lt;/span&gt; 	 &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;10s&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; http_directory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;http&amp;#34;&lt;/span&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Disks
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;disks&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;scsi&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; storage_pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local-lvm&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; disk_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;30G&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; format&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;raw&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Networks
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;network_adapters&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; bridge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vmbr0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;virtio&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; }&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Spec
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; bios&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;seabios&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; qemu_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; scsi_controller&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;virtio-scsi-pci&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cpu_type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86-64-v2-AES&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; sockets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; memory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ballooning_minimum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Communication
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; communicator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ssh&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ssh_handshake_attempts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ssh_pty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ssh_timeout&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;10m&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ssh_username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;username&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; ssh_password&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;password&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt; # Cloud-init
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cloud_init&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt; cloud_init_storage_pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local-lvm&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Dans ce fichier &lt;code&gt;packer.pkr.hcl&lt;/code&gt;, je donne au &lt;em&gt;&lt;strong&gt;builder&lt;/strong&gt;&lt;/em&gt; les informations à propos de la machine cible ainsi que les spécifications de celle-ci pour Proxmox, mais également les informations à propos du boot (chemin du fichier ISO, commande lors du boot, etc).&lt;/p&gt;
&lt;h4 id="fichier-preseed"&gt;Fichier preseed
&lt;/h4&gt;&lt;p&gt;La partie boot va permettre de donner à la machine virtuelle, futur template, le chemin du fichier preseed.cfg auto-hébergé sur un serveur web lancé par Packer lors du processus. Ce fichier permet de répondre aux questions posées au cours du processus d&amp;rsquo;installation de l&amp;rsquo;OS, sans avoir à saisir manuellement les réponses pendant l&amp;rsquo;installation, cela permet d&amp;rsquo;automatiser entièrement l&amp;rsquo;installation. Un exemple de ce fichier est disponible sur la &lt;a class="link" href="https://www.debian.org/releases/stable/example-preseed.txt" target="_blank" rel="noopener"
 &gt;documentation&lt;/a&gt; debian.&lt;/p&gt;
&lt;h3 id="création-du-template"&gt;Création du template
&lt;/h3&gt;&lt;p&gt;Une fois la configuration prête, il faut lancer la construction du template via la CLI de Packer avec les commandes ci-dessous :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;# Initialisation du dépôt pour récupérer les dépendances&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;packer init packer.pkr.hcl
&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;# Validation de la syntaxe et configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;packer validate packer.pkr.hcl
&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;# Construction de l&amp;#39;image, en remplaçant le template si déjà existant&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;packer build -on-error&lt;span class="o"&gt;=&lt;/span&gt;ask -force packer.pkr.hcl
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Le template est désormais bien présent dans Proxmox avec toutes les caractéristiques que j&amp;rsquo;ai indiquées dans le fichier &lt;code&gt;packer.pkr.hcl&lt;/code&gt; et il est désormais prêt à être utilisé pour de futurs déploiements de machine virtuelle.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/proxmox-packer-template/images/proxmox.png"
 alt="Template 1101 pckr-debian-12 dans Proxmox avec les tags debian12 et template, 1 vCPU, 1 GiB de mémoire et un disque de 30 GiB" width="800px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Template dans Proxmox&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Maintenant passons à l&amp;rsquo;étape suivante : la configuration dynamique avec Cloud-Init !&lt;/p&gt;
&lt;h2 id="cloud-init"&gt;Cloud-init
&lt;/h2&gt;&lt;p&gt;Cloud-Init est un outil open-source permettant d&amp;rsquo;automatiser la configuration initiale des machines virtuelles lors de leur premier démarrage. En utilisant des fichiers de configuration (user-data et meta-data), Cloud-Init peut effectuer diverses tâches comme la création d&amp;rsquo;utilisateurs, l&amp;rsquo;injection de clés SSH, la configuration réseau ou encore l&amp;rsquo;installation de paquets. Cet outil est compatible avec de nombreuses plateformes cloud et environnements de virtualisation, offrant ainsi une méthode agnostique pour personnaliser dynamiquement les instances sans intervention manuelle.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/proxmox-packer-template/images/cloudinit.svg"
 alt="Logo Cloud-Init" width="500px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Logo Cloud-Init&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;&lt;strong&gt;Exemple de fichier user-data :&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;#user-data.yml&lt;/span&gt;&lt;span class="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;#cloud-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="nt"&gt;ssh_pwauth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;disable_root&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="nt"&gt;users&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;user&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;gecos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;user&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;groups&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;users, sudo&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;shell&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;/bin/bash&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ssh_authorized_keys&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;ssh-ed25519 AAAA.......&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sudo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ALL=(ALL) NOPASSWD:ALL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;lock_passwd&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="nt"&gt;package_update&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="nt"&gt;package_upgrade&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;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Dans mon cas, j&amp;rsquo;ai choisi d&amp;rsquo;utiliser cloud-init pour me permettre de :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Désactiver l&amp;rsquo;authentification par mot de passe via SSH et également le compte root.&lt;/li&gt;
&lt;li&gt;Créer un utilisateur avec une clé SSH autorisée, un accès sudo sans mot de passe et un mot de passe verrouillé.&lt;/li&gt;
&lt;li&gt;Effectuer automatiquement les mises à jour et les montées de version des paquets.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une fois appliqué au premier démarrage de la machine, celle-ci est prête à l&amp;rsquo;emploi, et aussi prête à recevoir du provisionnement via un outil comme Ansible. C&amp;rsquo;était l&amp;rsquo;objectif recherché de mon côté, car je souhaitais pouvoir lancer mes playbooks Ansible via une connexion par clé SSH une fois la machine virtuelle déployée sans avoir à copier manuellement ma clé ou bien devoir me connecter une première fois via un mot de passe.&lt;/p&gt;
&lt;h2 id="terraform"&gt;Terraform
&lt;/h2&gt;&lt;p&gt;Afin de pouvoir déployer ma machine virtuelle grâce à mon template et au fichier cloud-init, j&amp;rsquo;ai fait le choix d&amp;rsquo;utiliser terraform. Terraform est un outil d&amp;rsquo;&lt;em&gt;&lt;strong&gt;infrastructure as code&lt;/strong&gt;&lt;/em&gt;, c&amp;rsquo;est-à-dire un outil qui permet de définir son infrastructure via des fichiers de configuration déclaratifs au format HCL (&lt;em&gt;&lt;strong&gt;Hashicorp Configuration Language&lt;/strong&gt;&lt;/em&gt;). Pour réaliser ceci, Terraform se repose sur des &lt;em&gt;&lt;strong&gt;providers&lt;/strong&gt;&lt;/em&gt; qui vont permettre la communication avec l&amp;rsquo;infrastructure sous-jacente. Cette méthode permet de gérer l&amp;rsquo;infrastructure comme du code, ce qui simplifie la gestion des versions et la reproductibilité des déploiements.&lt;/p&gt;
&lt;p&gt;Ces &lt;em&gt;&lt;strong&gt;plugins&lt;/strong&gt;&lt;/em&gt; mettent à disposition des types de ressources (créer/modifier des ressources) et des sources de données (lecture d&amp;rsquo;informations existantes) qui permettent à Terraform de manager l&amp;rsquo;infrastructure derrière ce plugin. Les &lt;em&gt;&lt;strong&gt;providers&lt;/strong&gt;&lt;/em&gt; passent par le SDK ou bien l&amp;rsquo;API du fournisseur afin de réaliser toutes les actions indiquées par la configuration Terraform.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/proxmox-packer-template/images/terraform.png"
 alt="Logo HashiCorp Terraform" width="500px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Logo Terraform&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Dans le cas de mon homelab, j&amp;rsquo;ai fait le choix d&amp;rsquo;utiliser le &lt;em&gt;&lt;strong&gt;provider&lt;/strong&gt;&lt;/em&gt; &lt;a class="link" href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs" target="_blank" rel="noopener"
 &gt;bpg&lt;/a&gt;, qui est un &lt;em&gt;&lt;strong&gt;provider&lt;/strong&gt;&lt;/em&gt; réalisé par un membre de la communauté et qui permet de gérer tout son environnement Proxmox (resource pool, users, VM, ISO).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#providers.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;provider&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;proxmox&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;endpoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;endpoint&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;api_token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;api_token&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;insecure&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;ssh&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;root&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;private_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;private_key&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Pour le déploiement de la machine virtuelle en utilisant le template et cloud-init pour le provisionnement, j&amp;rsquo;ai utilisé la ressource &lt;a class="link" href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_vm" target="_blank" rel="noopener"
 &gt;proxmox_virtual_environment_vm&lt;/a&gt;, qui permet de créer/gérer une machine virtuelle en lui passant toutes les caractéristiques nécessaires.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Exemple de ressource terraform pour une machine virtuelle dans Proxmox :&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;#virtual_machine.tf
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;proxmox_virtual_environment_vm&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;example_vm&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;example&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;example VM&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;debian12&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;terraform&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;node_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pve&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pool_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;example&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;reboot_after_update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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="nx"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cpu&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cores&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;sockets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;x86-64-v2-AES&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;memory&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;dedicated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;floating&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;initialization&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;user_data_file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;proxmox_virtual_environment_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_data_cloud_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;dns&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;servers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;9.9.9.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.1.1.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;ip_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;ipv4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;192.168.1.101/24&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;192.168.1.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;clone&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;datastore_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local-lvm&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;node_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pve&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Mais dans mon cas les paramètres qui m&amp;rsquo;intéressent sont :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_vm#initialization-1" target="_blank" rel="noopener"
 &gt;initialization&lt;/a&gt; : Configuration pour cloud-init où je spécifie la configuration DNS, l&amp;rsquo;adresse IP de ma machine et je donne l&amp;rsquo;ID de mon fichier cloud-init également managé via terraform via la ressource &lt;a class="link" href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_file" target="_blank" rel="noopener"
 &gt;proxmox_virtual_environment_file&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;initialization&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;user_data_file_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;proxmox_virtual_environment_file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user_data_cloud_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;dns&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;servers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;9.9.9.9&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;1.1.1.1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;ip_config&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;ipv4&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;192.168.1.101/24&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;gateway&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;192.168.1.1&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://registry.terraform.io/providers/bpg/proxmox/latest/docs/resources/virtual_environment_vm#clone-1" target="_blank" rel="noopener"
 &gt;clone&lt;/a&gt; : Configuration du clonage où je donne les informations à propos de mon template présent dans Proxmox.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;clone&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;vm_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;datastore_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;local-lvm&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;full&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;node_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;pve&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Je peux déclencher mon projet qui contient mon &lt;em&gt;&lt;strong&gt;Infrastructure as Code&lt;/strong&gt;&lt;/em&gt;, en y ajoutant la nouvelle ressource. Et terraform va s&amp;rsquo;occuper de cloner le template et de lui fournir la configuration cloud-init afin de provisionner la machine virtuelle.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai donc comme résultat une VM avec une configuration standard que j&amp;rsquo;ai définie pour correspondre à mon besoin et qui est prête à recevoir du provisionnement avec un outil comme Ansible.&lt;/p&gt;
&lt;h2 id="diagramme-de-séquence"&gt;Diagramme de séquence
&lt;/h2&gt;&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/proxmox-packer-template/images/sequence_diagram.svg"
 alt="Diagramme de séquence : Packer crée le template depuis l&amp;#39;ISO dans Proxmox, puis Terraform clone le template et cloud-init provisionne la VM" width="800px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Diagramme de séquence&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;En conclusion, la mise en place d&amp;rsquo;un processus automatisé pour la création et le déploiement de machines virtuelles sur Proxmox via Packer, Cloud-Init et Terraform m&amp;rsquo;a permis de gagner en efficacité, en reproductibilité et en flexibilité dans mon homelab.&lt;/p&gt;
&lt;p&gt;Ce workflow garantit une standardisation des environnements, réduit le risque d&amp;rsquo;erreurs manuelles et facilite le provisionnement des machines avec Ansible ou d&amp;rsquo;autres outils d&amp;rsquo;automatisation. Grâce à cette solution, je peux désormais déployer rapidement des machines virtuelles prêtes à l&amp;rsquo;emploi, tout en adaptant leur configuration selon mes besoins.&lt;/p&gt;</description></item><item><title>Secret Kubernetes depuis Azure Key Vault</title><link>https://mzinutti.fr/posts/k8s-eso-azvault/</link><pubDate>Sat, 23 Nov 2024 10:20:38 +0000</pubDate><guid>https://mzinutti.fr/posts/k8s-eso-azvault/</guid><description>&lt;img src="https://mzinutti.fr/posts/k8s-eso-azvault/images/cover.png" alt="Featured image of post Secret Kubernetes depuis Azure Key Vault" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;La gestion sécurisée des secrets dans Kubernetes est cruciale pour maintenir la sécurité dans nos applications. Dans cet article, je vais donc présenter la gestion des secrets dans Kubernetes en utilisant &lt;a class="link" href="https://external-secrets.io/latest/" target="_blank" rel="noopener"
 &gt;&lt;em&gt;&lt;strong&gt;External Secrets Operator&lt;/strong&gt;&lt;/em&gt;&lt;/a&gt; et &lt;a class="link" href="https://azure.microsoft.com/en-us/products/key-vault/" target="_blank" rel="noopener"
 &gt;&lt;em&gt;&lt;strong&gt;Azure Key Vault&lt;/strong&gt;&lt;/em&gt;&lt;/a&gt; via une authentification par &lt;a class="link" href="https://azure.github.io/azure-workload-identity/docs/introduction.html" target="_blank" rel="noopener"
 &gt;&lt;em&gt;&lt;strong&gt;workload identity&lt;/strong&gt;&lt;/em&gt;&lt;/a&gt;. Ceci dans le but d&amp;rsquo;avoir une solution robuste et sécurisée.&lt;/p&gt;
&lt;h2 id="contexte"&gt;Contexte
&lt;/h2&gt;&lt;p&gt;J&amp;rsquo;ai mis en place cette solution dans le cadre d&amp;rsquo;un projet d&amp;rsquo;une semaine durant mon master. Le but était le déploiement d&amp;rsquo;une stack applicative dans le cloud via un workflow GitOps. Le projet est disponible sur &lt;a class="link" href="https://gitlab.com/projet-cicd-m2-do" target="_blank" rel="noopener"
 &gt;le groupe GitLab projet-cicd-m2-do&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Dans ce projet, un cluster &lt;strong&gt;Azure Kubernetes Service (AKS)&lt;/strong&gt; était déployé via terraform avec la configuration suivante requise pour mettre en place la solution :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;oidc_issuer_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;⬆️ Permet d&amp;rsquo;activer l&amp;rsquo;émetteur OIDC (OpenID Connect) du cluster AKS. Cela signifie que le cluster AKS génère une URL d&amp;rsquo;émission de token OIDC, essentielle pour la relation de confiance que je vous présente plus tard dans cet article.&lt;/p&gt;
&lt;blockquote class="alert alert-note"&gt;
 &lt;div class="alert-header"&gt;
 &lt;span class="alert-icon"&gt;📝&lt;/span&gt;
 &lt;span class="alert-title"&gt;Note&lt;/span&gt;
 &lt;/div&gt;
 &lt;div class="alert-body"&gt;
 &lt;p&gt;L&amp;rsquo;OIDC est un protocole d&amp;rsquo;authentification moderne qui repose sur le standard OAuth 2.0. Il permet à des applications de vérifier l&amp;rsquo;identité d&amp;rsquo;un utilisateur en s&amp;rsquo;appuyant sur un fournisseur d&amp;rsquo;identité et de récupérer des informations sur cet utilisateur de manière sécurisée.&lt;/p&gt;
 &lt;/div&gt;
 &lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;workload_identity_enabled&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;⬆️
Active la fonctionnalité Azure Workload Identity, pour permettre aux pods Kubernetes d&amp;rsquo;accéder aux ressources Azure en utilisant les identités managées.&lt;/p&gt;
&lt;h2 id="composants"&gt;Composants
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;em&gt;External Secret Operator&lt;/em&gt; (ESO) : Opérateur Kubernetes qui permet d&amp;rsquo;intégrer un système de gestion des secrets externes.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Azure Key Vault&lt;/em&gt; : Service cloud Azure permettant de stocker les secrets et d’y accéder en toute sécurité.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Workload Identity&lt;/em&gt; : Méthode pour s&amp;rsquo;authentifier à une ressource Azure sans explicitement configurer des identifiants&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="azure-key-vault"&gt;Azure Key Vault
&lt;/h2&gt;&lt;p&gt;Dès que le cluster AKS est opérationnel, il faut déployer la ressource &lt;em&gt;&lt;strong&gt;Azure Key Vault&lt;/strong&gt;&lt;/em&gt;. Pour ce faire, j&amp;rsquo;ai utilisé terraform afin de pouvoir faire de l&amp;rsquo;IaC pour fiabiliser et automatiser mon déploiement. Pour le &lt;em&gt;&lt;strong&gt;vault&lt;/strong&gt;&lt;/em&gt; ainsi que les autres ressources Azure, j&amp;rsquo;ai utilisé le provider terraform &lt;a class="link" href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs" target="_blank" rel="noopener"
 &gt;azurerm&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Le code suivant va permettre la création du vault dans le &lt;em&gt;&lt;strong&gt;resource group&lt;/strong&gt;&lt;/em&gt; et la &lt;em&gt;&lt;strong&gt;location&lt;/strong&gt;&lt;/em&gt; définis, avec le &lt;em&gt;&lt;strong&gt;RBAC (Role Based Access Control)&lt;/strong&gt;&lt;/em&gt; activé pour manager les accès et le SKU (Stock Keeping Unit) sur standard. D&amp;rsquo;autres arguments sont disponibles, mais j&amp;rsquo;ai préféré simplifier au vu de la durée du projet afin d&amp;rsquo;avoir un POC rapidement.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-tf" data-lang="tf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azurerm_key_vault&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vault&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;join&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="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;org&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;vault&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;location&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tenant_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_client_config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;current&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tenant_id&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="nx"&gt;sku_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;standard&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enable_rbac_authorization&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Une fois le vault déployé, on peut mettre ses secrets dedans via l&amp;rsquo;UI ou la CLI d&amp;rsquo;azure.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-eso-azvault/images/vault.png"
 alt="Liste des secrets MySQL et PostgreSQL stockés dans le Key Vault Azure ocf-core-sdv-vault-test" width="800px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Azure Vault UI&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="managed-identity"&gt;Managed Identity
&lt;/h2&gt;&lt;p&gt;Une fois le &lt;em&gt;&lt;strong&gt;vault&lt;/strong&gt;&lt;/em&gt; déployé, il va falloir associer le rôle à la bonne identité afin de pouvoir accéder à nos secrets au travers de celle-ci.&lt;/p&gt;
&lt;h3 id="kubelet-identity"&gt;Kubelet Identity
&lt;/h3&gt;&lt;p&gt;Lors de la création d&amp;rsquo;un cluster AKS, une &lt;em&gt;&lt;strong&gt;managed identity&lt;/strong&gt;&lt;/em&gt; de type &lt;em&gt;&lt;strong&gt;user assigned&lt;/strong&gt;&lt;/em&gt; va être créée dans le &lt;em&gt;&lt;strong&gt;resource group&lt;/strong&gt;&lt;/em&gt;, celui-ci est géré automatiquement par Azure et permet de stocker les ressources infrastructures réelles nécessaires au fonctionnement du cluster AKS. Cette identité managée se nomme &lt;em&gt;&lt;strong&gt;kubelet_identity&lt;/strong&gt;&lt;/em&gt; et elle va permettre au kubelet présent sur les nœuds du cluster d&amp;rsquo;accéder à d&amp;rsquo;autres ressources Azure. C&amp;rsquo;est au travers de cette identité que je vais pouvoir accéder au vault.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-eso-azvault/images/kubelet_identity.png"
 alt="Schéma de la Kubelet Identity du cluster AKS qui s&amp;#39;authentifie auprès de Microsoft Entra ID pour accéder à un service Azure" width="600px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Interaction entre Kubelet Identity et les ressources Azure&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;J&amp;rsquo;ai assigné à cette identité les droits de lecture sur les secrets présents dans le vault grâce au rôle &lt;code&gt;Key Vault Secrets User&lt;/code&gt; :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azurerm_role_assignment&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;keyvault_secrets_user&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;scope&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_key_vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;vault&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;role_definition_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Key Vault Secrets User&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;principal_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubelet_identity&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;object_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="fédération-didentité"&gt;Fédération d&amp;rsquo;identité
&lt;/h3&gt;&lt;p&gt;Lorsque les bons droits sont assignés, il va falloir lier cette identité à un &lt;em&gt;&lt;strong&gt;service account&lt;/strong&gt;&lt;/em&gt; dans Kubernetes afin de pouvoir accéder aux données de mon vault dans Azure.&lt;/p&gt;
&lt;p&gt;Pour ce faire, il faut utiliser une &lt;em&gt;&lt;strong&gt;federated identity credential&lt;/strong&gt;&lt;/em&gt; qui va permettre de déléguer l&amp;rsquo;authentification à un fournisseur d&amp;rsquo;identité externe dans mon cas le cluster AKS et ainsi créer une &lt;strong&gt;relation de confiance&lt;/strong&gt; entre AKS et l&amp;rsquo;identité managée dans Microsoft Entra ID.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;azurerm_federated_identity_credential&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;identity&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;kub-federated-identity&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;resource_group_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;node_resource_group&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;audience&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;api://AzureADTokenExchange&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;issuer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;oidc_issuer_url&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;parent_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kubelet_identity&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;user_assigned_identity_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;system:serviceaccount:default:workload-identity-sa&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ici l&amp;rsquo;&lt;em&gt;&lt;strong&gt;audience&lt;/strong&gt;&lt;/em&gt; représente le destinataire prévu du jeton émis par l&amp;rsquo;émetteur de l&amp;rsquo;OIDC, le &lt;em&gt;&lt;strong&gt;subject&lt;/strong&gt;&lt;/em&gt; correspond à l&amp;rsquo;identité de l&amp;rsquo;entité (le service account dans mon cas) à laquelle l&amp;rsquo;identité fédérée est associée et &lt;em&gt;&lt;strong&gt;issuer&lt;/strong&gt;&lt;/em&gt; est l&amp;rsquo;URL d&amp;rsquo;émetteur OIDC du cluster AKS.&lt;/p&gt;
&lt;h3 id="diagramme-de-séquence"&gt;Diagramme de séquence
&lt;/h3&gt;&lt;p&gt;Voici un diagramme un peu plus précis sur le mécanisme de connexion au Vault via identité managée et fédération :&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-eso-azvault/images/sequence_diagram.png"
 alt="Diagramme de séquence : le pod ESO échange le token OIDC de son service account contre un token Azure via la fédération d&amp;#39;identité, puis lit le secret dans Azure Key Vault et crée le secret Kubernetes" width="800px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Diagramme de séquence pour la connexion au Vault&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="external-secrets-operator"&gt;External Secrets Operator
&lt;/h2&gt;&lt;p&gt;Désormais, il faut mettre en place l&amp;rsquo;opérateur Kubernetes, &lt;em&gt;&lt;strong&gt;External Secrets Operator (ESO)&lt;/strong&gt;&lt;/em&gt; afin de pouvoir effectuer la connexion au vault via le provider Azure Key Vault fourni par ESO. Pour ce faire, des &lt;em&gt;&lt;strong&gt;Custom Resource Definitions (CRD)&lt;/strong&gt;&lt;/em&gt; existent et permettent de définir comment accéder au vault (Secret Store) et quelles sont les données à récupérer (External Secret).&lt;/p&gt;
&lt;h3 id="secret-store"&gt;Secret Store
&lt;/h3&gt;&lt;p&gt;Dans cette ressource, il faut spécifier l&amp;rsquo;URL du vault, l&amp;rsquo;id du tenant Azure, le type d&amp;rsquo;authentification et le &lt;em&gt;&lt;strong&gt;service account&lt;/strong&gt;&lt;/em&gt; associé. C&amp;rsquo;est avec le &lt;em&gt;&lt;strong&gt;secret store&lt;/strong&gt;&lt;/em&gt; que je vais pouvoir me connecter à Azure Key Vault.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;external-secrets.io/v1beta1&lt;/span&gt;&lt;span class="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;SecretStore&lt;/span&gt;&lt;span class="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;azure-secret-store&lt;/span&gt;&lt;span class="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;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;azurekv&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;authType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;WorkloadIdentity&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;vaultUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${azurerm_key_vault.vault.vault_uri}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;tenantId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;${azurerm_key_vault.vault.tenant_id}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;serviceAccountRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;workload-identity-sa&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="external-secret"&gt;External Secret
&lt;/h3&gt;&lt;p&gt;Cette ressource va permettre de définir quel secret j&amp;rsquo;ai besoin de synchroniser entre le vault et le secret Kubernetes. Il faut définir le &lt;em&gt;&lt;strong&gt;secret store&lt;/strong&gt;&lt;/em&gt; à utiliser, notre secret cible dans Kubernetes et les secrets à récupérer depuis le vault Azure.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;external-secrets.io/v1beta1&lt;/span&gt;&lt;span class="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;ExternalSecret&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;my-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;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;refreshInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secretStoreRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;azure-secret-store&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;SecretStore&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;target&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;ocf-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;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;secretKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;pgadmin-password&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;remoteRef&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;pgadmin-password&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="service-account"&gt;Service Account
&lt;/h3&gt;&lt;p&gt;Il faut également créer le &lt;em&gt;&lt;strong&gt;service account&lt;/strong&gt;&lt;/em&gt; en lui ajoutant l&amp;rsquo;annotation suivante afin de l&amp;rsquo;associer à l&amp;rsquo;ID de l&amp;rsquo;identité : Kubelet Identity.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;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;annotations&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;azure.workload.identity/client-id&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;${azurerm_kubernetes_cluster.aks.kubelet_identity[0].client_id}&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;workload-identity-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;default&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="déploiement"&gt;Déploiement
&lt;/h2&gt;&lt;p&gt;Pour la partie déploiement, je suis aussi passé par du terraform en utilisant le provider &lt;a class="link" href="https://registry.terraform.io/providers/hashicorp/helm/latest/docs" target="_blank" rel="noopener"
 &gt;helm&lt;/a&gt; et &lt;a class="link" href="https://registry.terraform.io/providers/gavinbunney/kubectl/latest/docs" target="_blank" rel="noopener"
 &gt;kubectl&lt;/a&gt;. Ceci permet d&amp;rsquo;avoir une infrastructure unifiée et automatisée de bout en bout.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-eso-azvault/images/deployment.png"
 alt="Ressources déployées par Terraform : cluster AKS, Azure Key Vault, role assignment, identité fédérée, chart Helm et manifests Kubernetes" width="700px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Déploiement avec terraform&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="eso"&gt;ESO
&lt;/h3&gt;&lt;p&gt;Pour ESO, une chart helm est fournie dans la &lt;a class="link" href="https://external-secrets.io/latest/introduction/getting-started/" target="_blank" rel="noopener"
 &gt;documentation&lt;/a&gt;, celle-ci est déployée dans le namespace &lt;code&gt;external-secrets&lt;/code&gt;. Cette ressource terraform attend la fin du déploiement du cluster AKS pour être créée grâce au &lt;code&gt;depends_on = [azurerm_kubernetes_cluster.aks]&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;helm_release&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;eso&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;azurerm_kubernetes_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aks&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;external-secrets&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;external-secrets&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;repository&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://charts.external-secrets.io&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;chart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;external-secrets&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;create_namespace&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.10.5&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-eso-azvault/images/eso-pods.png"
 alt="Les trois pods External Secrets Operator à l&amp;#39;état Running : external-secrets, cert-controller et webhook" width="700px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Déploiement des pods ESO&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Une fois ESO déployé, il faut créer nos CRD afin de configurer la connexion au vault et les secrets à récupérer mais également le &lt;em&gt;&lt;strong&gt;service account&lt;/strong&gt;&lt;/em&gt;. Tout ceci à l&amp;rsquo;aide de manifests Kubernetes qui permettent de déclarer les ressources en yaml.&lt;/p&gt;
&lt;h3 id="crd-et-service-account"&gt;CRD et Service Account
&lt;/h3&gt;&lt;p&gt;Pour les manifests, l&amp;rsquo;utilisation du provider terraform kubectl va me permettre d&amp;rsquo;utiliser des variables des ressources terraform directement dans les manifests pour avoir quelque chose de dynamique, comme on peut voir dans ce &lt;a class="link" href="#secret-store" &gt;manifest&lt;/a&gt;. Ceci évite alors de mettre des informations sensibles en dur dans les manifests.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-terraform" data-lang="terraform"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;kubectl_manifest&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;sa&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;helm_release&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eso&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;yaml_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;YAML&lt;/span&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;apiVersion: v1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: ServiceAccount
&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="o"&gt;YAML&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;kubectl_manifest&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;secretstore&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;kubectl_manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sa&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;yaml_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;YAML&lt;/span&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;apiVersion: external-secrets.io/v1beta1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: SecretStore
&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="o"&gt;YAML&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;kubectl_manifest&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;externalsecret&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;kubectl_manifest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;secretstore&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;yaml_body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;YAML&lt;/span&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;apiVersion: external-secrets.io/v1beta1
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;kind: ExternalSecret
&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="o"&gt;YAML&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Les 3 ressources terraform sont déclarées, elles correspondent aux 3 manifests / ressources Kubernetes qu&amp;rsquo;il faut créer dans le cluster pour configurer et utiliser ESO.&lt;/p&gt;
&lt;h2 id="résultat"&gt;Résultat
&lt;/h2&gt;&lt;p&gt;La connexion à Azure Key Vault est validée. Ceci grâce à la ressource &lt;em&gt;&lt;strong&gt;secret-store&lt;/strong&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-eso-azvault/images/ss.png"
 alt="Description du SecretStore azure-secret-store avec l&amp;#39;authentification WorkloadIdentity et le statut Valid" width="500px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Connexion à Azure Key Vault avec le secret store&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Une fois connecté au vault, la synchronisation des secrets va s&amp;rsquo;effectuer. On peut le voir sur la ressource &lt;em&gt;&lt;strong&gt;external-secret&lt;/strong&gt;&lt;/em&gt; qui est dans l&amp;rsquo;état &lt;code&gt;SecretSynced&lt;/code&gt;. Ceci va donc récupérer les secrets du vault et les mettre dans un secret Kubernetes.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-eso-azvault/images/es.png"
 alt="Statut de l&amp;#39;ExternalSecret avec la condition Ready à True et la raison SecretSynced" width="600px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Synchronisation des secrets entre le vault et Kubernetes&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;Le secret a bien été créé et il est bien présent dans Kubernetes comme on peut le voir. Pour cette démonstration les &lt;em&gt;&lt;strong&gt;passwords&lt;/strong&gt;&lt;/em&gt; sont tous les mêmes, ce qui est bien sûr à ne pas faire dans un environnement de production.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/k8s-eso-azvault/images/secret.png"
 alt="Secret Kubernetes contenant les mots de passe MySQL, PostgreSQL et pgAdmin synchronisés depuis le vault" width="400px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Secret Kubernetes&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;La mise en place de cette solution sur un temps restreint a été un réel challenge pour moi, car je n&amp;rsquo;avais jamais réalisé ceci auparavant. J&amp;rsquo;ai pu travailler et approfondir mes compétences sur Azure et plus spécifiquement les rôles et identités de ce cloud provider.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;objectif a été atteint, et la complexité de la solution m&amp;rsquo;a motivé à rédiger cet article, premièrement afin d&amp;rsquo;avoir une trace de cette réalisation en faisant un retour d&amp;rsquo;expérience, mais également dans le but de pouvoir aider les personnes qui tomberont tôt ou tard dessus.&lt;/p&gt;</description></item><item><title>Veille Technologique</title><link>https://mzinutti.fr/posts/veille/</link><pubDate>Fri, 01 Nov 2024 22:56:48 +0000</pubDate><guid>https://mzinutti.fr/posts/veille/</guid><description>&lt;img src="https://mzinutti.fr/posts/veille/cover.png" alt="Featured image of post Veille Technologique" /&gt;&lt;h2 id="limportance-de-la-veille-technologique"&gt;L&amp;rsquo;importance de la veille technologique
&lt;/h2&gt;&lt;p&gt;En tant que DevOps, la veille technologique est une pratique cruciale, elle va permettre de se maintenir à jour et de surveiller de manière constante l&amp;rsquo;apparition de nouveaux outils dans un domaine qui est en constante évolution.&lt;/p&gt;
&lt;p&gt;Ceci pour différentes raisons, premièrement en tant que DevOps, il faut constamment trouver des solutions permettant d&amp;rsquo;améliorer les pipelines CI/CD et les différents process en place en entreprise, la recherche de nouveaux outils, nouvelles versions ou nouvelles technologies est donc primordiale.&lt;/p&gt;
&lt;p&gt;Ensuite, dans mon cas, cette veille va me permettre d&amp;rsquo;être force de proposition et de devenir un élément moteur dans mon environnement en apportant ma connaissance sur des sujets récents.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="outils-pour-la-veille-technologique"&gt;Outils pour la veille technologique
&lt;/h2&gt;&lt;p&gt;Les outils utilisés pour la veille sont le nerf de la guerre, ce sont eux qui permettent une gestion rapide et efficace de cette veille. Il faut donc réévaluer périodiquement ces outils afin qu&amp;rsquo;ils ne deviennent pas obsolètes et qu&amp;rsquo;ils conviennent toujours au besoin.&lt;/p&gt;
&lt;p&gt;Il est de ce fait nécessaire de faire une veille sur ses outils de veille !&lt;/p&gt;
&lt;h3 id="feeder"&gt;Feeder
&lt;/h3&gt;&lt;p&gt;Après avoir passé quelque temps à utiliser feedly quotidiennement, je n&amp;rsquo;étais pas convaincu par l&amp;rsquo;application, j&amp;rsquo;avais besoin d&amp;rsquo;un outil plus simple et plus épuré.&lt;/p&gt;
&lt;p&gt;Suite à des recherches, je suis tombé sur &lt;a class="link" href="https://github.com/spacecowboy/Feeder" target="_blank" rel="noopener"
 &gt;Feeder&lt;/a&gt;, un lecteur/agrégateur de flux RSS open source disponible sur Android. L&amp;rsquo;application ne nécessite pas de connexion via un compte et elle tourne localement ce qui permet d&amp;rsquo;avoir de la confidentialité sur mes données.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Logo Feeder" class="gallery-image" data-flex-basis="243px" data-flex-grow="101" height="260" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://mzinutti.fr/posts/veille/feeder.png" width="264"&gt;&lt;/p&gt;
&lt;p&gt;Niveau design l&amp;rsquo;application est très simpliste et épuré et ceci se confirme aussi dans sa configuration qui possède les mêmes atouts. Feeder permet également de sauvegarder des articles, les ajouter en favoris ou même d&amp;rsquo;effectuer des backups de sa configuration. Ce sont des fonctionnalités très pratiques et nécessaires à un outil de ce type.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/veille/feeder_view.png"
 alt="Liste d&amp;#39;articles de la catégorie IT dans l&amp;#39;application Feeder sur Android" width="400px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Interface Feeder&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;

&lt;p&gt;Feeder me permet donc de récupérer les articles de mes principaux sites d&amp;rsquo;IT cités dans la partie suivante et de pouvoir les rendre accessibles depuis l&amp;rsquo;application. Je peux profiter par exemple des temps de transport afin de quotidiennement effectuer ma veille.&lt;/p&gt;
&lt;p&gt;Je répartis les flux RSS dans différentes catégories en fonction de leur domaine :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DevOps&lt;/li&gt;
&lt;li&gt;Linux&lt;/li&gt;
&lt;li&gt;IT Global&lt;/li&gt;
&lt;li&gt;Cybersécurité&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="wiki"&gt;Wiki
&lt;/h3&gt;&lt;p&gt;Après avoir récolté des informations via mes différentes sources, je dois les trier dans le but de garder exclusivement celles que je trouve intéressantes et/ou utiles. Une fois le tri effectué, je mets en forme les différents éléments que j&amp;rsquo;ai conservés afin de les mettre dans mon wiki. (J&amp;rsquo;ai justement écrit &lt;a class="link" href="https://mzinutti.fr/posts/wiki/" target="_blank" rel="noopener"
 &gt;un article sur la création de ce wiki&lt;/a&gt;.)&lt;/p&gt;
&lt;p&gt;Ceci permet donc d&amp;rsquo;avoir un rendu propre et condensé afin de pouvoir retrouver les éléments qui m&amp;rsquo;intéressent en un rien de temps ce qui est tout le but de la veille pour moi.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="sources-dinformation"&gt;Sources d’information
&lt;/h2&gt;&lt;p&gt;Pour ce qui est des sources d&amp;rsquo;informations, j&amp;rsquo;en utilise plusieurs mais je trouve que le format article / post est le meilleur, celui qui me correspond le plus. Ceci, car souvent les contenus écrits vont droit au but, ce qui n&amp;rsquo;est pas toujours le cas dans une vidéo ou un podcast, ce format permet donc d&amp;rsquo;être efficace et concis.&lt;/p&gt;
&lt;h3 id="sites-web"&gt;Sites web
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;Blog
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://blog.stephane-robert.info/" target="_blank" rel="noopener"
 &gt;Stéphane Robert&lt;/a&gt; : &lt;strong&gt;DevOps&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://une-tasse-de.cafe/" target="_blank" rel="noopener"
 &gt;Une tasse de café - Quentin Joly&lt;/a&gt; : &lt;strong&gt;DevOps&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.it-connect.fr/" target="_blank" rel="noopener"
 &gt;IT Connect&lt;/a&gt; : &lt;strong&gt;Admin Sys / IT Global&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://korben.info/" target="_blank" rel="noopener"
 &gt;Korben&lt;/a&gt; : &lt;strong&gt;IT Global&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://itsfoss.com/" target="_blank" rel="noopener"
 &gt;🇬🇧 It&amp;rsquo;s foss&lt;/a&gt; : &lt;strong&gt;Linux&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Agrégateur RSS
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.journalduhacker.net/" target="_blank" rel="noopener"
 &gt;Journal du hacker&lt;/a&gt; : &lt;strong&gt;IT Global&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://news.ycombinator.com/" target="_blank" rel="noopener"
 &gt;🇬🇧 Hacker News&lt;/a&gt; : &lt;strong&gt;IT Global&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="chaînes-youtube"&gt;Chaînes YouTube
&lt;/h3&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/@xavki" target="_blank" rel="noopener"
 &gt;Xavki&lt;/a&gt; : &lt;strong&gt;DevOps&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/@yrougy" target="_blank" rel="noopener"
 &gt;Yves Rougy&lt;/a&gt; : &lt;strong&gt;Linux&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/@Underscore_" target="_blank" rel="noopener"
 &gt;Underscore&lt;/a&gt; : &lt;strong&gt;IT Global / Cybersécurité&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/@DevoxxFRvideos" target="_blank" rel="noopener"
 &gt;Devoxx&lt;/a&gt; : &lt;strong&gt;DevOps / Linux&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.youtube.com/@NetworkChu%f0%9f%87%ac%f0%9f%87%a7" target="_blank" rel="noopener"
 &gt;🇬🇧 NetworkChuck&lt;/a&gt; : &lt;strong&gt;Cybersécurité&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="communauté"&gt;Communauté
&lt;/h3&gt;&lt;p&gt;Les flux communautaires présents ci-dessous sont principalement une communauté anglo-saxonne, il faut donc pouvoir maîtriser la langue de Shakespeare afin de ne pas passer à côté de toutes ces informations !&lt;/p&gt;
&lt;h4 id="medium"&gt;Medium
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://medium.com/tag/devops" target="_blank" rel="noopener"
 &gt;Devops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://medium.com/tag/kubernetes" target="_blank" rel="noopener"
 &gt;Kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://medium.com/tag/linux" target="_blank" rel="noopener"
 &gt;Linux&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://medium.com/tag/cloud" target="_blank" rel="noopener"
 &gt;Cloud&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="reddit"&gt;Reddit
&lt;/h4&gt;&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://www.reddit.com/r/devops/" target="_blank" rel="noopener"
 &gt;r/devops&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.reddit.com/r/homelab/" target="_blank" rel="noopener"
 &gt;r/homelab&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.reddit.com/r/kubernetes/" target="_blank" rel="noopener"
 &gt;r/kubernetes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://www.reddit.com/r/selfhosted/" target="_blank" rel="noopener"
 &gt;r/selfhosted&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Pour conclure, je trouve que la veille me permet d&amp;rsquo;apprendre quelque chose chaque jour dans un domaine de l&amp;rsquo;IT, ceci est en quelque sorte un processus d&amp;rsquo;amélioration continue à échelle humaine et me permet de ne pas stagner, mais d&amp;rsquo;évoluer dans mes compétences.&lt;/p&gt;
&lt;p&gt;Personnellement, j&amp;rsquo;apprécie également mettre en pratique mes différentes découvertes au travers d&amp;rsquo;un POC dans mon homelab afin de me faire rapidement un avis sur le sujet.&lt;/p&gt;</description></item><item><title>Création d'un wiki personnel</title><link>https://mzinutti.fr/posts/wiki/</link><pubDate>Sun, 06 Oct 2024 22:56:48 +0000</pubDate><guid>https://mzinutti.fr/posts/wiki/</guid><description>&lt;img src="https://mzinutti.fr/posts/wiki/cover.png" alt="Featured image of post Création d'un wiki personnel" /&gt;&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Avec le nombre d&amp;rsquo;informations qui arrivent vers moi chaque jour dans de nombreux domaines, des pages de notes manuscrites éparpillées un peu partout et jamais réutilisées, et toujours le souci de régler un problème pour la énième fois sans se souvenir de la solution comme toutes les fois précédentes. 😆&lt;/p&gt;
&lt;p&gt;Il me manquait donc un outil pour me servir de &lt;strong&gt;second cerveau&lt;/strong&gt; 🧠 afin d&amp;rsquo;avoir toutes ces pages de notes ou ces erreurs déjà résolues &lt;strong&gt;rassemblées&lt;/strong&gt; au même endroit.&lt;/p&gt;
&lt;p&gt;L&amp;rsquo;expression du &lt;strong&gt;besoin&lt;/strong&gt; était de ce fait présente, avec des conditions obligatoires qui se sont dessinées au fil des différents tests :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Rapidité d&amp;rsquo;accessibilité&lt;/li&gt;
&lt;li&gt;Rédaction des pages en Markdown&lt;/li&gt;
&lt;li&gt;Outil léger&lt;/li&gt;
&lt;li&gt;Hébergement local&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="quest-ce-que-mkdocs-"&gt;Qu&amp;rsquo;est-ce que MkDocs ?
&lt;/h2&gt;
 &lt;blockquote&gt;
 &lt;p&gt;MkDocs est un générateur de site statique rapide et simple, destiné à la création de documentation. Les fichiers de la documentation sont écrits en Markdown et configurés à l&amp;rsquo;aide d&amp;rsquo;un seul fichier de configuration YAML.&lt;/p&gt;

 &lt;/blockquote&gt;
&lt;p&gt;Cette définition présente sur le site de &lt;a class="link" href="https://www.mkdocs.org/" target="_blank" rel="noopener"
 &gt;MkDocs&lt;/a&gt; m&amp;rsquo;a directement inspiré une fois que je suis tombé dessus, car ceci répondait à mes 4 besoins exprimés ci-dessus, et j&amp;rsquo;ai donc décidé de me lancer dans le test
de cet outil.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/wiki/mkdocs.png"
 alt="Page d&amp;#39;accueil du site MkDocs avec le thème par défaut" width="700px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Site Mkdocs&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;La première version de mon &lt;strong&gt;wiki&lt;/strong&gt; utilisait cet outil. J&amp;rsquo;ai utilisé un &lt;strong&gt;Dockerfile&lt;/strong&gt; afin de pouvoir conteneuriser l&amp;rsquo;outil et de pouvoir l&amp;rsquo;utiliser en local sans besoin d&amp;rsquo;installer le paquet python &lt;strong&gt;mkdocs&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Le seul bémol de cet outil est que l&amp;rsquo;interface utilisateur pique un peu les yeux. C&amp;rsquo;est ceci qui m&amp;rsquo;a motivé à trouver un thème !&lt;/p&gt;
&lt;h3 id="material"&gt;Material
&lt;/h3&gt;&lt;p&gt;Material est un framework qui s&amp;rsquo;utilise par-dessus &lt;strong&gt;MkDocs&lt;/strong&gt; et va ainsi permettre de générer un site statique personnalisable via une multitude d&amp;rsquo;options, mais également la possibilité d&amp;rsquo;ajouter des plugins.&lt;/p&gt;
&lt;p&gt;Ceci va permettre d&amp;rsquo;avoir un rendu très propre tout en minimisant la partie configuration, car bon nombre de fonctionnalités sont déjà intégrées dans le framework et une simple ligne dans le fichier de configuration permet de les ajouter.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/wiki/mkdocs_example.png"
 alt="Documentation GoCert générée avec MkDocs et le thème Material, en mode sombre avec la navigation latérale et la table des matières" width="800px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Exemple MkDocs avec Material&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;hr&gt;
&lt;h2 id="mise-en-place-du-wiki"&gt;Mise en place du wiki
&lt;/h2&gt;&lt;p&gt;Pour mettre en place cet outil, la &lt;a class="link" href="https://squidfunk.github.io/mkdocs-material/creating-your-site/" target="_blank" rel="noopener"
 &gt;documentation&lt;/a&gt; de &lt;strong&gt;Material&lt;/strong&gt; explique le déploiement via l&amp;rsquo;utilitaire &lt;strong&gt;mkdocs&lt;/strong&gt; ou via conteneur &lt;strong&gt;docker&lt;/strong&gt;. Pour ma part, je suis parti sur l&amp;rsquo;option &lt;strong&gt;docker&lt;/strong&gt; afin de pouvoir utiliser ce conteneur dans mes différents environnements sans besoin d&amp;rsquo;avoir l&amp;rsquo;utilitaire présent sur toutes mes machines.&lt;/p&gt;
&lt;p&gt;Pour commencer, il faut initialiser le projet dans le but de créer le squelette de notre wiki. Pour ceci, on passe la commande &lt;code&gt;new .&lt;/code&gt; au conteneur &lt;strong&gt;docker&lt;/strong&gt; possédant déjà l&amp;rsquo;utilitaire &lt;strong&gt;mkdocs&lt;/strong&gt; en entrypoint.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;docker run --rm -it -v &lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;PWD&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;:/docs squidfunk/mkdocs-material new .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Une fois le projet initialisé, on se retrouve avec l&amp;rsquo;arborescence suivante :&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├─ docs/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └─ index.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└─ mkdocs.yml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;docs&lt;/strong&gt; : Représente l&amp;rsquo;arborescence de notre wiki&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;mkdocs.yaml&lt;/strong&gt; : Fichier de configuration du wiki&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="configuration"&gt;Configuration
&lt;/h3&gt;&lt;p&gt;Pour la configuration du wiki, celle-ci s&amp;rsquo;effectue via le fichier &lt;code&gt;mkdocs.yaml&lt;/code&gt; à la racine du projet. On va pouvoir y configurer le thème ainsi que les features que l&amp;rsquo;on souhaite, listées dans &lt;a class="link" href="https://squidfunk.github.io/mkdocs-material/setup/" target="_blank" rel="noopener"
 &gt;la documentation de configuration de Material&lt;/a&gt;. Mais également les extensions Python que l&amp;rsquo;on souhaite utiliser.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;span class="lnt"&gt;52
&lt;/span&gt;&lt;span class="lnt"&gt;53
&lt;/span&gt;&lt;span class="lnt"&gt;54
&lt;/span&gt;&lt;span class="lnt"&gt;55
&lt;/span&gt;&lt;span class="lnt"&gt;56
&lt;/span&gt;&lt;span class="lnt"&gt;57
&lt;/span&gt;&lt;span class="lnt"&gt;58
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Configuration du site&lt;/span&gt;&lt;span class="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;site_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;My site&lt;/span&gt;&lt;span class="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;site_url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://mydomain.org/mysite&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Configuration du thème material&lt;/span&gt;&lt;span class="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;theme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;material&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;fontawesome/brands/gitlab&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;logo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;assets/logo.png&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;# Ajout de features&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;features&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;content.code.copy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;navigation.instant&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;navigation.tabs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;navigation.tabs.sticky&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;navigation.sections&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;navigation.path&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;navigation.top&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;# Automatic light &amp;amp; dark mode&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;palette&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;# Palette toggle for automatic mode&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;media&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;(prefers-color-scheme)&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;toggle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;material/brightness-auto&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;Switch to light mode&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&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="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Palette toggle for light mode&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;media&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;(prefers-color-scheme: light)&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;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;default&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;material/brightness-7&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;Switch to dark mode&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&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="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# Palette toggle for dark mode&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;media&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;(prefers-color-scheme: dark)&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;scheme&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;slate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;icon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;material/brightness-4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;Switch to system preference&lt;/span&gt;&lt;span class="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;# Déclaration des plugins du thème material&lt;/span&gt;&lt;span class="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;plugins&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;search&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Extension python pour le markdown&lt;/span&gt;&lt;span class="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;markdown_extensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;pymdownx.superfences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;custom_fences&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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;mermaid&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;class&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;mermaid&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;!!python/name:pymdownx.superfences.fence_code_format&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;admonition&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;pymdownx.details&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;pymdownx.superfences&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&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;attr_list&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;pymdownx.emoji&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;emoji_index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;!!python/name:material.extensions.emoji.twemoji&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;emoji_generator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;!!python/name:material.extensions.emoji.to_svg&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Dans mon cas, j&amp;rsquo;ai ajouté différentes features permettant par exemple de copier les blocs de code, d&amp;rsquo;avoir des onglets par nom de sections ou bien le choix entre light &amp;amp; dark mode. J&amp;rsquo;ai ajouté le plugin &lt;code&gt;search&lt;/code&gt; afin d&amp;rsquo;avoir une barre de recherche dans mon wiki.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai ajouté des extensions Python permettant de mettre des émojis, des schémas mermaid ou bien d&amp;rsquo;ajouter des classes HTML/CSS à mes blocs de markdown.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="ajout-de-contenu"&gt;Ajout de contenu
&lt;/h2&gt;&lt;p&gt;Pour ce qui va être du contenu, il se situera sous le répertoire &lt;code&gt;docs/&lt;/code&gt;, chaque dossier représentera une section et chaque fichier représentera une page.&lt;/p&gt;
&lt;p&gt;Il est possible de créer une architecture très modulable en ayant N sections avec N sous-sections et N fichiers par sous-section. Ou bien créer uniquement des fichiers. Le fonctionnement laisse un libre recours à l&amp;rsquo;imagination.&lt;/p&gt;
&lt;p&gt;Le nom de dossier représentera le nom de la section / sous-section et pour les pages, le titre sera le nom du fichier ou le header (# Titre1) en markdown s&amp;rsquo;il y en a un.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;docs/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── assets
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── logo.png
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── Plantes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── ficus.jpg
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── Ficus.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── Tech
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├── Git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ └── config.md
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── Golang
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── issues.md
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="déploiement"&gt;Déploiement
&lt;/h2&gt;&lt;p&gt;Pour la partie déploiement, je lance ce projet en local dans un conteneur docker via le fichier &lt;code&gt;docker-compose.yml&lt;/code&gt;, car je n&amp;rsquo;ai pas d&amp;rsquo;intérêt à l&amp;rsquo;héberger sur le web actuellement.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;services:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mkdocs-material:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; restart: &lt;span class="s2"&gt;&amp;#34;always&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ports:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - &lt;span class="s2"&gt;&amp;#34;8000:8000&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; container_name: &lt;span class="s2"&gt;&amp;#34;wiki&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; volumes:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; - &lt;span class="s2"&gt;&amp;#34;/path/to/wiki:/docs&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; image: &lt;span class="s2"&gt;&amp;#34;squidfunk/mkdocs-material&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Je pense que l&amp;rsquo;ajout de ce wiki dans ma boîte à outils va me servir de plus en plus avec le temps. Toutes les connaissances acquises vont être regroupées au même endroit et triées par domaine. Ceci va me permettre une facilité et rapidité d&amp;rsquo;accès à l&amp;rsquo;information, augmentant donc ma productivité et amenant par la même occasion une réduction de ma perte de temps.&lt;/p&gt;</description></item><item><title>Golang CLI</title><link>https://mzinutti.fr/posts/go-cli/</link><pubDate>Wed, 10 Apr 2024 22:56:48 +0000</pubDate><guid>https://mzinutti.fr/posts/go-cli/</guid><description>&lt;img src="https://mzinutti.fr/posts/go-cli/cover.png" alt="Featured image of post Golang CLI" /&gt;&lt;p&gt;Ce projet a été réalisé dans le cadre de ma première année de Master à Sup De Vinci lors d&amp;rsquo;un module sur le langage Golang.&lt;/p&gt;
&lt;h2 id="introduction"&gt;Introduction
&lt;/h2&gt;&lt;p&gt;Le cahier des charges fourni par l&amp;rsquo;intervenant faisait la demande de réaliser une CLI en Golang pour une des solutions d&amp;rsquo;authentification imposées parmi les trois suivantes :&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a class="link" href="https://casdoor.org/" target="_blank" rel="noopener"
 &gt;Casdoor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://pocketbase.io/" target="_blank" rel="noopener"
 &gt;PocketBase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a class="link" href="https://goauthentik.io/" target="_blank" rel="noopener"
 &gt;Authentik&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Une documentation devait être fournie afin d&amp;rsquo;avoir un projet clé en main utilisable par n&amp;rsquo;importe qui.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="choix-des-solutions"&gt;Choix des solutions
&lt;/h2&gt;&lt;h3 id="backend"&gt;Backend
&lt;/h3&gt;&lt;p&gt;Après avoir comparé les 3 solutions d&amp;rsquo;authentification, j&amp;rsquo;ai remarqué qu&amp;rsquo;Authentik et Casdoor sont très complets, de ce fait, il faut un certain temps pour les prendre en main. Mais au vu de la deadline du projet ainsi que ses besoins, je suis parti sur l&amp;rsquo;outil &lt;strong&gt;PocketBase&lt;/strong&gt;. C&amp;rsquo;est un outil simple à déployer et à utiliser, il remplit pile les besoins requis pour le projet.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/go-cli/pocketbase.png"
 alt="Page d&amp;#39;accueil de PocketBase avec l&amp;#39;interface d&amp;#39;administration qui liste les enregistrements de la collection posts" width="800px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;PocketBase UI&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h3 id="framework-golang"&gt;Framework Golang
&lt;/h3&gt;&lt;p&gt;Pour le framework Go, j&amp;rsquo;ai vu qu&amp;rsquo;il existait 2 frameworks qui font l&amp;rsquo;unanimité dans ce domaine : &lt;a class="link" href="https://github.com/spf13/cobra" target="_blank" rel="noopener"
 &gt;&lt;strong&gt;Cobra&lt;/strong&gt; 🐍&lt;/a&gt; et &lt;a class="link" href="https://github.com/alecthomas/kong" target="_blank" rel="noopener"
 &gt;&lt;strong&gt;Kong&lt;/strong&gt; 🦍&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;En suivant des tutoriels sur YouTube qui utilisaient le framework &lt;strong&gt;Cobra&lt;/strong&gt;, la simplicité d&amp;rsquo;utilisation m&amp;rsquo;a permis de rapidement mettre en place un POC de ma CLI avec une gestion des flags très intuitive et une documentation automatisée. J&amp;rsquo;ai donc continué sur ce framework. Il y a énormément de projets qui utilisent Cobra comme on peut le voir dans cette &lt;a class="link" href="https://github.com/spf13/cobra/blob/main/site/content/projects_using_cobra.md" target="_blank" rel="noopener"
 &gt;liste&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai aussi ajouté le package &lt;a class="link" href="https://github.com/spf13/viper" target="_blank" rel="noopener"
 &gt;&lt;strong&gt;Viper&lt;/strong&gt;&lt;/a&gt; dans le projet qui va permettre la gestion de la configuration dans le projet Golang dans le but de manipuler le fichier conf.json.&lt;/p&gt;
&lt;p&gt;Et pour finir, l&amp;rsquo;ajout du package &lt;a class="link" href="https://github.com/manifoldco/promptui" target="_blank" rel="noopener"
 &gt;&lt;strong&gt;promptUI&lt;/strong&gt;&lt;/a&gt; va permettre d&amp;rsquo;améliorer l&amp;rsquo;UX grâce notamment aux prompts interactifs.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Prompt interactif" class="gallery-image" data-flex-basis="824px" data-flex-grow="343" height="122" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://mzinutti.fr/posts/go-cli/promptui.png" width="419"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="architecture"&gt;Architecture
&lt;/h2&gt;&lt;h3 id="cli"&gt;CLI
&lt;/h3&gt;&lt;p&gt;Au niveau de l&amp;rsquo;architecture de la CLI, j&amp;rsquo;ai choisi de me baser sur celle de &lt;strong&gt;Docker&lt;/strong&gt; que je trouve personnellement très intuitive et pratique à utiliser, et qui est également réalisée via &lt;strong&gt;Cobra&lt;/strong&gt;. Je suis donc parti sur le même modèle &lt;code&gt;command [ressource] [action] [flags]&lt;/code&gt;.&lt;/p&gt;
&lt;figure&gt;&lt;img src="https://mzinutti.fr/posts/go-cli/cli-architecture.png"
 alt="Schéma de la CLI go-cli : la commande racine, la ressource user avec les actions create, delete, inspect et update, et les commandes version, login et logout" width="700px"&gt;&lt;figcaption&gt;
 &lt;h4&gt;Architecture de la CLI&lt;/h4&gt;
 &lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p&gt;La CLI va donc pouvoir permettre de se connecter et déconnecter de PocketBase et aussi de réaliser les actions de &lt;strong&gt;CRUD&lt;/strong&gt; (Create, Read, Update, Delete) sur les utilisateurs présents dans le backend. Ce qui répond donc à la demande exprimée dans le cahier des charges.&lt;/p&gt;
&lt;p&gt;Une fois l&amp;rsquo;architecture de ma CLI schématisée, ceci marque la fin de la phase de conception et je peux donc me lancer dans la phase de programmation de cette CLI afin de passer dans le concret.&lt;/p&gt;
&lt;h3 id="code"&gt;Code
&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;├── cmd
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── auth
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ├── login.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └── logout.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── root.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── user
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ├── create.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ├── delete.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ├── inspect.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ├── update.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └── user.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── version.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── common
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── common.go
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── go.mod
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── go.sum
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── go-cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── main.go
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;main.go&lt;/strong&gt; : Permet d’initialiser le programme grâce à la fonction &lt;code&gt;main()&lt;/code&gt;, qui va être le point d&amp;rsquo;entrée de notre programme.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;cmd/&lt;/strong&gt; : Ce dossier regroupe l’ensemble des commandes disponibles dans la CLI. Les fichiers sont organisés en fonction de l&amp;rsquo;architecture de la CLI.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;common/&lt;/strong&gt; : Module qui regroupe les fonctions qui sont utilisées dans plusieurs modules du projet.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="déploiement"&gt;Déploiement
&lt;/h2&gt;&lt;p&gt;Pour le déploiement du projet, celui-ci se résume à un simple fichier &lt;code&gt;docker-compose.yaml&lt;/code&gt; afin de créer le conteneur &lt;strong&gt;PocketBase&lt;/strong&gt; qui contient la base de données &lt;strong&gt;SQLite&lt;/strong&gt;. On monte également le fichier pb_data afin d&amp;rsquo;avoir une base de données déjà fournie au niveau des utilisateurs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&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;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pocketbase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;container_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;pocketbase&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="m"&gt;8090&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&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="l"&gt;./pb_data:/pb/pb_data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;La &lt;strong&gt;CLI&lt;/strong&gt; fonctionne quant à elle via un binaire récupérable depuis les &lt;strong&gt;releases&lt;/strong&gt; GitLab ou bien en compilant le code directement en local.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="démonstration"&gt;Démonstration
&lt;/h2&gt;&lt;p&gt;&lt;img alt="Démonstration de l’utilisation de la CLI" class="gallery-image" data-flex-basis="362px" data-flex-grow="151" height="397" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://mzinutti.fr/posts/go-cli/demo.gif" width="600"&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;Conclusion
&lt;/h2&gt;&lt;p&gt;Ce projet a été assez challengeant, car il est intervenu après le module de cours d&amp;rsquo;une semaine sur le Golang. Il fallait donc encore approfondir ce langage pour bien appréhender ce projet. Ayant fait du C dans un passé pas si lointain que ça ce projet m&amp;rsquo;a rappelé quelques bons souvenirs. 😄&lt;/p&gt;
&lt;p&gt;J&amp;rsquo;ai également pris de l&amp;rsquo;expérience sur le fait de choisir les bons outils pour répondre aux attentes du projet, mais également pour gagner du temps sur le développement.&lt;/p&gt;
&lt;p&gt;Pour un peu plus de détails sur ce projet, le code est disponible sur le &lt;a class="link" href="https://gitlab.com/Toxma/go-cli" target="_blank" rel="noopener"
 &gt;dépôt GitLab go-cli&lt;/a&gt; !&lt;/p&gt;</description></item></channel></rss>