Introduction
La planification Kubernetes est flexible, mais garantir une haute disponibilité entre les domaines de défaillance nécessite un placement minutieux des pods. Les contraintes de répartition topologique permettent de contrôler la répartition des pods à travers la topologie du cluster, comme les nœuds, les zones ou les régions. Ce guide propose une approche pratique, étape par étape, pour mettre en place un environnement de laboratoire local sûr afin de tester et d'apprendre les contraintes de répartition topologique. En expérimentant localement, vous pouvez comprendre le comportement, résoudre les problèmes et gagner en confiance avant de déployer en production.
Un laboratoire local bien conçu réduit les retouches en vous permettant de tester les politiques de planification de manière isolée. Le premier pilote utile doit être étroit, mesurable et facile à inspecter localement avant le déploiement. Cet article explique comment créer un cluster multi-nœuds avec kind (Kubernetes in Docker), étiqueter les nœuds pour simuler des zones, déployer une charge de travail avec des contraintes de répartition topologique et vérifier la distribution.
Dans cet article, vous allez :
- Configurer un cluster Kubernetes multi-nœuds avec kind qui simule plusieurs zones.
- Étiqueter les nœuds avec des clés de topologie telles que
topology.kubernetes.io/zone. - Déployer une application d'exemple avec des contraintes de répartition topologique.
- Vérifier la distribution des pods et diagnostiquer les problèmes courants.
- Découvrir les modes de défaillance et les procédures de récupération.
Toutes les commandes et les exemples sont conçus pour fonctionner sur un poste de travail de développeur typique avec Docker installé.
Inventaire des versions et de l'environnement
Avant de plonger dans les contraintes de répartition topologique, assurez-vous que votre environnement est correctement configuré. Cette section couvre les prérequis et les versions spécifiques utilisées dans ce guide.
Prérequis
Vous devez installer les outils suivants sur votre machine locale :
- Docker : requis pour
kind. Installez Docker Desktop (Windows/Mac) ou Docker Engine (Linux). - kubectl : outil en ligne de commande Kubernetes. Assurez-vous qu'il correspond à la version du cluster à une version mineure près.
- kind : Kubernetes in Docker. Installez-le depuis les versions officielles.
Matrice des versions
Les commandes de ce guide sont testées avec les versions suivantes, mais les concepts s'appliquent largement :
| Composant | Version testée | Notes |
|---|---|---|
| kind | v0.20.0 | Prend en charge les clusters multi-nœuds |
| Kubernetes | v1.27.1 | Contraintes de répartition topologique en disponibilité générale |
| kubectl | v1.27.1 | Correspond à la version du serveur |
| Docker | 24.0.2 | Pour les conteneurs locaux |
Si vos versions diffèrent, ajustez en conséquence. L'essentiel est que Kubernetes prenne en charge topologySpreadConstraints dans les spécifications de pod (introduit en v1.19, stable en v1.25).
Conception de la topologie du cluster
Pour des tests significatifs, nous avons besoin de plusieurs nœuds avec des étiquettes de topologie différentes. Nous allons créer un cluster kind avec trois nœuds de travail, chacun simulant une zone différente :
worker1dans la zonezone-aworker2dans la zonezone-bworker3dans la zonezone-c
Chaque nœud aura également une étiquette node-role.kubernetes.io/worker pour la planification des pods réguliers (facultatif). Le cluster aura un nœud de plan de contrôle.
Configuration du cluster avec kind
Créez un fichier de configuration kind-config.yaml :
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
- role: worker
labels:
topology.kubernetes.io/zone: zone-a
- role: worker
labels:
topology.kubernetes.io/zone: zone-b
- role: worker
labels:
topology.kubernetes.io/zone: zone-c
Ensuite, créez le cluster :
kind create cluster --name topo-lab --config kind-config.yaml
Sortie attendue :
Creating cluster "topo-lab" ...
✓ Ensuring node image (kindest/node: v1.27.1) 🖼
✓ Preparing nodes 📦 📦 📦 📦
✓ Writing configuration 📜
✓ Starting control-plane 🕹️
✓ Installing CNI 🔌
✓ Installing StorageClass 💾
✓ Joining worker nodes 🚜
Set kubectl context to "kind-topo-lab"
You can now use your cluster with:
kubectl cluster-info --context kind-topo-lab
Vérifiez les nœuds et leurs étiquettes :
kubectl get nodes --show-labels
Sortie attendue (abrégée) :
NAME STATUS ROLES AGE VERSION LABELS
kind-control-plane Ready control-plane 1m v1.27.1 ...
kind-worker Ready <none> 1m v1.27.1 topology.kubernetes.io/zone=zone-a,...
kind-worker2 Ready <none> 1m v1.27.1 topology.kubernetes.io/zone=zone-b,...
kind-worker3 Ready <none> 1m v1.27.1 topology.kubernetes.io/zone=zone-c,...
Remarque : kind nomme les workers par défaut kind-worker, kind-worker2 et kind-worker3. Nous les avons correctement étiquetés.
Comprendre les contraintes de répartition topologique
Une contrainte de répartition topologique spécifie comment les pods doivent être répartis dans un domaine topologique. Les champs clés dans une spécification de pod sont :
topologyKey: la clé d'étiquette des nœuds qui définit un domaine (par exemple,topology.kubernetes.io/zone).maxSkew: la différence maximale autorisée du nombre de pods correspondants entre deux domaines topologiques.whenUnsatisfiable:DoNotSchedule(contrainte stricte) ouScheduleAnyway(contrainte souple).labelSelector: sélectionne les pods à compter pour le calcul du déséquilibre (généralement des pods avec les mêmes étiquettes que le pod lui-même).
Un mécanisme similaire, les contraintes de répartition topologique des pods, a évolué à partir des règles podAntiAffinity mais offre un contrôle plus fin sur la distribution des pods. Le planificateur tente de respecter la contrainte tout en considérant d'autres exigences de planification comme les ressources des nœuds, les teintures et les affinités.
Sélection d'un scénario pilote
Nous allons déployer un simple déploiement avec trois répliques, chacune avec une contrainte de répartition topologique pour répartir sur les trois zones avec maxSkew: 1. C'est une configuration courante de haute disponibilité.
Créez un fichier app.yaml :
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
Appliquez le déploiement :
kubectl apply -f app.yaml
Sortie attendue :
deployment.apps/web created
Vérifiez la distribution des pods :
kubectl get pods -o wide
Sortie attendue (similaire) :
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-5c8b6f7c9d-abcde 1/1 Running 0 10s 10.244.1.2 kind-worker <none> <none>
web-5c8b6f7c9d-fghij 1/1 Running 0 10s 10.244.2.2 kind-worker2 <none> <none>
web-5c8b6f7c9d-klmno 1/1 Running 0 10s 10.244.3.2 kind-worker3 <none> <none>
Nous observons un pod par zone, satisfaisant la contrainte.
Combinaison de plusieurs contraintes
En production, vous pourriez vouloir répartir les pods à la fois entre les zones et entre les nœuds au sein de chaque zone. Vous pouvez définir plusieurs contraintes de répartition topologique. Cependant, soyez prudent : augmenter le nombre de contraintes peut rendre la planification impossible si le cluster manque de diversité suffisante. Par exemple, si vous exigez maxSkew=1 à la fois sur la zone et le nom d'hôte avec seulement trois nœuds au total, certaines contraintes entreront en conflit. Utilisez ScheduleAnyway si vous voulez une distribution au mieux.
Vérification et diagnostic
Après le déploiement, la vérification est cruciale. Cette section fournit des commandes et des sorties attendues pour confirmer que les contraintes de répartition topologique fonctionnent comme prévu.
Vérification de la distribution des pods
La vérification principale est l'emplacement des pods. Utilisez kubectl get pods -o wide pour voir l'affectation aux nœuds. Pour compter les pods par zone, vous pouvez utiliser kubectl avec des colonnes personnalisées ou jq.
Exemple avec kubectl et awk :
kubectl get pods -l app=web -o wide | awk 'NR>1 {print $7}' | sort | uniq -c
Sortie attendue :
1 kind-worker
1 kind-worker2
1 kind-worker3
Cela montre un pod sur chaque nœud (et donc chaque zone).
Description du déploiement pour les événements
Vérifiez les événements du déploiement pour les décisions de planification :
kubectl describe deployment web
Regardez la section Events. S'il y a eu des échecs, ils y apparaîtraient. Pour une planification réussie, vous ne verrez peut-être aucun événement pertinent.
Inspection des annotations des pods
Lorsqu'un pod est planifié, le planificateur enregistre les contraintes de répartition topologique dans les annotations du pod (depuis Kubernetes 1.20). Vous pouvez inspecter :
kubectl get pod <pod-name> -o jsonpath='{.metadata.annotations}'
La sortie attendue inclut une annotation comme :
{"kubernetes.io/topology-spread-constraints":"[{\"maxSkew\":1,\"topologyKey\":\"topology.kubernetes.io/zone\",\"whenUnsatisfiable\":\"DoNotSchedule\",\"labelSelector\":{\"matchLabels\":{\"app\":\"web\"}}}]"}
Cela confirme que la contrainte a été appliquée.
Test du déséquilibre par mise à l'échelle
Pour vérifier l'application du déséquilibre, mettez à l'échelle le déploiement à 4 répliques :
kubectl scale deployment web --replicas=4
Vérifiez ensuite la distribution des pods. Avec maxSkew: 1 et trois zones, le planificateur essaie d'équilibrer les pods, mais avec 4 pods, une zone aura 2 pods, les autres 1 chacune. C'est acceptable car le déséquilibre est au plus de 1. Cependant, si vous mettez à l'échelle à 5, le planificateur peut ne pas pouvoir placer tous les pods car la contrainte ne peut pas être satisfaite (si whenUnsatisfiable: DoNotSchedule). Le pod supplémentaire restera Pending.
Vérifiez les pods en attente :
kubectl get pods -l app=web --field-selector=status.phase=Pending
La sortie attendue montre un pod en attente. Décrivez-le pour plus de détails :
kubectl describe pod <pending-pod-name>
Dans les événements, vous verrez un message comme :
0/4 nodes are available: 1 node(s) didn't match pod topology spread constraints, 3 node(s) had no free ports.
Cela indique que la contrainte est appliquée, même si le message peut mentionner des ports en raison des calculs de ressources des nœuds.
Pièges courants et comment les éviter
Comprendre ce qui peut mal tourner est la moitié de la bataille. Voici des pièges courants avec les contraintes de répartition topologique et comment les éviter.
Piège 1 : Étiquettes de nœuds manquantes ou incorrectes
Pourquoi cela arrive : les contraintes de répartition topologique reposent sur les étiquettes de nœuds pour définir les domaines. Si les nœuds n'ont pas l'étiquette requise (par exemple, topology.kubernetes.io/zone), le planificateur peut ne pas les considérer, ou il peut les traiter comme un seul domaine, conduisant à une distribution déséquilibrée.
Comment éviter : vérifiez toujours les étiquettes de nœuds avant de déployer. Utilisez kubectl get nodes --show-labels et assurez-vous que tous les nœuds prévus ont les bonnes étiquettes. En production, utilisez l'automatisation des pools de nœuds pour garantir l'étiquetage.
Récupération : ajoutez ou corrigez les étiquettes avec :
kubectl label node <node-name> topology.kubernetes.io/zone=zone-a --overwrite
Supprimez ensuite les pods pour forcer une replanification si nécessaire.
Piège 2 : maxSkew trop faible pour la taille du cluster
Pourquoi cela arrive : si maxSkew est plus petit que l'inégalité que vous devez tolérer, le planificateur ne peut pas satisfaire la contrainte. Par exemple, avec 5 répliques et 3 zones, vous ne pouvez pas avoir une distribution égale (5/3 = 1,67). Avec maxSkew: 1, le mieux que vous puissiez faire est 2,2,1 ou 2,1,2, ce qui est autorisé car la différence entre le maximum et le minimum est de 1. Mais si vous essayez d'obtenir une égalité parfaite (impossible avec 5), le planificateur laisserait des pods en attente.
Comment éviter : calculez le minimum et le maximum théoriques par domaine en fonction des répliques et du nombre de domaines. Assurez-vous que maxSkew est au moins ceil(replicas/domains) - floor(replicas/domains). Pour 5 répliques sur 3 zones, c'est ceil(5/3)=2 et floor(5/3)=1, différence 1, donc maxSkew: 1 fonctionne. Mais si vous essayez de répartir sur 2 zones avec 3 répliques et maxSkew: 1, c'est ceil(3/2)=2 et floor(3/2)=1, différence 1, cela fonctionne aussi. En général, testez avant d'appliquer des contraintes strictes.
Récupération : si des pods sont en attente, assouplissez la contrainte en augmentant maxSkew ou en passant whenUnsatisfiable à ScheduleAnyway, ou ajoutez plus de nœuds/domaines.
Piège 3 : Contraintes conflictuelles
Pourquoi cela arrive : combiner plusieurs contraintes de répartition topologique (par exemple, zone et nom d'hôte) peut rendre la planification impossible si le cluster est trop petit. Par exemple, exiger maxSkew=1 sur la zone et maxSkew=1 sur le nom d'hôte dans un cluster de 3 nœuds et 3 zones signifie que chaque pod doit être sur un nœud unique et dans une zone unique, ce qui est bien pour 3 pods, mais passer à 4 pods devient impossible car ajouter un pod à n'importe quel nœud violerait soit le déséquilibre de zone, soit celui du nom d'hôte.
Comment éviter : examinez les contraintes de manière holistique. Utilisez minDomains (Kubernetes 1.25+) pour spécifier un nombre minimum de domaines pour une contrainte, ce qui peut influencer les décisions de planification. Simulez ou testez d'abord en laboratoire.
Récupération : supprimez ou assouplissez l'une des contraintes, ou ajoutez des nœuds.
Piège 4 : Inadéquation du sélecteur d'étiquettes
Pourquoi cela arrive : le labelSelector dans la contrainte doit correspondre aux étiquettes du pod lui-même. Si vous définissez un sélecteur qui ne correspond pas au pod, la contrainte peut compter zéro pod ou des pods non prévus, conduisant à une planification inattendue.
Comment éviter : définissez toujours labelSelector.matchLabels avec les mêmes étiquettes que celles utilisées dans le modèle de pod (par exemple, app: web).
Récupération : corrigez le sélecteur et mettez à jour le déploiement :
kubectl patch deployment web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/topologySpreadConstraints/0/labelSelector/matchLabels/app", "value": "web"}]'
Piège 5 : Interférence avec les affinités de nœuds et les teintures
Pourquoi cela arrive : les sélecteurs de nœuds, les affinités ou les teintures peuvent restreindre les nœuds éligibles, réduisant le nombre de domaines et rendant la contrainte de répartition insatisfaisable.
Comment éviter : assurez-vous que les pods peuvent être planifiés sur tous les nœuds des domaines prévus. Par exemple, si vous utilisez des teintures, ajoutez des tolérances.
Récupération : ajustez les affinités/teintures ou assouplissez la contrainte.
Chemin d'expérimentation sûr
Maintenant que le cluster est prêt, nous pouvons explorer les contraintes de répartition topologique en toute sécurité. L'objectif est d'avoir une expérience contrôlée étroite et mesurable.
Commencez avec un simple déploiement comme montré ci-dessus. Expérimentez ensuite avec ces variations :
- Changez
whenUnsatisfiableenScheduleAnywaypour voir comment les pods se répartissent lorsque les contraintes ne peuvent pas être entièrement satisfaites. - Ajoutez une deuxième contrainte sur
kubernetes.io/hostnamepour répartir entre les nœuds au sein des zones. - Utilisez
minDomainspour imposer un nombre minimum de zones même si toutes n'ont pas de pods initialement. - Combinez avec une affinité de nœuds pour restreindre le placement à certaines zones.
Pour chaque expérience, enregistrez la distribution des pods et comprenez les décisions du planificateur.
Exemple : contrainte souple avec ScheduleAnyway
Modifiez le déploiement pour utiliser ScheduleAnyway :
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: web
Appliquez et mettez à l'échelle à 5 répliques. Observez que les 5 pods sont en cours d'exécution, mais que la distribution est inégale, par exemple 2 pods dans zone-a, 2 dans zone-b, 1 dans zone-c. Le planificateur a essayé de minimiser le déséquilibre mais n'a pas bloqué la planification.
Exemple : ajout d'une contrainte de nom d'hôte
Ajoutez une deuxième contrainte :
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
Avec 3 répliques, cela placera un pod sur chaque nœud (et zone). Avec 4 répliques, un pod sera en attente car aucun nœud ne peut héberger un deuxième pod sans violer le déséquilibre du nom d'hôte.
Cela démontre comment les contraintes interagissent.
Modes de défaillance et récupération
Comprendre ce qui peut mal tourner et comment récupérer est essentiel. Cette section couvre les modes de défaillance courants et la récupération étape par étape.
Pods bloqués en attente
Comme vu précédemment, si la contrainte de répartition topologique ne peut pas être satisfaite, les pods restent non planifiés. Cela peut arriver en raison de nœuds insuffisants, d'étiquettes mal configurées ou de conflits.
Étapes de récupération :
- Identifiez le pod en attente :
kubectl get pods --field-selector=status.phase=Pending
- Décrivez le pod pour voir la raison :
kubectl describe pod <pending-pod>
Recherchez des événements comme « 0/4 nodes are available... »
- Décidez d'une correction :
- Réduire :
kubectl scale deployment web --replicas=3 - Assouplir la contrainte : changez
maxSkewpour une valeur plus élevée ouwhenUnsatisfiableenScheduleAnyway. - Ajoutez plus de nœuds avec des étiquettes appropriées.
- Supprimez les contraintes conflictuelles.
Exemple de correctif pour assouplir :
kubectl patch deployment web -p '{"spec":{"template":{"spec":{"topologySpreadConstraints":[{"maxSkew":2,"topologyKey":"topology.kubernetes.io/zone","whenUnsatisfiable":"ScheduleAnyway","labelSelector":{"matchLabels":{"app":"web"}}}]}}}}'
Supprimez ensuite les pods en attente pour permettre la recréation :
kubectl delete pod -l app=web --field-selector=status.phase=Pending
Inadéquations des étiquettes de nœuds
Si un nœud n'a pas l'étiquette attendue, ajoutez-la :
kubectl label node <node-name> topology.kubernetes.io/zone=zone-a
Pour les nœuds mal étiquetés, corrigez l'étiquette :
kubectl label node <node-name> topology.kubernetes.io/zone=zone-b --overwrite
Après le réétiquetage, vous devrez peut-être supprimer les pods pour déclencher une replanification.
Retour en arrière
Pour revenir à un état connu d'un déploiement, vous pouvez utiliser kubectl rollout undo si vous avez une révision précédente :
kubectl rollout undo deployment/web
Ou appliquez un fichier YAML précédemment enregistré.
Recréation du cluster
Si le laboratoire devient instable, supprimez et recréez le cluster :
kind delete cluster --name topo-lab
kind create cluster --name topo-lab --config kind-config.yaml
Cela réinitialise tout.
Liste de contrôle opérationnelle
Utilisez cette liste de contrôle pour des opérations reproductibles lorsque vous travaillez avec des contraintes de répartition topologique en laboratoire.
Liste de contrôle pré-déploiement
- [ ] Vérifiez que le cluster est en cours d'exécution :
kubectl cluster-info - [ ] Vérifiez que les nœuds ont les bonnes étiquettes :
kubectl get nodes --show-labels - [ ] Assurez-vous que les clés de topologie requises sont présentes sur tous les nœuds prévus.
- [ ] Testez la planification avec un pod simple (par exemple,
nginx) sans contraintes pour confirmer que la planification de base fonctionne. - [ ] Décidez de
whenUnsatisfiableen fonction des exigences (strict ou souple). - [ ] Calculez le déséquilibre attendu en fonction des répliques et du nombre de domaines.
- [ ] Attribuez un propriétaire pour l'examen de la configuration du déploiement (par exemple, Priya Shah, responsable de l'ingénierie), qui doit approuver toute modification des contraintes.
Liste de contrôle du déploiement
- [ ] Appliquez le déploiement avec des contraintes de répartition topologique :
kubectl apply -f app.yaml - [ ] Vérifiez que les pods sont planifiés comme prévu :
kubectl get pods -o wide - [ ] Comptez les pods par domaine topologique en utilisant la commande awk.
- [ ] Vérifiez les pods en attente :
kubectl get pods --field-selector=status.phase=Pending - [ ] Examinez les événements en cas de problème :
kubectl describe deployment web - [ ] Confirmez que l'annotation de contrainte est présente sur un pod :
kubectl get pod <pod-name> -o jsonpath='{.metadata.annotations}'
Liste de contrôle post-déploiement
- [ ] Surveillez la distribution des pods après des événements de mise à l'échelle (par exemple, passez à 4 et 5 répliques).
- [ ] Testez les scénarios de défaillance : isolez un nœud (
kubectl cordon kind-worker) et voyez comment les pods se replanifient ; supprimez l'isolement après le test. - [ ] Validez que le déséquilibre reste dans les limites.
- [ ] Documentez tout comportement inattendu dans un carnet de laboratoire pour référence future.
- [ ] Examinez le déploiement chaque semaine avec l'équipe pour vous assurer que les contraintes correspondent toujours aux objectifs de disponibilité.
Liste de contrôle du retour en arrière
- [ ] Conservez les manifestes YAML précédents dans le contrôle de version.
- [ ] Utilisez
kubectl rollout undosi le déploiement est géré via des mises à jour progressives. - [ ] Si nécessaire, réduisez ou supprimez le déploiement.
- [ ] Recréez le cluster si nécessaire.
Exemples de commandes opérationnelles :
# Vérifiez le déséquilibre en comptant les pods par nœud
kubectl get pods -l app=web -o wide | awk 'NR>1 {print $7}' | sort | uniq -c
# Obtenez les événements d'un déploiement
kubectl describe deployment web | tail -20
# Vérifiez les pods en attente
kubectl get pods --field-selector=status.phase=Pending
Conclusion
Dans ce guide, vous avez mis en place un laboratoire Kubernetes local pour tester en toute sécurité les contraintes de répartition topologique. Vous avez appris à :
- Créer un cluster multi-zones avec kind.
- Étiqueter les nœuds avec des clés de topologie.
- Déployer des applications avec des contraintes de répartition topologique.
- Vérifier la distribution des pods et diagnostiquer les échecs.
- Récupérer des problèmes courants.
En suivant l'approche de pilote étroit, vous pouvez explorer systématiquement le comportement de planification Kubernetes sans risquer les environnements de production. Les points clés à retenir sont :
- Commencez avec un scénario clair et testable.
- Utilisez
whenUnsatisfiable: ScheduleAnywaylors de l'exploration initiale. - Vérifiez toujours avec
kubectl get pods -o wideetdescribe. - Ayez un plan de retour en arrière.
Prochaines étapes :
- Expérimentez avec plusieurs contraintes et affinités de nœuds.
- Testez les contraintes souples et observez le déséquilibre sous charge.
- Explorez le paramètre
minDomains(Kubernetes 1.25+) pour encore plus de contrôle. - Intégrez les contraintes de répartition topologique dans vos tests CI/CD.
Avec ce laboratoire, vous pouvez acquérir la confiance et l'expertise nécessaires pour appliquer efficacement les contraintes de répartition topologique en production.