Scheduling: Affinity, Taints & Topology Spread
Control where Pods land so a single node or zone failure can't take your whole service down — using taints, affinity, and topology spread constraints.
By default the scheduler packs Pods wherever they fit. In production you want more say: keep replicas apart, steer workloads onto the right hardware, and survive a zone outage. Four tools cover it.
Taints and tolerations (repel)
A taint on a node repels Pods that do not tolerate it. Use it to reserve GPU nodes, spot instances, or control-plane nodes for specific workloads. The node says "stay away unless you have a pass"; the toleration is that pass.
Node affinity (attract)
Node affinity pulls Pods toward nodes with matching labels — requiredDuringScheduling for a hard rule, preferredDuringScheduling for a soft one. Great for "run on nodes with SSDs" or "prefer the compute pool."
Pod anti-affinity (spread replicas)
Pod anti-affinity keeps copies of the same app off the same node, so losing one node cannot take out every replica.
Topology spread constraints (the modern default)
topologySpreadConstraints is the cleaner, newer way to say "spread my Pods evenly across zones (or nodes)." A maxSkew of 1 across the topology.kubernetes.io/zone key means no zone ever holds more than one extra replica than another — so a full zone outage costs you at most a fraction of your capacity.
Example
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 6
selector:
matchLabels: { app: web }
template:
metadata:
labels: { app: web }
spec:
# Spread the 6 replicas evenly across availability zones
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels: { app: web }
# Prefer the general compute pool, tolerate spot nodes
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: node-pool
operator: In
values: ["compute"]
tolerations:
- key: "spot"
operator: "Equal"
value: "true"
effect: "NoSchedule"
containers:
- name: web
image: nginx:1.27When to use it
- A team uses topologySpreadConstraints to distribute pods evenly across three availability zones, replacing the more brittle podAntiAffinity approach.
- A platform team uses PriorityClasses to ensure critical system pods are never evicted in favour of batch workloads when nodes run low on resources.
- An operator uses node affinity with requiredDuringScheduling to pin GPU workloads to nodes with the nvidia.com/gpu resource without hard-coding node names.
More examples
TopologySpreadConstraints for AZ spread
Spreads web pods so no availability zone has more than 1 extra pod compared to other zones, ensuring balanced HA placement.
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: webPriorityClass for critical workloads
Creates a PriorityClass with value 1,000,000 and assigns it to a pod so it can preempt lower-priority pods when resources are scarce.
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
description: "Critical production services"
---
spec:
priorityClassName: high-priorityMultiple scheduling constraints combined
Combines priority class, topology spread, and node affinity preference so pods land on the best available nodes with soft constraints.
spec:
priorityClassName: high-priority
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: api
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: node-type
operator: In
values: [compute-optimized]
Discussion