TL;DR
podAntiAffinity est une règle de placement évaluée par le scheduler Kubernetes. Elle demande de ne pas regrouper un nouveau pod avec des pods existants sélectionnés par leurs labels, à l’intérieur d’un domaine défini par un label de nœud. Avec topologyKey: kubernetes.io/hostname, le domaine est le nœud lui-même. Avec topology.kubernetes.io/zone, il peut s’agir d’une zone de disponibilité.
Deux comportements sont disponibles. requiredDuringSchedulingIgnoredDuringExecution est une contrainte dure : si aucun nœud ne respecte la règle, le pod reste Pending. preferredDuringSchedulingIgnoredDuringExecution est une préférence : le scheduler cherche le meilleur placement, mais il peut regrouper les pods lorsque la capacité ou la topologie l’impose.
L’anti-affinité répond bien à une intention du type « ces réplicas ne doivent jamais partager le même nœud » ou « évitons de les regrouper ». Pour répartir équitablement un grand nombre de réplicas avec un écart mesurable, topologySpreadConstraints est souvent plus expressive grâce à maxSkew.
La vidéo de référence
Cet article accompagne la vidéo Kubernetes 039 – Affinités : le podAntiAffinity, publiée sur la chaîne xavki et intégrée à la playlist Kubernetes v2. Le README du dépôt de la formation associe précisément cet identifiant YouTube à l’épisode 039. Il résume l’objectif par la séparation des pods pour améliorer la disponibilité et la tolérance aux pannes.
La page YouTube expose une piste de sous-titres français générés automatiquement. Lors de la préparation de l’article, les endpoints de sous-titres ont toutefois répondu avec un contenu vide. Aucun passage n’est donc présenté ici comme une citation ou une paraphrase de la vidéo. Le dépôt, son historique Git et la documentation officielle Kubernetes constituent la base technique de l’article.
Un détail important concerne l’organisation du dépôt. Les manifests de l’épisode 039 ne sont plus dans l’arbre courant. Le commit historique 16f9baf, intitulé add 039 affinity, contient les exemples strict et préféré utilisés ci-dessous. Le dossier actuel consacré à l’épisode 040 traite, lui, de l’affinité de nœud des volumes persistants. Cette distinction évite de mélanger deux sujets voisins mais différents.
podAntiAffinity Kubernetes : pourquoi séparer les pods ?
Imaginons une API répliquée trois fois sur un cluster de trois workers. Sans règle supplémentaire, Kubernetes peut placer deux réplicas sur le même worker et le troisième sur un autre. Ce placement n’est pas nécessairement mauvais : le scheduler tient compte des ressources, des contraintes et de ses scores. Mais il ne peut pas deviner que les trois réplicas représentent le même risque opérationnel.
Si le worker qui héberge deux réplicas tombe, deux tiers de la capacité disparaissent d’un coup. Le Deployment recréera les pods, mais leur redémarrage dépendra de la capacité restante, des images à télécharger, des volumes et de l’état général du cluster. La réplication seule ne garantit donc pas une bonne dispersion.
podAntiAffinity permet d’exprimer cette intention dans la spécification du pod. La règle ne cible pas directement un nom de pod, qui change à chaque recréation. Elle sélectionne un ensemble de pods avec un labelSelector, puis indique le domaine dans lequel la cohabitation doit être interdite ou évitée.
Le modèle mental est simple :
- Le scheduler examine le pod à placer.
- Il lit son
podAntiAffinity. - Il recherche les pods existants correspondant au sélecteur.
- Il observe les valeurs du label de nœud indiqué par
topologyKey. - Il filtre ou note les nœuds selon le type de règle.
L’anti-affinité ne rend pas une application hautement disponible à elle seule. Il faut toujours plusieurs nœuds, une capacité suffisante, des probes, des budgets de disruption adaptés, une stratégie de mise à jour cohérente et, selon l’application, une architecture de données tolérante aux pannes. Elle apporte néanmoins une propriété essentielle : Kubernetes connaît explicitement les placements que l’on considère dangereux.
Comment fonctionne podAntiAffinity ?
La règle se place dans spec.affinity.podAntiAffinity du template de pod. Dans un Deployment, elle se trouve donc sous spec.template.spec. Ses éléments principaux sont les suivants :
labelSelectordésigne les pods avec lesquels on ne veut pas cohabiter ;topologyKeydésigne un label porté par les nœuds et définit la frontière du domaine ;namespacesounamespaceSelectorpeuvent élargir la recherche au-delà du namespace courant ;- le nom du bloc,
required...oupreferred..., détermine si la règle est bloquante ou seulement souhaitable.
Par défaut, lorsque namespaces et namespaceSelector ne sont pas renseignés, le sélecteur recherche les pods dans le namespace du pod à planifier. Ce détail est important dans les clusters multi-équipes : deux applications portant le même label dans des namespaces distincts ne s’influencent pas automatiquement.
Le labelSelector accepte matchLabels et matchExpressions. Plusieurs conditions sont combinées par un ET logique. Il faut donc choisir des labels stables et suffisamment précis. Un simple app: backend peut convenir dans un laboratoire isolé, mais une plateforme partagée gagnera souvent à utiliser des dimensions comme app.kubernetes.io/name, app.kubernetes.io/instance et app.kubernetes.io/component.
Le mot IgnoredDuringExecution mérite aussi une explication. La règle est évaluée lors du scheduling. Si les labels ou la topologie changent après le placement, Kubernetes n’expulse pas automatiquement le pod déjà en cours d’exécution pour rétablir l’anti-affinité. Le scheduler choisit une destination ; il n’est pas un contrôleur de rééquilibrage permanent.
requiredDuringSchedulingIgnoredDuringExecution : la contrainte stricte
La variante requiredDuringSchedulingIgnoredDuringExecution impose une condition dure. Un nœud qui violerait la règle est éliminé de la liste des destinations possibles. Lorsque tous les nœuds sont éliminés, le pod reste Pending.
Le manifest suivant reprend le principe de l’exemple historique ajouté dans le commit 16f9baf. Dix réplicas nginx portent le label app: backend. Chaque nouveau replica refuse un nœud qui héberge déjà un pod app: backend dans le même namespace.
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-required
spec:
replicas: 10
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: backend
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
Sur un cluster qui possède trois workers éligibles et correctement étiquetés, cette règle autorise au plus un pod correspondant par valeur de hostname. Trois réplicas peuvent donc être distribués. Les suivants ne deviennent pas magiquement exécutables : sans quatrième domaine éligible, ils restent en attente.
Ce résultat est souvent exactement celui que l’on veut pour une contrainte de sûreté. Il vaut mieux rendre l’insuffisance de capacité visible que violer une règle « jamais deux ensemble ». En revanche, cette décision doit être consciente. Une anti-affinité dure trop ambitieuse peut empêcher un déploiement, ralentir une mise à jour ou maintenir des réplicas indisponibles lors de la maintenance d’un nœud.
preferredDuringSchedulingIgnoredDuringExecution : la préférence
La variante preferredDuringSchedulingIgnoredDuringExecution transforme l’interdiction en préférence. Chaque terme reçoit un weight compris entre 1 et 100. Le scheduler ajoute ces préférences au calcul de score des nœuds, en plus de ses autres plugins de scoring. Le poids exprime l’importance relative de la préférence ; il ne constitue pas un pourcentage ni une garantie.
L’exemple historique de l’épisode 039 utilise cinq réplicas et un poids de 100 :
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend-preferred
spec:
replicas: 5
selector:
matchLabels:
app: backend-preferred
template:
metadata:
labels:
app: backend-preferred
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: backend-preferred
topologyKey: kubernetes.io/hostname
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
Le scheduler essaie ici d’espacer les réplicas, mais il peut en placer plusieurs sur le même nœud. Cette souplesse est utile lorsque la disponibilité des pods prime sur une séparation parfaite, par exemple dans un environnement de développement ou sur un cluster dont le nombre de nœuds varie.
Une confusion fréquente consiste à penser qu’un poids de 100 rend la règle obligatoire. Ce n’est pas le cas. Le caractère strict ou souple vient du bloc choisi. Le poids ne s’applique qu’au score de la variante preferred.
topologyKey : définir le domaine de séparation
topologyKey est la clé d’un label de nœud. Les nœuds partageant la même valeur pour cette clé appartiennent au même domaine topologique. La règle compare donc des domaines, pas seulement des noms de machines.
Les deux usages les plus courants sont :
kubernetes.io/hostnamepour séparer les pods par nœud ;topology.kubernetes.io/zonepour les séparer par zone de disponibilité.
On peut inspecter les labels avec :
kubectl get nodes --show-labels
kubectl get nodes -L kubernetes.io/hostname,topology.kubernetes.io/zone
La cohérence des labels est indispensable. Si certains nœuds ne portent pas la clé attendue ou si ses valeurs ne représentent pas la topologie réelle, le résultat peut être surprenant. Dans une infrastructure personnalisée, on peut ajouter un label de rack ou de salle, mais il faut le gérer comme une donnée d’infrastructure fiable.
La documentation Kubernetes signale aussi une limitation spécifique : l’admission controller LimitPodHardAntiAffinityTopology peut limiter par défaut le topologyKey de l’anti-affinité dure à kubernetes.io/hostname. Le comportement dépend de la configuration du cluster. Avant d’utiliser une règle dure par zone personnalisée, il faut vérifier les politiques d’admission de la plateforme.
Enfin, choisir le bon domaine dépend du risque. Séparer par hostname protège contre la perte d’un nœud. Séparer par zone protège contre une panne de zone, mais exige plusieurs zones et suffisamment de capacité dans chacune. Il est possible de combiner plusieurs contraintes, à condition de comprendre qu’elles doivent toutes être satisfaites pour une règle stricte.
Démonstration avec plusieurs réplicas nginx
Pour un laboratoire, créons un namespace dédié et appliquons le manifest strict enregistré dans backend-required.yml :
kubectl create namespace demo-antiaffinity
kubectl apply -n demo-antiaffinity -f backend-required.yml
kubectl get pods -n demo-antiaffinity -o wide
Avant d’appliquer, on peut demander au client Kubernetes de vérifier la structure du manifest sans créer de ressource :
kubectl apply --dry-run=client -n demo-antiaffinity -f backend-required.yml
kubectl explain deployment.spec.template.spec.affinity.podAntiAffinity
Le nombre de replicas doit être adapté au cluster. Avec trois workers et dix réplicas strictement séparés par hostname, le laboratoire cherche volontairement à produire des pods Pending. Cette situation n’est pas un bug du Deployment : le contrôleur crée le nombre de pods demandé, puis le scheduler constate que certains ne disposent d’aucune destination admissible.
Pour observer plus facilement le contraste, on peut commencer avec un nombre de réplicas égal au nombre de workers, vérifier la dispersion, puis augmenter progressivement :
kubectl scale deployment/backend-required -n demo-antiaffinity --replicas=3
kubectl get pods -n demo-antiaffinity -o wide
kubectl scale deployment/backend-required -n demo-antiaffinity --replicas=10
kubectl get pods -n demo-antiaffinity -o wide
Les commandes et les résultats attendus sont fournis comme procédure de laboratoire. Cette production éditoriale n’a pas exécuté le scénario sur un cluster Kubernetes local ; elle a validé la structure documentaire et la syntaxe YAML, pas le comportement dynamique d’une infrastructure particulière.
Vérifier le placement et les pods Pending
kubectl get pods -o wide affiche le nœud associé à chaque pod. Il permet de vérifier rapidement si les réplicas en cours d’exécution sont bien répartis. Pour une vue structurée :
kubectl get pods -n demo-antiaffinity \
-l app=backend \
-o custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName
Un pod Pending doit ensuite être décrit. Les événements de scheduling fournissent la raison exploitable :
kubectl describe pod -n demo-antiaffinity <nom-du-pod-pending>
kubectl get events -n demo-antiaffinity \
--sort-by=.metadata.creationTimestamp
Dans la section Events, le scheduler peut indiquer qu’aucun nœud ne satisfait l’anti-affinité. Il faut toutefois lire le message complet : manque de CPU, taints non tolérés, node affinity, volumes ou ports demandés peuvent éliminer d’autres nœuds dans la même décision.
Un bon diagnostic suit trois niveaux. Vérifier d’abord le sélecteur et les labels des pods existants. Vérifier ensuite le topologyKey et les labels des nœuds. Vérifier enfin toutes les autres contraintes de scheduling. Ajouter un nœud ne résout rien si ce nœud n’est pas éligible ou s’il partage la même valeur topologique que les autres.
Après le laboratoire, nettoyer le namespace supprime toutes les ressources de démonstration :
kubectl delete namespace demo-antiaffinity
podAffinity, podAntiAffinity et topologySpreadConstraints
Ces trois mécanismes parlent de placement, mais ils n’expriment pas la même intention.
| Mécanisme | Question exprimée | Effet typique | Cas d’usage |
|---|---|---|---|
podAffinity |
Avec quels pods faut-il se rapprocher ? | Attire le pod vers un domaine contenant des pods sélectionnés | Rapprocher une application d’un cache ou d’un service local |
podAntiAffinity |
Avec quels pods faut-il éviter de cohabiter ? | Écarte le pod d’un domaine contenant des pods sélectionnés | Isoler des réplicas ou des rôles concurrents |
topologySpreadConstraints |
Comment équilibrer quantitativement un groupe ? | Limite l’écart de réplicas entre domaines avec maxSkew |
Distribuer régulièrement une application entre nœuds ou zones |
Une règle dure podAntiAffinity par hostname peut produire « au plus un pod correspondant par nœud ». C’est une interdiction binaire. Une contrainte de répartition décrit plutôt un équilibre. Par exemple :
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: backend
Ici, maxSkew: 1 limite l’écart entre les domaines éligibles. DoNotSchedule rend la contrainte bloquante ; ScheduleAnyway conserverait une préférence pour réduire le déséquilibre. D’autres paramètres, tels que minDomains, nodeAffinityPolicy et nodeTaintsPolicy, permettent d’affiner les domaines pris en compte.
Le choix pratique se formule ainsi : si la règle métier est « jamais ensemble », l’anti-affinité est directe. Si l’objectif est « distribuer six réplicas aussi régulièrement que possible », les topology spread constraints décrivent mieux le résultat attendu.
Limites, performances et pièges fréquents
La première limite est la capacité. Une règle stricte crée volontairement des pods Pending lorsque le nombre de domaines est insuffisant. Il faut rapprocher le nombre de réplicas, le nombre de domaines éligibles, les opérations de maintenance et la capacité de l’autoscaler.
La deuxième limite est le coût du calcul. L’inter-pod affinity et anti-affinity oblige le scheduler à comparer le pod avec d’autres pods et avec la topologie. La documentation officielle indique que ce mécanisme peut ralentir fortement le scheduling dans les clusters de plusieurs centaines de nœuds. Ce n’est pas une interdiction absolue, mais un signal pour tester la latence et envisager des contraintes de répartition plus simples.
La troisième limite concerne les labels. Un sélecteur trop large peut faire interagir des workloads sans rapport. Un sélecteur trop étroit peut ne rien sélectionner. Une faute sur la valeur de topologyKey peut produire un comportement inattendu. Les labels de topologie doivent être cohérents sur tous les nœuds concernés.
Un autre piège consiste à confondre placement et éviction. IgnoredDuringExecution signifie qu’une violation apparue après le scheduling n’entraîne pas le déplacement automatique du pod. Pour rééquilibrer un cluster existant, il faut une opération contrôlée ou un composant adapté, tel que le descheduler, avec des politiques soigneusement évaluées.
Enfin, la préemption ne garantit pas toujours une solution. Si la règle interdit structurellement tout domaine disponible, évincer un pod de moindre priorité ne crée pas un nouveau domaine. Le diagnostic doit porter sur la topologie et le sélecteur, pas seulement sur les ressources.
Bonnes pratiques pour la production
Commencez par nommer le risque que vous voulez couvrir : perte d’un processus, d’un nœud, d’un rack ou d’une zone. Le topologyKey doit représenter cette frontière. Une règle par hostname ne protège pas d’une panne de zone si tous les workers appartiennent à la même zone.
Préférez des labels applicatifs standards et stables. Testez le sélecteur avant le déploiement avec kubectl get pods -l .... Documentez également la portée des namespaces lorsque la règle traverse des équipes ou des environnements.
Réservez required aux invariants que l’application ne peut pas violer. Pour un souhait d’optimisation, utilisez preferred. Une préférence maintient plus facilement la disponibilité pendant une panne ou une maintenance, tandis qu’une règle dure privilégie l’isolation au prix de pods potentiellement indisponibles.
Testez les scénarios dégradés : retrait d’un nœud, cordon et drain, RollingUpdate, pic de replicas et absence temporaire d’une zone. Vérifiez aussi les paramètres du Deployment. Une stratégie de rollout peut créer des pods supplémentaires via maxSurge; une anti-affinité dure exige alors de la capacité temporaire dans de nouveaux domaines.
Combinez l’anti-affinité avec des probes, des requests réalistes, un PodDisruptionBudget lorsque c’est pertinent et une supervision des pods Pending. L’anti-affinité traite le placement initial ; elle ne remplace ni la santé applicative ni la gestion des interruptions volontaires.
Enfin, mesurez. Sur un grand cluster, observez la latence du scheduler et le nombre de pods non planifiables. Une règle lisible dans un manifest peut devenir coûteuse lorsqu’elle doit être évaluée sur des milliers de pods.
Liens utiles
- Vidéo Kubernetes 039 : le podAntiAffinity
- Playlist Kubernetes v2 de xavki
- Dépôt Kubernetes Tutorials New Version
- Manifest strict historique de l’épisode 039
- Manifest préféré historique de l’épisode 039
- Documentation officielle : assigner les pods aux nœuds
- Documentation officielle : Pod Topology Spread Constraints
- Documentation officielle : labels et sélecteurs
FAQ
Quelle différence entre podAffinity et podAntiAffinity ?
podAffinity rapproche le nouveau pod de pods sélectionnés dans un domaine topologique. podAntiAffinity l’en éloigne. Les deux utilisent des labels de pods et un topologyKey, mais expriment des intentions opposées.
Quelle différence entre required et preferred ?
requiredDuringSchedulingIgnoredDuringExecution est une condition obligatoire au moment du scheduling. Si aucun nœud ne la respecte, le pod reste Pending. preferredDuringSchedulingIgnoredDuringExecution participe au score des nœuds, mais n’interdit pas un placement moins idéal.
Pourquoi un pod reste-t-il Pending avec podAntiAffinity ?
Tous les nœuds éligibles peuvent appartenir à des domaines contenant déjà un pod sélectionné par une règle stricte. Vérifiez les événements du pod, le labelSelector, les namespaces, les labels de nœuds et les autres contraintes de scheduling.
Comment choisir le topologyKey ?
Choisissez le label qui représente le domaine de panne à isoler. kubernetes.io/hostname sépare par nœud ; topology.kubernetes.io/zone sépare par zone. Assurez-vous que les nœuds portent des valeurs cohérentes et que la politique d’admission autorise cette clé.
Quand préférer topologySpreadConstraints ?
Utilisez topologySpreadConstraints lorsque vous voulez répartir un groupe de réplicas avec un écart mesurable entre domaines. maxSkew exprime cet équilibre plus précisément qu’une interdiction binaire d’anti-affinité.
podAntiAffinity déplace-t-il un pod déjà en cours d’exécution ?
Non. Les variantes présentées sont IgnoredDuringExecution. Une modification ultérieure des labels ou de la topologie n’entraîne pas, à elle seule, l’éviction ou le déplacement automatique du pod.
Un poids de 100 rend-il une préférence obligatoire ?
Non. Le poids 100 donne une forte contribution au score parmi les préférences, mais la règle reste souple. Seul le bloc requiredDuringSchedulingIgnoredDuringExecution exprime une contrainte dure.
Conclusion
podAntiAffinity donne au scheduler une information que le nombre de réplicas ne suffit pas à transmettre : certains pods ne doivent pas partager le même domaine de panne. La règle stricte protège cet invariant quitte à laisser des pods Pending. La règle préférée recherche une meilleure dispersion sans bloquer le déploiement.
Le point décisif n’est pas la longueur du YAML, mais la précision de l’intention. Il faut choisir un sélecteur stable, un topologyKey fidèle à l’infrastructure et un niveau de contrainte adapté au risque. Lorsque l’objectif devient un équilibre quantifié entre plusieurs domaines, topologySpreadConstraints complète ou remplace avantageusement l’anti-affinité.
Pour prolonger la série, l’épisode suivant aborde l’affinité de nœud appliquée aux volumes persistants : un autre exemple de la manière dont Kubernetes transforme une intention déclarative en décision de placement.
Transparence IA
Ce contenu a été partiellement rédigé ou structuré avec l aide d outils d intelligence artificielle, puis relu, corrigé et complété par l auteur avant publication.