Skip to main content
RunBook Academy

KubernetesXXV · Topology Spread ConstraintsTopology spread

maxSkew — the imbalance budget

Advanced⏱ ~16 minkubectlkubeadm

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

Not yet marked complete on this device.

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"]
WorkloadRecommended maxSkewRationale
Latency-critical1Even distribution prevents hot nodes
HA replicas1-2Strict separation with some flexibility
Best-effort2-3Tolerates imbalance for fewer nodes
BatchanyUse 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

  1. Q1. What does maxSkew 1 mean for a 3-domain topology with Pod counts [3, 3, 5]?

  2. Q2. maxSkew is measured as the difference between the maximum and minimum Pod count across domains.

  3. 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.

  4. 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.