Skip to main content
RunBook Academy

KubernetesXXVI · Taints and TolerationsScheduling and node lifecycle

Taints for node problems — NotReady, unreachable, pressure

Advanced⏱ ~17 minkubectl

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

Not yet marked complete on this device.

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

TaintEffectSet byWhen
node.kubernetes.io/not-readyNoExecutenode controllerNode stops reporting Ready
node.kubernetes.io/unreachableNoExecutenode controllerNode stops heart-beating
node.kubernetes.io/unschedulableNoSchedulekubectl cordonOperator marks node unschedulable
node.kubernetes.io/memory-pressureNoSchedulekubeletMemory pressure detected
node.kubernetes.io/disk-pressureNoSchedulekubeletDisk pressure detected
node.kubernetes.io/pid-pressureNoSchedulekubeletPID pressure detected
node.kubernetes.io/network-unavailableNoSchedulekubeletCNI is not ready
node.kubernetes.io/initializingNoSchedulekubeletNode is still in initializing phase
node.cloudprovider.kubernetes.io/uninitializedNoSchedulecloud providerProvider 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-ready is set when the node controller runs the monitor loop and finds the node’s Ready condition false.
  • unreachable is 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’s pid_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

  1. Q1. Which component applies the `node.kubernetes.io/unreachable` taint?

  2. Q2. A node tainted `node.kubernetes.io/unreachable` is definitely down.

  3. 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.

  4. 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-ready and unreachable for 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-pressure keeps 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-ready taint 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.Conditions as 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.