## 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 : ```bash 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 : ```bash 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é : ```bash 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 : ```bash 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 : ```bash 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 api worker map[nodeAffinity:map[...] podAntiAffinity:map[...]] ``` Si un déploiement utilise déjà l'affinité, examinez son YAML pour comprendre ses contraintes actuelles : ```bash 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 : ```bash 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 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 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 : ```bash kubectl describe pod -n kube-system -l component=kube-scheduler | grep -A 5 "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 : ```yaml 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` : ```bash 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` : ```bash 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 : ```bash 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 : ```bash 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 web-5f7c9b6c7-fghij 1/1 Running 0 10s 10.244.2.7 node-2 web-5f7c9b6c7-klmno 1/1 Running 0 10s 10.244.3.9 node-3 ``` ### 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 : ```yaml 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 : ```yaml 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 : ```bash 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` : ```yaml 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 : ```bash 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 : ```bash kubectl get pods -n my-app -o wide ``` Si un pod est en `Pending`, examinez les événements de planification : ```bash kubectl describe pod -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 : ```bash kubectl get 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 : ```bash 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 : ```yaml - 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 : ```yaml 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 : ```bash 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 :** 1. Vérifiez le nombre actuel de nœuds et le placement des pods : ```bash kubectl get nodes kubectl get pods -n my-app -l app=web -o wide ``` 2. 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`. 3. Appliquez le manifeste assoupli : ```bash kubectl apply -f web-deployment-relaxed.yaml -n my-app ``` 4. Vérifiez que les pods sont maintenant planifiés : ```bash 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 :** ```bash 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 : ```bash 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 : ```bash 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` : ```bash kubectl rollout undo deployment/web -n my-app ``` Vérifiez l'état du rollout : ```bash 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 : ```bash kubectl label namespace shared env=staging --overwrite ``` Vérifiez : ```bash 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 : ```yaml - 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/` - [ ] 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/ -n my-app` - [ ] Inspecter les événements de pod pour les avertissements de planification : `kubectl describe pod ` - [ ] 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 pod` et 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 : ```bash #!/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é.