Introduction
Les règles d'affinité et d'anti-affinité des pods Kubernetes offrent un contrôle précis sur la planification des pods en fonction des étiquettes des autres pods. Combinées à l'automatisation CI/CD, ces règles vous aident à garantir la haute disponibilité, la localité des ressources et l'isolation des pannes lors de chaque déploiement. Cet article fournit un flux de travail pratique, axé sur les commandes, pour intégrer l'affinité et l'anti-affinité des pods dans vos pipelines, de la validation de version à la vérification automatisée et au rollback.
Ce guide s'adresse aux développeurs, aux consultants DevOps et aux équipes techniques de startups qui gèrent des clusters Kubernetes et souhaitent intégrer les contraintes de planification dans leur processus de livraison. Nous mettons l'accent sur la sécurité opérationnelle : observer avant de modifier, limiter le rayon d'impact, utiliser des espaces réservés au lieu de secrets, vérifier le résultat et documenter la récupération si l'état attendu n'est pas atteint.
Vous apprendrez à :
- Vérifier votre version de Kubernetes et la configuration actuelle de planification.
- Définir l'affinité et l'anti-affinité des pods dans les manifestes de déploiement.
- Valider les contraintes de planification localement avant la fusion.
- Automatiser le déploiement avec des stratégies de rollout sûres.
- Diagnostiquer les échecs courants comme les pods non planifiables ou les inadéquations de topologie.
- Effectuer un rollback en toute sécurité lorsqu'un changement casse le placement des pods.
À la fin, vous disposerez d'un pipeline reproductible qui rend le comportement de planification prévisible et auditable.
Inventaire de version et d'environnement
Avant d'appliquer une règle d'affinité ou d'anti-affinité, confirmez que votre cluster prend en charge les fonctionnalités que vous comptez utiliser. L'affinité et l'anti-affinité des pods sont stables depuis Kubernetes 1.6, mais le comportement de topologyKey et les options de namespaceSelector ont évolué. Pour les pipelines de production, utilisez Kubernetes 1.24 ou une version ultérieure où la fonctionnalité est mature et bien documentée.
Vérifier la version de Kubernetes et la disponibilité de l'API
Exécutez la commande suivante pour vérifier la version du serveur :
kubectl version --short
La sortie attendue inclut les versions client et serveur, par exemple :
Client Version: v1.29.2
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.28.7
Si la version du serveur est inférieure à 1.24, testez d'abord toutes les règles d'affinité dans un cluster de staging, car certains cas limites autour de matchLabelKeys et mismatchLabelKeys peuvent se comporter différemment.
Vérifier le planificateur et la répartition actuelle des pods
Prenez un instantané en lecture seule du placement actuel des pods avant tout changement. Cela vous aide à comparer après le déploiement et à détecter tout mouvement involontaire. Exécutez :
kubectl get pods -o wide --all-namespaces | tee pods-before.txt
La sortie -o wide inclut les noms de nœuds et les adresses IP des pods, ce qui est essentiel pour comprendre les décisions de planification. Enregistrez la sortie avec un horodatage dans le nom de fichier pour l'auditabilité :
kubectl get pods -o wide --all-namespaces > pods-before-$(date +%Y%m%d-%H%M%S).txt
Si vous avez de nombreux espaces de noms, limitez la sortie à votre espace de noms d'application :
kubectl get pods -o wide -n my-app > pods-before-$(date +%Y%m%d-%H%M%S).txt
Inspecter les règles d'affinité existantes
Vérifiez si vos déploiements actuels ont déjà de l'affinité ou de l'anti-affinité configurée :
kubectl get deployments -n my-app -o custom-columns=NAME:.metadata.name,AFFINITY:.spec.template.spec.affinity
Cela imprime un tableau comme :
NAME AFFINITY
web <none>
api <none>
worker map[nodeAffinity:map[...] podAntiAffinity:map[...]]
Si un déploiement utilise déjà l'affinité, examinez son YAML pour comprendre ses contraintes actuelles :
kubectl get deployment worker -n my-app -o yaml | grep -A 50 affinity
Lister les nœuds et les étiquettes de topologie
Les règles d'affinité et d'anti-affinité reposent souvent sur des étiquettes de topologie de nœud telles que kubernetes.io/hostname, topology.kubernetes.io/zone ou topology.kubernetes.io/region. Listez vos nœuds et leurs étiquettes pour connaître les clés de topologie disponibles :
kubectl get nodes --show-labels
Extrait de sortie attendu :
NAME STATUS ROLES AGE VERSION LABELS
node-1 Ready control-plane 21d v1.28.7 kubernetes.io/hostname=node-1,topology.kubernetes.io/zone=us-east-1a,topology.kubernetes.io/region=us-east-1
node-2 Ready <none> 21d v1.28.7 kubernetes.io/hostname=node-2,topology.kubernetes.io/zone=us-east-1b,topology.kubernetes.io/region=us-east-1
node-3 Ready <none> 21d v1.28.7 kubernetes.io/hostname=node-3,topology.kubernetes.io/zone=us-east-1c,topology.kubernetes.io/region=us-east-1
Pour les clusters multi-zones, l'utilisation de topology.kubernetes.io/zone comme clé de topologie répartit les pods entre les domaines de défaillance. Pour les clusters de développement à nœud unique, seul kubernetes.io/hostname a du sens.
Confirmer les CRD et les contrôleurs d'admission requis
Si vous prévoyez d'utiliser des fonctionnalités de planification avancées comme les contraintes de répartition de topologie de pod ou matchLabelKeys, assurez-vous que les feature gates pertinentes sont activées. Vérifiez la configuration du planificateur :
kubectl -n kube-system get pod kube-scheduler-<node-name> -o yaml | grep feature-gates
La plupart des services Kubernetes gérés (EKS, GKE, AKS) les activent par défaut, mais les clusters auto-gérés peuvent nécessiter un basculement manuel des drapeaux.
Chemin de configuration sûr
L'objectif est de définir des règles d'affinité et d'anti-affinité des pods qui correspondent à vos objectifs de disponibilité et de performance, sans rendre le cluster impossible à planifier. Commencez par un manifeste minimal, validez-le localement, puis élargissez la portée.
Comprendre les types d'affinité et d'anti-affinité
Kubernetes prend en charge deux types d'affinité de pod :
- requiredDuringSchedulingIgnoredDuringExecution : La règle doit être satisfaite pour que le pod soit planifié. Si aucun nœud ne répond à la règle, le pod reste non planifiable.
- preferredDuringSchedulingIgnoredDuringExecution : Le planificateur essaie de satisfaire la règle mais ne la garantit pas. C'est utile pour les contraintes souples.
De même, les règles d'anti-affinité peuvent être obligatoires ou préférées. L'anti-affinité empêche les pods d'être colocalisés sur le même domaine de topologie (comme le même nœud ou la même zone).
Exemple : répartir les pods web sur les nœuds avec anti-affinité
Supposons que vous ayez un déploiement web avec 3 réplicas et que vous souhaitiez vous assurer que chaque réplica s'exécute sur un nœud différent pour la résilience. Ajoutez le podAntiAffinity suivant sous la spécification du pod :
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: my-app
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
Ici, la règle dit : ne planifiez pas ce pod sur un nœud qui a déjà un pod avec l'étiquette app=web. Le topologyKey: kubernetes.io/hostname limite la règle à un seul nœud.
Valider la syntaxe du manifeste et le dry-run
Avant d'appliquer, validez la syntaxe YAML et simulez le changement avec --dry-run=client :
kubectl apply -f web-deployment.yaml --dry-run=client
Sortie attendue :
deployment.apps/web created (dry run)
Pour un dry-run côté serveur qui vérifie les contrôleurs d'admission, utilisez --dry-run=server :
kubectl apply -f web-deployment.yaml --dry-run=server
Le dry-run côté serveur détectera des problèmes comme des clés de topologie invalides ou des incohérences de sélecteur d'étiquettes.
Appliquer et observer la planification
Appliquez le manifeste et surveillez la planification des pods :
kubectl apply -f web-deployment.yaml
kubectl rollout status deployment/web -n my-app
Sortie attendue :
deployment "web" successfully rolled out
Ensuite, vérifiez la répartition des pods :
kubectl get pods -n my-app -l app=web -o wide
Vous devriez voir un pod par nœud, comme :
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-5f7c9b6c7-abcde 1/1 Running 0 10s 10.244.1.5 node-1 <none> <none>
web-5f7c9b6c7-fghij 1/1 Running 0 10s 10.244.2.7 node-2 <none> <none>
web-5f7c9b6c7-klmno 1/1 Running 0 10s 10.244.3.9 node-3 <none> <none>
Utiliser l'anti-affinité préférée pour plus de flexibilité
L'anti-affinité obligatoire peut entraîner des échecs de planification si le cluster n'a pas assez de nœuds. Par exemple, si vous n'avez que deux nœuds et 3 réplicas, l'anti-affinité obligatoire sur le nom d'hôte laissera un pod en attente. Utilisez preferredDuringSchedulingIgnoredDuringExecution pour permettre une répartition souple :
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- web
topologyKey: kubernetes.io/hostname
Préférez l'anti-affinité souple pour la plupart des cas d'utilisation CI/CD où la disponibilité compte plus que le placement strict.
Ajouter de l'affinité de pod pour colocaliser des services
Parfois, vous voulez que certains pods s'exécutent près les uns des autres, comme un cache et ses consommateurs. Utilisez podAffinity pour placer des pods sur le même nœud ou la même zone :
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- redis-cache
topologyKey: kubernetes.io/hostname
Cela force le nouveau pod à se planifier sur un nœud où un pod avec app=redis-cache s'exécute déjà.
Valider que le sélecteur correspond aux pods existants
Si le sélecteur ne correspond à aucun pod, la règle d'affinité peut ne jamais être satisfaite. Vérifiez les étiquettes de vos pods cibles prévus :
kubectl get pods -n my-app -l app=redis-cache --show-labels
La sortie devrait montrer l'étiquette app=redis-cache. Sinon, ajustez les étiquettes de votre manifeste ou le sélecteur.
Utiliser namespaceSelector pour l'affinité inter-namespaces
Par défaut, l'affinité ne considère que les pods du même espace de noms. Pour inclure des pods d'autres espaces de noms, ajoutez namespaceSelector :
podAffinityTerm:
labelSelector:
matchLabels:
app: shared-service
namespaceSelector:
matchLabels:
environment: staging
topologyKey: kubernetes.io/hostname
Cette règle recherche des pods avec app=shared-service dans les espaces de noms étiquetés environment=staging. Assurez-vous que l'étiquette de l'espace de noms est définie :
kubectl label namespace shared env=staging --overwrite
kubectl get namespace shared --show-labels
Vérification et diagnostics
Après le déploiement avec des règles d'affinité, vérifiez que les pods sont planifiés comme prévu et que les règles n'ont pas introduit de nouveaux échecs. Utilisez une combinaison de commandes kubectl et d'inspection des événements.
Vérifier l'état des pods et les événements
Tout d'abord, listez tous les pods de l'espace de noms avec une sortie large :
kubectl get pods -n my-app -o wide
Si un pod est en Pending, examinez les événements de planification :
kubectl describe pod <nom-du-pod-en-attente> -n my-app
Recherchez des événements comme :
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 40s default-scheduler 0/3 nodes are available: 3 node(s) didn't match pod anti-affinity rules.
Cela vous indique que la règle d'anti-affinité est trop stricte ou que le cluster manque de nœuds.
Vérifier que les règles d'affinité sont attachées
Confirmez que les pods en cours d'exécution ont la spécification d'affinité attendue :
kubectl get pod <nom-du-pod> -n my-app -o jsonpath='{.spec.affinity}' | jq .
La sortie attendue inclut le bloc podAntiAffinity ou podAffinity que vous avez défini.
Tester le rollout dans un espace de noms de staging
Ne fusionnez jamais les changements d'affinité directement en production. Utilisez un espace de noms de staging ou un déploiement canary pour vérifier le comportement avec un trafic réel. Par exemple, créez un espace de noms séparé staging et appliquez-y votre manifeste :
kubectl create namespace staging
kubectl apply -f web-deployment.yaml -n staging
Exécutez ensuite les mêmes vérifications dans le staging. Si tout passe, propagez à la production via votre pipeline CI/CD.
Vérification automatisée avec kubectl en CI
Dans votre pipeline CI, ajoutez une étape de vérification qui s'exécute après kubectl apply. Par exemple, dans un workflow GitHub Actions :
- name: Verify pod distribution
run: |
kubectl get pods -n my-app -l app=web -o json | jq -e '.items | length == 3'
kubectl get pods -n my-app -l app=web -o json | jq -e '[.items[].spec.nodeName] | unique | length == 3'
La première commande vérifie que 3 réplicas sont en cours d'exécution. La seconde vérifie qu'ils sont répartis sur 3 nœuds différents (en supposant un cluster à 3 nœuds). Si l'une échoue, l'étape échoue.
Utiliser les contraintes de répartition de topologie de pod pour un contrôle supplémentaire
Les contraintes de répartition de topologie de pod offrent un moyen plus granulaire de répartir les pods entre les nœuds, les zones ou d'autres domaines. Combinez-les avec l'anti-affinité pour un placement robuste. Exemple :
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
Cette contrainte garantit que le nombre de pods par zone diffère d'au plus 1. Si le cluster ne peut pas la satisfaire, le pod reste en attente.
Surveiller les journaux du planificateur pour le débogage
Si vous soupçonnez un dysfonctionnement du planificateur, vérifiez ses journaux :
kubectl logs -n kube-system -l component=kube-scheduler --tail=100
Recherchez des lignes mentionnant des échecs de predicate ou de priority liés à vos pods. Cela nécessite un accès au plan de contrôle, qui peut ne pas être disponible sur les services gérés. Dans ce cas, fiez-vous aux événements de pod.
Modes de défaillance et récupération
Même avec une planification minutieuse, les règles d'affinité et d'anti-affinité peuvent provoquer des échecs de déploiement. Comprenez les modes de défaillance courants et comment récupérer en toute sécurité, en utilisant des stratégies de rollback intégrées au CI/CD.
Mode de défaillance 1 : Pods bloqués en Pending en raison d'une anti-affinité insatisfaisable
Symptôme : Les nouveaux pods restent en Pending et les événements montrent FailedScheduling avec didn't match pod anti-affinity rules.
Cause : L'anti-affinité obligatoire ne peut pas être satisfaite car le cluster manque de nœuds suffisants ou d'autres pods occupent tous les nœuds éligibles.
Récupération :
- Vérifiez le nombre actuel de nœuds et le placement des pods :
kubectl get nodes
kubectl get pods -n my-app -l app=web -o wide
- Si le cluster a moins de nœuds que le nombre de réplicas requis, vous devez soit augmenter le nombre de nœuds, soit assouplir la règle en
preferredDuringSchedulingIgnoredDuringExecution.
- Appliquez le manifeste assoupli :
kubectl apply -f web-deployment-relaxed.yaml -n my-app
- Vérifiez que les pods sont maintenant planifiés :
kubectl rollout status deployment/web -n my-app
Mode de défaillance 2 : Règle d'affinité référençant une étiquette de pod inexistante
Symptôme : Les pods sont planifiés, mais pas colocalisés ou répartis comme prévu. Aucun événement d'erreur.
Cause : Le labelSelector dans le terme d'affinité ne correspond à aucun pod existant, donc la règle n'a aucun effet. Cela arrive souvent en raison d'une faute de frappe dans la clé ou la valeur de l'étiquette.
Diagnostic :
kubectl get pods -n my-app --show-labels
Comparez avec les matchLabels de votre manifeste. En cas de non-concordance, corrigez le sélecteur et réappliquez.
Récupération : Mettez à jour le manifeste de déploiement et déployez une nouvelle révision :
kubectl apply -f web-deployment-fixed.yaml -n my-app
kubectl rollout restart deployment/web -n my-app
Mode de défaillance 3 : Clé de topologie invalide pour la topologie du cluster
Symptôme : Le planificateur ne trouve pas de nœuds avec la clé de topologie spécifiée, ou la clé ne représente pas un domaine de défaillance significatif.
Exemple : Utiliser topology.kubernetes.io/zone sur un cluster à zone unique, ou utiliser une étiquette personnalisée que les nœuds n'ont pas.
Diagnostic : Listez les étiquettes des nœuds :
kubectl get nodes --show-labels | grep topology
Si la clé est absente, la règle ne correspondra jamais. Choisissez une clé valide comme kubernetes.io/hostname pour les clusters à zone unique.
Récupération : Modifiez la clé de topologie dans le manifeste et réappliquez.
Mode de défaillance 4 : Le pipeline CI/CD a appliqué des changements sans vérification
Symptôme : Une étape du pipeline a appliqué un manifeste, mais aucune vérification n'a été exécutée, et le déploiement de production est maintenant cassé.
Cause : Absence de vérifications automatisées après kubectl apply.
Récupération : Revenez à la révision précédente du déploiement en utilisant kubectl rollout undo :
kubectl rollout undo deployment/web -n my-app
Vérifiez l'état du rollout :
kubectl rollout status deployment/web -n my-app
Cela revient à la dernière configuration connue, y compris les paramètres d'affinité précédents.
Mode de défaillance 5 : Affinité inter-namespaces échouant en raison des étiquettes d'espaces de noms
Symptôme : Les pods avec namespaceSelector ne peuvent pas être planifiés, et les événements montrent qu'aucun espace de noms ne correspond.
Cause : Les espaces de noms cibles n'ont pas les étiquettes requises.
Récupération : Ajoutez les étiquettes :
kubectl label namespace shared env=staging --overwrite
Vérifiez :
kubectl get namespace shared --show-labels
Puis réappliquez le déploiement.
Automatiser le rollback dans le CI/CD
Dans votre pipeline, ajoutez une étape de rollback qui se déclenche automatiquement en cas d'échec de la vérification. Par exemple :
- name: Apply manifest
run: kubectl apply -f web-deployment.yaml -n my-app
- name: Verify rollout
run: kubectl rollout status deployment/web -n my-app --timeout=120s
- name: Rollback on failure
if: failure()
run: |
echo "Rolling back to previous revision"
kubectl rollout undo deployment/web -n my-app
kubectl rollout status deployment/web -n my-app
Cela garantit que tout déploiement échoué est automatiquement annulé.
Documenter les runbooks de récupération
Pour chaque déploiement avec une planification complexe, maintenez un runbook qui inclut :
- La commande pour capturer la répartition actuelle des pods.
- La commande pour inspecter les événements de planification.
- La commande exacte de rollback.
- Les coordonnées de l'ingénieur d'astreinte.
Stockez le runbook dans votre dépôt à côté des manifestes pour le versionner.
Liste de contrôle des opérations
Utilisez cette liste de contrôle avant et après chaque exécution CI/CD qui modifie l'affinité ou l'anti-affinité des pods. Elle impose l'observation, la vérification et la récupérabilité.
Liste de contrôle pré-déploiement
- [ ] Confirmer que la version du serveur Kubernetes prend en charge les fonctionnalités prévues :
kubectl version --short - [ ] Prendre un instantané de la répartition actuelle des pods :
kubectl get pods -o wide --all-namespaces > pods-before-$(date +%Y%m%d-%H%M%S).txt - [ ] Lister les étiquettes de topologie de nœud disponibles :
kubectl get nodes --show-labels - [ ] Valider le manifeste avec un dry-run côté serveur :
kubectl apply -f manifest.yaml --dry-run=server - [ ] Vérifier que les sélecteurs d'étiquettes correspondent aux pods existants :
kubectl get pods -n my-app --show-labels - [ ] S'assurer que les clés de topologie sont valides pour la topologie du cluster
- [ ] Confirmer que l'anti-affinité obligatoire ne dépasse pas la capacité disponible
- [ ] Configurer un espace de noms de staging pour les tests pré-production
- [ ] Définir la commande de rollback dans le pipeline :
kubectl rollout undo deployment/<name> - [ ] Ajouter des étapes de vérification au pipeline qui contrôlent le placement des pods et le nombre de réplicas
Liste de contrôle post-déploiement
- [ ] Vérifier l'état du rollout :
kubectl rollout status deployment/<name> -n my-app - [ ] Inspecter les événements de pod pour les avertissements de planification :
kubectl describe pod <pod-name> - [ ] Confirmer que la répartition des pods correspond aux attentes :
kubectl get pods -o wide -n my-app - [ ] Comparer la répartition des pods à l'instantané pré-déploiement
- [ ] Si des pods sont en Pending, diagnostiquer avec
kubectl describe podet corriger ou rollback - [ ] Enregistrer le nouvel instantané de répartition des pods pour une comparaison future
- [ ] Mettre à jour le runbook avec tout nouveau mode de défaillance rencontré
- [ ] Informer l'équipe de l'état du déploiement
Exemple de script pré-déploiement
Voici un script combiné que vous pouvez exécuter localement avant de pousser les changements :
#!/bin/bash
set -e
echo "Kubernetes version:"
kubectl version --short
echo "Snapshotting current pods..."
kubectl get pods -o wide --all-namespaces > pods-before-$(date +%Y%m%d-%H%M%S).txt
echo "Listing node labels:"
kubectl get nodes --show-labels
echo "Validating manifest dry-run..."
kubectl apply -f web-deployment.yaml --dry-run=server
echo "All pre-deployment checks passed."
Exécutez-le et vérifiez qu'il n'y a pas d'erreurs avant d'appliquer.
Conclusion
L'affinité et l'anti-affinité des pods Kubernetes sont des outils puissants pour contrôler le placement des pods, mais elles doivent être intégrées au CI/CD avec la même rigueur que tout autre changement d'infrastructure. Cet article a fourni un chemin pratique : commencer par des vérifications de version et d'environnement, définir une configuration sûre, vérifier automatiquement et se préparer aux défaillances avec un rollback documenté.
Copier des commandes sans vérifier les prérequis et la sortie attendue n'est pas une procédure opérationnelle. Choisissez plutôt une vérification à faible risque pour votre environnement, enregistrez l'état actuel, exécutez la vérification documentée et comparez le résultat avec le signal attendu. Examinez les dépendances telles que l'affinité de nœud, le kube-scheduler et les pods eux-mêmes pour vous assurer que tout le chemin de planification est sain.
Un flux de travail technique fiable rend les défaillances visibles, protège les valeurs sensibles, limite les changements à la ressource prévue et définit la vérification de récupération avant qu'un incident ne force la décision. En suivant la liste de contrôle et les exemples de cet article, vous pouvez automatiser en toute confiance l'affinité et l'anti-affinité des pods dans vos pipelines CI/CD.
N'oubliez pas de versionner vos manifestes, de rendre vos pipelines idempotents et de tenir vos runbooks à jour. Les contraintes de planification font partie de la résilience de votre application, et les traiter comme des citoyens de première classe dans votre processus de livraison se traduira par la stabilité et la prévisibilité.