KubernetesXXV · Topology Spread ConstraintsTopology spread
Topology spread constraints — balanced distribution across domains
What you'll learn
- Describe topologySpreadConstraints and its purpose
- Configure maxSkew, topologyKey, whenUnsatisfiable, labelSelector
- Distinguish DoNotSchedule from ScheduleAnyway
- Reason about when to use topology spread vs anti-affinity
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
topologySpreadConstraints is the modern Kubernetes
mechanism for distributing Pods evenly across topology
domains (nodes, zones, regions). It is more flexible than
pod anti-affinity — it allows some imbalance via
maxSkew — and cheaper to evaluate. This lesson covers
the syntax, the semantics, and when to reach for topology
spread vs anti-affinity.
The shape
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
The structure:
maxSkew— the maximum allowed difference in Pod count between any two domains.topologyKey— the placement domain (e.g.,kubernetes.io/ hostname,topology.kubernetes.io/zone).whenUnsatisfiable—DoNotSchedule(hard) orScheduleAnyway(soft).labelSelector— the Pods to count.
flowchart LR
A[Pod arrives] --> B["Count matching Pods<br/>per domain"]
B --> C{Max skew<br/>exceeded?}
C -->|yes| D{whenUnsatisfiable}
C -->|no| E["Schedule on<br/>least-loaded domain"]
D -->|DoNotSchedule| F[Pending]
D -->|ScheduleAnyway| G["Schedule anyway<br/>with skew"]
How it works
flowchart TB
A["Cluster: 3 nodes"] --> B["Pod label app=web"]
B --> C[web-1 on node-01]
B --> D[web-2 on node-02]
B --> E[web-3 on node-03]
F[web-4 arrives] --> G{Count per node:<br/>1, 1, 1}
G --> H{maxSkew 1,<br/>skew is 0}
H --> I[Schedule on any node]
With 3 nodes and maxSkew: 1, all nodes should have
within 1 Pod of each other. The fourth Pod is placed on
the least-loaded node (any of them, since they all have 1).
maxSkew
flowchart LR
A["Domain A: 3 Pods"] -->|maxSkew 1| B{Domain B has 3 or 4 Pods?}
C["Domain B: 2 Pods"] --> B
B -->|yes| D[Allowed]
B -->|no| E[Skew exceeded]
maxSkew: 1 means the count in any domain can differ from
the minimum by at most 1. maxSkew: 2 allows a difference
of 2. maxSkew: 3 allows a difference of 3.
The skew is measured from the minimum. A cluster with
[3, 3, 5] has skew 2 (max - min); with maxSkew: 2,
this is acceptable.
whenUnsatisfiable
whenUnsatisfiable: DoNotSchedule # hard
whenUnsatisfiable: ScheduleAnyway # soft
DoNotSchedule is the hard variant: if the constraint
cannot be satisfied, the Pod is unschedulable. ScheduleAnyway
is the soft variant: the Pod is scheduled, but with the
imbalance noted.
flowchart TB
A[Constraint cannot be satisfied] --> B{whenUnsatisfiable}
B -->|DoNotSchedule| C[Pod Pending]
B -->|ScheduleAnyway| D["Pod scheduled<br/>with skew"]
labelSelector
The Pods to count are determined by the labelSelector:
labelSelector:
matchLabels:
app: web
Only Pods with app: web are counted. The constraint
applies to Pods created by the same Deployment (which
match the selector) but also to any other Pod with the
label.
flowchart TB
A[Pod with topology constraint] --> B["Count Pods matching<br/>labelSelector per domain"]
B --> C["web-1: 1 Pod on node-01"]
B --> D["web-2: 1 Pod on node-02"]
B --> E["web-3: 0 Pods on node-03"]
A --> F{web-4 arrives}
F --> G{node-03 has 0<br/>vs nodes 01-02 have 1}
G -->|maxSkew 1| H["Schedule on node-03<br/>balance the skew"]
Topology key
| Topology key | Domain |
|---|---|
kubernetes.io/hostname | Per node |
topology.kubernetes.io/zone | Per zone |
topology.kubernetes.io/region | Per region |
The key defines the unit of distribution. With
kubernetes.io/hostname, Pods spread across nodes. With
topology.kubernetes.io/zone, Pods spread across zones.
Topology spread vs pod anti-affinity
| Aspect | Topology spread | Pod anti-affinity |
|---|---|---|
| Goal | Balanced distribution | Strict separation |
| Mechanism | Count Pods per domain | Walk Pods per node, evaluate selector |
| Cost | O(domains) | O(nodes × Pods per node) |
| Failure mode | ScheduleAnyway continues | Pending Pod |
| Skew | Bounded by maxSkew | Strict (no co-location) |
flowchart TB
A{Spread requirement}
A -->|Balanced| B[Topology spread]
A -->|Strict separation| C[Pod anti-affinity]
B --> D[Cheaper, configurable skew]
C --> E[More expensive, no skew]
For most “spread replicas” use cases, topology spread is the right tool. Anti-affinity is for strict separation when even one co-located Pod is unacceptable.
Multiple constraints
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: web
Two constraints: hostname (one per node, hard) and zone (balanced across zones, soft). The Pod must satisfy both.
flowchart TB
A["Constraint 1:<br/>hostname, DoNotSchedule"] --> B{Skew > 1?}
B -->|yes| C[Pending]
B -->|no| D["Constraint 2:<br/>zone, ScheduleAnyway"]
D --> E{Skew > 1?}
E -->|yes| F[Schedule with skew]
E -->|no| G[Schedule balanced]
Failure modes
Failure 1: maxSkew too tight
maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
A cluster with 5 nodes and 6 replicas: 5 Pods spread across 5 nodes; the 6th is Pending (no node can host it without skew > 1).
The fix: increase maxSkew, add nodes, or change to
ScheduleAnyway.
Failure 2: labelSelector matches no Pods
labelSelector:
matchLabels:
app: missing-app
No Pods match the selector; the count is always 0. Topology spread has no effect. The fix: verify the selector.
Failure 3: topology key not on any node
topologyKey: custom.example.com/rack
No node has the label. The scheduler cannot determine domains; the constraint fails. The fix: verify the cluster’s labels.
Quiz
Knowledge check · 4 questions
Q1. What does topologySpreadConstraints do?
Q2. topologySpreadConstraints can evict existing Pods when the topology changes.
Q3. Your team uses topology spread with maxSkew 1 and DoNotSchedule across nodes for a 6-replica Deployment on a 4-node cluster. 2 Pods are Pending. Diagnose.
Deployment with replicas 6 and topologySpreadConstraints maxSkew 1 DoNotSchedule. Cluster has 4 nodes.
Q4. Compare topology spread and pod anti-affinity in terms of goal and failure mode.
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Default to topology spread for replica distribution. Anti-affinity is for strict separation; topology spread is for balanced distribution.
- Choose maxSkew deliberately. maxSkew 1 is strict; maxSkew 2-3 allows some imbalance but tolerates node failures.
- Combine hostname and zone constraints. Layered protection: one per node, balanced across zones.
- Use ScheduleAnyway for flexibility. A workload that
prefers balance but accepts some imbalance uses
ScheduleAnywayto avoid Pending Pods. - Audit the resulting distribution. A dashboard that surfaces per-domain Pod counts catches imbalance.
Topology spread is the modern answer to “spread replicas.” Operators who use it deliberately have workloads that distribute predictably with sensible fallbacks.