DevOpsInterviewPrep logo
← ☸️ Containers & Kubernetes
Foundational

Kubernetes scheduling constraints: affinity, taints and topology

Understand how Kubernetes combines resource requests, affinity, taints and topology spread. Diagnose Pending pods by finding which nodes satisfy every hard rule.

TL;DR: A pod needs a node that satisfies all its hard scheduling constraints. Tolerations remove a taint-based exclusion; affinity selects or prefers placement, and topology rules constrain distribution across failure domains.

Placement is an intersection

The scheduler filters out ineligible nodes, scores eligible candidates and binds the pod to a selected node. Resource requests participate in that decision, alongside placement constraints. Total free CPU across a cluster therefore does not establish that a particular pod can run anywhere. Its requested memory might not fit on any eligible node, or its storage and placement requirements might select different zones.

nodeSelector requires matching labels. Required node affinity also filters nodes; preferred node affinity influences scoring without guaranteeing placement. Within node affinity, expressions in a term combine with AND, while separate selector terms combine with OR. If both nodeSelector and node affinity are specified, both must be satisfied. Kubernetes documents the details in assigning pods to nodes.

rendering diagram…

This is a conceptual filter, not a claim about a fixed internal plugin execution order. The diagnostic question is which requirement eliminated each candidate.

A toleration does not reserve a node

A NoSchedule taint excludes a pod unless it has a matching toleration. Adding that toleration permits scheduling there; it does not attract the pod or prevent it from landing elsewhere. For a dedicated worker pool, a common design pairs the taint with required node labels: the taint keeps unrelated workloads out, and affinity keeps the intended workload in. NoExecute also affects pods already running on the node, unlike NoSchedule. See taints and tolerations.

Consider this hypothetical placement review. A pod requests 2 CPU and 3 GiB memory, requires workload=payments, and tolerates dedicated=payments:NoSchedule.

NodeRelevant labels and taintsFree requested capacityEligible?
APayments label; matching dedicated taint4 CPU, 8 GiBYes, before other constraints
BGeneral label; no taint8 CPU, 16 GiBNo: required label fails
CPayments label; untolerated maintenance taint8 CPU, 16 GiBNo: taint excludes it
DPayments label; matching dedicated taint1 CPU, 8 GiBNo: CPU request does not fit

Adding more B-shaped nodes cannot fix this pod's placement. That is the follow-up an interviewer can use to test whether “enable autoscaling” is an explanation or a guess. The provisioned node must satisfy the same constraints and provide the requested resources.

Availability can conflict with schedulability

Topology spread constraints compare counts of matching pods across eligible domains, commonly zones or hosts. With DoNotSchedule, the spread requirement is hard. With ScheduleAnyway, the scheduler prefers placements that reduce skew. The labelSelector determines which existing pods participate in the count; a selector that misses your workload measures the wrong population. Eligibility, minDomains and missing topology labels also affect the calculation. Read the topology spread rules before predicting an exact result.

For a simplified example, assume two eligible zones, no other restrictions, maxSkew: 1, DoNotSchedule, and matching pod counts of 2 and 1. The next pod can go into the second zone. If that zone has no fitting node, adding it to the first would produce counts of 3 and 1 and violate the spread constraint. Pending is the intended result of the hard availability policy.

Relaxing the constraint can restore capacity at the cost of concentrating replicas. Make that choice explicitly and define when to restore the original policy. A requirement whose name contains IgnoredDuringExecution also does not promise to evict a running pod when node labels later change.

Inspect the failed decision

Use the real pod name and namespace when running these read-only commands:

kubectl describe pod payments-api -n payments
kubectl get nodes --show-labels
kubectl get nodes -o custom-columns='NAME:.metadata.name,TAINTS:.spec.taints'

Scheduling events summarize reasons across candidates. Do not add their node counts as though every reason describes a disjoint set: one node can fail several checks. Compare the manifest's requests and constraints with node labels, taints and allocatable capacity. For storage-backed pods, include volume topology in that comparison.

Self-check: a pod tolerates the GPU pool's taint but has no node selector, affinity or GPU request. Must it land on a GPU node? No. The toleration only removes one exclusion. Express the resource requirement and intended placement explicitly; do not use toleration presence as proof of either GPU allocation or dedicated-node isolation.

When existing nodes cannot satisfy placement, node autoscaling explains the additional constraints between a pending pod and useful new capacity.

RELATED CONCEPTS
PRACTICE THIS IN REAL QUESTIONS