KubernetesXXVI · Taints and TolerationsScheduling and node lifecycle
Taints for node problems — NotReady, unreachable, pressure
What you'll learn
- Identify the built-in taints and the conditions that trigger them
- Explain how the node controller and kubelet apply them
- Decide which Pods should tolerate which taints
- Diagnose a node that is fighting a taint loop
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
The cluster itself applies a set of well-known taints when nodes fail. The operator does not add these; the node controller and the kubelet do. This lesson covers the six built-in taints, the mechanisms that set them, and the operational patterns for tolerating them deliberately.
The six built-in taints
| Taint | Effect | Set by | When |
|---|---|---|---|
node.kubernetes.io/not-ready | NoExecute | node controller | Node stops reporting Ready |
node.kubernetes.io/unreachable | NoExecute | node controller | Node stops heart-beating |
node.kubernetes.io/unschedulable | NoSchedule | kubectl cordon | Operator marks node unschedulable |
node.kubernetes.io/memory-pressure | NoSchedule | kubelet | Memory pressure detected |
node.kubernetes.io/disk-pressure | NoSchedule | kubelet | Disk pressure detected |
node.kubernetes.io/pid-pressure | NoSchedule | kubelet | PID pressure detected |
node.kubernetes.io/network-unavailable | NoSchedule | kubelet | CNI is not ready |
node.kubernetes.io/initializing | NoSchedule | kubelet | Node is still in initializing phase |
node.cloudprovider.kubernetes.io/uninitialized | NoSchedule | cloud provider | Provider has not initialized |
The first nine keys are the operator-facing surface. The
last two are internal: initializing is set by the kubelet
when the node is still starting; the cloud-provider one is
set by the cloud provider’s node lifecycle controller until
the node is fully ready.
The not-ready and unreachable taints
The node controller sets not-ready when the node’s
Status.Conditions[Type=Ready] is False, and unreachable
when the node’s lease (Lease object in
kube-node-lease) is not renewed.
# Substitute your own value before running:
NODE=worker-01
kubectl describe node "$NODE" | grep -A 5 "Conditions"
Conditions:
Type Status LastHeartbeatTime Reason
---- ------ ----------------- ------
Ready False 2026-08-16T10:00:00Z KubeletNotReady
MemoryPressure False 2026-08-16T10:00:00Z KubeletHasInsufficientMemory
DiskPressure False 2026-08-16T10:00:00Z KubeletHasNoDiskSpace
PIDPressure False 2026-08-16T10:00:00Z KubeletHasInsufficientPID
The Ready=False condition is the trigger. The node
controller sets the taint with tolerationSeconds: 300. The
kubelet then evicts any Pod that does not tolerate it after
300 seconds.
The difference between not-ready and unreachable:
not-readyis set when the node controller runs the monitor loop and finds the node’s Ready condition false.unreachableis set when the node controller cannot observe the node at all (the lease is not renewed).
In practice, both are cleared when the node recovers. A Pod that tolerates one of them must tolerate the other; the operator writes the same toleration for both.
tolerations:
- key: node.kubernetes.io/not-ready
operator: Exists
effect: NoExecute
tolerationSeconds: 300
- key: node.kubernetes.io/unreachable
operator: Exists
effect: NoExecute
tolerationSeconds: 300
The two are separate keys because of API stability; the behaviour is identical.
The pressure taints
The kubelet sets the three pressure taints based on
its periodic resource checks. The taints are NoSchedule
to prevent new Pods from landing on a node that is already
struggling. Existing Pods are not evicted by the taint;
the kubelet’s eviction loop (covered in Part XXXII) handles
running Pods.
flowchart TD
A[kubelet housekeeping loop] --> B{Resource check}
B -->|MemoryPressure| C[memory-pressure:NoSchedule]
B -->|DiskPressure| D[disk-pressure:NoSchedule]
B -->|PIDPressure| E[pid-pressure:NoSchedule]
C --> F[Scheduler rejects new Pods]
D --> F
E --> F
C --> G[kubelet evicts running Pods<br/>based on policy]
D --> G
E --> G
The thresholds for the three:
- MemoryPressure:
--memory-pressure-threshold(none by default; the kubelet uses the kernel’s PSI metrics). When the kernel reports memory pressure, the taint is added. - DiskPressure:
--disk-pressure-threshold(default 85% of node filesystem inodes or blocks). When the threshold is exceeded, the taint is added. - PIDPressure:
--pid-pressure-threshold(default 0.5 of the kernel’spid_max). When the ratio is exceeded, the taint is added.
These taints are non-overlapping. A node that is reporting memory pressure adds the memory taint; the disk and PID taints are unrelated.
The network-unavailable taint
The kubelet sets node.kubernetes.io/network-unavailable
when the CNI plugin has not yet configured the node. The
taint is removed when the CNI agent reports ready via the
node-status update.
A Pod that tolerates this taint can be scheduled before the CNI is ready. This is useful for:
- Pods that configure the network themselves (legacy CNI bootstrappers).
- Pods that join the network on first run (cluster
bootstrapping tools like
kubeadm). - Pods that explicitly want to run without a network (rare, but valid for certain sidecar patterns).
General workloads should not tolerate this taint. The Pod that lands before the network is ready cannot talk to the API server and cannot reach other Pods; it is a “Pod on a disconnected island.”
Tolerating not-ready: the stale-fence hazard
The most consequential choice in production is whether to
tolerate not-ready and unreachable. The default is
no; the Pod is evicted when the node fails. A Pod that
does tolerate them is saying “I will keep running on
this node even if the cluster thinks it is dead.”
flowchart LR
A[Node N] -->|heartbeat| B[API server]
B --> C{Node controller:<br/>lease fresh?}
C -->|No| D[Add not-ready/NoExecute]
D --> E{Pod tolerates<br/>not-ready?}
E -->|No| F[Evict after 300s]
E -->|Yes| G[Pod continues<br/>on possibly-failed node]
G --> H[Stale-fence hazard]
The stale-fence hazard is real: a Pod that tolerates the not-ready taint may keep serving traffic on a node that the rest of the cluster has forgotten. If the node’s network is partitioned, the Pod keeps trying to talk to the API server and other Pods, but the service-routing layer has removed the Pod from the EndpointSlice. The Pod runs, but it is invisible.
Production rule: only tolerate not-ready and
unreachable for workloads that can survive the network
partition (batch jobs, leaderless data layers, distributed
databases that run an internal consensus). Stateful
workloads that depend on the API server should not tolerate
the taint; they should be evicted and re-created.
Inspecting built-in taints
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
NAME TAINTS
node-1 <none>
node-2 [node.kubernetes.io/memory-pressure:NoSchedule]
node-3 [node.kubernetes.io/disk-pressure:NoSchedule]
node-4 [node.kubernetes.io/not-ready:NoExecute for 300s]
node-5 [node.kubernetes.io/unschedulable:NoSchedule]
The form is key:effect or key:effect for Ns when the
taint carries a tolerationSeconds (declared on the taint
itself, not on the Pod).
Quiz
Knowledge check · 4 questions
Q1. Which component applies the `node.kubernetes.io/unreachable` taint?
Q2. A node tainted `node.kubernetes.io/unreachable` is definitely down.
Q3. Decide what to do about a Pod still serving traffic on a node the cluster has declared unreachable.
`node-11` lost its uplink to the control plane 8 minutes ago. Its lease in `kube-node-lease` has stopped being renewed, the node controller has applied `node.kubernetes.io/unreachable:NoExecute`, and every other Pod on the node has been evicted. `cache-2`, a StatefulSet member, tolerates `unreachable` with `operator: Exists` and no `tolerationSeconds`, and is still answering clients that cached its Pod IP.
Q4. Which component applies `node.kubernetes.io/unreachable`, which applies `node.kubernetes.io/disk-pressure`, and what effect does each taint carry?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- The default 300s window is the right default. Most
workloads should tolerate
not-readyandunreachablefor the cluster-default 300 seconds. This rides out transient network partitions and gives the node controller time to recover the node. - Do not tolerate pressure taints on general Pods. A
Pod that tolerates
memory-pressurekeeps running on a node that is being starved. The right response is to add capacity or fix the leak; tolerating the taint hides the problem. - Watch for taint loops. A node that flaps between
Ready and NotReady adds and removes the
not-readytaint frequently. Each removal resets the eviction clock. The Pod may never be evicted because the cluster keeps recovering the node. The fix is to investigate the root cause (network, disk, kubelet, runC) and not just the symptoms. - Use the node’s
Status.Conditionsas the source of truth. A node carries a taint only when the corresponding condition is set. If the condition is cleared but the taint remains, the node controller is stuck and the operator must intervene.