<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>OpenTofu on Matteo ZINUTTI</title><link>https://mzinutti.fr/categories/opentofu/</link><description>Recent content in OpenTofu on Matteo ZINUTTI</description><generator>Hugo -- gohugo.io</generator><language>fr-fr</language><lastBuildDate>Fri, 02 Oct 2026 16:09:34 +0200</lastBuildDate><atom:link href="https://mzinutti.fr/categories/opentofu/index.xml" rel="self" type="application/rss+xml"/><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;Passer un GPU NVIDIA dans un cluster Kubernetes (En cours d&amp;rsquo;écriture)&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; 2. [Passer un GPU NVIDIA dans un cluster Kubernetes](https://mzinutti.fr/posts/gpu-passthrough-talos/)
&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 sur un GPU passé au cluster. 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&lt;/code&gt;, &lt;code&gt;nvidia-container-toolkit&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 partie 2, 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 partie 2.&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></channel></rss>