2026
Kubernetes Taints and Tolerations: Nodes That Say No
Node affinity lets pods choose where they go. Taints let nodes decide what they will accept. A tainted node repels every pod that does not carry a matching toleration. It is how Kubernetes reserves node pools, handles node conditions and manages workload isolation without separate clusters.
Node affinity lets pods express a preference for certain nodes. Taints work the other way around. They let nodes express a preference about which pods they are willing to accept. A node with a taint is saying: unless you specifically account for this, you do not belong here.
The practical effect is node reservation. A tainted node repels all pods that do not carry a matching toleration. Pods that do carry the toleration are allowed through. The node is not locked down to a single team or workload type. It is simply unavailable to anything that has not opted in.
This mechanism shows up in more places than most people realise. Control plane nodes are tainted to keep application workloads off them. Nodes under memory pressure get tainted automatically by the kubelet. Cloud providers taint spot instances when reclaiming them. The taint system is load-bearing infrastructure that the cluster itself relies on, not just an optional feature for advanced configurations.
How taints work
A taint is a key-value pair with an effect, applied to a node. A toleration is the matching declaration on a pod that says it can accept that taint. The scheduler checks a pod's tolerations against the taints on each candidate node. If every taint on the node is covered by a toleration on the pod, the node is eligible. If any taint is not covered, the node is skipped.
Adding and removing a taint with kubectl
# Add a taint
kubectl taint nodes node01 dedicated=gpu-team:NoSchedule
# Remove a taint (note the trailing minus sign)
kubectl taint nodes node01 dedicated=gpu-team:NoSchedule-
# View taints on all nodes
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
A toleration on a pod must match both the key and the value of the taint, or use the Exists operator to match on key alone. The effect field in the toleration must also match the effect on the taint. A toleration for NoSchedule does not cover aNoExecute taint. If the effect is omitted from the toleration, it matches all effects for that key.
Taint effects
The effect determines what the taint actually does to pods that do not tolerate it. Choosing the right effect matters because the behaviour difference between them is significant.
NoSchedule
New pods without a matching toleration will not be scheduled onto the node. Pods that are already running on the node before the taint was added continue to run undisturbed. This is the most commonly used effect. It reserves the node for future placement without disrupting anything currently running.
PreferNoSchedule
A soft version of NoSchedule. The scheduler tries to avoid placing pods without a matching toleration on the node but will do so if there is no better option available. Useful for nudging the scheduler without creating a hard reservation. In practice it is used less often than the other two effects because the soft guarantee is hard to reason about.
NoExecute
Both prevents new pods from scheduling and evicts any pods already running on the node that do not have a matching toleration. Existing pods that do have a toleration may also specify a tolerationSeconds value, which lets them continue running for a fixed period before being evicted. Used for node maintenance, draining nodes gracefully and responding to node problems automatically.
What tolerations look like in a manifest
Tolerations are defined in the pod spec. A Deployment, StatefulSet or DaemonSet passes them down to every pod it creates through the pod template. Writing them once in the template is enough.
Tolerations for a GPU workload with a graceful eviction window
spec:
tolerations:
- key: dedicated
operator: Equal
value: gpu-team
effect: NoSchedule
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 300
- key: node.kubernetes.io/unreachable
operator: Exists
effect: NoExecute
tolerationSeconds: 300
The first toleration allows this pod onto nodes tainted for the GPU team. The second and third are tolerations for built-in node condition taints with a five-minute window. Without those, a pod on a node that becomes temporarily unreachable would be evicted the moment the NoExecute taint is applied. ThetolerationSeconds field gives it five minutes to wait for the node to recover before Kubernetes moves it elsewhere.
Built-in taints
Kubernetes applies several taints automatically based on node conditions. Understanding them explains behaviour that can otherwise look mysterious when pods are evicted or fail to schedule without any obvious reason.
node.kubernetes.io/not-readyApplied automatically when the node's Ready condition is False. Pods with the NoExecute effect toleration can specify how long they are willing to wait on a not-ready node before being evicted. Kubernetes itself uses this taint to move workloads off nodes that stop reporting healthy.
node.kubernetes.io/unreachableApplied when the node controller cannot reach the node. Similar to not-ready but triggered by network partition rather than the node itself reporting a problem. The default eviction timeout for pods on unreachable nodes is five minutes.
node.kubernetes.io/memory-pressureApplied when the node is under memory pressure. The kubelet adds this taint when available memory drops below a configured threshold. New pods will not be scheduled onto the node until memory pressure is relieved.
node.kubernetes.io/disk-pressureApplied when available disk space on the node is running low. Like memory pressure, it prevents new scheduling and signals that the node needs attention before it can accept more work.
node-role.kubernetes.io/control-planePresent on control plane nodes in most cluster setups. Prevents regular application pods from landing on nodes that run the API server, scheduler and controller manager. DaemonSets that need to run cluster-wide must explicitly tolerate this taint.
Worth noting
Kubernetes automatically adds tolerations for not-ready and unreachable with a 300-second window to every pod that does not already specify them. This is done by the DefaultTolerationSeconds admission controller. It means pods will not be immediately evicted from a node that briefly loses connectivity. The five-minute window is the default. It can be changed cluster-wide or overridden per pod.
Common patterns
Taints show up in the same situations repeatedly. The use cases are different but the underlying mechanism is the same in each.
Dedicated nodes
A node pool reserved for a specific team or workload type gets a taint with a team or workload key. Only pods from that team, which carry the matching toleration, land there. Everyone else's pods are redirected to shared node pools automatically.
GPU nodes
GPU nodes are expensive. Without a taint, the scheduler might place ordinary CPU workloads on them simply because they have capacity. Adding a taint ensures only pods that actually need the GPU and know to request it, consume those nodes.
Node maintenance
Before taking a node offline for maintenance, an operator adds a NoExecute taint. Pods without a toleration are evicted. Those with a toleration and a tolerationSeconds value get a grace period to finish their work. The node drains cleanly without requiring manual pod deletion.
Spot instance handling
Cloud providers typically taint spot or preemptible nodes when reclaiming them. Workloads that can handle interruption run on spot nodes and tolerate the interruption taint. Critical workloads omit the toleration and avoid spot nodes entirely.
Things that go wrong
Most taint problems produce one of two symptoms: pods stuck in Pending because they cannot find an untainted node, or pods unexpectedly evicted because a NoExecute taint appeared without a matching toleration.
Tainting without checking existing pods
Adding a NoExecute taint to a node that is already running pods will evict any pod without a matching toleration immediately. If those pods are part of a Deployment, they reschedule elsewhere, but the brief disruption can be surprising. Check what is running on a node before applying a NoExecute taint to it outside of a planned drain.
Tolerating everything
A toleration with an empty key and operator set to Exists matches every taint on every node. It is sometimes added as a quick fix to get a pod past a scheduling problem. It also defeats the entire purpose of the taint by letting the pod run anywhere. Tolerations should be specific about which taints they accept.
Forgetting DaemonSets
When a new taint is added to a class of nodes, DaemonSets that are supposed to run on those nodes stop getting pods placed there. Infrastructure DaemonSets like log collectors need explicit tolerations for every taint that might exist in the cluster. Missing one means those nodes go unmonitored.
Confusing taints with node affinity
Taints push pods away from nodes. Node affinity pulls pods toward nodes. They are complementary but not interchangeable. A taint on a GPU node prevents non-GPU pods from landing there. Node affinity on a GPU pod ensures it lands on a GPU node. Both are usually needed together to fully reserve a node pool for specific workloads.
Where it fits
Taints and tolerations sit in the scheduling layer alongside node affinity. The two mechanisms solve related but distinct problems. Taints are driven by the node: the node decides what it does not want. Node affinity is driven by the pod: the pod decides where it wants to go. To fully reserve a node pool for a specific workload you typically need both. The taint prevents unwanted pods from landing. The node affinity ensures the intended pods actually prefer those nodes over the general pool.
In the context of cluster operations, taints are one of the primary tools for node lifecycle management. Cordoning a node prevents new scheduling but does not evict anything. Adding a NoExecute taint starts moving workloads off the node. The kubectl drain command combines both: it cordons the node and then evicts pods in a controlled way. Understanding that drain uses taints internally explains why pods with certain tolerations survive a drain when others do not.
For teams running multi-tenant clusters, taints are one of the core mechanisms for isolating workloads without separate clusters. Combined with RBAC, namespaces and ResourceQuotas, they give platform teams the tools to let different groups share infrastructure without stepping on each other. A dedicated node pool per team, isolated by taints and affinity, is a reasonable starting point for most multi-team setups that do not warrant the overhead of separate clusters.