πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Deployments intermediate ⏱ 15 minutes K8s 1.28+

Kubernetes topologySpreadConstraints Guide

Configure pod topology spread constraints for even distribution across zones, nodes, and racks. maxSkew, topologyKey, whenUnsatisfiable.

By Luca Berton β€’ β€’ πŸ“– 5 min read

πŸ’‘ Quick Answer: `topologySpreadConstraints` ensures pods are evenly distributed across failure domains (zones, nodes, racks). Set `maxSkew: 1` with `topologyKey: topology.kubernetes.io/zone` to spread pods across availability zones with at most 1 pod difference between zones.

The Problem

Pod anti-affinity prevents pods from landing on the same node, but doesn’t guarantee even distribution. With 6 replicas across 3 zones, you might get 4-1-1 instead of 2-2-2. Topology spread constraints enforce balanced placement.

flowchart TB
    subgraph WITHOUT["Without topologySpreadConstraints"]
        Z1A["Zone A<br/>4 pods 😰"]
        Z1B["Zone B<br/>1 pod"]
        Z1C["Zone C<br/>1 pod"]
    end
    subgraph WITH["With topologySpreadConstraints (maxSkew: 1)"]
        Z2A["Zone A<br/>2 pods βœ…"]
        Z2B["Zone B<br/>2 pods βœ…"]
        Z2C["Zone C<br/>2 pods βœ…"]
    end

The Solution

Spread Across Zones

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-app
spec:
  replicas: 6
  template:
    metadata:
      labels:
        app: web-app
    spec:
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: DoNotSchedule
          labelSelector:
            matchLabels:
              app: web-app
      containers:
        - name: app
          image: nginx

Spread Across Nodes

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: kubernetes.io/hostname
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: web-app

Multi-Level Spread (Zone + Node)

topologySpreadConstraints:
  # First: even across zones
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: web-app
  # Second: even across nodes within each zone
  - maxSkew: 1
    topologyKey: kubernetes.io/hostname
    whenUnsatisfiable: ScheduleAnyway
    labelSelector:
      matchLabels:
        app: web-app

Parameters Explained

ParameterValuesEffect
`maxSkew`Integer β‰₯ 1Max allowed difference in pod count between topologies
`topologyKey`Node label keyGroups nodes into topology domains
`whenUnsatisfiable``DoNotSchedule` / `ScheduleAnyway`Block scheduling or soft preference
`labelSelector`Label matchWhich pods count toward the spread
`minDomains`Integer β‰₯ 1Minimum domains to spread across (K8s 1.25+)
`matchLabelKeys`Label keysAuto-scope to same revision (K8s 1.27+)

whenUnsatisfiable Options

# Hard constraint β€” pod stays Pending if spread can't be satisfied
whenUnsatisfiable: DoNotSchedule

# Soft constraint β€” scheduler tries its best, but places pod anyway
whenUnsatisfiable: ScheduleAnyway

matchLabelKeys (Rolling Update Safe)

Prevents new revision pods from being blocked by old revision’s distribution:

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: web-app
    matchLabelKeys:
      - pod-template-hash    # Only count pods from same ReplicaSet

minDomains

Ensure pods spread across at least N domains:

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    minDomains: 3            # Must use at least 3 zones
    labelSelector:
      matchLabels:
        app: web-app

Verify Spread

# Check pod distribution across zones
kubectl get pods -l app=web-app -o wide | awk '{print $7}' | sort | uniq -c
#   2 worker-zone-a-01
#   2 worker-zone-b-01
#   2 worker-zone-c-01

# Check node zone labels
kubectl get nodes -L topology.kubernetes.io/zone
# NAME                  ZONE
# worker-zone-a-01      us-east-1a
# worker-zone-b-01      us-east-1b
# worker-zone-c-01      us-east-1c

vs Pod Anti-Affinity

FeaturetopologySpreadConstraintspodAntiAffinity
Even distributionβœ… Enforces balance❌ Only prevents co-location
maxSkew controlβœ… Fine-grained❌ All-or-nothing
Soft preferenceβœ… ScheduleAnywayβœ… preferredDuringScheduling
Multi-levelβœ… Zone + node combined⚠️ Complex config
Rolling update safeβœ… matchLabelKeys❌ Can block rollouts

Common Issues

IssueCauseFix
Pods stuck PendingNot enough zones/nodes for maxSkewUse `ScheduleAnyway` or increase nodes
Uneven after scale-downScheduler doesn’t rebalance existing podsUse Descheduler for rebalancing
Rolling update blockedOld pods counted in spreadAdd `matchLabelKeys: [pod-template-hash]`
topologyKey not foundNodes missing the labelLabel nodes: `kubectl label node X topology.kubernetes.io/zone=us-east-1a`
Spread ignored`ScheduleAnyway` used β€” it’s best-effortSwitch to `DoNotSchedule` for strict

Best Practices

  • Use zone spread for production services β€” survives AZ failures
  • Combine zone (hard) + node (soft) β€” even across zones, best-effort across nodes
  • Set `matchLabelKeys: [pod-template-hash]` β€” prevents rolling update deadlocks
  • Use `ScheduleAnyway` for node-level β€” avoid Pending pods in small clusters
  • Pair with PodDisruptionBudget β€” spread handles placement, PDB handles eviction
  • Use Descheduler to rebalance after scale events

Key Takeaways

  • `topologySpreadConstraints` enforces even pod distribution across zones/nodes
  • `maxSkew: 1` means at most 1 pod difference between any two domains
  • `DoNotSchedule` = hard constraint, `ScheduleAnyway` = soft preference
  • Combine zone (hard) + node (soft) for production HA deployments
  • `matchLabelKeys` prevents rolling update deadlocks (K8s 1.27+)
  • Superior to pod anti-affinity for even distribution across failure domains
#topology-spread #scheduling #high-availability #multi-zone #pod-placement
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens