A node affinity tells a pod where it should go. A taint does the opposite: it tells every pod where it must not go, unless it explicitly says otherwise. It’s a default repulsion mechanism, not a preference, and that difference in direction changes everything about how it’s used.
The taint repels, the toleration allows an exception
A taint placed on a node repels every pod that doesn’t explicitly declare it tolerates it:
kubectl taint nodes gpu-node-1 workload=gpu-only:NoSchedule
Without a matching toleration in its spec, no pod gets scheduled on gpu-node-1, including every pod already running elsewhere on the cluster that never needed to know this taint existed. A pod that still wants to land there has to explicitly declare it tolerates that specific taint:
spec:
tolerations:
- key: "workload"
operator: "Equal"
value: "gpu-only"
effect: "NoSchedule"
Tolerating a taint doesn’t attract the pod toward that node, it only removes the obstacle that would have excluded it. A pod tolerating gpu-node-1 can just as easily get scheduled elsewhere if another taint-free node also fits: to actually force placement, the toleration needs pairing with a positive node affinity.
Three effects, three severity levels
NoSchedule prevents any new intolerant pod from being scheduled on the node, but leaves in place pods already running there before the taint was added. PreferNoSchedule is a softer version: the scheduler avoids the node if an alternative exists, without formally forbidding it if no other option is available. NoExecute is the strictest of the three: it immediately evicts already-running pods that don’t tolerate the taint, not just new ones.
# NoExecute with a time-limited toleration: the pod accepts
# the taint, but only for 300 seconds
tolerations:
- key: "node.kubernetes.io/unreachable"
operator: "Exists"
effect: "NoExecute"
tolerationSeconds: 300
tolerationSeconds explains a default behavior that’s often misunderstood: when a node becomes unreachable, Kubernetes automatically applies a NoExecute taint to it, and every pod tolerates that taint by default for 300 seconds before eviction, the standard grace period before a pod is considered genuinely lost.
Why the Cluster Autoscaler cares
A misunderstood taint explains some cases where the Cluster Autoscaler seems to behave inconsistently: a node tainted for a specific purpose (GPU, dedicated workload) never picks up generic pods waiting elsewhere, which can leave that node underused without the autoscaler removing it, as long as a tolerating pod keeps running there. That’s not an autoscaler bug: it’s the taint doing exactly what it was put there to do.
Takeaway
A taint repels by default, a toleration removes the obstacle without ever actively attracting a pod. The three effects (NoSchedule, PreferNoSchedule, NoExecute) form a severity scale, from mild discouragement to immediate eviction of already-running pods. Understanding this repulsion mechanism, distinct from positive affinity, avoids misdiagnosing a pod that refuses to schedule where it was expected to, a scenario that comes up regularly the moment a Kubernetes migration introduces dedicated-purpose nodes.