Skip to main content
RunBook Academy

KubernetesXXVI · Taints and TolerationsScheduling and node lifecycle

Taints and tolerations — the node-repulsion model

Advanced⏱ ~16 minkubectl

What you'll learn

  • Explain what a taint does and what a toleration does
  • Trace the matching model between taints and tolerations
  • Distinguish the three effect types and their operational consequences
  • Identify when taints and tolerations are the right tool versus when node affinity or pod anti-affinity is the right tool

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.

Taints and tolerations are Kubernetes’ node-repulsion model: a node carrying a taint rejects Pods that do not carry a matching toleration. Unlike node affinity, which is a Pod attraction rule (“run on nodes like X”), taints are a node rejection rule (“do not run here unless explicitly permitted”). The two complement each other: taints exclude by default, tolerations opt-in. This lesson establishes the model and the three effect types.

What is a taint

A taint is a key=value:effect triple attached to a Node. The key and value are arbitrary labels; the effect is one of three enumerated values that the scheduler and the kubelet both interpret.

kubectl taint nodes node-1 dedicated=prod:NoSchedule
node/node-1 tainted

The taint is recorded on the Node object:

kubectl get node node-1 -o jsonpath='{.spec.taints}' | jq
[
  {
    "key": "dedicated",
    "value": "prod",
    "effect": "NoSchedule"
  }
]

A second taint on the same key replaces the first; taints are not additive on the same key. Removing a taint requires the full key=value:effect triple:

kubectl taint nodes node-1 dedicated:NoSchedule-
kubectl taint nodes node-1 dedicated=prod:NoSchedule-   # equivalent

What is a toleration

A toleration is a Pod-level declaration that says “the Pod accepts one or more taints.” A toleration matches a taint when the key, value (if specified), and effect all match. The Pod then becomes a candidate for the tainted node.

apiVersion: v1
kind: Pod
metadata:
  name: billing-api
spec:
  tolerations:
    - key: dedicated
      operator: Equal
      value: prod
      effect: NoSchedule
  containers:
    - name: app
      image: registry.example.com/billing:1.4.0

The operator field is one of Equal (default) or Exists. Equal requires the value to match; Exists matches on key alone, which is useful for tolerating “any taint with this key” (for example, node.kubernetes.io/not-ready).

The matching model

A toleration matches a taint when key, value, and effect all satisfy the operator. The scheduler treats a node as feasible for a Pod when every taint on the node is matched by at least one of the Pod’s tolerations. A single unmatched taint rejects the node for that Pod.

flowchart LR
    N[Node N1<br/>taints: dedicated=prod:NoSchedule,<br/>gpu=true:NoSchedule] --> S{Scheduler}
    P1[Pod P1<br/>tolerations: dedicated=prod:NoSchedule] --> S
    P2[Pod P2<br/>tolerations: none] --> S
    S -->|matched all taints| A[P1 feasible on N1]
    S -->|unmatched gpu=true| B[P2 rejected]

The matching is all-tolerations-all-taints: the Pod must have at least one toleration for every taint on the node. A single toleration that matches all taints is enough; a Pod with three tolerations that match two taints is rejected.

The three effect types

The effect is the most important field. It determines what happens to a Pod that does not match the taint:

EffectOn unscheduled PodOn running Pod
NoScheduleScheduler will not place the Pod hereNo effect — the Pod keeps running
PreferNoScheduleScheduler tries to avoid this node, but will use it if no other node is feasibleNo effect
NoExecuteScheduler will not place the Pod herePod is evicted (gracefully via SIGTERM, then SIGKILL)

The three effects have a single ordering from weakest to strongest. NoExecute is the only one that removes running Pods; the other two are scheduling decisions only.

The built-in taints

Kubernetes itself adds a small set of well-known taints to nodes in specific conditions. These are the operational taints an operator will encounter daily:

TaintEffectWhen applied
node.kubernetes.io/not-readyNoExecuteNode controller marks the node NotReady
node.kubernetes.io/unreachableNoExecuteNode controller cannot reach the node
node.kubernetes.io/unschedulableNoSchedulekubectl cordon (set by spec.unschedulable)
node.kubernetes.io/memory-pressureNoSchedulekubelet detects memory pressure
node.kubernetes.io/disk-pressureNoSchedulekubelet detects disk pressure
node.kubernetes.io/pid-pressureNoSchedulekubelet detects PID pressure
node.kubernetes.io/network-unavailableNoScheduleCNI has not yet configured the node
node.kubernetes.io/initializingNoScheduleNode is still in Phase: Initializing
node.cloudprovider.kubernetes.io/uninitializedNoScheduleCloud provider has not yet initialized the node

A Pod tolerates all of these by default only if every tolerations entry references the key. The control plane injects some of these tolerations into Pods that opt in via node.kubernetes.io/not-ready, unreachable, and unschedulable tolerations; without explicit tolerations, the Pods are evicted when the condition is reported.

Inspecting taints and tolerations

# Substitute your own values before running:
NODE=node-19
POD=billing-7d8f-abcde

kubectl describe node "$NODE" | grep -i taint
kubectl get pod "$POD" -o jsonpath='{.spec.tolerations}' | jq
Taints:             dedicated=prod:NoSchedule
                    node.kubernetes.io/unschedulable:NoSchedule

A scheduled Pod that is “Pending” with a FailedScheduling event often has a missing toleration. The next lesson walks the diagnostic flow.

Quiz

Knowledge check · 4 questions

  1. Q1. A node carries the taint `workload=gpu:NoSchedule`. Which Pod is admitted to that node?

  2. Q2. A `nodeSelector` matching a tainted node's labels is enough to schedule a Pod onto it.

  3. Q3. Work out why a Deployment that already carries a toleration is still Pending on the only nodes that could run it.

    The `inference-api` Deployment has 3 replicas, all Pending for 11 minutes. Its Pod template tolerates `dedicated=prod:NoSchedule`, and the four candidate nodes are labelled for the prod pool. `kubectl describe pod` reports `0/9 nodes are available: 4 node(s) had untolerated taint {nvidia.com/gpu: present}, 5 node(s) didn't match Pod's node affinity`.

  4. Q4. A node carries the taint `hardware=fpga-rev7:NoSchedule`, and the value changes at every firmware upgrade. Write the toleration that keeps working across upgrades, and name the operator that makes it work.

Passing score: 75%. Answers are checked in this browser.

Production discipline

Taints and tolerations are how production clusters reserve nodes for specific workloads. The standard pattern:

  • Infrastructure nodes (CNI, logging, monitoring agents) carry dedicated=infra:NoExecute so non-tolerating workloads cannot land there.
  • GPU nodes carry nvidia.com/gpu=present:NoSchedule plus the nvidia.com/gpu toleration on the GPU Pod, so non-GPU Pods do not waste capacity.
  • Maintenance cordoning uses kubectl cordon which sets node.kubernetes.io/unschedulable:NoSchedule; this is non-evicting, which is exactly what you want before a drain.
  • Failure response uses node.kubernetes.io/not-ready (auto-set by the node controller); almost no Pod should tolerate it, because running on a node that has lost its lease is a stale-fence hazard.

The principle: taints should name a reason, not a Pod. A taint called security=privileged:NoSchedule is a reason; a taint called app=payments:NoSchedule is a Pod label and should be a label, not a taint.