Créer et gérer plusieurs clusters Kubernetes Talos avec Docker

Créer et gérer plusieurs clusters Kubernetes Talos sur Docker avec communication inter-clusters

RV
Rémi Verchère
Platform & Cloud Native
10 min de lecture

J'ai récemment eu besoin de créer une architecture multi-clusters en local pour une démo ArgoCD. J'ai utilisé Talos Linux pour créer rapidement des clusters Kubernetes avec Docker sur mon laptop, mais j'ai rencontré quelques surprises lors de la mise en place d'un tel setup. Voici quelques pistes pour s'en sortir.

Préambule

Dans cet article on va causer réseau, avec des IPs pour joindre les divers nodes, qu'on devra définir. Voici un récapitulatif pour mon setup :

ClusterNodeIP réseau dédiéIP réseau partagé
cluster-1cluster-1-controlplane-110.5.10.2172.30.0.11
cluster-1cluster-1-worker-110.5.10.3172.30.0.12
cluster-2cluster-2-controlplane-110.5.20.1172.30.0.21
cluster-2cluster-2-worker-110.5.20.2172.30.0.22

Aussi, vous trouverez les scripts pour automatiser tout ce que je détaille dans l'article ici, utilisé pour ma conférence sur ArgoCD multi-clusters : dépôt git.

On aura besoin de la cli talosctl. Pour information je suis en v1.11.5 au moment de la publication de l'article :

$ talosctl version
Client:
        Tag:         v1.11.5
        SHA:         undefined
        Built:       2025-11-06T12:35:51Z
        Go version:  go1.25.4
        OS/Arch:     darwin/arm64

Création de plusieurs clusters Talos

Commençons doucement : je veux créer deux clusters Talos locaux qui doivent communiquer entre eux pour tester ArgoCD en mode multi-clusters. Docker permet de créer 1 cluster Kind avec Docker Desktop, mais ici j'en ai besoin de plusieurs, autant m'amuser avec Talos.

La création de clusters est très simple si l'on suit la documentation. Chaque cluster devra alors avoir son propre nom et propre CIDR :

  • Nom : cluster-1
  • CIDR : 10.5.10.0/24
  • Version : v1.33.4
$ talosctl cluster create --name cluster-1 --kubernetes-version v1.33.4 --cidr 10.5.10.0/24
validating CIDR and reserving IPs
generating PKI and tokens
creating state directory in "/path/to/talos/clusters/cluster-1"
creating network cluster-1
creating controlplane nodes
creating worker nodes
waiting for API
bootstrapping cluster
waiting for etcd to be healthy: OK
waiting for etcd members to be consistent across nodes: OK
waiting for etcd members to be control plane nodes: OK
waiting for apid to be ready: OK
waiting for [...]: OK
waiting for coredns to report ready: OK
waiting for all k8s nodes to report schedulable: OK
 
merging kubeconfig into "/path/to/kubeconfig"
PROVISIONER           docker
NAME                  cluster-1
NETWORK NAME          cluster-1
NETWORK CIDR          10.5.10.0/24
NETWORK GATEWAY       10.5.10.1
NETWORK MTU           1500
KUBERNETES ENDPOINT   https://127.0.0.1:62233
 
NODES:
 
NAME                        TYPE           IP          CPU    RAM      DISK
/cluster-1-controlplane-1   controlplane   10.5.10.2   2.00   2.1 GB   -
/cluster-1-worker-1         worker         10.5.10.3   2.00   2.1 GB   -
$ talosctl cluster create --name cluster-2 --kubernetes-version v1.33.4 --cidr 10.5.20.0/24
[...]

Après quelques minutes, j'ai mes deux clusters opérationnels :

$ kubectl config get-contexts
CURRENT   NAME              CLUSTER     AUTHINFO          NAMESPACE
          admin@cluster-1   cluster-1   admin@cluster-1   default
*         admin@cluster-2   cluster-2   admin@cluster-2   default
 
$ NAME                       STATUS   ROLES           AGE     VERSION
cluster-1-controlplane-1   Ready    control-plane   4m54s   v1.33.4
cluster-1-worker-1         Ready    <none>          4m54s   v1.33.4

Parfait ! Maintenant, essayons de faire communiquer les deux clusters.

Visibilité inter-clusters

Lorsqu'on a créé les clusters, on a défini pour chacun un CIDR : 10.5.10.0/24 et 10.5.20.0/24, ce qui correspond à 2 networks Docker :

$ docker network ls
NETWORK ID     NAME                    DRIVER    SCOPE
f2c1d59b61f2   cluster-1               bridge    local
c31015d9e6d3   cluster-2               bridge    local
 
$ docker network inspect cluster-1 -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
10.5.10.0/24
 
$ docker network inspect cluster-2 -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
10.5.20.0/24

Le problème est que chaque cluster a son propre réseau Docker isolé. Les conteneurs du cluster-1 ne peuvent pas joindre le réseau du cluster-2.

Réseaux Docker isolés

Création d'un réseau partagé

Pour permettre la communication entre ces 2 clusters, il faut créer un réseau Docker commun et y connecter les nœuds de chaque cluster.

Créons d'abord un réseau partagé talos-shared :

$ docker network create --driver bridge --subnet=172.30.0.0/24 talos-shared
3e1f0e92331f7078ee72cd0cfa99a01bf35c9f012efc52e34b10fc3563e3d795
 
$ docker network ls
f2c1d59b61f2   cluster-1               bridge    local
c31015d9e6d3   cluster-2               bridge    local
3e1f0e92331f   talos-shared            bridge    local

Maintenant, connectons les nœuds du cluster-1 à ce réseau :

$ docker network connect --ip 172.30.0.11 talos-shared cluster-1-controlplane-1
$ docker network connect --ip 172.30.0.12 talos-shared cluster-1-worker-1

Et ceux du cluster-2 :

$ docker network connect --ip 172.30.0.21 talos-shared cluster-2-controlplane-1
$ docker network connect --ip 172.30.0.22 talos-shared cluster-2-worker-1

Testons la connectivité réseau avec kubectl debug depuis le cluster 1 vers le 2 :

kubectl --context admin@cluster-1 debug -n kube-system -it --image alpine node/cluster-1-worker-1 -- ping -c 2 172.30.0.21
64 bytes from 172.30.0.21: seq=1 ttl=64 time=0.529 ms
 
--- 172.30.0.21 ping statistics ---
2 packets transmitted, 2 packets received, 0% packet loss
round-trip min/avg/max = 0.092/0.310/0.529 ms

Excellent ! Les nœuds se voient maintenant.

Réseau Docker partagé

Maintenant, tentons de nous connecter à l'API server du cluster-2 depuis le cluster-1. On va récupérer son IP :

$ kubectl --context admin@cluster-2 config view --minify -o jsonpath='{.clusters[0].cluster.server}'
https://127.0.0.1:62406

Bon, ça démarre mal... 127.0.0.1 est l'IP locale, pas sûr que ça fonctionne. En effet, lors de la création des clusters Talos + Docker, on passe par le host local pour accéder à l'API Server. Bon, on va quand même tenter avec l'IP du réseau partagé 172.30.0.21 !

Accédons à cette IP depuis un pod du cluster-1 sur le port par défaut de l'api-server, port 6443 :

kubectl --context admin@cluster-1 debug -n kube-system -it --image alpine node/cluster-1-worker-1 -- /bin/sh
/ # apk add curl
/ # curl -k https://172.30.0.21:6443
{
  "kind": "Status",
  "apiVersion": "v1",
  "metadata": {},
  "status": "Failure",
  "message": "Unauthorized",
  "reason": "Unauthorized",
  "code": 401
}/ #

Ok, on arrive à joindre l'API Server du cluster-2 depuis le cluster-1, on a une erreur 401 mais au moins ça répond !

Maintenant allons plus loin avec une commande kubectl. On édite le kubeconfig pour que le serveur soit bien server: https://172.30.0.21:6443, puis on exécute une commande depuis un conteneur dans le même sous-réseau pour valider l'accès à l'api-server :

$ docker run --network talos-shared -v ${PWD}:/tmp alpine/k8s:1.33.4 kubectl --kubeconfig=/tmp/kubeconfig --context admin@cluster-2 get nodes
pod:/# kubectl get nodes
NAME                       STATUS   ROLES           AGE   VERSION
cluster-2-controlplane-1   Ready    control-plane   73m   v1.33.4
cluster-2-worker-1         Ready    <none>          74m   v1.33.4

Ok, ça marche !

Bon 🤔... quand j'avais testé pour ma démo avec ArgoCD, j'avais une erreur sur les Subject Alternative Names (certSANs), car Talos a généré un certificat seulement avec les 2 IPs initiales : celle en 10.5.20.2 et 127.0.0.1. Si vous avez une erreur d'IP, alors on va recréer les clusters avec les bonnes SANs.

Validité du certificat de l'API Server

Le certificat de l'API server de Talos ne contient que les IPs du réseau dédié (10.5.20.2) et localhost. Il faut ajouter l'IP du réseau partagé (172.30.0.21 par exemple pour le cluster-2) dans les Subject Alternative Names (SANs) du certificat.

On voit en effet par défaut qu'elle ne fait pas partie des certSANs :

$ talosctl --context cluster-1 -n 127.0.0.1 get mc -o yaml | yq '.spec | fromyaml | .cluster.apiServer.certSANs'
- 10.5.20.2
- 127.0.0.1

On va alors recréer les clusters avec un patch de configuration :

# Suppression des clusters existants
$ talosctl cluster destroy --name cluster-1
$ talosctl cluster destroy --name cluster-2
 
# Recréation avec patch des certSANs
$ talosctl cluster create \
    --name cluster-1 \
    --kubernetes-version v1.33.4 \
    --cidr 10.5.10.0/24 \
    --config-patch-control-plane '[{"op": "replace", "path": "/cluster/apiServer/certSANs", "value": ["172.30.0.11", "10.5.10.2", "127.0.0.1"]}]'
 
$ talosctl cluster create \
    --name cluster-2 \
    --kubernetes-version v1.33.4 \
    --cidr 10.5.20.0/24 \
    --config-patch-control-plane '[{"op": "replace", "path": "/cluster/apiServer/certSANs", "value": ["172.30.0.21", "10.5.20.2", "127.0.0.1"]}]'

On reconnecte ensuite les nœuds au réseau partagé :

$ docker network connect --ip 172.30.0.11 talos-shared cluster-1-controlplane-1
$ docker network connect --ip 172.30.0.12 talos-shared cluster-1-worker-1
$ docker network connect --ip 172.30.0.21 talos-shared cluster-2-controlplane-1
$ docker network connect --ip 172.30.0.22 talos-shared cluster-2-worker-1

Vérifions le certSANs :

$ talosctl --context cluster-1 -n 127.0.0.1 get mc -o yaml | yq '.spec | fromyaml | .cluster.apiServer.certSANs'
- 172.30.0.11
- 10.5.10.2
- 127.0.0.1

On a maintenant un certSANs valide, des nodes accessibles depuis n'importe quel autre node partageant le réseau, impeccable !

Upgrade Kubernetes inside Docker

Maintenant que j'ai mes clusters locaux qui peuvent discuter entre eux, je souhaite les mettre à jour. La documentation dit que non, mais je suis joueur !

$ talosctl --context cluster-1 -n 127.0.0.1 upgrade-k8s --to 1.34.0
error detecting the lowest Kubernetes version Get "https://10.5.10.2:6443/api/v1/namespaces/kube-system/pods": dial tcp 10.5.10.2:6443: i/o timeout

Ah ! En effet, ça ne fonctionne pas... Le problème est que talosctl essaie de communiquer avec le control plane via 10.5.10.2:6443, mais depuis l'hôte Docker, on doit joindre le control plane du conteneur via localhost (ce qui est défini dans notre KUBECONFIG).

Si on regarde la configuration de la machine, l'endpoint du control plane pointe vers https://10.5.10.2:6443 :

$ talosctl -n 127.0.0.1 get mc -o yaml | yq '.spec | fromyaml | .cluster.controlPlane.endpoint'
https://10.5.10.2:6443

Patcher temporairement l'endpoint

Pour faire l'upgrade, on va temporairement changer l'endpoint du control plane pour qu'il pointe vers l'IP du réseau local, faire l'upgrade, puis remettre la configuration d'origine. Voici comment faire pour le cluster-1 :

  1. Récupérer l'URL du serveur depuis le kubeconfig

    $ SERVER_URL=$(kubectl config view --context admin@cluster-1 --minify \
        -o jsonpath='{.clusters[0].cluster.server}')
    $ echo $SERVER_URL
    https://127.0.0.1:51189
  2. Sauvegarder l'ancien endpoint sur le réseau dédié au cluster (10.5.10.2)

    $ OLD_URL=$(talosctl --context cluster-1 -n 127.0.0.1 get mc -o yaml | \
        yq '.spec | fromyaml | .cluster.controlPlane.endpoint' | grep '^https' | head -n1)
    $ echo $OLD_URL
    https://10.5.10.2:6443
  3. Patcher l'endpoint pour utiliser l'IP du réseau localhost avec $SERVER_URL

    $ talosctl --context cluster-1 -n 127.0.0.1 patch mc \
        --patch '[{"op": "replace", "path": "/cluster/controlPlane/endpoint", "value": "https://127.0.0.1:51189"}]'
    patched MachineConfigs.config.talos.dev/v1alpha1 at the node 127.0.0.1
    Applied configuration without a reboot
  4. Lancer l'upgrade Kubernetes depuis notre laptop !

    $ talosctl --context cluster-1 -n 127.0.0.1 upgrade-k8s --to 1.34.0
    automatically detected the lowest Kubernetes version 1.33.4
    discovered controlplane nodes ["10.5.20.2"]
    discovered worker nodes ["10.5.20.3"]
    > "10.5.20.2": Talos version 1.11.5 is compatible with Kubernetes version 1.34.0
    > "10.5.20.3": Talos version 1.11.5 is compatible with Kubernetes version 1.34.0
    checking for removed Kubernetes component flags
    checking for removed Kubernetes API resource versions
    > "10.5.20.2": pre-pulling registry.k8s.io/kube-apiserver:v1.34.0
    > "10.5.20.2": pre-pulling registry.k8s.io/kube-controller-manager:v1.34.0
    > "10.5.20.2": pre-pulling registry.k8s.io/kube-scheduler:v1.34.0
    > "10.5.20.2": pre-pulling ghcr.io/siderolabs/kubelet:v1.34.0
    > "10.5.20.3": pre-pulling ghcr.io/siderolabs/kubelet:v1.34.0
    updating "kube-apiserver" to version "1.34.0"
    > "10.5.20.2": starting update
    [...]
  5. Une fois terminé, remettre l'endpoint d'origine ($OLD_URL)

    $ talosctl --context cluster-1 -n 127.0.0.1 patch mc \
        --patch '[{"op": "replace", "path": "/cluster/controlPlane/endpoint", "value": "https://10.5.10.2:6443"}]'
  6. Redémarrer les conteneurs pour appliquer la configuration

    $ docker container restart cluster-1-controlplane-1 cluster-1-worker-1
    

Vérifions que l'upgrade a bien fonctionné :

$ kubectl --context admin@cluster-1 get nodes
NAME                       STATUS     ROLES           AGE   VERSION
cluster-1-controlplane-1   Ready      control-plane   18m   v1.34.0
cluster-1-worker-1         Ready      <none>          18m   v1.34.0

Parfait ! L'upgrade Kubernetes a réussi 🎉, même si la doc officielle nous dit que c'est impossible 😜. Le redémarrage des conteneurs permet de s'assurer que la configuration d'origine (avec l'endpoint localhost) est bien appliquée.

Récapitulatif du workflow d'upgrade

Conclusion

Créer plusieurs clusters Talos locaux avec Docker est simple, mais permettre leur communication nécessite quelques ajustements :

  1. Créer un réseau Docker partagé pour que les nœuds puissent communiquer
  2. Patcher les certSANs lors de la création pour inclure l'IP du réseau partagé dans le certificat TLS
  3. Patcher temporairement le controlPlane endpoint lors des upgrades Kubernetes pour utiliser l'IP du host

Avec cette approche, on peut s'amuser à gérer tout plein de clusters localement avec Docker, faut-il encore avoir une machine qui supporte autant de nodes 🤯 !

Ressources

Quelques ressources utiles pour aller plus loin :

GenAI

Je me suis aidé de Claude Code pour rédiger l'article, même si au final j'ai ré-écrit plus de 80% de l'article après beaucoup de discussions pour valider les commandes et corriger ce que je voulais vraiment présenter. Il m'a été très utile cependant pour la création des images SVG.

© Gravitek. Tous droits réservés.

logo

Gravitek est une Société à taille humaine, guidée par la qualité de service et la construction d'une relation durable avec ses clients.