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

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 :
$ talosctl version
Client:
Tag: v1.11.5
SHA: undefined
Built: 2025-11-06T12:35:51Z
Go version: go1.25.4
OS/Arch: darwin/arm64Cré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.4Parfait ! 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/24Le 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 :
$ 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 localMaintenant, 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-1Et 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-1Testons 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 msExcellent ! 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 :
$ kubectl --context admin@cluster-2 config view --minify -o jsonpath='{.clusters[0].cluster.server}'
https://127.0.0.1:62406Bon, ç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.4Ok, ç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 :
$ 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.1On 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-1Vé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.1On 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 timeoutAh ! 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:6443Patcher 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
$ 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 -
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 -
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 -
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 [...] -
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"}]' -
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.0Parfait ! 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.