Si vous êtes des habitués de ce blog, vous savez déjà que je suis un énorme fan de Talos, une distribution minimaliste et sécurisée qui a pour seul but de faire tourner les composants de Kubernetes. Si j’en crois la date de publication de mon article sur ce sujet ça fait bientôt un an que j’en bouffe tous les jours (même si en réalité, j’ai commencé bien avant).

J’aime toujours autant ce projet, et je suis toujours aussi convaincu de son potentiel. Talos me semble être de-facto une des meilleures options pour déployer un Kubernetes en garantissant une sécurité maximale sans non-plus sacrifier la simplicité et la flexibilité.

Bref, pas besoin de vous refaire un topo sur les avantages que je lui trouve.

J’ai eu l’occasion de développer des extensions pour Talos, d’utiliser le SDK, de faire mumuse avec Omni (système pour gérer du Talos à grande échelle), de faire du Talos dans Talos dans Talos. J’ai également installé des clusters avec du baremetal OVH, des Raspberry Pi, des ZimaBoard, des VMs sur Proxmox, Openstack, AWS…

Aujourd’hui, je m’attaque à une autre facette de Talos : le provisionnement de clusters Kubernetes sur des VMs Proxmox via la Cluster API.

Je n’ai pas les compétences pour faire un article complet sur la Cluster API, je n’ai pas non-plus testé plusieurs providers ni plusieurs clouds. Dans cet article, je vais donc plutôt vous présenter mon cheminement pour déployer un cluster Talos sur Proxmox via la Cluster API, en essayant de détailler les étapes, les problèmes rencontrés et les solutions trouvées.

Mais déjà : qu’est-ce que la Cluster API ?

Cluster API

alt text

La Cluster API est un projet open-source qui vise à automatiser le déploiement, la configuration et la gestion de clusters Kubernetes.

Started by the Kubernetes Special Interest Group (SIG) Cluster Lifecycle, the Cluster API project uses Kubernetes-style APIs and patterns to automate cluster lifecycle management for platform operators. The supporting infrastructure, like virtual machines, networks, load balancers, and VPCs, as well as the Kubernetes cluster configuration are all defined in the same way that application developers operate deploying and managing their workloads.

Si on résume, la Cluster API (qu’on va abréger en CAPI dans cet article) permet d’utiliser des ressources Kubernetes pour décrire et gérer des clusters Kubernetes… depuis un cluster Kubernetes ! On profite alors de toutes les fonctionnalités de Kubernetes : boucle de réconciliation, contrôle d’accès, gestion des secrets, etc.

Ainsi, nous allons avoir un cluster de management qui va se charger de créer des clusters de workload.

Les composants de la CAPI sont les suivants :

  • Déjà, le Cluster-API Controller, qui est le composant principal de la CAPI ;
  • Le Bootstrap Provider, responsable de la génération des certificats et des configurations nécessaires pour créer le cluster. C’est aussi lui qui va piloter les workers pour qu’ils rejoignent le cluster ;
  • Le Infrastructure Provider, qui va gérer les ressources nécessaires pour le cluster. C’est lui qui va créer les VMs, les réseaux, les disques, etc. Bref : piloter le cloud ou l’hyperviseur ;
  • Le IPAM Provider qui va attribuer les adresses IP aux machines du cluster (en fonction des besoins et des contraintes de l’Infrastructure Provider) ;
  • Et enfin le Control Plane Provider, qui s’assure que les control plane sont bien configurés et fonctionnels (ceux-ci peuvent être des VMs, ou managés par un service cloud).

Je vous vois venir :

Mais sur quelles plateformes est-ce que je peux utiliser la CAPI ?

La CAPI est extensible et il existe des providers (un peu comme pour Pulumi, ou Terraform) pour de nombreux environnements : AWS, Azure, GCP, OpenStack, Hetzner, vcluster, etc.

Pour commencer à jouer avec la CAPI, on doit d’abord installer le client en ligne de commande clusterctl :

Dans l’idée, on va piocher les composants nécessaires pour notre environnement. Je vais donc faire ma liste de courses pour mon projet Proxmox + Talos :

  • Bootstrap Provider : Celui de Talos (pré-configuré dans CAPI) ;
  • Infrastructure Provider : J’utilise le projet de IONOS (1&1) qui intègre Proxmox (également pré-configuré dans CAPI) ;
  • IPAM Provider : Je vais utiliser le projet in-cluster pour gérer nous-même les IP (encore une fois, pré-configuré dans CAPI) ;
  • Last but not least, le Control Plane Provider sera celui de Talos (à votre avis, pré-configuré dans CAPI ou pas ?).

Je lève également une petite alerte sur le fait que notre clusterctl va par défaut utiliser les dernières releases de chaque projet Github (et donc, potentiellement, des versions instables), pas glop tout ça. On peut ainsi spécifier des versions précises dans le fichier de configuration présent ~/.cluster-api/clusterctl.yaml.

providers:
  - name: "talos"
    url: "https://github.com/siderolabs/cluster-api-bootstrap-provider-talos/releases/download/v0.6.7/bootstrap-components.yaml"
    type: "BootstrapProvider"
  - name: "talos"
    url: "https://github.com/siderolabs/cluster-api-control-plane-provider-talos/releases/download/v0.5.8/control-plane-components.yaml"
    type: "ControlPlaneProvider"
  - name: "proxmox"
    url: "https://github.com/ionos-cloud/cluster-api-provider-proxmox/releases/download/v0.6.2/infrastructure-components.yaml"
    type: "InfrastructureProvider"

Ok ! On a nos composants, il nous manque quoi ?

Cluster de Management

Comme dit en introduction, on va avoir besoin d’un premier cluster pour gérer les autres. Je vais alors créer un cluster mono-noeud rapidos (et je vais le faire sur mon cluster Proxmox actuel). J’aurais pu utiliser un cluster éphémère sous KIND ou talosctl cluster create mais pour les besoins de l’article, je vais quand même déployer un cluster basé sur des machines virtuelles.

Si vous voulez suivre un guide pour déployer un Talos, je vous invite à suivre mon article sur le sujet (il est un peu vieux, mais les bases sont là).

Je démarre alors une ISO Talos sur mon Proxmox. alt text

On génère la configuration pour le cluster de management :

talosctl gen secrets
talosctl gen config capi https://192.168.1.6:6443

Je vais également modifier le champ cluster.allowSchedulingOnControlPlanes pour le mettre à true dans le fichier controlplane.yaml. L’idée est d’autoriser les pods à se scheduler sur le control plane (et heureusement qu’on a un seul noeud).

Une fois le fichier modifié, on peut terminer la configuration du cluster de management :

$ talosctl bootstrap -n 192.168.1.6 -e 192.168.1.6 --talosconfig ./talosconfig
$ talosctl kubeconfig -n 192.168.1.6 -e 192.168.1.6 --talosconfig ./talosconfig --merge=false
$ kubectl --kubeconfig kubeconfig get nodes
NAME            STATUS   ROLES           AGE   VERSION
talos-gag-kjo   Ready    control-plane   32s   v1.31.2

On a notre cluster de management, on peut passer à la suite.

Configurer Proxmox

Pour déployer des VMs, nous allons devoir fournir les accès d’une certaine manière à la CAPI (sinon on n’ira pas bien loin). Je dédie donc un utilisateur sur mon Proxmox pour gérer les machines depuis notre cluster.

# Dans Proxmox
$ pveum user add capmox@pve
$ pveum aclmod / -user capmox@pve -role PVEVMAdmin
$ pveum user token add capmox@pve capi -privsep
┌──────────────┬──────────────────────────────────────┐
│ key          │ value                                │
╞══════════════╪══════════════════════════════════════╡
│ full-tokenid │ capmox@pve!capi                      │
├──────────────┼──────────────────────────────────────┤
│ info         │ {"privsep":"0"}├──────────────┼──────────────────────────────────────┤
│ value        │ 918a2c6d-8f30-47i7-8d46-e72c2a882ec8 │
└──────────────┴──────────────────────────────────────┘

Avec ça, je peux modifier mon fichier de configuration de la CAPI pour ajouter les informations de connexion à Proxmox :

# ~/.cluster-api/clusterctl.yaml
PROXMOX_URL: "https://192.168.1.182:8006"
PROXMOX_TOKEN: 'capmox@pve!capi'
PROXMOX_SECRET: "918a2c6d-8f30-47i7-8d46-e72c2a882ec8"

Information

Si vous n’aimez pas les fichiers de configuration, vous pouvez aussi utiliser des variables d’environnement (e.g. export PROXMOX_URL="https://192.168.1.182:8006" PROXMOX_TOKEN: 'capmox@pve!capi' etc).

On peut maintenant installer CAPI ainsi que nos différents providers :

$ clusterctl init --infrastructure proxmox --ipam in-cluster --control-plane talos --bootstrap talos
$ kubectl get pods -A
NAMESPACE                     NAME                                                       READY   STATUS    RESTARTS      AGE
cabpt-system                  cabpt-controller-manager-6dcd86fd55-6q4nh                  1/1     Running   0             6m1s
cacppt-system                 cacppt-controller-manager-7757bc6849-grm6b                 1/1     Running   0             6m
capi-ipam-in-cluster-system   capi-ipam-in-cluster-controller-manager-8696b5d999-kmkxm   1/1     Running   0             5m59s
capi-system                   capi-controller-manager-59c7f9c475-fx2jb                   1/1     Running   0             6m1s
capmox-system                 capmox-controller-manager-674bdf77bd-8tpzw                 1/1     Running   0             5m59s
cert-manager                  cert-manager-74b56b6655-rt6cc                              1/1     Running   0             19h
cert-manager                  cert-manager-cainjector-55d94dc4cc-qk7h4                   1/1     Running   0             19h
cert-manager                  cert-manager-webhook-564f647c66-dkfrm                      1/1     Running   0             19h
kube-system                   coredns-b588ffbd5-rsbhl                                    1/1     Running   0             20h
kube-system                   coredns-b588ffbd5-x2j7g                                    1/1     Running   0             20h
kube-system                   kube-apiserver-talos-gag-kjo                               1/1     Running   0             20h
kube-system                   kube-controller-manager-talos-gag-kjo                      1/1     Running   2 (20h ago)   20h
kube-system                   kube-flannel-b5wpl                                         1/1     Running   1 (20h ago)   20h
kube-system                   kube-proxy-jmlkw                                           1/1     Running   1 (20h ago)   20h
kube-system                   kube-scheduler-talos-gag-kjo                               1/1     Running   3 (20h ago)   20h

Oui, un dodo et une journée de travail se sont écoulés entre la création du cluster de management et l’installation de CAPI.

Ok, on a la même base maintenant, on va pouvoir continuer en pairing.

Objectif : Un ControlPlane !

Commençons simple, je veux juste un control-plane pour démarrer. Une première étape serait de détailler les ressources que l’on veut pour notre cluster. Alors, de quoi a-t-on besoin pour déployer un control-plane Talos sur Proxmox ?

De la définition d’un cluster !

C’est un bon début, on va d’abord commencer par créer un cluster avant d’y ajouter des machines. Pour cela, je m’appuie de la documentation de CAPI pour comprendre qu’il me faut une ressource de type Cluster. Celle-ci possède également une dépendance sur une autre ressource ProxmoxCluster

Dans cet objet ProxmoxCluster, on va définir les paramètres de notre cluster Kubernetes, c.a.d. configurer les adresses IP, la gateway, les DNS, que les noeuds utiliseront etc. mais surtout un endpoint de l’API-Server d’un control-plane.

apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
kind: ProxmoxCluster
metadata:
  name: proxmox-cluster
  namespace: default
spec:
  allowedNodes:
  - homelab-proxmox-02
  controlPlaneEndpoint:
    host: 192.168.1.220
    port: 6443
  dnsServers:
  - 8.8.8.8
  - 8.8.4.4
  ipv4Config:
    addresses:
    - 192.168.1.210-192.168.1.219
    gateway: 192.168.1.254
    prefix: 24

On peut ensuite créer notre Cluster. Ici, on voit bien que l’on fait référence à notre ProxmoxCluster (provider Proxmox) coté infrastructureRef et à notre TalosControlPlane (provider Talos) coté controlPlaneRef (il n’existe pas encore, mais à la vitesse où on va, il ne va pas tarder).

apiVersion: cluster.x-k8s.io/v1beta1
kind: Cluster
metadata:
  name: coffee-cluster
  namespace: default
spec:
  controlPlaneRef:
    apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
    kind: TalosControlPlane
    name: talos-cp # Existe pas encore
  infrastructureRef:
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
    kind: ProxmoxCluster
    name: proxmox-cluster

So far, so good. On a nos ressources bien présentes dans le cluster.

$ kubectl get cluster,proxmoxcluster
NAME                                      CLUSTERCLASS   PHASE          AGE    VERSION
cluster.cluster.x-k8s.io/coffee-cluster                  Provisioning   102s
NAME                                                             CLUSTER   READY   ENDPOINT
proxmoxcluster.infrastructure.cluster.x-k8s.io/proxmox-cluster                     {"host":"192.168.1.220","port":6443}

Trop facile 😎, plus qu’à créer notre ControlPlane…

On précise bien que le ControlPlane est une machine gérée par Proxmox (once again, on mélange la configuration de l’infrastructure et des machines). On peut également ajouter des paramètres supplémentaires pour influer sur la configuration Talos que le provider va générer (ici, on ne précise que le type de la machine, mais on pourrait aller plus loin).

apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
kind: TalosControlPlane
metadata:
  name: talos-cp
spec:
  version: v1.32.0
  replicas: 1
  infrastructureTemplate:
    kind: ProxmoxMachineTemplate
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
    name: control-plane-template
    namespace: default
  controlPlaneConfig:
    controlplane:
      generateType: controlplane

On applique, on relance… Il ne reste plus que le ProxmoxMachineTemplate à créer pour enfin voir notre ControlPlane démarrer.

apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
kind: ProxmoxMachineTemplate
metadata:
  name: control-plane-template
  namespace: default
spec:
  template:
    spec:
      disks:
        bootVolume:
          disk: scsi0
          sizeGb: 20
      format: qcow2
      full: true
      memoryMiB: 2048
      network:
        default:
          bridge: vmbr0
          model: virtio
      numCores: 2
      numSockets: 1
      sourceNode: homelab-proxmox-02
      templateID: ??? # A remplir

Comme pour les providers Terraform quand on ne fait pas de Cloud-Init, on va devoir créer une machine ’template’ que l’on dupliquera pour chaque machine du cluster. Comme nous utilisons Talos, il n’est pas nécessaire de booter cette machine pour l’installer, il suffit juste que la machine soit créée, l’image (ISO) de Talos comme disque bootable et surtout que les qemu-guest-agent soient activés coté Proxmox.

Il n’est pas non-plus nécessaire d’être très attentif aux ressources que l’on alloue à cette machine. CAPI les modifiera en fonction de ce qu’on a défini dans nos ressources comme montré plus haut.

Pour récupérer l’ISO de Talos, on peut la télécharger depuis Factory. Dans notre cas, comme nous sommes sur des VMs classiques, on peut sélectionner “Bare-metal Machine” qui correspond aux machines standards (virtuelles ou physiques).

alt text

Activer les Qemu Guest Agent sur Proxmox :

alt text

J’obtiens alors la machine possédant l’ID 124 ! Je peux alors remplir le champ .spec.template.spec.templateID de mon ProxmoxMachineTemplate.


Maintenant, notre cluster possède ce qu’il faut pour déployer une première machine, elle peut démarrer à tout moment 😁 !

Et là, c’est long… très long…

alt text

Y’a un truc qui coince, checkons les logs du provider Proxmox.

I0131 16:44:24.941681       1 proxmoxmachine_controller.go:187] "Bootstrap data secret reference is not yet available" controller="proxmoxmachine" controllerGrou
I0131 16:44:24.990591       1 find.go:63] "vmid doesn't exist yet" controller="proxmoxmachine" controllerGroup="infrastructure.cluster.x-k8s.io" controllerKind="
E0131 16:44:25.060475       1 proxmoxmachine_controller.go:209] "error reconciling VM" err="cannot reserve 2147483648B of memory on node homelab-proxmox-02: 0B a
E0131 16:44:25.119065       1 controller.go:324] "Reconciler error" err="failed to reconcile VM: cannot reserve 2147483648B of memory on node homelab-proxmox-02:

J’aurais pu attendre longtemps 😅 ! À savoir que 2147483648B = 2GiB, et que j’ai largement la quantité de mémoire disponible sur mon nœud Proxmox.

alt text

Beh, direction les issues github alors… ⌛


En creusant un peu, je suis tombé sur ce message :

By default our scheduler only allows to allocate as much memory to guests as the host has. This might not be a desirable behaviour in all cases. For example, one might to explicitly want to overprovision their host’s memory, or to reserve bit of the host’s memory for itself.

Parce que je n’ai plus rien à perdre, je modifie la configuration de mon ProxmoxCluster pour ajouter un schedulerHint qui va permettre de over-provisionner la mémoire.

apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
kind: ProxmoxCluster
metadata:
  name: proxmox-cluster
  namespace: default
spec:
+  schedulerHints:
+    memoryAdjustment: 0
  allowedNodes:
  - homelab-proxmox-02
  controlPlaneEndpoint:
    host: 192.168.1.220
    port: 6443
  dnsServers:
  - 8.8.8.8
  - 8.8.4.4
  ipv4Config:
    addresses:
    - 192.168.1.210-192.168.1.219
    gateway: 192.168.1.254
    prefix: 24

Y’a aucune raison que ça marche, mais je tente quand même.

alt text Bonjour, toi !

C’est une avancée incroyable !! 🎉 Mais il y a comme un truc qui cloche 🤔, une incohérence entre les labels que CAPI a ajoutés à la machine et ce que me dit l’écran de la VM.

alt text

Ce n’est pas la bonne adresse IP, et il ne se passe plus grand-chose coté CAPI.

 E0131 17:04:39.039618       1 controller.go:324] "Reconciler error" err="failed to reconcile VM: error waiting for agent: error waiting for agent: the operat │
│ ion has timed out" controller="proxmoxmachine" controllerGroup="infrastructure.cluster.x-k8s.io" controllerKind="ProxmoxMachine" ProxmoxMachine="default/cont │
│ rol-plane-template-4jtgp" namespace="default" name="control-plane-template-4jtgp" reconcileID="294544f4-4de0-4a91-b686-47a0d51a1f2a"  

Qu’est-ce que c’est que ce binz ?

Ma théorie est que CAPI n’arrive pas à configurer l’adresse IP. Je vois deux manières qu’il aurait pu utiliser pour le faire :

  • Soit, obtenir l’adresse IP et lui envoyer sa configuration en paramétrant l’adresse IP dans la configuration Talos ;
  • Soit, lui paramétrer son adresse IP par Cloud-Init (et en profiter pour lui donner sa configuration).

Bref, quelque chose déconne et ça semble lié au fait que l’adresse IP ne soit pas correctement configurée. Procédons par élimination : je suis plutôt sûr que mon Proxmox n’est pas en cause, ma configuration semble correspondre à ce que CAPI attend, il ne reste qu’un potentiel problème : Talos.

Le setup Proxmox-Talos serait particulier ? J’ai pourtant déployé de nombreux clusters Talos sous Proxmox sans rencontrer de problème. La réponse est en fait assez simple : CAPI demande à Proxmox de paramétrer l’adresse IP de la machine à travers Cloud-Init, mais Talos ne le supporte pas lorsqu’on utilise l’image ISO “Bare-metal Machine” (qui est celle que j’ai utilisée).

Il fallait utiliser l’image “nocloud” (celle utilisée pour les clouds/hyperviseurs classiques) :

alt text

Qui aurait pu se douter que l’image ‘cloud’ était la seule compatible avec cloud-init ?

alt text


Je modifie la template de ma machine pour utiliser l’image “nocloud” et je relance le déploiement.

Ça démarre, on voit que la machine récupère sa configuration IP, qu’elle a bien rejoint le cluster (puisqu’on peut voir le nom du cluster sur la console), mais elle crash et redémarre en boucle.

On peut apercevoir le message d’erreur suivant :

29.285667 [talos] controller runtime finished
29.287109 [talos] error running phase 2 in install sequence: task 1/1: fail task "upgrade" failed: exit code 1
29.2884421 [talos] no system disk found, nothing to

On pensait que c’était gagné, mais il semblerait que non. Mais ça tombe bien, j’ai une petite idée de ce qui ne va pas : Talos ne sait absolument pas quel disque utiliser. Pour confirmer ça, je peux directement aller lire la configuration de la machine, non pas via l’API de Talos (la machine n’est pas assez longtemps démarrée pour ça), mais directement depuis Kubernetes où la configuration Talos est stockée.

alt text

Le champ “disk” est vide, ce qui explique pourquoi Talos ne sait pas où installer le système. Pour pallier à ça, on peut modifier cette configuration via l’objet TalosControlPlane.

apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
kind: TalosControlPlane
metadata:
  name: talos-cp
spec:
  version: v1.32.0
  replicas: 1
  infrastructureTemplate:
    kind: ProxmoxMachineTemplate
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
    name: control-plane-template
    namespace: default
  controlPlaneConfig:
    controlplane:
      generateType: controlplane
+     strategicPatches:
+       - |
+         - op: replace
+           path: /machine/install
+           value:
+             disk: /dev/sda

On applique, on croise les doigts, et on attend.

alt text

Incroyable, ça a marché ! 😄 On a notre ControlPlane qui tourne sur Proxmox. On retrouve des logs traditionnels de Talos nous demandant de bootstrap le cluster.

Dans un contexte normal, on aurait dû lancer la commande talosctl bootstrap. Dans un déploiement CAPI, cette étape est censée être automatisée. En listant le status de notre objet ‘ProxmoxMachine’, on voit qu’il est toujours en “pending”.

# kubectl describe proxmoxmachine control-plane-template-n62jk
Name:         control-plane-template-n62jk
Namespace:    default
API Version:  infrastructure.cluster.x-k8s.io/v1alpha1
Kind:         ProxmoxMachine
# ...
Status:
  Addresses:
    Address:                control-plane-template-n62jk
    Type:                   Hostname
    Address:                192.168.1.210
    Type:                   InternalIP
  Bootstrap Data Provided:  true
  Ip Addresses:
    net0:
      ipv4:      192.168.1.210
  Proxmox Node:  homelab-proxmox-02
  Vm Status:     pending
Events:          <none>

Oh shit, here we go again.

Bon, c’est quoi le problème maintenant ? Regardons les logs du pod du provider Proxmox :

E0131 19:27:13.724453       1 controller.go:324] "Reconciler error" err="failed to reconcile VM: error waiting for agent: error waiting for agent: the operation has timed out" controller="proxmoxmachine" controllerGroup="infrastructure.cluster.x-k8s.io" controllerKind="ProxmoxMachine" ProxmoxMachine="default/control-plane-template-n62jk" namespace="default" name="control-plane-template-n62jk" reconcileID="d6d0983
3-8d38-4518-bd49-b94522f560bd"

L’erreur error waiting for agent ne peut pas venir de n’importe où : ça doit forcément être lié aux Qemu-guest-agent (CAPI doit faire des requêtes à l’hyperviseur pour vérifier si la machine est conforme).

En effet, les QGAs ne semblent pas être fonctionnels. Pour corriger ça, on peut encore passer par un patch dans la configuration Talos pour demander l’installation de l’extension nécessaire.

apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
kind: TalosControlPlane
metadata:
  name: talos-cp
spec:
  version: v1.32.0
  replicas: 1
  infrastructureTemplate:
    kind: ProxmoxMachineTemplate
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
    name: capi-quickstart-control-plane
    namespace: default
  controlPlaneConfig:
    controlplane:
      generateType: controlplane
      strategicPatches:
        - |
          - op: replace
            path: /machine/install
            value:
              disk: /dev/sda
+              extensions:
+                - image: ghcr.io/siderolabs/qemu-guest-agent:9.2.0

Machine redéployée et… on est toujours en “pending” :

# ...
Status:
  Addresses:
    Address:                control-plane-template-hcthr
    Type:                   Hostname
    Address:                192.168.1.210
    Type:                   InternalIP
  Bootstrap Data Provided:  true
  Conditions:
    Last Transition Time:  2025-01-31T19:47:42Z
    Reason:                PoweringOn
    Severity:              Info
    Status:                False
    Type:                  Ready
    Last Transition Time:  2025-01-31T19:47:42Z
    Reason:                PoweringOn
    Severity:              Info
    Status:                False
    Type:                  VMProvisioned
  Ip Addresses:
    net0:
      ipv4:      192.168.1.210
  Proxmox Node:  homelab-proxmox-02
  Vm Status:     pending
Events:          <none>

Retournons voir les logs du provider pour comprendre cette nouvelle erreur.

E0131 19:51:41.225099       1 controller.go:324] "Reconciler error" err="failed to reconcile VM: unable to get cloud-init status: no pid returned from agent exec command" controller="proxmoxmachine" controllerGroup="infrastructure.cluster.x-k8s.io" controllerKind="ProxmoxMachine" ProxmoxMachine="default/control-plane-template-hcthr" namespace="default" name="control-plane-template-hcthr" reconcileID="099e3e4a-64ca-4b3f-8fb5-be280e3de35e"

no pid returned from agent exec command signifie que CAPI essaye de lancer une commande bash sur la machine via les QGAs.

Essayer de lancer un terminal dans Talos ? You fool !

Pour une fois, la solution n’est pas dans la configuration de Talos, mais plutôt dans celle du provider Proxmox. On va le modifier comme ceci pour demander à ne pas vérifier le status de la machine via Cloud-Init.

apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
kind: ProxmoxMachineTemplate
metadata:
  name: control-plane-template
  namespace: default
spec:
  template:
    spec:
      disks:
        bootVolume:
          disk: scsi0
          sizeGb: 20
      format: qcow2
      full: true
      memoryMiB: 2048
      network:
        default:
          bridge: vmbr0
          model: virtio
      numCores: 2
      numSockets: 1
      sourceNode: homelab-proxmox-02
      templateID: 124
+      checks:
+        skipCloudInitStatus: true

Je relance… ON EST ENFIN EN “READY” ! 🎉

Status:
  # ...
  Ip Addresses:
    net0:
      ipv4:      192.168.1.210
  Proxmox Node:  homelab-proxmox-02
  Ready:         true
  Vm Status:     ready
Events:          <none>

On peut maintenant essayer de déployer d’autres control-planes en modifiant le nombre de replicas dans la configuration du TalosControlPlane.

apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
kind: TalosControlPlane
metadata:
  name: talos-cp
spec:
  version: v1.32.0
-  replicas: 1
+  replicas: 3
  infrastructureTemplate:
    kind: ProxmoxMachineTemplate
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
    name: control-plane-template
    namespace: default
  controlPlaneConfig:
    controlplane:
      generateType: controlplane
      strategicPatches:
        - |
          - op: replace
            path: /machine/install
            value:
              disk: /dev/sda
              extensions:
                - image: ghcr.io/siderolabs/qemu-guest-agent:9.2.0

En quelques secondes, on obtient deux nouvelles machines qui tournent sur Proxmox.

alt text

Il reste quand même un petit ‘hic’ : on a défini que les machines devaient rejoindre le cluster dont l’API-Server est exposée sur l’adresse 192.168.1.220. Mais aucune machine ne correspond à cette IP, donc aucune machine n’arrive à rejoindre le cluster. De plus, si on attribue manuellement cette adresse à une machine : elle deviendra un SPOF pour notre cluster (spoiler: c’est pas bien).

Normalement, dans un contexte Cloud, c’est à ce moment qu’on ajoute un LoadBalancer pour exposer le port 6443 de nos control-planes à travers une adresse IP qui n’est pas attachée à une machine. En environnement on-prem, on peut utiliser un service comme KeepAlived ou du HAProxy.

Mais c’est sans compter sur Talos (❤️) qui propose une solution pour établir une VIP pour joindre l’API-Server (justement ce qu’on cherche à faire).

apiVersion: controlplane.cluster.x-k8s.io/v1alpha3
kind: TalosControlPlane
metadata:
  name: talos-cp
spec:
  version: v1.32.0
  replicas: 3
  infrastructureTemplate:
    kind: ProxmoxMachineTemplate
    apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
    name: control-plane-template
    namespace: default
  controlPlaneConfig:
    controlplane:
      generateType: controlplane
      strategicPatches:
        - |
          - op: replace
            path: /machine/install
            value:
              disk: /dev/sda
              extensions:
                - image: ghcr.io/siderolabs/qemu-guest-agent:9.2.0
+          - op: add
+            path: /machine/install/extraKernelArgs
+            value:
+              - net.ifnames=0
+          - op: add
+            path: /machine/network/interfaces
+            value:
+              - interface: eth0
+                dhcp: false
+                vip:
+                  ip: 192.168.1.220

Pour détailler un peu plus, on ajoute l’option net.ifnames=0 (1er patch) au kernel pour que les interfaces réseau soient nommées eth0, eth1, et deviennent prédictibles (sinon, on a un nommage dynamique). Une fois qu’on a le nom de l’interface réseau, on peut y ajouter une adresse IP virtuelle (2ème patch) qui sera utilisée pour joindre l’API-Server.

Au démarrage de la première machine, celle-ci va récupérer l’adresse IP 192.168.1.220 (en plus de son actuelle adresse).

alt text

Grâce à ça, les machines rejoignent automatiquement le cluster sans qu’on ait à s’en occuper (et l’étape de bootstrap est automatiquement réalisée).

Ajouter des workers

On peut désormais passer à la suite : déployer des workers ! Comme c’est assez straightforward, je peux directement vous donner les manifests.

apiVersion: cluster.x-k8s.io/v1beta1
kind: MachineDeployment
metadata:
  name: machinedeploy-workers
  namespace: default
spec:
  clusterName: coffee-cluster
  replicas: 3
  selector:
    matchLabels: null
  template:
    spec:
      bootstrap:
        configRef:
          apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
          kind: TalosConfigTemplate
          name: talosconfig-workers
      clusterName: coffee-cluster
      infrastructureRef:
        apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
        kind: ProxmoxMachineTemplate
        name: worker-template
      version: v1.32.0
---
apiVersion: infrastructure.cluster.x-k8s.io/v1alpha1
kind: ProxmoxMachineTemplate
metadata:
  name: worker-template
  namespace: default
spec:
  template:
    spec:
      disks:
        bootVolume:
          disk: scsi0
          sizeGb: 20
      format: qcow2
      full: true
      memoryMiB: 2048
      network:
        default:
          bridge: vmbr0
          model: virtio
      numCores: 2
      numSockets: 1
      sourceNode: homelab-proxmox-02
      templateID: 124
      checks:
        skipCloudInitStatus: true
---
apiVersion: bootstrap.cluster.x-k8s.io/v1alpha3
kind: TalosConfigTemplate
metadata:
  name: talosconfig-workers
spec:
  template:
    spec:
      generateType: worker
      talosVersion: v1.9
      configPatches:
          - op: replace
            path: /machine/install
            value:
              disk: /dev/sda

En tenant compte de ce que l’on a appris durant le déploiement des control-planes, c’est un jeu d’enfant.

Voilà, on a un cluster Kubernetes complet déployé sur Proxmox avec Talos et la Cluster API !

Récupérer les accès

Si on déploie un cluster, c’est quand même pour pouvoir l’utiliser, non ? Alors voyons comment récupérer les accès à notre cluster.

Que ça soit pour le KubeConfig ou pour le TalosConfig, ça se passe de la même manière : nous avons des secrets qui ont été générés par CAPI et que l’on peut récupérer. Le secret contenant le KubeConfig est sous la forme <cluster-name>-kubeconfig et le secret contenant le TalosConfig est sous la forme <cluster-name>-talosconfig.

Commençons par le KubeConfig :

$ kubectl get secret coffee-cluster-kubeconfig -o yaml | yq .data.value | base64 -d > kubeconfig
$ kubectl --kubeconfig kubeconfig get nodes
NAME                                STATUS   ROLES           AGE   VERSION
control-plane-template-d4kfb        Ready    control-plane   10m   v1.32.0
control-plane-template-hhrgq        Ready    control-plane   10m   v1.32.0
control-plane-template-s4gwg        Ready    control-plane   10m   v1.32.0
machinedeploy-workers-h87sj-dp799   Ready    <none>          56s   v1.32.0
machinedeploy-workers-h87sj-kjg64   Ready    <none>          58s   v1.32.0
machinedeploy-workers-h87sj-z7cs2   Ready    <none>          57s   v1.32.0

Puis, le TalosConfig :

$ kubectl get secret coffee-cluster-talosconfig -o yaml | yq .data.talosconfig | base64 -d > talosconfig
$ talosctl --talosconfig talosconfig -n 192.168.1.220 version
Client:
        Tag:         v1.8.3
        SHA:         6494aced
        Built:
        Go version:  go1.22.9
        OS/Arch:     darwin/arm64
Server:
        NODE:        192.168.1.220
        Tag:         v1.9.3
        SHA:         d40df438
        Built:
        Go version:  go1.23.5
        OS/Arch:     linux/amd64
        Enabled:     RBAC

Prêt à l’emploi !

Conclusion

Il s’agissait là de mon premier contact avec la Cluster API, et malgré les quelques difficultés rencontrées, j’ai trouvé ça très intéressant de réussir à faire fonctionner ensemble ces composants qui n’ont à priori pas été conçus initialement pour ça.

Pour rendre tout ça plus clair, j’ai résumé les liens entre les différentes ressources grâce à ce schéma :

Schéma de la CAPI

Si vous voulez retrouver les manifestes utilisés dans cet article, vous pouvez les retrouver ici.

Comme pour tout projet utilisant des providers tiers, sa complexité est directement liée à la qualité de la documentation. J’ai quelques fois dû piocher des informations dans le code, et surtout dans les descriptions des ressources via kubectl explain pour comprendre comment les ressources étaient liées entre elles, ainsi que le contenu des champs.

J’ai encore d’autres choses à tester avec la CAPI, notamment l’intégration d’un programme gérant l’auto-scaling (ou même karpenter), l’installation d’applications depuis le cluster de management, ou encore l’usage des ClusterClass.

Je suis sûr que nous en reparlerons bientôt !

Bon kawa ☕ !