
Kubernetes ne programme pas les Pods "au hasard". Vous pouvez contrôler précisément où s'exécutent vos workloads grâce à plusieurs mécanismes complémentaires. Ce guide présente les outils modernes de scheduling : du simple nodeSelector jusqu'aux topologySpreadConstraints pour la haute disponibilité.
Ce que vous allez apprendre
Section intitulée « Ce que vous allez apprendre »- Cibler des nœuds spécifiques avec
nodeSelectoretnodeAffinity - Gérer les relations entre Pods avec
podAffinity/podAntiAffinity - Répartir proprement les replicas avec
topologySpreadConstraints - Protéger et réserver des nœuds avec
taints/tolerations - Débugger les Pods bloqués en
Pending
Vue d'ensemble des mécanismes
Section intitulée « Vue d'ensemble des mécanismes »Kubernetes propose sept mécanismes de placement, et la confusion vient de ce qu'ils ne répondent pas à la même question. Certains désignent où poser un Pod, d'autres définissent qui a le droit d'entrer sur un nœud, d'autres encore organisent la répartition d'un groupe de replicas. La colonne « Agit sur » est le meilleur repère : un mécanisme qui agit sur le nœud ne se combine pas de la même façon qu'un mécanisme qui agit sur les Pods entre eux.
| Mécanisme | Objectif | Agit sur |
|---|---|---|
nodeName | Forcer un nœud précis (bypass scheduler) | Nœud |
nodeSelector | Filtrer par labels (simple) | Nœud |
nodeAffinity | Filtrer par labels (avancé, préférence/contrainte) | Nœud |
podAffinity | Rapprocher des Pods liés | Pods |
podAntiAffinity | Éloigner des Pods | Pods |
topologySpreadConstraints | Répartir les replicas entre domaines | Topologie |
taints / tolerations | Protéger/réserver des nœuds | Nœud |
Quatre familles se dégagent, et les nommer évite les trois quarts des
confusions. nodeSelector et nodeAffinity servent à cibler des nœuds.
taints et tolerations servent à protéger ou réserver des nœuds, ce qui
est l'opération inverse. podAffinity et podAntiAffinity gèrent les
relations entre Pods, sans rien dire des nœuds. Et
topologySpreadConstraints répartit un groupe de replicas entre des
domaines. La section 8 revient sur ce choix, une fois les six mécanismes
détaillés.
Labels standardisés Kubernetes
Section intitulée « Labels standardisés Kubernetes »Kubernetes définit des labels standardisés pour la topologie. Préférez-les aux labels custom quand ils existent :
| Label | Description |
|---|---|
kubernetes.io/hostname | Nom du nœud |
topology.kubernetes.io/zone | Zone de disponibilité |
topology.kubernetes.io/region | Région |
node.kubernetes.io/instance-type | Type d'instance (cloud) |
# Voir les labels d'un nœudkubectl get nodes --show-labels1. nodeName : contourner le scheduler
Section intitulée « 1. nodeName : contourner le scheduler »Le champ nodeName lie directement un Pod à un nœud, sans passer par le scheduler. C'est le seul mécanisme de cette page qui ne formule pas une contrainte à satisfaire, mais une affectation figée : le kubelet du nœud nommé prend le Pod tel quel. Aucune vérification de ressources, de taint ou de label n'a lieu, ce qui explique les comportements déroutants quand le nœud ne convient pas.
apiVersion: v1kind: Podmetadata: name: pod-on-specific-nodespec: containers: - name: my-container image: my-image:1.0.0 nodeName: my-node-1La preuve que le taint est ignoré
Section intitulée « La preuve que le taint est ignoré »L'affirmation « aucune vérification n'a lieu » se vérifie en une
manipulation. Un nœud est protégé par un taint NoSchedule, et un Pod
sans la moindre toleration lui est adressé par nodeName :
kubectl taint nodes worker-3 reserve=oui:NoSchedulekubectl run direct --image=nginx:1.31.3-alpine3.24@sha256:4a73073bd557c65b759505da037898b61f1be6cbcc3c2c3aeac22d2a470c1752 \ --overrides='{"spec":{"nodeName":"worker-3"}}'direct 1/1 Running worker-3Le Pod tourne, sur un nœud qui aurait refusé n'importe quel autre. Le même
Pod visant ce nœud par nodeSelector reste, lui, en Pending. C'est la
démonstration la plus nette de ce que nodeName contourne.
Un nœud inexistant : le Pod ne reste pas Pending, il disparaît
Section intitulée « Un nœud inexistant : le Pod ne reste pas Pending, il disparaît »C'est le comportement le plus déroutant de ce champ, et le seul qui laisse le
lecteur sans rien à diagnostiquer. Un nodeName mal orthographié produit bien
un Pod Pending, mais pas durablement :
| Moment | Ce que montre kubectl get pods |
|---|---|
| Création | Pending, avec spec.nodeName déjà renseigné |
| Environ 1 minute plus tard | le Pod n'existe plus |
Le ramasse-miettes du kube-controller-manager supprime de force les Pods liés
à un nœud absent du cluster. La suppression n'écrit aucun événement sur
l'objet : kubectl describe pod répond NotFound, et la méthode de diagnostic
habituelle, lire les events, ne rend rien du tout.
Dans un Deployment, la conséquence est pire : le contrôleur recrée un Pod, le ramasse-miettes le supprime une minute plus tard, et le cycle ne s'arrête jamais. Un âge de Pod qui ne dépasse jamais la minute est la signature de ce défaut.
Cas d'usage :
- Dépannage ou tests sur un nœud précis
- Situations très spécifiques où vous savez exactement où placer le Pod
À éviter en production, préférez nodeSelector ou nodeAffinity pour des règles dynamiques.
2. nodeSelector : le filtre simple
Section intitulée « 2. nodeSelector : le filtre simple »nodeSelector est la méthode la plus simple pour contraindre un Pod à des nœuds ayant un label spécifique. Le scheduler ne retient que les nœuds portant tous les labels demandés, avec une égalité stricte. La mise en place se fait en deux temps : poser le label sur les nœuds concernés, puis le réclamer dans le Pod.
Poser un label sur un nœud
Section intitulée « Poser un label sur un nœud »Un label est une paire clé/valeur libre stockée sur l'objet Node. Il persiste aux redémarrages, mais disparaît si le nœud est retiré puis réenregistré dans le cluster.
kubectl label nodes node-gpu accelerator=nvidia-gpuUtiliser nodeSelector
Section intitulée « Utiliser nodeSelector »Le champ se place directement sous spec, au même niveau que containers, sans passer par le bloc affinity.
apiVersion: v1kind: Podmetadata: name: pod-gpuspec: containers: - name: my-container image: my-image:1.0.0 nodeSelector: accelerator: nvidia-gpu- Seuls les nœuds ayant
accelerator=nvidia-gpupeuvent accueillir ce Pod - Si aucun nœud ne correspond, le Pod reste en
Pending - Limitation : égalités strictes uniquement (
key=value)
3. nodeAffinity : préférences et contraintes
Section intitulée « 3. nodeAffinity : préférences et contraintes »nodeAffinity offre plus de flexibilité que nodeSelector : il accepte des opérateurs (appartenance à une liste, existence d'une clé, comparaison numérique) et distingue une contrainte bloquante d'une simple préférence. Les noms de ces deux modes sont longs mais se décomposent en deux parties, ce qui s'applique au scheduling et ce qui est ignoré pendant l'exécution.
| Mode | Comportement |
|---|---|
requiredDuringSchedulingIgnoredDuringExecution | Obligatoire, le Pod ne sera pas programmé si aucun nœud ne correspond |
preferredDuringSchedulingIgnoredDuringExecution | Préférence, Kubernetes essaie de respecter la règle, mais peut la contourner |
Le suffixe IgnoredDuringExecution est la partie la plus informative de ces
noms à rallonge : la règle est évaluée une seule fois, au moment du
placement. Si le nœud perd ensuite le label qui l'avait rendu éligible, le Pod
reste où il est. Un seul mécanisme de cette page échappe à cette logique, le
taint en effet NoExecute, qui expulse des Pods déjà en place.
Affinité obligatoire
Section intitulée « Affinité obligatoire »L'opérateur In accepte plusieurs valeurs, ce que nodeSelector ne sait pas faire. Si aucun nœud ne correspond, le Pod reste en Pending indéfiniment, sans autre message que l'événement FailedScheduling.
apiVersion: v1kind: Podmetadata: name: pod-gpu-requiredspec: containers: - name: my-container image: my-image:1.0.0 affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: accelerator operator: In values: - nvidia-gpu - amd-gpuLe Pod ne sera programmé que sur un nœud avec accelerator=nvidia-gpu ou accelerator=amd-gpu.
Affinité préférée
Section intitulée « Affinité préférée »Le mode préféré prend une liste pondérée : le champ weight, de 1 à 100, départage les nœuds candidats quand plusieurs règles s'appliquent. Le Pod est toujours planifié, même si aucune préférence n'est satisfaite.
apiVersion: v1kind: Podmetadata: name: pod-gpu-preferredspec: containers: - name: my-container image: my-image:1.0.0 affinity: nodeAffinity: preferredDuringSchedulingIgnoredDuringExecution: - weight: 100 preference: matchExpressions: - key: accelerator operator: In values: - nvidia-gpuKubernetes préférera un nœud GPU, mais programmera le Pod ailleurs si aucun n'est disponible.
Opérateurs disponibles
Section intitulée « Opérateurs disponibles »Ces six opérateurs s'utilisent dans les matchExpressions, en mode obligatoire comme en mode préféré. Les deux derniers, Gt et Lt, comparent la valeur du label comme un entier, et c'est là que se cache un piège de diagnostic.
Un nœud dont le label vaut coeurs=quatre est simplement écarté, comme s'il ne portait pas le label du tout. Aucun message ne signale que la valeur n'était pas un nombre : l'événement reste le générique node(s) didn't match Pod's node affinity/selector. Mieux vaut le savoir, car rien dans la sortie ne vous mettra sur la piste.
La valeur écrite dans la règle n'est pas validée non plus. Un values: ["beaucoup"] derrière un Gt est accepté par l'API, le Pod est créé, et il reste en Pending avec ce même message générique. Vérifiez la valeur vous-même, l'API ne le fera pas pour vous.
| Opérateur | Description |
|---|---|
In | La valeur est dans la liste |
NotIn | La valeur n'est pas dans la liste |
Exists | La clé existe (peu importe la valeur) |
DoesNotExist | La clé n'existe pas |
Gt | Valeur supérieure à (numérique) |
Lt | Valeur inférieure à (numérique) |
4. podAffinity et podAntiAffinity : relations entre Pods
Section intitulée « 4. podAffinity et podAntiAffinity : relations entre Pods »Ces mécanismes influencent la répartition des Pods les uns par rapport aux autres, pas par rapport aux nœuds. La règle ne cite donc aucun nom de nœud : elle décrit les Pods à chercher via un labelSelector, puis indique avec topologyKey à quelle échelle appliquer le rapprochement ou l'éloignement. Le scheduler évalue cette règle à chaque placement, ce qui a un coût dont la fin de section parle.
podAffinity : rapprocher des Pods
Section intitulée « podAffinity : rapprocher des Pods »Le rapprochement sert quand deux composants échangent beaucoup et que la latence réseau pèse, typiquement une application et son cache Redis. Les placer sur le même nœud supprime un saut réseau, au prix d'une perte de résilience si ce nœud tombe.
apiVersion: v1kind: Podmetadata: name: web-app labels: app: webspec: containers: - name: web-container image: my-web-image:1.0.0 affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: - labelSelector: matchExpressions: - key: app operator: In values: - redis topologyKey: kubernetes.io/hostnameCe Pod sera programmé sur le même nœud qu'un Pod ayant app=redis.
podAntiAffinity : éloigner des Pods
Section intitulée « podAntiAffinity : éloigner des Pods »L'anti-affinité évite le SPOF (Single Point Of Failure, point de défaillance unique) : sans elle, rien n'empêche le scheduler de poser les trois replicas d'un service sur le même nœud, et la panne de ce nœud emporte tout le service. Notez que la règle porte sur le label du Deployment lui-même, app=web, un Pod se repousse donc de ses propres frères.
apiVersion: apps/v1kind: Deploymentmetadata: name: web-appspec: 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: web-container image: my-web-image:1.0.0Kubernetes empêchera plusieurs Pods app=web de tourner sur le même nœud.
topologyKey : niveau de la contrainte
Section intitulée « topologyKey : niveau de la contrainte »Le topologyKey est le label de nœud qui définit le périmètre de la règle. Deux nœuds portant la même valeur pour ce label appartiennent au même domaine, et la contrainte s'applique à l'intérieur de ce domaine. Choisir zone plutôt que hostname change complètement le résultat : la règle devient bien plus stricte, puisqu'elle exclut alors toute une zone d'un coup.
| topologyKey | Effet |
|---|---|
kubernetes.io/hostname | Répartition par nœud |
topology.kubernetes.io/zone | Répartition par zone |
topology.kubernetes.io/region | Répartition par région |
5. topologySpreadConstraints : répartir proprement les replicas
Section intitulée « 5. topologySpreadConstraints : répartir proprement les replicas »topologySpreadConstraints est le mécanisme moderne pour contrôler la répartition des Pods entre domaines de topologie (nœuds, zones, régions). Il est souvent plus adapté que podAntiAffinity pour la haute disponibilité.
Pourquoi utiliser topologySpreadConstraints ?
Section intitulée « Pourquoi utiliser topologySpreadConstraints ? »La comparaison ci-dessous porte sur quatre besoins réels. La différence tient au type de règle : podAntiAffinity exprime une interdiction binaire, alors que topologySpreadConstraints exprime un écart toléré entre domaines, ce qui autorise un déséquilibre maîtrisé au lieu de bloquer le placement.
| Besoin | podAntiAffinity | topologySpreadConstraints |
|---|---|---|
| Jamais 2 Pods sur le même nœud | Strict, c'est son usage | Possible, mais ce n'est pas son usage |
| Répartir équitablement entre zones | Complexe à exprimer | Conçu pour |
| Tolérer un léger déséquilibre | Impossible | maxSkew |
| Répartir sur zones ET nœuds | Complexe à exprimer | Plusieurs contraintes empilées |
Exemple basique
Section intitulée « Exemple basique »Ce Deployment répartit ses 6 replicas entre les nœuds avec un écart maximal de 1. Le labelSelector doit désigner les Pods du Deployment lui-même, sinon la contrainte compte les mauvais Pods et le calcul d'écart n'a plus de sens.
apiVersion: apps/v1kind: Deploymentmetadata: name: web-appspec: replicas: 6 selector: matchLabels: app: web template: metadata: labels: app: web spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: web containers: - name: web-container image: my-web-image:1.0.0Pourquoi un maxSkew de 1 refuse parfois un Pod que vous croyiez plaçable
Section intitulée « Pourquoi un maxSkew de 1 refuse parfois un Pod que vous croyiez plaçable »Voici la mesure qui surprend le plus, faite sur un cluster de 4 nœuds, un
control-plane tainté et 3 workers. Un Deployment de 4 replicas avec
maxSkew: 1 sur kubernetes.io/hostname et DoNotSchedule :
worker-1 Runningworker-2 Runningworker-3 Running Pending0/4 nodes are available: 1 node(s) had untolerated taint(s),3 node(s) didn't match pod topology spread constraints.Trois workers, quatre replicas, une répartition 2/1/1 : l'écart vaut 1, la
contrainte devrait passer. Elle ne passe pas, et la raison tient à un réglage
implicite. Le champ nodeTaintsPolicy vaut Ignore par défaut : le nœud de
control-plane, bien qu'inaccessible à ce Pod, compte quand même comme un
domaine. La répartition réelle est donc 2/1/1/0, et l'écart vaut 2.
Deux façons de débloquer, mesurées sur le même cluster :
| Champ ajouté à la contrainte | Résultat |
|---|---|
nodeTaintsPolicy: Honor | 4 replicas placés, les domaines taintés sont exclus du calcul |
whenUnsatisfiable: ScheduleAnyway | 4 replicas placés, la contrainte devient une préférence |
Retenez surtout le principe : un domaine vide compte dans le calcul de l'écart. C'est vrai du control-plane comme de toute zone où aucun Pod ne peut aller, et c'est la cause la plus fréquente d'un spread qui refuse sans raison apparente.
Paramètres clés
Section intitulée « Paramètres clés »Quatre champs suffisent à décrire une contrainte de répartition. Celui qui décide en cas d'impasse est whenUnsatisfiable : avec DoNotSchedule, un Pod qui déséquilibrerait trop reste en Pending ; avec ScheduleAnyway, il est placé quand même, sur le nœud le moins chargé.
| Paramètre | Description |
|---|---|
maxSkew | Écart maximum toléré entre domaines (1 = équilibré) |
topologyKey | Label définissant les domaines (kubernetes.io/hostname, topology.kubernetes.io/zone) |
whenUnsatisfiable | DoNotSchedule (strict) ou ScheduleAnyway (best-effort) |
labelSelector | Sélectionne les Pods concernés par le spread |
nodeTaintsPolicy | Ignore par défaut : les nœuds taintés comptent comme domaines |
nodeAffinityPolicy | Honor par défaut : les nœuds exclus par l'affinité ne comptent pas |
Répartition multi-niveaux (zones + nœuds)
Section intitulée « Répartition multi-niveaux (zones + nœuds) »On peut empiler plusieurs contraintes, qui sont alors toutes évaluées pour chaque placement. Le dosage recommandé est celui ci-dessous : strict sur les zones, où le déséquilibre coûte la haute disponibilité, et souple sur les nœuds, où l'exiger en plus rendrait souvent le placement impossible.
spec: topologySpreadConstraints: # D'abord répartir entre zones - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: web # Puis répartir entre nœuds dans chaque zone - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: app: webPour le cas précis de la répartition de replicas, topologySpreadConstraints
est généralement le meilleur outil : il exprime un déséquilibre toléré avec
maxSkew, là où une anti-affinité stricte ne connaît que l'interdiction pure, et
il se combine sur plusieurs niveaux de topologie, zone puis nœud.
6. Taints et Tolerations : protéger et réserver des nœuds
Section intitulée « 6. Taints et Tolerations : protéger et réserver des nœuds »Les taints et tolerations fonctionnent à l'inverse de l'affinité :
- Un taint sur un nœud repousse les Pods par défaut
- Une toleration sur un Pod lui permet d'ignorer ce taint
Ajouter un taint à un nœud
Section intitulée « Ajouter un taint à un nœud »La clé et la valeur sont libres, l'effet doit valoir exactement l'une des trois valeurs listées juste après.
kubectl taint nodes <node-name> <key>=<value>:<effect>Effets disponibles
Section intitulée « Effets disponibles »L'effet détermine à quel moment le taint agit. Les deux premiers ne concernent que le placement futur, le troisième s'applique aussi aux Pods déjà en cours d'exécution et provoque leur expulsion, ce qui en fait le seul des trois capable d'interrompre un service en production.
| Effet | Comportement |
|---|---|
NoSchedule | Bloque les nouveaux Pods sans toleration |
PreferNoSchedule | Évite les nouveaux Pods sans toleration (best-effort) |
NoExecute | Bloque + expulse les Pods existants sans toleration |
Exemple : réserver des nœuds GPU
Section intitulée « Exemple : réserver des nœuds GPU »Un nœud GPU coûte plusieurs fois le prix d'un nœud standard, et rien n'empêche par défaut un Pod quelconque de venir l'occuper. Le taint inverse cette situation : le nœud devient inaccessible à tout ce qui ne le tolère pas explicitement.
# Tainter le nœud GPUkubectl taint nodes node-gpu accelerator=nvidia-gpu:NoScheduleTous les Pods standards seront bloqués sur ce nœud.
Ajouter une toleration à un Pod
Section intitulée « Ajouter une toleration à un Pod »La toleration se déclare sous spec.tolerations et doit correspondre champ par champ au taint posé sur le nœud, effet compris. L'exemple ci-dessous montre la combinaison complète, avec le nodeSelector qui, lui, dirige effectivement le Pod vers le nœud GPU.
apiVersion: v1kind: Podmetadata: name: pod-gpuspec: containers: - name: my-container image: my-image:1.0.0 tolerations: - key: "accelerator" operator: "Equal" value: "nvidia-gpu" effect: "NoSchedule" # Pour CIBLER le nœud, ajoutez aussi un selector nodeSelector: accelerator: nvidia-gpuLes deux champs se répartissent le travail : la toleration ouvre la porte,
le nodeSelector indique le chemin. Retirez le selector et le Pod reste
autorisé sur le nœud réservé, mais rien ne l'y envoie : il ira où le scheduler
trouve de la place.
Opérateurs de toleration
Section intitulée « Opérateurs de toleration »Il n'existe que deux opérateurs, et Equal est celui appliqué par défaut si vous omettez le champ. Utilisez Exists avec précaution : combiné à un effect laissé vide, il tolère tous les taints du nœud, y compris ceux que Kubernetes pose en cas de panne.
| Opérateur | Description |
|---|---|
Equal | La clé et la valeur doivent correspondre |
Exists | La clé doit exister (valeur ignorée) |
tolerationSeconds : tolérer temporairement NoExecute
Section intitulée « tolerationSeconds : tolérer temporairement NoExecute »Avec NoExecute, vous pouvez permettre à un Pod de rester temporairement sur un nœud devenu tainté :
tolerations:- key: "node.kubernetes.io/not-ready" operator: "Exists" effect: "NoExecute" tolerationSeconds: 300Le Pod restera 5 minutes sur un nœud devenu not-ready avant d'être évincé. Utile pour tolérer des perturbations réseau temporaires.
Combien de temps dure réellement une expulsion NoExecute
Section intitulée « Combien de temps dure réellement une expulsion NoExecute »Trois Pods posés sur le même nœud, avec trois tolerations différentes, puis un
taint NoExecute appliqué à l'instant zéro :
| Toleration du Pod | Expulsé à |
|---|---|
| Aucune | t + 1 s |
tolerationSeconds: 20 sur ce taint | t + 21 s |
operator: Exists sans key ni effect | jamais, toujours en place après 46 s |
L'expulsion sans toleration est donc immédiate, pas différée : ne comptez
sur aucun délai de grâce pour réagir. Et la dernière ligne confirme
l'avertissement de la section précédente : une toleration réduite à
operator: Exists avale tous les taints, y compris ceux que Kubernetes pose
quand un nœud tombe. Un Pod écrit ainsi restera sur un nœud en panne.
Taints courants du système
Section intitulée « Taints courants du système »Kubernetes pose lui-même des taints sur les nœuds en difficulté, sans que vous interveniez. Savoir les reconnaître change le diagnostic : un Pod qui refuse de démarrer sur un nœud apparemment sain est souvent bloqué par l'un de ces taints automatiques, visible avec kubectl describe node.
| Taint | Description |
|---|---|
node.kubernetes.io/not-ready | Nœud pas prêt |
node.kubernetes.io/unreachable | Nœud injoignable |
node.kubernetes.io/disk-pressure | Pression disque |
node.kubernetes.io/memory-pressure | Pression mémoire |
node.kubernetes.io/unschedulable | Nœud en cordon |
Le taint du control-plane, et son nom exact
Section intitulée « Le taint du control-plane, et son nom exact »Dans la plupart des clusters installés par kubeadm, les nœuds de control
plane portent un taint qui écarte les charges de travail ordinaires. C'est
une pratique courante, pas une règle universelle, et son nom se relève
ainsi :
kubectl get node <control-plane> -o jsonpath='{.spec.taints[*].key}{":"}{.spec.taints[*].effect}'node-role.kubernetes.io/control-plane:NoScheduleSa valeur est vide, ce qui a une conséquence pratique : une toleration
écrite avec operator: Equal et value: "" fonctionne, mais operator: Exists
sur la seule clé est la forme habituelle et la plus lisible.
Supprimer un taint
Section intitulée « Supprimer un taint »Le tiret final est ce qui distingue la suppression de l'ajout, il se place après l'effet. Un nœud pouvant porter plusieurs taints, vérifiez le résultat avec kubectl describe node plutôt que de supposer que le placement est débloqué.
kubectl taint nodes <node-name> <key>:<effect>-# Exemple :kubectl taint nodes node-gpu accelerator:NoSchedule-7. Stratégies combinées : exemples pratiques
Section intitulée « 7. Stratégies combinées : exemples pratiques »En production, un seul mécanisme suffit rarement. Les trois scénarios ci-dessous montrent les associations qui reviennent le plus souvent, et surtout pourquoi elles se combinent : un taint protège le nœud mais n'y attire personne, il faut donc lui adjoindre une affinité ou un selector pour que le Pod y aille réellement.
Exemple 1 : Nœuds GPU dédiés
Section intitulée « Exemple 1 : Nœuds GPU dédiés »Objectif : réserver des nœuds GPU aux workloads ML/AI.
-
Labelliser et tainter les nœuds GPU
Fenêtre de terminal kubectl label nodes node-gpu-1 node-gpu-2 accelerator=nvidia-gpukubectl taint nodes node-gpu-1 node-gpu-2 accelerator=nvidia-gpu:NoSchedule -
Créer le Pod ML avec toleration + nodeAffinity
apiVersion: v1kind: Podmetadata:name: ml-trainingspec:containers:- name: trainingimage: tensorflow/tensorflow:2.21.0-gpu@sha256:61fe1ce25bd26b0a38e310463a5588d4067d2d01b6bdb058a3ca4f5cf2e18f15tolerations:- key: "accelerator"operator: "Equal"value: "nvidia-gpu"effect: "NoSchedule"affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: acceleratoroperator: Invalues:- nvidia-gpu
Exemple 2 : Haute disponibilité multi-zones
Section intitulée « Exemple 2 : Haute disponibilité multi-zones »Objectif : répartir 6 replicas équitablement entre 3 zones.
apiVersion: apps/v1kind: Deploymentmetadata: name: web-haspec: replicas: 6 selector: matchLabels: app: web-ha template: metadata: labels: app: web-ha spec: topologySpreadConstraints: - maxSkew: 1 topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: DoNotSchedule labelSelector: matchLabels: app: web-ha - maxSkew: 1 topologyKey: kubernetes.io/hostname whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: app: web-ha containers: - name: web image: nginx:1.31.3-alpine3.24@sha256:4a73073bd557c65b759505da037898b61f1be6cbcc3c2c3aeac22d2a470c1752Résultat : 2 replicas par zone, répartis sur différents nœuds dans chaque zone.
Exemple 3 : Nœuds infrastructure réservés
Section intitulée « Exemple 3 : Nœuds infrastructure réservés »Objectif : réserver des nœuds pour les DaemonSets et composants d'infrastructure.
# Tainter les nœuds infrakubectl taint nodes infra-1 infra-2 dedicated=infrastructure:NoSchedulekubectl label nodes infra-1 infra-2 node-role=infrastructure# DaemonSet monitoring avec tolerationapiVersion: apps/v1kind: DaemonSetmetadata: name: prometheus-node-exporterspec: selector: matchLabels: app: node-exporter template: metadata: labels: app: node-exporter spec: tolerations: - key: "dedicated" operator: "Equal" value: "infrastructure" effect: "NoSchedule" # Ce DaemonSet tourne sur TOUS les nœuds, y compris infra containers: - name: exporter image: prom/node-exporter:v1.12.1@sha256:1b4e4438faca4dd7e001dd445d161a4a2091b0fededa84093b3a8dfeae1f1be08. Comment choisir le bon mécanisme ?
Section intitulée « 8. Comment choisir le bon mécanisme ? »Partez toujours de l'objectif, jamais du mécanisme. Le tableau ci-dessous fait cette traduction pour les six besoins rencontrés en pratique. Une seule ligne demande de combiner deux mécanismes, celle des nœuds réservés : le taint verrouille l'accès, l'affinité dirige le Pod, et oublier le second est l'erreur la plus fréquente sur ce sujet.
| Objectif | Solution recommandée |
|---|---|
| Cibler un type de nœud (GPU, SSD) | nodeSelector ou nodeAffinity |
| Réserver un nœud à certains workloads | taints + tolerations (+ affinity pour cibler) |
| Rapprocher des Pods liés | podAffinity |
| Éloigner quelques Pods sensibles | podAntiAffinity |
| Répartir des replicas entre nœuds/zones | topologySpreadConstraints |
| Tolérer temporairement un nœud instable | tolerationSeconds |
La question à se poser est celle du besoin, pas du mécanisme. Vous voulez
cibler un nœud : nodeSelector si une égalité suffit, nodeAffinity dès
qu'il faut un opérateur ou une préférence. Vous voulez protéger ou réserver
un nœud : taints et tolerations, en n'oubliant pas d'ajouter de quoi diriger
le Pod. Vous voulez répartir des replicas : topologySpreadConstraints.
Vous voulez exprimer une relation entre Pods : podAffinity ou
podAntiAffinity. Ces besoins se combinent souvent, et c'est l'objet de la
section précédente.
9. Débugger les Pods en Pending
Section intitulée « 9. Débugger les Pods en Pending »Un Pod reste Pending quand le scheduler ne trouve pas de nœud satisfaisant toutes les contraintes. L'information utile n'est pas dans le statut du Pod mais dans ses events, où le scheduler écrit le nombre de nœuds écartés et la raison de chaque exclusion. C'est toujours là qu'il faut regarder en premier, avant même de relire le manifest.
Commandes de diagnostic
Section intitulée « Commandes de diagnostic »Ces quatre commandes se lancent dans cet ordre : d'abord la raison donnée par le scheduler, puis l'état réel des nœuds pour la confronter.
# Voir pourquoi un Pod est Pendingkubectl describe pod <pod-name>
# Voir les events récents (très utile)kubectl get events --sort-by=.lastTimestamp
# Voir les labels de tous les nœudskubectl get nodes --show-labels
# Voir les taints d'un nœudkubectl describe node <node-name> | grep -A5 TaintsCauses courantes
Section intitulée « Causes courantes »La première colonne reprend les messages tels que Kubernetes 1.37 les écrit : cherchez le vôtre plutôt que de raisonner par déduction. Les formulations ont changé au fil des versions, et une recherche sur un ancien libellé ne rend rien.
| Message dans les events | Cause probable | Solution |
|---|---|---|
0/4 nodes are available: | En-tête, toujours suivi du détail par cause | Lire la suite de la ligne |
node(s) didn't match Pod's node affinity/selector | Label manquant sur les nœuds | kubectl label nodes |
node(s) had untolerated taint(s) | Toleration manquante | Ajouter la toleration au Pod |
node(s) didn't match pod anti-affinity rules | Plus de replicas que de domaines libres | Réduire les replicas ou ajouter des nœuds |
node(s) didn't match pod topology spread constraints | Écart dépassé, souvent à cause d'un domaine vide | nodeTaintsPolicy: Honor, ou ScheduleAnyway |
Insufficient cpu, Insufficient memory | Ressources insuffisantes | Ajuster les requests ou ajouter des nœuds |
Le message complet comporte deux parties séparées par un point, et la seconde est presque toujours ignorée :
0/4 nodes are available: 1 node(s) had untolerated taint(s),3 node(s) didn't match pod anti-affinity rules.preemption: 0/4 nodes are available: 1 Preemption is not helpful forscheduling, 3 No preemption victims found for incoming pod.La partie preemption: dit si le scheduler aurait pu libérer de la place en
expulsant un Pod moins prioritaire. No preemption victims found signifie qu'il
a cherché et n'a trouvé personne à sacrifier : inutile d'espérer que la
situation se débloque seule.
Exemple de diagnostic complet
Section intitulée « Exemple de diagnostic complet »Voici l'enchaînement complet sur un cas réel, du message d'erreur jusqu'à la vérification des taints nœud par nœud. La boucle finale est celle qui manque le plus souvent : kubectl describe node ne s'utilise pas sur un seul nœud quand on cherche pourquoi aucun ne convient.
# 1. Identifier le problèmekubectl describe pod my-pending-pod | grep -A10 Events
# 2. Vérifier les nœuds disponibleskubectl get nodes -o wide
# 3. Vérifier les labelskubectl get nodes --show-labels | grep -E "accelerator|zone"
# 4. Vérifier les taintsfor node in $(kubectl get nodes -o name); do echo "=== $node ===" kubectl describe $node | grep -A3 TaintsdoneÀ retenir
Section intitulée « À retenir »| Mécanisme | Rôle | Côté |
|---|---|---|
nodeSelector | Filtre simple par label | Pod → Nœud |
nodeAffinity | Filtre avancé (préférence/contrainte) | Pod → Nœud |
podAffinity | Rapproche des Pods | Pod → Pod |
podAntiAffinity | Éloigne des Pods | Pod → Pod |
topologySpreadConstraints | Répartit les replicas | Pod → Topologie |
taints | Repousse les Pods non autorisés | Nœud |
tolerations | Autorise un Pod sur nœud tainté | Pod |
Points essentiels :
topologySpreadConstraintsest le mécanisme moderne pour la répartition, ne l'oubliez pas- Une toleration n'attire pas un Pod, elle l'autorise seulement
- Pour cibler un nœud tainté : toleration +
nodeSelector/nodeAffinity - L'inter-pod affinity peut ralentir le scheduling dans les grands clusters
- Utilisez labels standardisés (
kubernetes.io/hostname,topology.kubernetes.io/zone) - Un domaine vide compte dans le calcul de
maxSkew, control-plane compris - Un
nodeNameerroné ne laisse pas unPending: le Pod est supprimé au bout d'une minute
Testez vos connaissances
Section intitulée « Testez vos connaissances »Sept questions sur ce qui distingue les six mécanismes de placement, et surtout sur le seul d'entre eux qui peut déplacer un Pod déjà en cours d'exécution.
Contrôle de connaissances
Validez vos connaissances avec ce quiz interactif
Informations
- Le chronomètre démarre au clic sur Démarrer
- Questions à choix multiples, vrai/faux et réponses courtes
- Vous pouvez naviguer entre les questions
- Les résultats détaillés sont affichés à la fin
Lance le quiz et démarre le chronomètre
Vérification
(0/0)Profil de compétences
Quoi faire maintenant
Ressources pour progresser
Des indices pour retenter votre chance ?
Nouveau quiz complet avec des questions aléatoires
Retravailler uniquement les questions ratées
Retour à la liste des certifications
Pour aller plus loin
Section intitulée « Pour aller plus loin »- ResourceQuota et LimitRange : Encadrer la consommation par namespace une fois le placement maîtrisé.
- Introduction à l'Autoscaling : Laisser le cluster ajuster la capacité que vous venez d'apprendre à répartir.