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 :
| Cluster | Node | IP réseau dédié | IP réseau partagé |
|---|---|---|---|
| cluster-1 | cluster-1-controlplane-1 | 10.5.10.2 | 172.30.0.11 |
| cluster-1 | cluster-1-worker-1 | 10.5.10.3 | 172.30.0.12 |
| cluster-2 | cluster-2-controlplane-1 | 10.5.20.1 | 172.30.0.21 |
| cluster-2 | cluster-2-worker-1 | 10.5.20.2 | 172.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 :
1$ talosctl version
2Client:
3 Tag: v1.11.5
4 SHA: undefined
5 Built: 2025-11-06T12:35:51Z
6 Go version: go1.25.4
7 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
1$ talosctl cluster create --name cluster-1 --kubernetes-version v1.33.4 --cidr 10.5.10.0/24
2validating CIDR and reserving IPs
3generating PKI and tokens
4creating state directory in "/path/to/talos/clusters/cluster-1"
5creating network cluster-1
6creating controlplane nodes
7creating worker nodes
8waiting for API
9bootstrapping cluster
10waiting for etcd to be healthy: OK
11waiting for etcd members to be consistent across nodes: OK
12waiting for etcd members to be control plane nodes: OK
13waiting for apid to be ready: OK
14waiting for [...]: OK
15waiting for coredns to report ready: OK
16waiting for all k8s nodes to report schedulable: OK
17
18merging kubeconfig into "/path/to/kubeconfig"
19PROVISIONER docker
20NAME cluster-1
21NETWORK NAME cluster-1
22NETWORK CIDR 10.5.10.0/24
23NETWORK GATEWAY 10.5.10.1
24NETWORK MTU 1500
25KUBERNETES ENDPOINT https://127.0.0.1:62233
26
27NODES:
28
29NAME TYPE IP CPU RAM DISK
30/cluster-1-controlplane-1 controlplane 10.5.10.2 2.00 2.1 GB -
31/cluster-1-worker-1 worker 10.5.10.3 2.00 2.1 GB -
1$ talosctl cluster create --name cluster-2 --kubernetes-version v1.33.4 --cidr 10.5.20.0/24
2[...]
Après quelques minutes, j'ai mes deux clusters opérationnels :
1$ kubectl config get-contexts
2CURRENT NAME CLUSTER AUTHINFO NAMESPACE
3 admin@cluster-1 cluster-1 admin@cluster-1 default
4* admin@cluster-2 cluster-2 admin@cluster-2 default
5
6$ NAME STATUS ROLES AGE VERSION
7cluster-1-controlplane-1 Ready control-plane 4m54s v1.33.4
8cluster-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 :
1$ docker network ls
2NETWORK ID NAME DRIVER SCOPE
3f2c1d59b61f2 cluster-1 bridge local
4c31015d9e6d3 cluster-2 bridge local
5
6$ docker network inspect cluster-1 -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
710.5.10.0/24
8
9$ docker network inspect cluster-2 -f '{{range .IPAM.Config}}{{.Subnet}}{{end}}'
1010.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.
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 :
1$ docker network create --driver bridge --subnet=172.30.0.0/24 talos-shared
23e1f0e92331f7078ee72cd0cfa99a01bf35c9f012efc52e34b10fc3563e3d795
3
4$ docker network ls
5f2c1d59b61f2 cluster-1 bridge local
6c31015d9e6d3 cluster-2 bridge local
73e1f0e92331f talos-shared bridge local
Maintenant, connectons les nœuds du cluster-1 à ce réseau :
1$ docker network connect --ip 172.30.0.11 talos-shared cluster-1-controlplane-1
2$ docker network connect --ip 172.30.0.12 talos-shared cluster-1-worker-1
Et ceux du cluster-2 :
1$ docker network connect --ip 172.30.0.21 talos-shared cluster-2-controlplane-1
2$ 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 :
1kubectl --context admin@cluster-1 debug -n kube-system -it --image alpine node/cluster-1-worker-1 -- ping -c 2 172.30.0.21
264 bytes from 172.30.0.21: seq=1 ttl=64 time=0.529 ms
3
4--- 172.30.0.21 ping statistics ---
52 packets transmitted, 2 packets received, 0% packet loss
6round-trip min/avg/max = 0.092/0.310/0.529 ms
Excellent ! Les nœuds se voient maintenant.
Maintenant, tentons de nous connecter à l'API server du cluster-2 depuis le cluster-1. On va récupérer son IP :
1$ kubectl --context admin@cluster-2 config view --minify -o jsonpath='{.clusters[0].cluster.server}'
2https://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 :
1kubectl --context admin@cluster-1 debug -n kube-system -it --image alpine node/cluster-1-worker-1 -- /bin/sh
2/ # apk add curl
3/ # curl -k https://172.30.0.21:6443
4{
5 "kind": "Status",
6 "apiVersion": "v1",
7 "metadata": {},
8 "status": "Failure",
9 "message": "Unauthorized",
10 "reason": "Unauthorized",
11 "code": 401
12}/ #
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 :
1$ docker run --network talos-shared -v ${PWD}:/tmp alpine/k8s:1.33.4 kubectl --kubeconfig=/tmp/kubeconfig --context admin@cluster-2 get nodes
2pod:/# kubectl get nodes
3NAME STATUS ROLES AGE VERSION
4cluster-2-controlplane-1 Ready control-plane 73m v1.33.4
5cluster-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.2et127.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 :
1$ talosctl --context cluster-1 -n 127.0.0.1 get mc -o yaml | yq '.spec | fromyaml | .cluster.apiServer.certSANs'
2- 10.5.20.2
3- 127.0.0.1
On va alors recréer les clusters avec un patch de configuration :
1# Suppression des clusters existants
2$ talosctl cluster destroy --name cluster-1
3$ talosctl cluster destroy --name cluster-2
4
5# Recréation avec patch des certSANs
6$ talosctl cluster create \
7 --name cluster-1 \
8 --kubernetes-version v1.33.4 \
9 --cidr 10.5.10.0/24 \
10 --config-patch-control-plane '[{"op": "replace", "path": "/cluster/apiServer/certSANs", "value": ["172.30.0.11", "10.5.10.2", "127.0.0.1"]}]'
11
12$ talosctl cluster create \
13 --name cluster-2 \
14 --kubernetes-version v1.33.4 \
15 --cidr 10.5.20.0/24 \
16 --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é :
1$ docker network connect --ip 172.30.0.11 talos-shared cluster-1-controlplane-1
2$ docker network connect --ip 172.30.0.12 talos-shared cluster-1-worker-1
3$ docker network connect --ip 172.30.0.21 talos-shared cluster-2-controlplane-1
4$ docker network connect --ip 172.30.0.22 talos-shared cluster-2-worker-1
Vérifions le certSANs :
1$ talosctl --context cluster-1 -n 127.0.0.1 get mc -o yaml | yq '.spec | fromyaml | .cluster.apiServer.certSANs'
2- 172.30.0.11
3- 10.5.10.2
4- 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 !
1$ talosctl --context cluster-1 -n 127.0.0.1 upgrade-k8s --to 1.34.0
2error 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 :
1$ talosctl -n 127.0.0.1 get mc -o yaml | yq '.spec | fromyaml | .cluster.controlPlane.endpoint'
2https://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 :
Récupérer l'URL du serveur depuis le kubeconfig
1$ SERVER_URL=$(kubectl config view --context admin@cluster-1 --minify \ 2 -o jsonpath='{.clusters[0].cluster.server}') 3$ echo $SERVER_URL 4https://127.0.0.1:51189Sauvegarder l'ancien endpoint sur le réseau dédié au cluster (
10.5.10.2)1$ OLD_URL=$(talosctl --context cluster-1 -n 127.0.0.1 get mc -o yaml | \ 2 yq '.spec | fromyaml | .cluster.controlPlane.endpoint' | grep '^https' | head -n1) 3$ echo $OLD_URL 4https://10.5.10.2:6443Patcher l'endpoint pour utiliser l'IP du réseau localhost avec
$SERVER_URL1$ talosctl --context cluster-1 -n 127.0.0.1 patch mc \ 2 --patch '[{"op": "replace", "path": "/cluster/controlPlane/endpoint", "value": "https://127.0.0.1:51189"}]' 3patched MachineConfigs.config.talos.dev/v1alpha1 at the node 127.0.0.1 4Applied configuration without a rebootLancer l'upgrade Kubernetes depuis notre laptop !
1$ talosctl --context cluster-1 -n 127.0.0.1 upgrade-k8s --to 1.34.0 2automatically detected the lowest Kubernetes version 1.33.4 3discovered controlplane nodes ["10.5.20.2"] 4discovered worker nodes ["10.5.20.3"] 5> "10.5.20.2": Talos version 1.11.5 is compatible with Kubernetes version 1.34.0 6> "10.5.20.3": Talos version 1.11.5 is compatible with Kubernetes version 1.34.0 7checking for removed Kubernetes component flags 8checking for removed Kubernetes API resource versions 9> "10.5.20.2": pre-pulling registry.k8s.io/kube-apiserver:v1.34.0 10> "10.5.20.2": pre-pulling registry.k8s.io/kube-controller-manager:v1.34.0 11> "10.5.20.2": pre-pulling registry.k8s.io/kube-scheduler:v1.34.0 12> "10.5.20.2": pre-pulling ghcr.io/siderolabs/kubelet:v1.34.0 13> "10.5.20.3": pre-pulling ghcr.io/siderolabs/kubelet:v1.34.0 14updating "kube-apiserver" to version "1.34.0" 15> "10.5.20.2": starting update 16[...]Une fois terminé, remettre l'endpoint d'origine (
$OLD_URL)1$ talosctl --context cluster-1 -n 127.0.0.1 patch mc \ 2 --patch '[{"op": "replace", "path": "/cluster/controlPlane/endpoint", "value": "https://10.5.10.2:6443"}]'Redémarrer les conteneurs pour appliquer la configuration
1$ docker container restart cluster-1-controlplane-1 cluster-1-worker-1
Vérifions que l'upgrade a bien fonctionné :
1$ kubectl --context admin@cluster-1 get nodes
2NAME STATUS ROLES AGE VERSION
3cluster-1-controlplane-1 Ready control-plane 18m v1.34.0
4cluster-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.
Conclusion
Créer plusieurs clusters Talos locaux avec Docker est simple, mais permettre leur communication nécessite quelques ajustements :
- Créer un réseau Docker partagé pour que les nœuds puissent communiquer
- Patcher les certSANs lors de la création pour inclure l'IP du réseau partagé dans le certificat TLS
- 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 :
- Documentation officielle Talos
- Talos Docker Mode
- Machine Config Patches
- ArgoCD Multi-Clusters
- Scripts utilisés pour la démo ArgoCD
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.
