Taints and tolerations work together to repel Pods from nodes. A taint on a node says "don't schedule here unless you explicitly tolerate me." A toleration on a Pod says "I can handle that taint." This is the opposite of node affinity — affinity attracts Pods to nodes; taints repel Pods from nodes.

1. How Taints & Tolerations Work

GPU Node Taint: gpu=true:NoSchedule "Only GPU workloads allowed" Web Pod No toleration → ❌ Blocked ML Pod Tolerates gpu=true → ✅

Taint Format

# Taint = key=value:effect
# Three parts: key, value (optional), effect

# Apply a taint:
kubectl taint nodes worker-3 gpu=true:NoSchedule

# Remove a taint (trailing minus):
kubectl taint nodes worker-3 gpu=true:NoSchedule-

# View taints:
kubectl describe node worker-3 | grep Taints
# Taints: gpu=true:NoSchedule

The Three Effects

EffectImpact on SchedulingImpact on Running Pods
NoSchedulePod won't be scheduled here (hard)Existing Pods stay (not evicted)
PreferNoScheduleScheduler tries to avoid, but may place here if no other optionExisting Pods stay
NoExecutePod won't be scheduled hereExisting Pods are evicted (unless they tolerate)
NoExecute is the only effect that evicts running Pods. NoSchedule and PreferNoSchedule only affect future scheduling — Pods already running on the node when the taint is added continue undisturbed. NoExecute actively removes Pods that don't tolerate it.

2. Writing Tolerations

Exact Match

# Tolerates the specific taint gpu=true:NoSchedule
spec:
  tolerations:
    - key: "gpu"
      operator: "Equal"
      value: "true"
      effect: "NoSchedule"

Exists Operator (Key-Only Match)

# Tolerates ANY taint with key "gpu" regardless of value:
spec:
  tolerations:
    - key: "gpu"
      operator: "Exists"
      effect: "NoSchedule"

# Tolerates ALL taints with key "gpu" (any effect):
    - key: "gpu"
      operator: "Exists"         # no effect specified = matches all effects

# Tolerates ALL taints on ALL keys (nuclear option):
    - operator: "Exists"         # no key = matches everything

Toleration with tolerationSeconds (for NoExecute)

# "I can tolerate this taint, but only for 300 seconds, then evict me"
spec:
  tolerations:
    - key: "node.kubernetes.io/unreachable"
      operator: "Exists"
      effect: "NoExecute"
      tolerationSeconds: 300     # Stay for 5 min, then get evicted

Matching Rules

OperatorMatches WhenValue Required?
EqualKey AND value AND effect all match the taintYes
ExistsKey (and optionally effect) match — value is ignoredNo (value field omitted)
# Summary of matching:
# Taint: dedicated=gpu:NoSchedule
#
# Toleration matches if:
# key=dedicated, operator=Equal, value=gpu, effect=NoSchedule  ✓
# key=dedicated, operator=Exists, effect=NoSchedule            ✓
# key=dedicated, operator=Exists                               ✓ (any effect)
# operator=Exists                                              ✓ (matches all)
# key=dedicated, operator=Equal, value=cpu, effect=NoSchedule  ✗ (wrong value)
# key=other, operator=Equal, value=gpu, effect=NoSchedule      ✗ (wrong key)
CKA/CKAD tip: the most common toleration patterns are:
operator: Equal with exact key/value/effect (specific taint)
operator: Exists with key only (tolerate any value for that key)
operator: Exists with no key (tolerate everything — used by DaemonSets)

3. Built-in Taints (Kubernetes-Managed)

Kubernetes automatically adds/removes these taints based on node conditions:

TaintAdded WhenEffect
node.kubernetes.io/not-readyNode condition Ready=FalseNoExecute
node.kubernetes.io/unreachableNode condition Ready=Unknown (lost contact)NoExecute
node.kubernetes.io/memory-pressureNode has MemoryPressure conditionNoSchedule
node.kubernetes.io/disk-pressureNode has DiskPressure conditionNoSchedule
node.kubernetes.io/pid-pressureNode has PIDPressure conditionNoSchedule
node.kubernetes.io/unschedulableNode is cordoned (kubectl cordon)NoSchedule
node-role.kubernetes.io/control-planeControl plane nodes (kubeadm)NoSchedule

Default Tolerations on All Pods

Kubernetes automatically adds these tolerations to every Pod (unless overridden):

# All Pods tolerate not-ready and unreachable for 300s:
tolerations:
  - key: node.kubernetes.io/not-ready
    operator: Exists
    effect: NoExecute
    tolerationSeconds: 300
  - key: node.kubernetes.io/unreachable
    operator: Exists
    effect: NoExecute
    tolerationSeconds: 300

# This means: if a node goes NotReady, its Pods stay for 5 minutes
# before being evicted. Gives time for transient failures to recover.
The 300-second grace period: When a node becomes unreachable, Pods aren't immediately evicted. They tolerate the unreachable taint for 300s (default). After 5 minutes, the toleration expires and the Pod is evicted (rescheduled elsewhere). You can change this per-Pod by setting a different tolerationSeconds.

4. Common Use Cases

Dedicated Nodes (GPU, High-Memory)

# Taint the GPU nodes:
kubectl taint nodes gpu-worker-1 dedicated=gpu:NoSchedule
kubectl taint nodes gpu-worker-2 dedicated=gpu:NoSchedule

# Only ML Pods tolerate it:
spec:
  tolerations:
    - key: "dedicated"
      operator: "Equal"
      value: "gpu"
      effect: "NoSchedule"
  nodeSelector:
    hardware: gpu                 # Also use nodeSelector to ATTRACT to GPU nodes
    
# Without nodeSelector, the Pod could land on ANY node
# Toleration only means "I'm allowed on GPU nodes" — not "I must go there"
Tolerations are permissive, not directive. A toleration says "I can go here" but doesn't say "I must go here." Without node affinity or nodeSelector, a tolerating Pod might still be scheduled on a non-tainted node. For dedicated nodes, always combine: taint (repels others) + nodeSelector/affinity (attracts desired Pods).

Maintenance / Drain

# Cordon a node (adds unschedulable taint):
kubectl cordon worker-1
# Taint: node.kubernetes.io/unschedulable:NoSchedule

# Drain (cordon + evict all Pods):
kubectl drain worker-1 --ignore-daemonsets --delete-emptydir-data

# DaemonSets tolerate the unschedulable taint automatically
# (that's why --ignore-daemonsets is needed for drain)

Spot/Preemptible Nodes

# Cloud providers taint spot instances:
# Taint: cloud.google.com/gke-spot=true:NoSchedule (GKE)
# Taint: node.kubernetes.io/preemptible=true:NoSchedule

# Only cost-tolerant workloads should schedule there:
spec:
  tolerations:
    - key: "cloud.google.com/gke-spot"
      operator: "Equal"
      value: "true"
      effect: "NoSchedule"
Spot/preemptible node taints ensure only appropriate workloads (batch, stateless, fault-tolerant) land on interruptible nodes. Critical services without the toleration stay on reliable on-demand nodes. Combined with PDBs, this gives you cheap compute without risking availability.

Summary

ConceptKey Point
TaintApplied to nodes — repels Pods that don't tolerate it
TolerationApplied to Pods — "I can handle this taint"
NoScheduleDon't schedule new Pods; existing Pods stay
PreferNoScheduleSoft preference — avoid if possible
NoExecuteDon't schedule + evict existing non-tolerating Pods
tolerationSecondsHow long to stay after a NoExecute taint is added
Equal operatorKey + value + effect must all match
Exists operatorKey matches (any value); omit key to match all taints
Toleration ≠ AttractionTolerations permit, they don't direct. Combine with nodeSelector/affinity.
Built-in taintsK8s auto-taints for not-ready, unreachable, pressure, cordon
Default 300sAll Pods tolerate not-ready/unreachable for 5min before eviction

📝 Quiz: Taints & Tolerations

Q1: A node has taint env=production:NoSchedule. A Pod has no tolerations. Can it schedule there?

No. The Pod doesn't tolerate the taint, so the scheduler won't place it on that node. The Pod must have a toleration matching key=env, value=production, effect=NoSchedule (or use operator: Exists for the key).

Q2: A running Pod is on node-1. You add a NoSchedule taint to node-1. What happens to the Pod?

Nothing. NoSchedule only affects future scheduling. The running Pod continues undisturbed. Only NoExecute evicts existing Pods. If you want to remove existing Pods, use NoExecute or kubectl drain.

Q3: You add a NoExecute taint to node-2. Pod A has a matching toleration with tolerationSeconds: 60. Pod B has no toleration. What happens?

Pod B: Evicted immediately (no toleration for NoExecute).
Pod A: Stays for 60 seconds, then is evicted. The tolerationSeconds gives it a grace period — useful for draining connections gracefully before eviction.

Q4: A Pod has tolerations: [{operator: "Exists"}]. What does this tolerate?

Everything. An Exists operator with no key matches ALL taints on ALL nodes (any key, any value, any effect). The Pod can schedule on any node regardless of taints. This is used by critical system DaemonSets (kube-proxy, CNI) that must run everywhere.

Q5: You taint a node for GPU workloads and a Pod has the toleration. But the Pod still schedules on non-GPU nodes. Why?

Tolerations are permissive, not directive. A toleration says "I'm allowed on this tainted node" but doesn't say "I must go there." The scheduler may place the Pod on any feasible node — including non-tainted ones. Fix: add nodeSelector or nodeAffinity to attract the Pod specifically to GPU nodes.

Q6: A node becomes unreachable (network partition). When are its Pods rescheduled to other nodes?

After approximately 5 minutes and 40 seconds: The node controller marks the node NotReady after ~40s of missed heartbeats and adds node.kubernetes.io/unreachable:NoExecute. All Pods have a default tolerationSeconds: 300 for this taint. So Pods tolerate it for 300s, then are evicted and rescheduled. Total: ~40s detection + 300s toleration = ~340s.