Skip to main content
RunBook Academy

KubernetesXXV · Topology Spread ConstraintsTopology spread

Topology spread constraints — balanced distribution across domains

Advanced⏱ ~17 minkubectlkubeadm

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

Not yet marked complete on this device.

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).
  • whenUnsatisfiableDoNotSchedule (hard) or ScheduleAnyway (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 keyDomain
kubernetes.io/hostnamePer node
topology.kubernetes.io/zonePer zone
topology.kubernetes.io/regionPer 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

AspectTopology spreadPod anti-affinity
GoalBalanced distributionStrict separation
MechanismCount Pods per domainWalk Pods per node, evaluate selector
CostO(domains)O(nodes × Pods per node)
Failure modeScheduleAnyway continuesPending Pod
SkewBounded by maxSkewStrict (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

  1. Q1. What does topologySpreadConstraints do?

  2. Q2. topologySpreadConstraints can evict existing Pods when the topology changes.

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

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