Introduction
Les contraintes de répartition topologique Kubernetes (Topology Spread Constraints) sont une fonctionnalité puissante de planification qui garantit que les pods sont répartis uniformément dans les domaines topologiques de votre cluster, tels que les zones, les régions ou les nœuds. Si le concept de base est simple, une utilisation avancée implique des interactions nuancées avec l'affinité de nœuds, l'anti-affinité de pods, les nœuds non planifiables et la mise à l'échelle automatique du cluster. Cet article explore en profondeur les mécanismes, fournit des exemples pratiques avec les sorties attendues et vous prépare à résoudre les modes de défaillance courants.
À la fin, vous comprendrez comment configurer les contraintes pour une haute disponibilité, éviter les blocages de planification et vérifier efficacement votre configuration.
Inventaire des versions et de l'environnement
Avant de travailler avec les contraintes de répartition topologique, vérifiez votre version Kubernetes et les capacités de l'environnement. Les contraintes de répartition topologique sont stables depuis la v1.19 et continuent d'évoluer. Vérifiez la version de votre serveur :
kubectl version --short
Sortie attendue (exemple) :
Client Version: v1.27.3
Kustomize Version: v5.0.1
Server Version: v1.27.3
Assurez-vous que la porte de fonctionnalité EvenPodsSpread est activée (par défaut depuis v1.18) et que vos nœuds ont des étiquettes appropriées pour les clés de topologie. Les clés courantes incluent :
topology.kubernetes.io/zonetopology.kubernetes.io/regionkubernetes.io/hostname
Listez les étiquettes des nœuds pour confirmer :
kubectl get nodes --show-labels
Extrait d'exemple :
NAME STATUS ROLES AGE VERSION LABELS
node-1 Ready <none> 10d v1.27.3 kubernetes.io/hostname=node-1,topology.kubernetes.io/zone=us-east-1a,topology.kubernetes.io/region=us-east-1
node-2 Ready <none> 10d v1.27.3 kubernetes.io/hostname=node-2,topology.kubernetes.io/zone=us-east-1b,topology.kubernetes.io/region=us-east-1
node-3 Ready <none> 10d v1.27.3 kubernetes.io/hostname=node-3,topology.kubernetes.io/zone=us-east-1c,topology.kubernetes.io/region=us-east-1
Pour un test pratique, commencez par un déploiement minimal et observez son comportement de planification avant d'ajouter des contraintes.
Observation en lecture seule
Avant d'apporter des modifications, capturez l'état actuel de la planification :
kubectl get pods -o wide
Sortie d'exemple :
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-7d9b7456c4-abcde 1/1 Running 0 5m 10.244.1.5 node-1 <none> <none>
nnginx-7d9b7456c4-fghij 1/1 Running 0 5m 10.244.2.10 node-2 <none> <none>
nnginx-7d9b7456c4-klmno 1/1 Running 0 5m 10.244.3.7 node-3 <none> <none>
Cela montre que les pods sont répartis uniformément entre les nœuds par hasard, mais sans garantie.
Chemin de configuration sûr
Comprendre l'objet de contrainte
Une contrainte de répartition topologique est définie dans la spécification du pod. Voici un exemple de base réparti entre les zones :
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
Appliquez-le :
kubectl apply -f web-deployment.yaml
Vérifiez la répartition :
kubectl get pods -o wide --selector=app=web
Sortie d'exemple :
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-7d9b7456c4-abcde 1/1 Running 0 10s 10.244.1.6 node-1 <none> <none>
web-7d9b7456c4-fghij 1/1 Running 0 10s 10.244.2.11 node-2 <none> <none>
web-7d9b7456c4-klmno 1/1 Running 0 10s 10.244.3.8 node-3 <none> <none>
Ici, chaque pod a atterri dans une zone différente, satisfaisant maxSkew: 1.
Paramètres avancés
maxSkew est la différence maximale autorisée dans le nombre de pods entre deux domaines topologiques. Elle doit être supérieure à zéro.
whenUnsatisfiable peut être DoNotSchedule (strict) ou ScheduleAnyway (souple). Avec DoNotSchedule, si la contrainte ne peut pas être satisfaite, le pod reste en attente. Avec ScheduleAnyway, le planificateur le place quand même, mais le déséquilibre peut être violé.
minDomains (bêta en v1.25) spécifie un nombre minimum de domaines éligibles. Le planificateur ne planifiera pas si moins de domaines sont disponibles, même si le déséquilibre est satisfait. Cela est utile pour garantir la disponibilité lors de pannes de zones.
Exemple avec minDomains :
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
minDomains: 3 # nécessite au moins 3 zones
labelSelector:
matchLabels:
app: web
Interaction avec l'affinité de nœuds et l'anti-affinité
Les contraintes de répartition topologique fonctionnent avec l'affinité de nœuds et l'anti-affinité de pods, mais l'effet combiné peut être complexe. Le planificateur prend en compte toutes les contraintes, et la plus restrictive domine.
Exemple combinant avec l'affinité de nœuds :
Supposons que vous vouliez répartir les pods entre les zones mais uniquement sur des nœuds avec SSD. Ajoutez une affinité de nœuds :
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disktype
operator: In
values:
- ssd
Désormais, les pods ne seront planifiés que sur des nœuds avec disktype=ssd, puis répartis entre les zones de ces nœuds.
Interaction avec l'anti-affinité de pods :
L'anti-affinité de pods peut empêcher la colocalisation des pods. Si la répartition et l'anti-affinité sont définies, les règles d'anti-affinité peuvent prévaloir sur les contraintes de répartition. Par exemple, si l'anti-affinité exige que chaque pod soit sur un nœud différent, et que la répartition veut maxSkew=1 entre les zones, le planificateur doit satisfaire les deux. Comprenez ces priorités pour éviter des pods en attente inattendus.
Vérification et diagnostics
Observer la répartition avec des commandes
Après avoir appliqué un déploiement avec des contraintes de répartition, vérifiez la distribution réelle :
kubectl get pods -o wide -l app=web
Comptez les pods par nœud ou zone :
kubectl get pods -l app=web -o json | jq -r '.items[].spec.nodeName' | sort | uniq -c
Sortie d'exemple :
1 node-1
1 node-2
1 node-3
Pour inspecter la contrainte sur un pod en cours d'exécution :
kubectl get pod web-7d9b7456c4-abcde -o yaml | grep -A 10 topologySpreadConstraints
Diagnostiquer les pods en attente
Si les pods restent en attente, décrivez-les :
kubectl describe pod web-7d9b7456c4-abcde
Recherchez des événements comme :
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 2m default-scheduler 0/3 nodes are available: 3 node(s) didn't match topology spread constraints.
Cela indique que la contrainte ne peut pas être satisfaite avec la topologie actuelle des nœuds.
Vérifiez les décisions de planification à l'aide des journaux de débogage du planificateur si nécessaire :
kubectl logs -n kube-system kube-scheduler-<master-node> | grep -i spread
Modes de défaillance et récupération
Scénario 1 : Domaines insuffisants
Si vous avez moins de domaines topologiques que requis par minDomains ou que maxSkew ne peut pas être satisfait, les pods seront en attente. Exemple : vous demandez une répartition entre les zones avec minDomains: 3, mais il n'existe que deux zones. Le planificateur ne placera aucun pod.
Récupération :
- Réduisez
minDomainsà 2 ou supprimez-le. - Augmentez
maxSkewpour permettre une répartition inégale. - Ajoutez des nœuds dans de nouvelles zones.
- Changez temporairement
whenUnsatisfiableenScheduleAnywaypour une contrainte souple.
Scénario 2 : Violation du déséquilibre après mise à l'échelle
Si vous réduisez puis augmentez l'échelle, le déséquilibre peut dépasser maxSkew. Le planificateur tente de rééquilibrer, mais il n'évince pas les pods. De nouveaux pods peuvent être planifiés pour réduire le déséquilibre, mais les pods existants restent. Avec le temps, le déséquilibre peut se normaliser.
Exemple :
Initialement : 3 pods dans 3 zones, déséquilibre=0. Réduisez à 1 pod, qui reste dans la zone-1. Augmentez à 3 : le planificateur voit que la zone-1 a 1 pod, les autres 0. Pour maintenir maxSkew=1, il place un pod dans la zone-2 et un dans la zone-3. Le résultat est à nouveau un pod par zone.
Si vous passez à 4 réplicas, avec maxSkew=1, le planificateur peut placer deux pods dans une zone et un dans chacune des autres, ce qui donne un déséquilibre=1 (acceptable).
Scénario 3 : Interaction avec l'autoscaler de cluster
Les contraintes de répartition topologique peuvent empêcher l'autoscaler de cluster de monter en charge correctement. Si un pod est non planifiable en raison des contraintes de répartition, l'autoscaler peut ne pas savoir quel groupe de nœuds mettre à l'échelle. Pour éviter cela, assurez-vous que les groupes de nœuds correspondent aux zones topologiques et utilisez topologySpreadConstraints avec ScheduleAnyway ou fiez-vous à l'anti-affinité de pods.
Scénario 4 : Répartition topologique des pods avec sélecteur de nœuds
Si un sélecteur de nœuds limite la planification à un sous-ensemble de nœuds, le planificateur ne considère que ces nœuds pour la répartition. Cela peut entraîner un déséquilibre. Par exemple, si le sélecteur de nœuds ne sélectionne que des nœuds dans la zone A, tous les pods y vont indépendamment de la répartition. Assurez-vous que les sélecteurs s'alignent sur les domaines topologiques.
Liste de contrôle des opérations
Utilisez cette liste de contrôle pour implémenter et exploiter en toute sécurité les contraintes de répartition topologique :
| Étape | Action | Commande / Vérification | Exemple |
|---|---|---|---|
| 1 | Vérifier la version Kubernetes et les portes de fonctionnalité | kubectl version --short | Serveur v1.27+, EvenPodsSpread activé |
| 2 | Inspecter les étiquettes de topologie des nœuds | kubectl get nodes --show-labels | Les nœuds ont des étiquettes topology.kubernetes.io/zone |
| 3 | Appliquer un déploiement de test sans contraintes | kubectl apply -f test.yaml | Les pods se planifient normalement |
| 4 | Ajouter des contraintes de répartition à une copie | Modifier le manifeste, puis kubectl apply -f constrained.yaml | Les pods se répartissent comme prévu |
| 5 | Vérifier la distribution réelle | kubectl get pods -o wide -l app=test | Pods répartis uniformément entre les zones |
| 6 | Tester les scénarios de défaillance | Réduire/augmenter l'échelle, simuler une perte de zone | Observer le déséquilibre et le comportement en attente |
| 7 | Documenter le plan de retour en arrière | Conserver le manifeste d'origine : kubectl apply -f original.yaml | Prêt à revenir en arrière si des problèmes surviennent |
| 8 | Surveiller dans le temps | kubectl get pods -o wide périodiquement ou utiliser kube-state-metrics | Le déséquilibre reste dans les limites |
Exemple concret : application web multi-zones
Parcourons un exemple complet pour une application web de type production avec 6 réplicas répartis sur 3 zones, avec maxSkew=1 et DoNotSchedule.
- Étiquetez les nœuds (si ce n'est pas déjà fait) :
kubectl label node node-1 topology.kubernetes.io/zone=us-east-1a
kubectl label node node-2 topology.kubernetes.io/zone=us-east-1b
kubectl label node node-3 topology.kubernetes.io/zone=us-east-1c
- Créez un namespace :
kubectl create namespace webapp
- Définissez le manifeste de déploiement
webapp-deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: webapp
namespace: webapp
spec:
replicas: 6
selector:
matchLabels:
app: webapp
template:
metadata:
labels:
app: webapp
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: webapp
containers:
- name: app
image: nginx:1.25
ports:
- containerPort: 80
- Appliquez et attendez :
kubectl apply -f webapp-deployment.yaml
kubectl rollout status deployment/webapp -n webapp
- Vérifiez la distribution :
kubectl get pods -n webapp -o wide -l app=webapp
Sortie attendue : 2 pods par zone, tous en cours d'exécution.
- Vérifiez le déséquilibre à l'aide d'un petit script :
kubectl get pods -n webapp -l app=webapp -o json | jq -r '.items[] | .spec.nodeName' | \
xargs -I {} kubectl get node {} --show-labels | grep topology.kubernetes.io/zone | \
awk -F'topology.kubernetes.io/zone=' '{print $2}' | cut -d',' -f1 | sort | uniq -c
Sortie d'exemple :
2 us-east-1a
2 us-east-1b
2 us-east-1c
- Simulez une panne de zone en cordonnant les nœuds d'une zone :
kubectl cordon node-1 # en supposant que node-1 est dans us-east-1a
Les pods existants continuent de fonctionner. Si vous passez à 9 réplicas, le planificateur placera les nouveaux pods dans les deux zones restantes, mais ne dépassera peut-être pas maxSkew de 1 si possible. Cependant, avec seulement deux zones, le déséquilibre maximal peut encore être de 1 si la répartition est égale.
- Décordonnez pour restaurer :
kubectl uncordon node-1
Cette liste de contrôle et cet exemple démontrent la préparation opérationnelle.
Conclusion
Les contraintes de répartition topologique Kubernetes offrent un contrôle fin sur la distribution des pods, améliorant la disponibilité et l'utilisation des ressources. En comprenant les paramètres et les interactions avec d'autres fonctionnalités de planification, vous pouvez concevoir des déploiements multi-zones robustes. Vérifiez toujours avec des commandes réelles, anticipez les modes de défaillance et ayez un plan de retour en arrière. Commencez par un petit test, observez le comportement du planificateur et montez en charge en toute confiance.