Une affinité de nœud dit à un pod où il devrait aller. Un taint fait l’inverse : il dit à tous les pods où ils ne doivent pas aller, sauf exception explicite. C’est un mécanisme de répulsion par défaut, pas une préférence, et cette différence de direction change tout dans la façon dont on l’utilise.

Le taint repousse, la toleration autorise une exception

Un taint posé sur un nœud repousse tout pod qui ne déclare pas explicitement le tolérer :

kubectl taint nodes gpu-node-1 workload=gpu-only:NoSchedule

Sans toleration correspondante dans sa spec, aucun pod n’est planifié sur gpu-node-1, y compris tous les pods déjà en place sur le cluster qui n’ont jamais eu besoin d’en connaître l’existence. Un pod qui veut malgré tout s’y poser doit déclarer explicitement qu’il tolère ce taint précis :

spec:
  tolerations:
    - key: "workload"
      operator: "Equal"
      value: "gpu-only"
      effect: "NoSchedule"

Tolérer un taint n’attire pas le pod vers ce nœud, ça retire seulement l’obstacle qui l’en aurait exclu. Un pod qui tolère gpu-node-1 peut tout aussi bien être planifié ailleurs si un autre nœud sans taint convient également : pour forcer réellement un placement, il faut combiner la toleration à une affinité de nœud positive.

Trois effets, trois niveaux de sévérité

NoSchedule empêche tout nouveau pod non tolérant d’être planifié sur le nœud, mais laisse en place les pods qui y tournaient déjà avant l’ajout du taint. PreferNoSchedule est une version assouplie : le scheduler évite le nœud si une alternative existe, sans l’interdire formellement si aucune autre option n’est disponible. NoExecute est le plus strict des trois : il évince immédiatement les pods déjà en cours d’exécution qui ne tolèrent pas le taint, pas seulement les nouveaux.

# NoExecute avec une tolérance limitée dans le temps : le pod
# accepte le taint, mais seulement pendant 300 secondes
tolerations:
  - key: "node.kubernetes.io/unreachable"
    operator: "Exists"
    effect: "NoExecute"
    tolerationSeconds: 300

tolerationSeconds explique un comportement par défaut souvent mal compris : quand un nœud devient injoignable, Kubernetes lui applique automatiquement un taint NoExecute, et chaque pod tolère ce taint par défaut pendant 300 secondes avant éviction, le délai de grâce standard avant qu’un pod soit considéré comme réellement perdu.

Pourquoi le cluster autoscaler s’en soucie

Un taint mal compris explique une part des cas où le Cluster Autoscaler semble se comporter de façon incohérente : un nœud taintée pour un usage spécifique (GPU, workload dédié) n’accueille jamais les pods génériques en attente ailleurs, ce qui peut laisser ce nœud sous-utilisé sans que l’autoscaler ne le retire pour autant, tant qu’un pod tolérant ce taint continue d’y tourner. Ce n’est pas un bug de l’autoscaler : c’est le taint qui fait exactement ce pour quoi il a été posé.

À retenir

Un taint repousse par défaut, une toleration retire l’obstacle sans jamais attirer activement un pod. Les trois effets (NoSchedule, PreferNoSchedule, NoExecute) forment une échelle de sévérité, du simple découragement à l’éviction immédiate des pods déjà en place. Comprendre ce mécanisme de répulsion, distinct de l’affinité positive, évite de mal diagnostiquer un pod qui refuse de se planifier là où on l’attendait, un scénario qui revient régulièrement dès qu’une migration Kubernetes introduit des nœuds à usage dédié.