KubernetesXXV · Topology Spread ConstraintsTopology spread
maxSkew — the imbalance budget
What you'll learn
- Describe how maxSkew is measured and enforced
- Reason about skew 1 vs skew 2 vs skew 3
- Choose maxSkew for a workload class
- Predict the distribution given replicas and domain count
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
maxSkew is the imbalance budget: the maximum allowed
difference in Pod count between any two domains. The
choice of value is part of the workload design — strict
skew produces balanced distribution but may produce
Pending Pods when nodes are scarce; loose skew tolerates
imbalance. This lesson covers how maxSkew is measured, the
trade-offs, and how to choose.
How maxSkew is measured
flowchart LR
A[3 domains] --> B["Domain A: 4 Pods"]
A --> C["Domain B: 3 Pods"]
A --> D["Domain C: 5 Pods"]
B --> E["Max: 5"]
C --> E
D --> E
E --> F["Min: 3"]
F --> G["Skew: 5 - 3 = 2"]
The scheduler’s definition is per domain: the skew of a
domain is the number of matching Pods in it minus the
global minimum, the smallest count across the eligible
domains. The skew of a distribution is the largest of
those, which comes out as max(per-domain count) - min(per-domain count). A cluster with [4, 3, 5] Pods
across 3 domains has skew 2.
That per-domain form is what matters when a Pod is
arriving. For each candidate domain the scheduler asks:
if this Pod lands here, is (count here + 1) - global minimum still within maxSkew? Domains that fail the
test are filtered out under DoNotSchedule. The Pod is
only Pending when every domain fails.
maxSkew semantics
maxSkew: 1 # strict: every domain has same or +-1 Pods
maxSkew: 2 # moderate: domains differ by at most 2
maxSkew: 3 # loose: domains differ by at most 3
The constraint is satisfied when the skew is <= maxSkew.
A skew of 3 with maxSkew: 3 is fine. A skew of 4 with
maxSkew: 3 is not.
flowchart TB
A[Current skew] --> B{maxSkew}
B -->|skew <= maxSkew| C[Satisfied]
B -->|skew > maxSkew| D{Violated}
D --> E{whenUnsatisfiable}
E -->|DoNotSchedule| F[Pending]
E -->|ScheduleAnyway| G[Schedule with skew]
skew 1 in practice
A Deployment with replicas: 6 and 6 nodes, maxSkew: 1:
flowchart TB
N1[node-01] --> P1[web-1]
N2[node-02] --> P2[web-2]
N3[node-03] --> P3[web-3]
N4[node-04] --> P4[web-4]
N5[node-05] --> P5[web-5]
N6[node-06] --> P6[web-6]
Each node has 1 Pod; skew is 0. With maxSkew: 1, this
satisfies the constraint.
With replicas: 6 and 3 nodes, maxSkew: 1:
flowchart TB
N1[node-01] --> P1[web-1, web-4]
N2[node-02] --> P2[web-2, web-5]
N3[node-03] --> P3[web-3, web-6]
Each node has 2 Pods; skew is 0. With maxSkew: 1,
this satisfies.
With replicas: 7 and 3 nodes, maxSkew: 1:
flowchart TB
N1[node-01] --> P1[web-1, web-4, web-7]
N2[node-02] --> P2[web-2, web-5]
N3[node-03] --> P3[web-3, web-6]
Work the arithmetic through rather than assuming. Before
web-7 arrives the distribution is [2, 2, 2] and the
global minimum is 2. Placing web-7 on node-01 gives that
node 3, so its skew is 3 - 2 = 1, which is exactly
maxSkew: 1. The constraint is satisfied and the Pod
schedules; the result is [3, 2, 2], skew 1.
An 8th Pod is refused only by node-01: landing there
would give 4 - 2 = 2. It goes to node-02 or node-03
instead, producing [3, 3, 2], still skew 1. A 9th Pod
must take node-03, giving [3, 3, 3].
maxSkew: 1 across 3 nodes therefore accommodates any
replica count — it just dictates the order in which the
nodes fill. A Pending Pod under DoNotSchedule means
the only domains that would satisfy the constraint have
no room for it, not that the replica count is awkward.
skew 2 in practice
A higher maxSkew does not change the arithmetic; it
widens the set of domains the scheduler may use at each
step. That only matters when the domain the constraint
prefers cannot take the Pod.
Take 3 nodes, maxSkew: 1, and a distribution of
[3, 3, 2] — where node-03 is nearly full of other
workloads and has no room left for this one. The 9th Pod
must go to node-03 to keep its skew at 1, node-03 cannot
fit it, and under DoNotSchedule the Pod stays Pending.
flowchart TB
A["Current: 3, 3, 2 — global minimum 2"] --> B{"maxSkew: 1"}
B --> C["Only node-03 qualifies: 3 - 2 = 1"]
C --> D[node-03 has no capacity]
D --> E[Pod Pending]
A --> F{"maxSkew: 2"}
F --> G["node-01 also qualifies: 4 - 2 = 2"]
G --> H["Scheduled: 4, 3, 2"]
With maxSkew: 2 the same Pod may land on node-01,
because 4 - 2 = 2 is within budget, and the
distribution becomes [4, 3, 2]. The looser budget buys
availability at the cost of a hotter node — which is the
whole trade-off the value expresses.
Choosing maxSkew
flowchart TB
A{Workload class}
A -->|Latency-critical| B["maxSkew: 1"]
A -->|HA replica| C["maxSkew: 1-2"]
A -->|Best-effort| D["maxSkew: 2-3"]
A -->|Batch| E["maxSkew: any<br/>use ScheduleAnyway"]
| Workload | Recommended maxSkew | Rationale |
|---|---|---|
| Latency-critical | 1 | Even distribution prevents hot nodes |
| HA replicas | 1-2 | Strict separation with some flexibility |
| Best-effort | 2-3 | Tolerates imbalance for fewer nodes |
| Batch | any | Use ScheduleAnyway; imbalance is fine |
Skew during node failure
flowchart TB
A["6 replicas on 6 nodes<br/>skew 0"] --> B[Node 3 fails]
B --> C[web-3 on node-03 fails]
C --> D["Deployment controller<br/>creates web-7"]
D --> E{web-7 schedules<br/>on remaining 5 nodes}
E --> F["Distribution: 2, 1, ?, 1, 1, 1"]
F --> G{node-03 lost<br/>effective nodes: 5}
G --> H["Skew with new distribution:<br/>maxSkew 1 still ok"]
After a node failure, the skew temporarily increases as
the Deployment recreates the lost Pod. With maxSkew: 1
and 6 replicas on 5 effective nodes (one node failed),
the distribution may be [2, 1, 1, 1, 1] — skew is 1,
which satisfies.
With replicas: 6 and 4 effective nodes (two failures),
distribution [2, 2, 1, 1] — skew 1, satisfies.
With replicas: 6 and 3 effective nodes (three failures),
distribution [2, 2, 2] — skew 0, satisfies.
The constraint handles node failures gracefully: the remaining Pods pack onto surviving nodes within the skew budget.
The skew during scale-up
flowchart TB
A[Scale 3 to 6 replicas] --> B[New Pods arrive]
B --> C["web-4 schedules<br/>on node with 1 Pod"]
C --> D["web-5 schedules<br/>on node with 1 Pod"]
D --> E["web-6 schedules<br/>on node with 1 Pod"]
E --> F["Distribution: 2, 2, 2<br/>skew 0"]
During scale-up, the scheduler prefers least-loaded domains. The distribution evolves to balance.
Skew vs zone failures
For zone-level topology, a zone failure loses all Pods in that zone. The skew temporarily spikes until the Deployment recreates the lost Pods in other zones.
flowchart TB
A[3 zones, 3 replicas] --> B[Zone us-east-1a fails]
B --> C[db-1 in zone us-east-1a fails]
C --> D["Distribution: 0, 1, 1"]
D --> E["Deployment recreates<br/>db-1 in another zone"]
E --> F["Distribution: 1, 1, 1<br/>skew 0"]
For HA workloads, maxSkew 1 across zones is the standard.
Production patterns
Pattern 1: HA web with strict skew
spec:
replicas: 6
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
Each node has 1 web Pod. A node failure loses 1 Pod.
Pattern 2: HA database with zone skew
spec:
replicas: 3
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: db
Each zone has 1 db Pod. A zone failure loses 1 Pod.
Pattern 3: best-effort with relaxed skew
spec:
replicas: 10
topologySpreadConstraints:
- maxSkew: 3
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: worker
Distribution [3, 3, 2, 2] is acceptable. Some imbalance
is tolerated for fewer nodes.
Quiz
Knowledge check · 4 questions
Q1. What does maxSkew 1 mean for a 3-domain topology with Pod counts [3, 3, 5]?
Q2. maxSkew is measured as the difference between the maximum and minimum Pod count across domains.
Q3. Your team uses maxSkew 1 and DoNotSchedule across zones for a 3-replica database. One zone fails. Diagnose.
Database with topologySpreadConstraints maxSkew 1 DoNotSchedule across zones. Zone us-east-1a fails; the Pod in that zone is lost.
Q4. How does maxSkew interact with node failures?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- maxSkew 1 is the strict default. Most HA workloads use maxSkew 1.
- maxSkew 2-3 for best-effort. A workload that tolerates some imbalance can use a higher skew.
- Combine with PDB. Topology spread distributes Pods; PDB bounds voluntary disruption. Both are needed for HA.
- Test with reduced node count. A cluster that loses a
node should still satisfy the constraint; verify with
kubectl drain --dry-run. - Audit the actual distribution. A dashboard that surfaces per-domain Pod counts catches skew violations.
maxSkew is the imbalance dial. Operators who choose deliberately have workloads that distribute predictably without producing Pending Pods.