Un Deployment à trois replicas ne garantit rien sur leur répartition : le scheduler peut parfaitement placer les trois pods sur le même nœud si rien ne l’en empêche explicitement. La perte de ce nœud unique emporte alors les trois replicas d’un coup, exactement le scénario qu’un PodDisruptionBudget est censé protéger, sauf qu’aucun PDB ne peut rien faire une fois que le placement initial a déjà tout concentré au même endroit.
Pod anti-affinity : répartir par exclusion relative
L’anti-affinity ne dit pas à un pod où aller dans l’absolu, elle lui dit d’éviter les nœuds où tournent déjà d’autres pods portant un label spécifique :
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: payment-api
topologyKey: "kubernetes.io/hostname"
requiredDuringSchedulingIgnoredDuringExecution est une contrainte dure : le scheduler refuse de placer le pod si aucun nœud ne satisfait la règle, quitte à le laisser en Pending. La variante preferred assouplit la contrainte : le scheduler essaie de respecter la répartition, sans bloquer le déploiement si le cluster n’a pas assez de nœuds disponibles pour y parvenir parfaitement. Le nom trompeur du champ mérite d’être lu lentement : « ignored during execution » signifie que la règle ne s’applique qu’au moment du placement, jamais après. Un pod déjà en cours d’exécution n’est jamais évincé si la topologie du cluster change et viole la règle après coup.
Topology spread constraints : une répartition mesurée, pas binaire
L’anti-affinity répond à une question binaire (ce nœud a-t-il déjà ce label, oui ou non). Les topology spread constraints répondent à une question de mesure : quel est l’écart maximal toléré entre le nombre de pods sur chaque zone ou nœud.
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: payment-api
maxSkew: 1 signifie qu’aucune zone ne peut héberger plus d’un pod de plus que la zone la moins chargée : avec trois zones et trois replicas, un pod par zone satisfait la contrainte, tout déséquilibre plus important la viole. whenUnsatisfiable détermine ce qui se passe si l’équilibre exact devient impossible : DoNotSchedule bloque comme une contrainte dure, ScheduleAnyway dégrade proprement en préférence, la même distinction que required/preferred pour l’anti-affinity, avec un vocabulaire différent.
Lequel choisir : la question posée, pas la préférence d’outil
L’anti-affinity répond bien à « ne jamais mettre ces deux pods spécifiques ensemble » (deux instances d’une même base de données qui ne doivent jamais partager un nœud, par exemple). Les topology spread constraints répondent mieux à « répartir N replicas aussi uniformément que possible entre zones ou nœuds », un problème d’équilibrage plutôt que d’exclusion. Les deux mécanismes se combinent sans conflit sur un même Deployment quand le besoin réel combine les deux questions.
Le lien avec le cluster autoscaler
Une contrainte d’anti-affinity trop stricte peut bloquer le scale down du cluster autoscaler : si évincer un pod d’un nœud signifie le replacer sur un nœud qui viole sa propre règle d’anti-affinity, l’autoscaler refuse le retrait plutôt que de violer une contrainte posée ailleurs. Un nœud qui semble sous-utilisé mais reste refusé au retrait cache souvent exactement ce genre de contrainte de placement, pas un bug de l’autoscaler.
À retenir
Trois replicas sans contrainte de répartition peuvent atterrir sur le même nœud, ce qu’aucun PodDisruptionBudget ne rattrape après coup. L’anti-affinity exclut par label de façon binaire ; les topology spread constraints mesurent un déséquilibre tolérable. Les deux se combinent, et tous deux peuvent bloquer un scale down du cluster autoscaler s’ils sont posés trop strictement. Poser cette répartition correctement dès le départ fait partie de ce qui protège réellement la haute disponibilité promise par plusieurs replicas.