Introduction

Depuis quelque temps, je m’intéresse à Talos, un système d’exploitation pour Kubernetes. J’ai installé mon premier cluster Talos en novembre 2023, et ma “production” (composée de 3 Raspberry Pi) tourne maintenant sous cet OS.

C’est une solution qui m’a séduit par sa simplicité, sa sécurité et sa facilité d’administration.

Aujourd’hui, je me décide enfin à écrire cette page pour présenter Talos, et partager mon expérience avec cet OS.

Qu’est-ce que Talos ?

Comme dit précédemment, Talos est un OS spécialement conçu pour Kubernetes. C’est un système immuable et minimaliste.

Qu’est-ce qu’un OS immuable ?

Après avoir bricolé quelques définitions, je suis tombé sur celle-ci qui me semble être la plus claire :

Une distribution immuable garantit que le cœur du système d’exploitation reste inchangé. Le système de fichiers racine d’une distribution immuable reste en lecture seule, ce qui permet de rester le même sur plusieurs instances. Bien sûr, vous pouvez changer les choses si vous le souhaitez. Mais la capacité reste désactivée par défaut.

Source: Linux-Console.net

En tenant compte de cela, Talos permet d’installer un nœud Kubernetes en quelques minutes sans avoir à se soucier de la configuration de l’OS. De plus, Talos ne se contente pas de s’administrer avec un simple SSH, il propose une API pour gérer les nœuds avec le même principe que kubectl avec Kubernetes.

Normalement à ce moment, certains grimacent en se disant “encore un truc qui sera impossible à débugger”. Mais SideroLabs (la société derrière Talos) a pensé à tout et l’absence d’un bash ne vous bloquera pas dans votre maintenance quotidienne. L’utilitaire talosctl comporte de nombreux outils comme la génération d’un rapport pcap pour débugger un problème réseau, la consultation des logs, des moyens de vérifier l’état du cluster, etc.

Bref, Talos est une solution mature avec une communauté active et utilisée par de nombreuses entreprises.

Les extensions

Du fait de sa nature minimaliste, Talos ne contient pas de paquets jugés “inutiles” par le plus grand nombre. Il est néanmoins possible d’installer des extensions pour lui ajouter des fonctionnalités. Par exemple, il est possible d’installer qemu-guest-agent pour avoir des informations sur la machine virtuelle, ou bien iscsi-tools pour monter des disques iSCSI.

Ces extensions sont disponibles sur un dépôt géré par SideroLabs : siderolabs/extensions


Si vous n’êtes pas encore convaincu, j’espère que la suite de cet article vous donnera envie de tester Talos.

Installer talosctl

Le point d’entrée pour utiliser Talos est talosctl. C’est l’utilitaire en CLI qui permet de générer la configuration du cluster, de l’appliquer sur les machines, de gérer les nœuds, de consulter les logs, etc. Il est possible de l’installer sur Linux, MacOS et Windows.

Sur Linux, on peut l’installer en exécutant la commande suivante :

curl -sL https://talos.dev/install | sh

Générer la configuration

Avant de faire quoi que ce soit, il faut générer les clés de chiffrement de notre cluster via la commande talosctl gen secrets. On obtient alors un fichier secrets.yaml qui contient les clés de chiffrement pour s’authentifier sur les nœuds du cluster.

Dès que les clés sont produites, on peut générer la configuration du cluster avec la commande talosctl gen config en précisant le nom du cluster ainsi qu’un endpoint d’un des nœuds controlplane (qui permettra aux nœuds de rejoindre le cluster).

talosctl gen config homelab-talos-dev https://192.168.128.110:6443 --with-secrets ./secrets.yaml --install-disk /dev/sda

On obtient les fichiers controlplane.yaml, worker.yaml et talosconfig. Ceux-ci contiennent la configuration de notre cluster, et le fichier talosconfig, les clés pour s’authentifier sur les différents nœuds du cluster à l’API de Talos.

Au début, je modifiais directement les fichiers pour ajouter des paramètres spécifiques à mon infrastructure, mais avec du recul, je me rends compte que ce n’est pas la bonne approche. Il est préférable de stocker le delta de configuration dans un fichier patch.yaml et de l’appliquer avec la commande talosctl gen config --config-patch @patch.yaml.

# pach.yaml
machine:
  install:
    extraKernelArgs:
      - net.ifnames=0
cluster:
  proxy:
    disabled: true
  network:
    cni:
      name: none # On installera Cilium manuellement
  apiServer:
    certSANs:
      - 192.168.1.1
      - 127.0.0.1

Comme ça, je n’ai qu’à modifier le fichier patch.yaml puis à regénérer les fichiers de configuration à chaque fois que je veux appliquer des changements.

Par contre, je ne peux pas avoir une configuration différente pour les controlplanes et les workers, il faudra alors générer un fichier de configuration par type de machine (on parlera d’une solution pour ça plus tard).

Installer le cluster

Ma configuration est prête (après le talosctl gen config --config-patch @patch.yaml), il ne reste qu’à l’envoyer sur les machines qui vont composer le cluster.

Pour installer ces serveurs, j’ai téléchargé l’image de Talos sur le dépôt Github de Talos et l’ai installée sur mes hyperviseurs Proxmox.

L’ISO de Talos est très légère, elle fait moins de 100Mo et aucune installation n’est nécessaire (elle se fera automatiquement lors de la réception de la configuration).

NomAdresse IPRôle
controlplane-01192.168.1.85Control Plane
controlplane-02192.168.1.79Control Plane
controlplane-03192.168.1.82Control Plane
worker-01192.168.1.83Worker
worker-02192.168.1.86Worker

Avant d’appliquer la configuration, je vais vérifier que les disques soient bien détectés par Talos (et qu’ils utilisent bien le bon disque pour l’installation).

$ talosctl disks --insecure -n 192.168.1.85 -e 192.168.1.85
DEV        MODEL           SERIAL   TYPE   UUID   WWID   MODALIAS      NAME   SIZE    BUS_PATH                                                                   SUBSYSTEM          READ_ONLY   SYSTEM_DISK
/dev/sda   QEMU HARDDISK   -        HDD    -      -      scsi:t-0x00   -      34 GB   /pci0000:00/0000:00:05.0/0000:01:01.0/virtio2/host2/target2:0:0/2:0:0:0/   /sys/class/block  

J’ai bel et bien utilisé /dev/sda pour l’installation de Talos, je peux donc appliquer la configuration sur les machines.

Information

Et si mes noeuds n’utilisent pas le même disque pour l’installation ?

Pour cela, il faudra dupliquer le fichier de configuration et modifier le champ installDisk pour chaque machine.

Plus bas, nous parlerons de talhelper qui propose une solution pour générer un fichier de configuration par machine.

# Appliquer la configuration sur les nœuds controlplane
talosctl apply-config --insecure -n 192.168.1.85 -e 192.168.1.85 --file controlplane.yaml
talosctl apply-config --insecure -n 192.168.1.79 -e 192.168.1.79 --file controlplane.yaml
talosctl apply-config --insecure -n 192.168.1.82 -e 192.168.1.82 --file controlplane.yaml

# Appliquer la configuration sur les nœuds worker
talosctl apply-config --insecure -n 192.168.1.83 -e 192.168.1.83 --file worker.yaml
talosctl apply-config --insecure -n 192.168.1.86 -e 192.168.1.86 --file worker.yaml

Les machines s’installent, on peut suivre l’avancement de l’installation directement sur l’hyperviseur (ou avec talosctl logs / talosctl dmesg).

Si tout se passe bien, une erreur devrait s’afficher :

trucmuche - Service "etcd" to be "up"

Cette erreur n’en est pas réellement une, elle signifie que la base de donnée etcd n’est pas encore initialisée. On va s’en occuper tout de suite avec la commande talosctl bootstrap en désignant un des controlplane.

talosctl bootstrap -e 192.168.1.85 --talosconfig ./talosconfig  --nodes 192.168.1.85

Maintenant, si on regarde les nœuds du cluster, on devrait voir que les control-planes ne sont pas en état Ready.

Aucun nœud ready

La raison : Nous n’avons encore aucun CNI ! Récupérons notre kubeconfig et résolvons ce problème.

$ talosctl kubeconfig -e 192.168.1.85 --talosconfig ./talosconfig  --nodes 192.168.1.85
$ kubectl get nodes
NAME            STATUS     ROLES           AGE     VERSION
talos-8tc-18b   NotReady   control-plane   6m5s    v1.29.1
talos-fiy-ula   NotReady   <none>          2m48s   v1.29.1
talos-x5n-ji0   NotReady   <none>          2m43s   v1.29.1
talos-xtb-22h   NotReady   control-plane   6m7s    v1.29.1
talos-ypm-jy8   NotReady   control-plane   6m3s    v1.29.1
$ kubectl describe node talos-8tc-18b
# [ ... ]
Conditions:
  Type             Status  LastHeartbeatTime                 LastTransitionTime                Reason                       Message
  ----             ------  -----------------                 ------------------                ------                       -------
  MemoryPressure   False   Thu, 22 Feb 2024 07:38:31 +0100   Thu, 22 Feb 2024 07:33:13 +0100   KubeletHasSufficientMemory   kubelet has sufficient memory available
  DiskPressure     False   Thu, 22 Feb 2024 07:38:31 +0100   Thu, 22 Feb 2024 07:33:13 +0100   KubeletHasNoDiskPressure     kubelet has no disk pressure
  PIDPressure      False   Thu, 22 Feb 2024 07:38:31 +0100   Thu, 22 Feb 2024 07:33:13 +0100   KubeletHasSufficientPID      kubelet has sufficient PID available
  Ready            False   Thu, 22 Feb 2024 07:38:31 +0100   Thu, 22 Feb 2024 07:33:13 +0100   KubeletNotReady              container runtime network not ready: NetworkRe
ady=false reason:NetworkPluginNotReady message:Network plugin returns error: cni plugin not initialized
# [ ... ]

Il manque bel et bien un CNI. Je vais alors installer Cilium (qui va également remplacer kube-proxy dans mon cas).

La documentation de Talos propose directement la commande pour installer Cilium en remplaçant kube-proxy. On va directement l’appliquer sur le cluster.

$ helm repo add cilium https://helm.cilium.io/
$ helm repo update
$ helm install \
    cilium \
    cilium/cilium \
    --version 1.15.0 \
    --namespace kube-system \
    --set ipam.mode=kubernetes \
    --set=kubeProxyReplacement=true \
    --set=securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
    --set=securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
    --set=cgroup.autoMount.enabled=false \
    --set=cgroup.hostRoot=/sys/fs/cgroup \
    --set=k8sServiceHost=localhost \
    --set=k8sServicePort=7445
NAME: cilium
LAST DEPLOYED: Fri Feb 23 09:43:35 2024
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
You have successfully installed Cilium with Hubble.

Your release version is 1.15.0.

For any further help, visit https://docs.cilium.io/en/v1.15/gettinghelp

Nos nœuds sont prêts à accueillir des pods !

Tous les nœuds sont ready

Définir les nœuds / endpoints

Depuis le début de cet article, nous précisons à chaque commande talosctl:

  • Une adresse IP d’un control-plane (endpoint) talos.
  • L’adresse IP de la machine sur laquelle nous exécutons la commande.
  • Le fichier de configuration talosconfig que nous avons généré (contenant le nécessaire pour s’authentifier sur le cluster).

Cela devient vite fastidieux, surtout si on a beaucoup de machines à gérer. Il est alors possible de définir ces informations dans un dossier ~/.talos comme le ferait kubectl avec ~/.kube.

talosctl --talosconfig=./talosconfig config endpoint 192.168.1.85 192.168.1.82 192.168.1.79 # Control plane
talosctl --talosconfig=./talosconfig config node 192.168.1.85 192.168.1.82 192.168.1.79 192.168.1.83 192.168.1.86 # controle plane + nodes
talosctl config merge ./talosconfig

Avec ces commandes, j’édite le fichier ./talosconfig pour ajouter les adresses IP des endpoints, et des machines du cluster, puis je fusionne ce fichier avec le fichier ~/.talos/config.

Information

Tout comme kubectl le permet : il est possible d’avoir plusieurs contextes pour gérer plusieurs clusters. Il suffit de rajouter un argument --context à la commande talosctl ou d’éditer le fichier ~/.talos/config pour rajouter un nouveau contexte.

Je n’ai plus à préciser l’adresse IP de l’endpoint ou de la machine sur laquelle j’exécute la commande, puis talosctl va aller chercher ces informations dans le fichier ~/.talos/config.

$ talosctl disks
NODE           DEV        MODEL           SERIAL   TYPE   UUID   WWID   MODALIAS      NAME   SIZE    BUS_PATH                                                                   SUBSYSTEM          READ_ONLY   SYSTEM_DISK
192.168.1.79   /dev/sda   QEMU HARDDISK   -        HDD    -      -      scsi:t-0x00   -      34 GB   /pci0000:00/0000:00:05.0/0000:01:01.0/virtio3/host2/target2:0:0/2:0:0:0/   /sys/class/block               *
192.168.1.85   /dev/sda   QEMU HARDDISK   -        HDD    -      -      scsi:t-0x00   -      34 GB   /pci0000:00/0000:00:05.0/0000:01:01.0/virtio3/host2/target2:0:0/2:0:0:0/   /sys/class/block               *
192.168.1.82   /dev/sda   QEMU HARDDISK   -        HDD    -      -      scsi:t-0x00   -      34 GB   /pci0000:00/0000:00:05.0/0000:01:01.0/virtio3/host2/target2:0:0/2:0:0:0/   /sys/class/block
# [ ... ]

Mettre à jour le cluster

Bien évidemment, Talos supporte le fait de mettre à jour le kubelet d’un nœud sans forcément devoir réinstaller le système complet.

Pour cela, l’utilitaire talosctl dispose d’un argument upgrade-k8s. Il suffit alors de préciser la version de kubelet que l’on souhaite installer et le nœud désigné sera mis à jour.

Avertissement

À savoir qu’il n’est pas possible de migrer de plusieurs versions majeures en un seul coup (ex, de 1.27 à 1.29). Il faudra alors mettre à jour chaque version majeure une par une (d’abord de 1.27 à 1.28, puis de 1.28 à 1.29).

Talos mettra donc à jour chaque nœud un par un, et le redémarrera (si nécessaire) pour appliquer les changements.

$ talosctl upgrade-k8s --to 1.29.1 -n 192.168.1.85 # Même s'il n'y a qu'un nœud précisé, ils seront tous mis à jour

Modifier la configuration d’un nœud

Il est possible à tout moment de modifier la configuration d’une des machines du cluster. C’est indispensable pour pouvoir mettre à jour les adresses IPs, ou rajouter des paramètres à une installation déjà existante.

Par exemple, si je veux changer le nom d’hôte de talos-8tc-18b en talos-controlplane-01, je peux le faire de plusieurs manières :

  • En éditant en interactif la configuration de la machine :
talosctl -n <IP1>,<IP2>,... edit machineconfig
  • En créant un patch.yaml et l’envoyant sur les nœuds :
- op: add
  path: /machine/network/hostname
  value: talos-controlplane-01
talosctl -n <IP1>,<IP2> patch machineconfig -p @patch.yaml
  • Ou bien en appliquant ce même patch en JSON directement dans la commande :
talosctl -n <IP> patch machineconfig -p '[{"op": "add", "path": "/machine/network/hostname", "value": "talos-controlplane-01"}]'

En fonction de la modification apportée, Talos va automatiquement redémarrer la machine pour appliquer la configuration (ou non, si ce n’est pas nécessaire).

Un fichier par machine avec talhelper

La configuration de talosctl est très pratique et agréable à utiliser. Seul le fichier patch.yaml est à sauvegarder sur un Git et les fichiers secret.yaml et talosconfig à garder en sécurité dans un vault ou équivalent (les autres fichiers controlplane.yaml et worker.yaml sont re-générables, la sauvegarde de ces fichiers est alors moins pertinente).

Mais je ne reste pas moins frustré par une limitation de talosctl :

  • En effet, il ne permet pas de générer un fichier de configuration par machine du cluster.

Ainsi, si je veux créer un cluster avec des machines drastiquement différentes, je vais devoir créer un fichier de configuration par machine (ce qui est fastidieux).

Pour palier à ces problèmes budimanjojo a développé talhelper.

talhelper est un programme en go qui répond à cette problématique : Il génère un fichier par machine du cluster à partir d’un unique fichier de configuration et va vous aider à générer les commandes pour appliquer la configuration sur les machines.

Utiliser talhelper

Comme dit précédemment, talhelper s’appuie sur un unique fichier de configuration : talconfig.yaml. Celui-ci va contenir les informations de chaque machine du cluster, à minima :

  • Le nom d’hôte ;
  • L’adresse IP ;
  • Le disque sur lequel installer talos.

De nombreux paramètres optionnels permettent de personnaliser la configuration de chaque machine. Par exemple, vous pouvez configurer des VIP, des routes, des disques additionnels, des extensions Talos (kata-containers, qemu-guest-agent, iscsi-tools), des patches pour la configuration de Talos, etc.

Pour créer ce fichier, j’utilise la template proposée sur le site de talhelper.

Remarque

À savoir que de nombreux paramètres ne sont pas valorisés dans la template. La documentation de talhelper est très correcte et même si un paramètre n’est pas pris en compte, il est possible de rajouter un patch qui sera appliqué lors de la génération de la configuration.

Par exemple, je souhaite désactiver l’usage du discovery dans mon talconfig, problème : talhelper ne possède aucun paramètre pour désactiver ça. Je peux alors créer un ‘patch’ qui sera appliqué lorsque talhelper génèrera la configuration.

Pour cela, il me suffit de mettre ces lignes dans mon talconfig.yaml :

patches:
  - |-
    - op: add
      path: /cluster/discovery/enabled
      value: false    

Mon fichier talconfig.yaml ressemble à ceci :

---
clusterName: homelab-talos-dev
talosVersion: v1.6.5
kubernetesVersion: v1.27.2
endpoint: https://192.168.1.85:6443
allowSchedulingOnMasters: true
cniConfig:
  name: none
patches:
  - |-
    - op: add
      path: /cluster/discovery/enabled
      value: true
    - op: replace
      path: /machine/network/kubespan
      value:
        enabled: true    
nodes:
  - hostname: controlplane-01
    ipAddress: 192.168.1.85
    controlPlane: true
    arch: amd64
    installDisk: /dev/sda
    nameservers:
      - 1.1.1.1
      - 8.8.8.8
    networkInterfaces:
      - interface: eth0
        addresses:
          - 192.168.1.85/24
        routes:
          - network: 0.0.0.0/0
            gateway: 192.168.1.1
        vip:
          ip: 192.168.1.80
  - hostname: controlplane-02
    ipAddress: 192.168.1.79
    controlPlane: true
    arch: amd64
    installDisk: /dev/sda
    nameservers:
      - 1.1.1.1
      - 8.8.8.8
    networkInterfaces:
      - interface: eth0
        addresses:
          - 192.168.1.79/24
        routes:
          - network: 0.0.0.0/0
            gateway: 192.168.1.1
        vip:
          ip: 192.168.1.80
  - hostname: controlplane-03
    ipAddress: 192.168.1.82
    controlPlane: true
    arch: amd64
    installDisk: /dev/sda
    nameservers:
      - 1.1.1.1
      - 8.8.8.8
    networkInterfaces:
      - interface: eth0
        addresses:
          - 192.168.1.82/24
        routes:
          - network: 0.0.0.0/0
            gateway: 192.168.1.1
        vip:
          ip: 192.168.1.80
  - hostname: worker-01
    ipAddress: 192.168.1.83
    controlPlane: false
    arch: amd64
    installDisk: /dev/sda
  - hostname: worker-02
    ipAddress: 192.168.1.86
    controlPlane: false
    arch: amd64
    installDisk: /dev/sda
controlPlane:
  patches:
    - |-
      - op: add
        path: /cluster/proxy/disabled
        value: true      
  schematic:
    customization:
      extraKernelArgs:
        - net.ifnames=0
      systemExtensions:
        officialExtensions:
#          - siderolabs/kata-containers # Disponible à la sortie de Talos 1.7
          - siderolabs/qemu-guest-agent
          - siderolabs/iscsi-tools
worker:
  schematic:
    customization:
      extraKernelArgs:
        - net.ifnames=0
      systemExtensions:
       officialExtensions:
#         - siderolabs/kata-containers # Disponible à la sortie de Talos 1.7
         - siderolabs/qemu-guest-agent
         - siderolabs/iscsi-tools

On commence par générer le fichier talsecret.yaml. Celui-ci est strictement équivalent au fichier secret.yaml généré par talosctl (il est donc possible d’utiliser celui que nous avons généré précédemment).

talhelper gensecret > talsecret.yaml # ou talosctl gen secrets 

Ensuite, on génère les fichiers de configuration pour chaque machine du cluster.

$ talhelper genconfig 
  There are issues with your talhelper config file:
  field: "talosVersion"
    * WARNING: "v1.6.5" might not be compatible with this Talhelper version you're using
  generated config for controlplane-01 in ./clusterconfig/homelab-talos-dev-controlplane-01.yaml
  generated config for controlplane-02 in ./clusterconfig/homelab-talos-dev-controlplane-02.yaml
  generated config for controlplane-03 in ./clusterconfig/homelab-talos-dev-controlplane-03.yaml
  generated config for worker-01 in ./clusterconfig/homelab-talos-dev-worker-01.yaml
  generated config for worker-01 in ./clusterconfig/homelab-talos-dev-worker-01.yaml
  generated client config in ./clusterconfig/talosconfig
  generated .gitignore file in ./clusterconfig/.gitignore

Information

talhelper va de lui-même générer un fichier .gitignore qui permet de prevenir un push des fichiers contenant les clés vers un Git.

Dans mon cas, il contient les lignes suivantes:

homelab-talos-dev-controlplane-01.yaml
homelab-talos-dev-controlplane-02.yaml
homelab-talos-dev-controlplane-03.yaml
homelab-talos-dev-worker-01.yaml
talosconfig

La dernière étape est d’apply les fichiers pour lancer l’installation du cluster. talhelper permet aussi de générer les commandes bash appliquant chaque fichier sur la bonne adresse IP.

Cela évite d’appliquer un fichier de configuration sur une mauvaise machine.

$ talhelper gencommand apply --extra-flags --insecure
  There are issues with your talhelper config file:
  field: "talosVersion"
    * WARNING: "v1.6.5" might not be compatible with this Talhelper version you're using
  talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.85 --file=./clusterconfig/homelab-talos-dev-controlplane-01.yaml --insecure;
  talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.79 --file=./clusterconfig/homelab-talos-dev-controlplane-02.yaml --insecure;
  talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.82 --file=./clusterconfig/homelab-talos-dev-controlplane-03.yaml --insecure;
  talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.83 --file=./clusterconfig/homelab-talos-dev-worker-01.yaml --insecure;
  talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.86 --file=./clusterconfig/homelab-talos-dev-worker-01.yaml --insecure;

Information

le flag --insecure permet d’apply sans préciser de clé de chiffrement, c’est obligatoire lorsque le nœud n’est pas encore installé. Par défaut, talhelper ne l’ajoute pas dans les commandes générées, c’est pourquoi j’utilise l’argument --extra-flags pour rajouter ce flag.

Je peux également lui demander de me fournir les commandes de bootstrap ou de récupération du kubeconfig (moins intéressant mais c’est pratique).

$ talhelper gencommand bootstrap
  talosctl bootstrap --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.85;
$ talhelper gencommand kubeconfig 
  talosctl kubeconfig --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.85;

Mais Talos ne s’arrête pas là ! Il propose également des fonctionnalités avancées pour faire des clusters multi-régions. Voyons ça de plus près.

Cluster Across-Region : KubeSpan

En plus de Talos, SideroLabs souhaite répondre au besoin des entreprises de faire des clusters hybrides et/ou multi-regions sans devoir exposer l’API d’un control-plane. Une solution courante est d’établir un VPN entre les différentes régions, mais cela peut être coûteux et complexe à mettre en place.

On pourrait par exemple faire un cluster où les control-planes seraient dans une infrastructure on-premise, et les workers sur un cloud provider (pour les workloads qui nécessitent de la puissance de calcul).

Cette fonctionnalité se nomme KubeSpan et est spécifique à Talos.

KubeSpan est basé sur Wireguard et utilise un service hébergé par Talos: Discovery.

Pour utiliser cette fonctionnalité, il faut bien sûr qu’elle soit activée dans la configuration de Talos (à /machine/network/kubespan/enabled).

Si vous avez un cluster déjà en place, vous pouvez envoyer ce fichier patch.yaml sur les machines pour activer le service discovery et KubeSpan.

machine:
  network:
    kubespan:
      enabled: true
cluster:
  discovery:
    enabled: true

Dans la pratique, Discovery va authentifier les machines du même cluster via le cluster id/secret. Ces valeurs sont générées automatiquement par talosctl (même si le kubspan/discovery est désactivé).

cluster:
  id: vPBXurnpNDVRVkF_DrM4SvA7YNV6xcwt7mnkSKOQQoI=
  secret: +LoNuCGKUDWW0Efo8tph/XkbFI7ffS+w2slsFell+bY=

Ces tokens sont à garder en sécurité, ils permettent à un nœud d’initier une connexion avec le service discovery vers un autre nœud (même si ça ne suffit pas pour s’enregistrer dans le cluster, il convient de garder ces tokens en sécurité).

Derrière le service discovery, il y a un service de Wireguard qui permet aux nœuds de communiquer entre eux sans avoir à faire du NAT pour exposer l’API de Kubernetes.

Je vais donc activer cette fonctionnalité sur mon cluster. Mon objectif est d’avoir les control-planes chez moi, et les workers sur Vultr.

Schéma Objectif

Astuce

Pour réinitialiser le cluster, talhelper propose une commande talhelper gencommand reset qui permet de supprimer les nœuds de ce dernier :

$ talhelper gencommand reset --extra-flags --graceful=false
There are issues with your talhelper config file:
field: "talosVersion"
  * WARNING: "v1.6.5" might not be compatible with this Talhelper version you're using
  talosctl reset --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.85 --graceful=false;
  talosctl reset --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.79 --graceful=false;
  talosctl reset --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.82 --graceful=false;
  # [ ... ]

Créer des nœuds sur Vultr

Vultr est un fournisseur de cloud qui propose des instances à des prix très compétitifs. Talos le présente comme un fournisseur de cloud compatible, donc je vais essayer de créer des nœuds worker sur Vultr.

Je me suis beaucoup appuyé sur la documentation de Vultr de Talos pour cette partie.

La première étape est d’envoyer une ISO de Talos sur Vultr. Pour cela, j’utilise la cli proposée par Vultr vultr-cli.

vultr-cli iso create --url https://github.com/siderolabs/talos/releases/download/v1.6.4/metal-amd64.iso

ID					FILE NAME		SIZE		STATUS		MD5SUM					SHA512SUM														DATE CREATED
61f5fdbe-b773-4aff-b3a5-9c1ae6eee70a	metal-amd64.iso		85168128	complete	65f0eadf019086c4b11f57977d5521b4	cd8f887b91a154132a55dc752a8718fd5ef37177963a5c10c1be295382264b4305757442eea0fa06bff98208d897512f056d3e9701f24678ffe35ad30336fd7a	2024-02-23T11:44:27+00:00

On garde l’ID de l’ISO qui servira à créer notre instance Talos (dans mon cas, 61f5fdbe-b773-4aff-b3a5-9c1ae6eee70a).

for i in 1 2; do
  vultr-cli instance create \
          --plan vc2-2c-4gb \
          --region cdg \
          --iso "61f5fdbe-b773-4aff-b3a5-9c1ae6eee70a" \
          --host talos-k8s-${i} \
          --label "Talos Kubernetes" \
          --tags talos,kubernetes \
          --ipv6
done

Après 1-2 minutes, les instances sont prêtes. On peut alors récupérer les adresses IP des nœuds et les ajouter à notre configuration.

Création des instances vultr

$ vultr-cli instance list
ID					IP		LABEL			OS			STATUS	REGION	CPU	RAM	DISK	BANDWIDTH	TAGS
ac9150e3-5b07-4d0c-b6a7-4ab1eedde7c3	45.32.146.110	Talos Kubernetes	Custom Installed	active	cdg	2	4096	80	4		[kubernetes, talos]
21fec645-f391-48cb-87a8-8ea11c223afc	217.69.4.72	Talos Kubernetes	Custom Installed	active	cdg	2	4096	80	4		[kubernetes, talos]

On va noter ces adresses IP et créer notre fichier talconfig.yaml avec les informations de nos nœuds.

---
clusterName: cross-region-cluster
talosVersion: v1.6.5
kubernetesVersion: v1.29.1
endpoint: https://192.168.1.85:6443
allowSchedulingOnMasters: true
cniConfig:
  name: none
patches:
  - |-
    - op: add
      path: /cluster/discovery/enabled
      value: true
    - op: replace
      path: /machine/network/kubespan
      value:
        enabled: true    
nodes:
  - hostname: controlplane-01
    ipAddress: 192.168.1.85
    controlPlane: true
    arch: amd64
    installDisk: /dev/sda
    networkInterfaces:
      - interface: eth0
        addresses:
          - 192.168.1.85/24
        routes:
          - network: 0.0.0.0/0
            gateway: 192.168.1.1
        vip:
          ip: 192.168.1.80
  - hostname: controlplane-02
    ipAddress: 192.168.1.79
    controlPlane: true
    arch: amd64
    installDisk: /dev/sda
    networkInterfaces:
      - interface: eth0
        addresses:
          - 192.168.1.79/24
        routes:
          - network: 0.0.0.0/0
            gateway: 192.168.1.1
        vip:
          ip: 192.168.1.80
  - hostname: controlplane-03
    ipAddress: 192.168.1.82
    controlPlane: true
    arch: amd64
    installDisk: /dev/sda
    networkInterfaces:
      - interface: eth0
        addresses:
          - 192.168.1.82/24
        routes:
          - network: 0.0.0.0/0
            gateway: 192.168.1.1
        vip:
          ip: 192.168.1.80
  - hostname: vultr-worker-01
    ipAddress: 45.32.146.110
    controlPlane: false
    arch: amd64
    installDisk: /dev/vda
  - hostname: vultr-worker-02
    ipAddress: 217.69.4.72
    controlPlane: false
    arch: amd64
    installDisk: /dev/vda
controlPlane:
  patches:
    - |-
      - op: add
        path: /cluster/proxy/disabled
        value: true      
  schematic:
    customization:
      extraKernelArgs:
        - net.ifnames=0
      systemExtensions:
        officialExtensions:
          - siderolabs/qemu-guest-agent
          - siderolabs/iscsi-tools
worker:
  schematic:
    customization:
      extraKernelArgs:
        - net.ifnames=0
      systemExtensions:
       officialExtensions:
         - siderolabs/iscsi-tools

⚠️ Attention, sur Vultr, le disque d’installation est /dev/vda et non /dev/sda.

$ talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.85 --file=./clusterconfig/cross-region-cluster-controlplane-01.yaml --insecure;
$ talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.79 --file=./clusterconfig/cross-region-cluster-controlplane-02.yaml --insecure;
$ talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.82 --file=./clusterconfig/cross-region-cluster-controlplane-03.yaml --insecure;
$ talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=45.32.146.110 --file=./clusterconfig/cross-region-cluster-vultr-worker-01.yaml --insecure;
$ talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=217.69.4.72 --file=./clusterconfig/cross-region-cluster-vultr-worker-02.yaml --insecure;
$ talosctl bootstrap --talosconfig=./clusterconfig/talosconfig --nodes=192.168.1.85;

J’installe ensuite Cilium sur mon cluster :

helm install \
    cilium \
    cilium/cilium \
    --version 1.15.0 \
    --namespace kube-system \
    --set ipam.mode=kubernetes \
    --set=kubeProxyReplacement=true \
    --set=securityContext.capabilities.ciliumAgent="{CHOWN,KILL,NET_ADMIN,NET_RAW,IPC_LOCK,SYS_ADMIN,SYS_RESOURCE,DAC_OVERRIDE,FOWNER,SETGID,SETUID}" \
    --set=securityContext.capabilities.cleanCiliumState="{NET_ADMIN,SYS_ADMIN,SYS_RESOURCE}" \
    --set=cgroup.autoMount.enabled=false \
    --set=cgroup.hostRoot=/sys/fs/cgroup \
    --set=k8sServiceHost=localhost \
    --set=k8sServicePort=7445

Mon cluster possède maintenant cinq nœuds, trois control-planes sur mon réseau local et deux workers sur Vultr.

$ kubectl get nodes
NAME              STATUS   ROLES           AGE     VERSION
controlplane-01   Ready    control-plane   6m43s   v1.29.1
controlplane-02   Ready    control-plane   6m40s   v1.29.1
controlplane-03   Ready    control-plane   6m42s   v1.29.1
vultr-worker-01   Ready    <none>          6m40s   v1.29.1
vultr-worker-02   Ready    <none>          6m45s   v1.29.1

Ajouter un nœud au cluster multi région

Je vais ajouter un sixième nœud à mon cluster, mais cette fois-ci sur mon infrastructure virtualisée chez OVH (dans un datacenter à Roubaix).

J’y ai déjà une machine virtuelle prête et en attente d’une configuration Talos. Dans son réseau privé, elle dispose de l’adresse IP 192.168.128.112.

Je l’ajoute alors à mon fichier talconfig.yaml :

  - hostname: ovh-worker-03
    ipAddress: 192.168.128.112
    controlPlane: false
    arch: amd64
    installDisk: /dev/sda

Je me connecte au bastion (sur lequel j’ai déjà configuré talosctl), et j’applique la configuration sur la machine.

$ talosctl apply-config --talosconfig=./clusterconfig/talosconfig --nodes=192.168.128.112 --file=./clusterconfig/cross-region-cluster-ovh-worker-03.yaml --insecure;

En l’état, ça ne fonctionne pas. La raison est que pour que le worker rejoigne le cluster, il doit pouvoir communiquer avec les controlplanes. Cette communication est faite avec le port 51820 (en UDP), le worker ou le control-plane doivent pouvoir se connecter au port de l’autre pour que la connexion soit établie.

$ talosctl get kubespanpeerstatus -n 192.168.1.85
192.168.1.85       kubespan   KubeSpanPeerStatus   DT8v2yiopnU6aPp3nxJurX/HfFTWy4dj1haWXINXjhc=   60   vultr-worker-01   45.32.146.110:51820   up   1723500   3403308
192.168.1.85       kubespan   KubeSpanPeerStatus   F2nTY5mP5aVBKRz5V1Bl5Ba93+G6EWY12SKT5PSnpFI=   62   vultr-worker-02   217.69.4.72:51820   up   449052   641764
192.168.1.85       kubespan   KubeSpanPeerStatus   UelYhx04oUfdS5X+yNf+1dOsvTzfBZc+WrH/GY4HKWU=   23   ovh-worker-03   5.39.75.213:51820   unknown   0   17168
192.168.1.85       kubespan   KubeSpanPeerStatus   Vbvp05aKjlLhXb6KD2zo5C6aneRQEpsLEZ/AQLjXlmU=   62   controlplane-03   192.168.1.82:51820   up   18745312   37628360
192.168.1.85       kubespan   KubeSpanPeerStatus   o9BLRyCBgMyJoNbEqdaD16PY2sPSLkXUaj4Vy+qBl3U=   63   controlplane-02   192.168.1.79:51820   up   17769228   27982564

Actuellement, les nœuds tentent des requêtes vers le port 51820 du nœud ovh-worker-03. Le client KubeSpan va essayer toutes les interfaces (IPv4 et IPv6) pour se connecter au nœud mais… impossible puisque je n’ai pas ouvert le port depuis mon routeur PFSense.

Une ouverture de port plus tard…

Connexion impossible

$ talosctl get kubespanpeerstatus -n 192.168.1.85 
NODE           NAMESPACE   TYPE                 ID                                             VERSION   LABEL             ENDPOINT              STATE   RX         TX
192.168.1.85   kubespan    KubeSpanPeerStatus   DT8v2yiopnU6aPp3nxJurX/HfFTWy4dj1haWXINXjhc=   75        vultr-worker-01   45.32.146.110:51820   up      2169468    4172648
192.168.1.85   kubespan    KubeSpanPeerStatus   F2nTY5mP5aVBKRz5V1Bl5Ba93+G6EWY12SKT5PSnpFI=   77        vultr-worker-02   217.69.4.72:51820     up      547696     748732
192.168.1.85   kubespan    KubeSpanPeerStatus   UelYhx04oUfdS5X+yNf+1dOsvTzfBZc+WrH/GY4HKWU=   38        ovh-worker-03     5.39.75.213:51820     up      314900     997748
192.168.1.85   kubespan    KubeSpanPeerStatus   Vbvp05aKjlLhXb6KD2zo5C6aneRQEpsLEZ/AQLjXlmU=   77        controlplane-03   192.168.1.82:51820    up      23345384   45761364
192.168.1.85   kubespan    KubeSpanPeerStatus   o9BLRyCBgMyJoNbEqdaD16PY2sPSLkXUaj4Vy+qBl3U=   78        controlplane-02   192.168.1.79:51820    up      22072656   33896416
$ kubectl get nodes
NAME              STATUS   ROLES           AGE    VERSION
controlplane-01   Ready    control-plane   33m    v1.29.1
controlplane-02   Ready    control-plane   33m    v1.29.1
controlplane-03   Ready    control-plane   33m    v1.29.1
ovh-worker-03     Ready    <none>          5m6s   v1.29.1
vultr-worker-01   Ready    <none>          33m    v1.29.1
vultr-worker-02   Ready    <none>          34m    v1.29.1

Ainsi, ovh-worker-03 a bel et bien rejoint le cluster, et est prêt à accueillir des applications !

Mon cluster de “production” étant constitué de 3 Raspberry Pi, je peux à présent ajouter des nœuds provenant de différents fournisseurs de cloud, et ce, sans avoir à me soucier de la configuration réseau.

Architecture du cluster multi-région

Benchmark réseau entre les nœuds

Forcément, avec un cluster multi-région, on peut se poser la question des performances réseau. J’ai donc utilisé le projet k8s-Bench-Suite proposant un script pour benchmarker les performances réseau entre les nœuds.

À savoir que les performances réseaux sont très dépendantes de la qualité de la connexion entre les nœuds.

Mon débit internet est de ~300Mbps (en symétrique) avec l’offre Sosh Fibre.

Mon débit internet :

   Speedtest by Ookla

      Server: LASOTEL - Lyon (id: 42393)
         ISP: Orange
Idle Latency:     2.01 ms   (jitter: 0.08ms, low: 1.93ms, high: 2.08ms)
    Download:   283.37 Mbps (data used: 208.0 MB)
                  9.38 ms   (jitter: 0.81ms, low: 5.76ms, high: 15.31ms)
      Upload:   297.00 Mbps (data used: 356.2 MB)
                 37.01 ms   (jitter: 1.11ms, low: 13.53ms, high: 41.06ms)
 Packet Loss:     0.0%

Débit des serveurs Vultr :

   Speedtest by Ookla

      Server: Nextmap - LeKloud - Paris (id: 33869)
         ISP: Vultr
Idle Latency:     1.39 ms   (jitter: 0.73ms, low: 0.94ms, high: 3.18ms)
    Download:  1257.66 Mbps (data used: 1.5 GB)
                  6.56 ms   (jitter: 19.39ms, low: 1.08ms, high: 452.37ms)
      Upload:  3550.69 Mbps (data used: 3.2 GB)
                  3.58 ms   (jitter: 4.17ms, low: 1.16ms, high: 74.61ms)
 Packet Loss:     0.0%

( On voit immédiatement où sera le goulot d’étranglement dans les performances réseau.)

Lançons le benchmark entre deux nœuds dans le même réseau (controlplane-01 et controlplane-02) :

=========================================================
 Benchmark Results
=========================================================
 Name            : knb-1012758
 Date            : 2024-02-24 07:49:12 UTC
 Generator       : knb
 Version         : 1.5.0
 Server          : controlplane-02
 Client          : controlplane-01
 UDP Socket size : auto
=========================================================
  Discovered CPU         : QEMU Virtual CPU version 2.5+
  Discovered Kernel      : 6.1.78-talos
  Discovered k8s version :
  Discovered MTU         : 1500
  Idle :
    bandwidth = 0 Mbit/s
    client cpu = total 8.23% (user 3.65%, nice 0.00%, system 3.19%, iowait 0.56%, steal 0.83%)
    server cpu = total 7.11% (user 3.14%, nice 0.00%, system 2.77%, iowait 0.60%, steal 0.60%)
    client ram = 1231 MB
    server ram = 1179 MB
  Pod to pod :
    TCP :
      bandwidth = 287 Mbit/s
      client cpu = total 70.99% (user 4.76%, nice 0.00%, system 45.39%, iowait 0.40%, steal 20.44%)
      server cpu = total 73.55% (user 4.42%, nice 0.00%, system 51.63%, iowait 0.22%, steal 17.28%)
      client ram = 1239 MB
      server ram = 1182 MB
    UDP :
      bandwidth = 194 Mbit/s
      client cpu = total 72.85% (user 8.15%, nice 0.00%, system 52.78%, iowait 0.18%, steal 11.74%)
      server cpu = total 65.34% (user 6.35%, nice 0.00%, system 42.11%, iowait 0.17%, steal 16.71%)
      client ram = 1241 MB
      server ram = 1180 MB
  Pod to Service :
    TCP :
      bandwidth = 288 Mbit/s
      client cpu = total 72.91% (user 5.87%, nice 0.00%, system 45.40%, iowait 0.20%, steal 21.44%)
      server cpu = total 75.97% (user 4.40%, nice 0.00%, system 52.85%, iowait 0.19%, steal 18.53%)
      client ram = 1247 MB
      server ram = 1182 MB
    UDP :
      bandwidth = 194 Mbit/s
      client cpu = total 72.61% (user 6.79%, nice 0.00%, system 52.33%, iowait 0.41%, steal 13.08%)
      server cpu = total 63.99% (user 6.40%, nice 0.00%, system 40.88%, iowait 0.35%, steal 16.36%)
      client ram = 1247 MB
      server ram = 1180 MB
=========================================================

Un débit de 288 Mbit/s en TCP en pod-to-service (ce qui réprésente la majorité des communications dans un cluster Kubernetes) est très bon.

Voyons maintenant les performances entre deux nœuds dans des réseaux différents (controlplane-01 et vultr-worker-01) :

=========================================================
 Benchmark Results
=========================================================
 Name            : knb-1020457
 Date            : 2024-02-24 08:13:47 UTC
 Generator       : knb
 Version         : 1.5.0
 Server          : vultr-worker-04
 Client          : controlplane-01
 UDP Socket size : auto
=========================================================
  Discovered CPU         : AMD EPYC-Rome Processor
  Discovered Kernel      : 6.1.78-talos
  Discovered k8s version :
  Discovered MTU         : 1500
  Idle :
    bandwidth = 0 Mbit/s
    client cpu = total 8.09% (user 3.55%, nice 0.00%, system 2.95%, iowait 0.75%, steal 0.84%)
    server cpu = total 2.52% (user 1.42%, nice 0.00%, system 0.92%, iowait 0.00%, steal 0.18%)
    client ram = 1232 MB
    server ram = 442 MB
  Pod to pod :
    TCP :
      bandwidth = 22.0 Mbit/s
      client cpu = total 14.33% (user 3.72%, nice 0.00%, system 8.78%, iowait 0.56%, steal 1.27%)
      server cpu = total 17.78% (user 1.69%, nice 0.00%, system 14.95%, iowait 0.00%, steal 1.14%)
      client ram = 1237 MB
      server ram = 443 MB
    UDP :
      bandwidth = 31.4 Mbit/s
      client cpu = total 68.58% (user 6.66%, nice 0.00%, system 58.55%, iowait 0.37%, steal 3.00%)
      server cpu = total 19.31% (user 1.89%, nice 0.00%, system 16.27%, iowait 0.00%, steal 1.15%)
      client ram = 1238 MB
      server ram = 445 MB
  Pod to Service :
    TCP :
      bandwidth = 15.0 Mbit/s
      client cpu = total 13.69% (user 3.63%, nice 0.00%, system 8.24%, iowait 0.70%, steal 1.12%)
      server cpu = total 14.28% (user 1.42%, nice 0.00%, system 11.85%, iowait 0.00%, steal 1.01%)
      client ram = 1241 MB
      server ram = 444 MB
    UDP :
      bandwidth = 29.5 Mbit/s
      client cpu = total 70.13% (user 6.76%, nice 0.00%, system 60.08%, iowait 0.42%, steal 2.87%)
      server cpu = total 18.93% (user 2.07%, nice 0.00%, system 15.43%, iowait 0.00%, steal 1.43%)
      client ram = 1238 MB
      server ram = 446 MB

On voit tout de suite que les performances entre deux nœuds dans des datacenters différents sont bien plus faibles. Dès qu’on passe par le réseau internet, les performances sont divisées par 10.

J’imagine qu’avec un cluster complètement en cloud, les performances seraient bien meilleures.

Avertissement

Bien sûr, ces benchmarks sont à prendre avec des pincettes. Il faudrait créer un réel scénario de test pour évaluer les performances réseau en testant des applications réelles, la fiabilité des connexions, s’il n’y a pas de pertes de paquets, etc.

Ce n’est qu’un test de performance réseau brut, il ne faut donc pas en tirer de conclusions hâtives.

Conclusion

Talos est un vrai coup de cœur pour moi. Il est simple à prendre en main, et propose des fonctionnalités avancés. La documentation est très complète et la communauté semble plutôt active.

Les quelques défauts de l’utilitaire talosctl sont largement compensés par talhelper. J’ai hâte de voir les futures évolutions de Talos (j’ai d’ailleurs entendu dire que les katacontainers étaient en cours d’intégration à Talos 1.7).

Les possibilités de Talos sont encore nombreuses, et je suis loin d’avoir tout exploré (on pourrait par exemple parler de Omni, le SaaS de SideroLabs pour déployer des noeuds en PXE, ou encore de l’intégration de Talos avec certains clouds providers…).

En attendant de voir tout ça, je vais continuer à utiliser Talos pour mes projets personnels en espérant que la communauté grandisse et que le projet continue d’évoluer.