KubernetesXXVII · Scheduling FailuresScheduling and node lifecycle
Affinity, taint, and topology failures
What you'll learn
- Diagnose a Pod rejected by node affinity, pod affinity, taints, or topology
- Trace the filter plugin order and the failure message
- Identify the common mis-configurations that cause impossible constraints
- Apply the standard fixes for affinity- and taint-driven failures
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
After resource failures, the dominant scheduling failures are affinity, taint, and topology rejections. The message is precise; the fix is to align the constraint with the cluster’s reality. This lesson walks the four common patterns and the diagnostic moves for each.
Node affinity failures
The NodeAffinity filter plugin rejects a node when the
Pod’s spec.affinity.nodeAffinity does not match any node
in the cluster. The failure message:
0/5 nodes are available: 5 node(s) didn't match Pod's node affinity.
The Pod’s spec:
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: workload
operator: In
values: [critical]
The Pod is asking for a node with workload=critical; the
cluster has no node with that label. The fix is to label a
node or remove the constraint.
The matchExpressions shape is the most common source of
silent failures. A typo in the key, a missing label, or a
label that does not match the value is enough to reject
every node.
kubectl get nodes -L workload
NAME WORKLOAD
node-1 critical
node-2 critical
node-3 standard
node-4 standard
node-5 standard
A Pod with operator: In, values: [critical] is feasible
on node-1 and node-2. If the cluster has been scaled
down to only node-3, node-4, node-5, the Pod is
rejected for the same spec. The fix is to either re-label
the nodes or relax the affinity.
Pod affinity and anti-affinity failures
The PodAffinity and PodAntiAffinity filter plugins
require a co-located (or anti-colocated) Pod. The failure
message:
0/5 nodes are available: 3 node(s) didn't match Pod's anti-affinity rules.
The Pod’s spec:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: cache
The Pod is asking “do not put me on a node with another Pod
with app=cache.” If the cluster has every node running
a cache Pod, the new Pod is rejected.
The fix is either:
- Ensure the cluster has enough nodes to satisfy the rule.
The rule requires a node without an
app=cachePod; if every node has one, there is no feasible node. - Relax the rule to
preferredinstead ofrequired. The Pod will land on a node that may have a co-located Pod, but the rule is best-effort. - Reduce the spread of the existing Pods. If the cache is concentrated on two nodes, the other three are feasible.
The topologyKey is critical. kubernetes.io/hostname
restricts the rule to a single node;
topology.kubernetes.io/zone restricts the rule to a zone;
topology.kubernetes.io/region restricts the rule to a
region. A topologyKey that does not label any node is
a failure mode (the constraint is unsatisfiable).
Taint failures
The TaintToleration filter plugin rejects a node that
carries a taint the Pod does not tolerate. The message:
0/5 nodes are available: 3 node(s) had taint {dedicated=prod:NoSchedule}, 2 node(s) had taint {gpu=true:NoSchedule}.
The fix is to add the toleration to the Pod or remove the taint from the node. The lesson on dedicated nodes (Part XXVI) covers the patterns.
A common production bug: a Pod is tolerating the dedicated taint but the cluster has a new node class with a different taint. The Pod is scheduled on the old node class only; the new class is rejected.
Topology failures
The NodeAffinity filter plugin also handles topology
constraints expressed via spec.topologySpreadConstraints.
The failure message:
0/5 nodes are available: 2 node(s) didn't match topology spread constraints.
The Pod’s spec:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: api
The constraint is “spread app=api across nodes with at
most 1 Pod difference.” If every node has 2 Pods and the
new Pod would make it 3, the rule is violated and the
Pod is rejected.
The whenUnsatisfiable: DoNotSchedule setting is the
hard-rejection mode. The ScheduleAnyway mode is a
soft scoring hint; the rule is preferred but not
required.
The filter interaction
The scheduler runs every filter plugin on every node. A node is feasible for the Pod only if every filter says feasible. The FailedScheduling event lists every filter that rejected every node, but the ordering of the messages is not always the order of the rejection.
flowchart TD
A[Pod under scheduling] --> B[NodeResourcesFit]
B --> C[NodeName]
C --> D[NodeAffinity]
D --> E[TaintToleration]
E --> F[NodePorts]
F --> G[PodAffinity]
G --> H[NodeAffinity]
H --> I[VolumeBinding]
I --> J[PodTopologySpread]
J --> K[All filters passed?<br/>feasible nodes]
A node that fails multiple filters is reported under each filter name. The operator reads the message and decodes which filters are rejecting the cluster.
The impossible-constraint failure mode
The most dangerous failure mode is a constraint that no node in the cluster can satisfy. The Pod is Pending forever; the operator checks the message, sees a “didn’t match” reason, and tries to fix the wrong thing.
# A Pod that requires a label no node has
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: cluster-version
operator: In
values: [v2.0]
0/5 nodes are available: 5 node(s) didn't match Pod's node affinity.
The fix is to label a node with cluster-version=v2.0 or
remove the constraint. The lesson is to validate the
constraint against the cluster’s actual labels before
deploying the Pod.
kubectl get nodes -L cluster-version
If no node has the label, the constraint is impossible. The operator should fail the CI build that introduced the constraint.
The diagnostic workflow
- Read the message. The filter plugins each have a
distinct format.
Insufficient memoryis the resource plugin;didn't match Pod's node affinityis the affinity plugin;had taintis the taint plugin. - Look at the cluster’s labels. A node affinity rejection means the cluster does not have the right labels. A taint rejection means the cluster has taints the Pod does not tolerate.
- Look at the cluster’s existing Pods. A pod affinity rejection means the cluster’s existing Pods are spread in a way that makes the rule unsatisfiable.
- Look at the cluster’s topology. A topology rejection means the cluster’s node distribution is too tight to satisfy the rule.
- Apply the fix. The fix is usually one of:
- Label a node.
- Remove the constraint.
- Tolerate the taint.
- Reduce the rule’s spread.
Quiz
Knowledge check · 4 questions
Q1. A Pod with `requiredDuringSchedulingIgnoredDuringExecution` node affinity is Pending. What changed if it was running before?
Q2. Removing a label from a node evicts the running Pods whose required node affinity depended on it.
Q3. Get a fourth Redis replica scheduled in a three-node cluster without weakening the spread guarantee more than necessary.
The `redis` StatefulSet declares `podAntiAffinity.requiredDuringSchedulingIgnoredDuringExecution` with `topologyKey: kubernetes.io/hostname` and a selector on `app=redis`. It has just been scaled from 3 to 4. `redis-3` is Pending with `0/3 nodes are available: 3 node(s) didn't match pod anti-affinity rules`.
Q4. A topologySpreadConstraint names `topologyKey: topology.kubernetes.io/zone`, but two nodes do not carry that label. What does the scheduler do with those two nodes, and which field decides whether the constraint rejects or merely deprioritises?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Validate constraints in CI. A Pod that requires a label the cluster does not have is a Pod that will be Pending forever. The CI pipeline should pull the cluster’s labels and validate every constraint against the actual labels.
- Prefer
preferredoverrequiredfor soft rules. ArequiredDuringSchedulingIgnoredDuringExecutionrule is a hard rejection. The same intent expressed aspreferredDuringSchedulingIgnoredDuringExecutionis a scoring hint. Use the soft form unless the rule is business-critical. - A cluster with a sustained “didn’t match” message is a cluster with a wrong constraint. The Pod’s spec is wrong, or the cluster’s labels are wrong. The fix is to fix the spec, not to add nodes.
- Audit affinity rules at every release. A new workload that requires a label the cluster does not have will fail at deployment. The CI should catch it; the operator should still audit.
- Topology constraints are difficult to test. A Pod
spread across nodes is hard to test in a CI cluster
with few nodes. The
preferredmode is the safer default; therequiredmode is for production where the cluster’s topology is known.