πŸ“š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 ⏱ 10 minutes K8s 1.28+

K8s Topology Spread: Distribute Pods

Configure Kubernetes topology spread constraints to distribute pods across zones, nodes, and regions. maxSkew, whenUnsatisfiable, and scheduling strategies.

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

πŸ’‘ Quick Answer: topologySpreadConstraints distributes pods evenly across failure domains. Set maxSkew: 1 with topologyKey: topology.kubernetes.io/zone to spread pods across availability zones. Use whenUnsatisfiable: DoNotSchedule (strict) or ScheduleAnyway (best-effort). Combine with pod anti-affinity for maximum HA.

The Problem

Without topology spread:

  • All replicas might land on the same node or zone
  • Node failure takes down all instances
  • Zone outage causes complete service downtime
  • Uneven resource utilization across the cluster

The Solution

Spread Across Zones

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 6
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      topologySpreadConstraints:
      - maxSkew: 1                           # Max difference between zones
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: DoNotSchedule     # Strict: don't schedule if skewed
        labelSelector:
          matchLabels:
            app: web
      containers:
      - name: web
        image: nginx:1.27
        resources:
          requests:
            cpu: 100m
            memory: 128Mi

# Result with 3 zones, 6 replicas:
# zone-a: 2 pods
# zone-b: 2 pods
# zone-c: 2 pods  ← evenly distributed

Spread Across Nodes

topologySpreadConstraints:
# Spread across zones (primary)
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      app: web

# Also spread across nodes within zones
- maxSkew: 1
  topologyKey: kubernetes.io/hostname
  whenUnsatisfiable: ScheduleAnyway        # Best-effort for nodes
  labelSelector:
    matchLabels:
      app: web

maxSkew Explained

maxSkew: 1 with 6 pods, 3 zones:
βœ… Allowed: [2, 2, 2] β€” skew = 0
βœ… Allowed: [3, 2, 2] β€” skew = 1
❌ Rejected: [4, 1, 1] β€” skew = 3

maxSkew: 2 with 6 pods, 3 zones:
βœ… Allowed: [4, 2, 2] β€” skew = 2
❌ Rejected: [5, 1, 0] β€” skew = 5

whenUnsatisfiable Options

# DoNotSchedule β€” strict, pod stays Pending if constraint can't be met
whenUnsatisfiable: DoNotSchedule

# ScheduleAnyway β€” best-effort, scheduler tries but doesn't block
whenUnsatisfiable: ScheduleAnyway

# Recommendation:
# - Zone spread: DoNotSchedule (HA is critical)
# - Node spread: ScheduleAnyway (flexibility for resource constraints)

matchLabelKeys (K8s 1.27+)

# Spread per-revision (during rolling updates)
topologySpreadConstraints:
- maxSkew: 1
  topologyKey: topology.kubernetes.io/zone
  whenUnsatisfiable: DoNotSchedule
  labelSelector:
    matchLabels:
      app: web
  matchLabelKeys:
  - pod-template-hash    # Only count pods from same ReplicaSet
  
# Without matchLabelKeys: old + new pods counted together
# β†’ new pods might cluster on one zone
# With matchLabelKeys: only new revision pods counted
# β†’ new pods spread evenly regardless of old pod placement

Combined with Affinity

spec:
  topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: web
  
  affinity:
    # Prefer nodes with SSD storage
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        preference:
          matchExpressions:
          - key: disk-type
            operator: In
            values: ["ssd"]
    
    # Avoid co-locating with cache pods
    podAntiAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 50
        podAffinityTerm:
          labelSelector:
            matchLabels:
              app: cache
          topologyKey: kubernetes.io/hostname

Cluster-Level Defaults

# Set default topology spread for all pods (kube-scheduler config)
apiVersion: kubescheduler.config.k8s.io/v1
kind: KubeSchedulerConfiguration
profiles:
- pluginConfig:
  - name: PodTopologySpread
    args:
      defaultingType: List
      defaultConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: ScheduleAnyway
      - maxSkew: 1
        topologyKey: kubernetes.io/hostname
        whenUnsatisfiable: ScheduleAnyway

Common Issues

Pods stuck Pending: β€œdoesn’t satisfy spread constraint”

Not enough zones/nodes to satisfy maxSkew. Reduce replicas, increase maxSkew, or switch to ScheduleAnyway.

Uneven distribution after node failure

Topology spread only affects scheduling β€” it doesn’t rebalance running pods. Use Descheduler for rebalancing.

Rolling update clusters new pods

Use matchLabelKeys: [pod-template-hash] (K8s 1.27+) to spread per revision.

Best Practices

  • Zone spread with DoNotSchedule β€” HA is non-negotiable
  • Node spread with ScheduleAnyway β€” best-effort avoids Pending pods
  • Set maxSkew: 1 for even distribution
  • Use matchLabelKeys for proper rolling update behavior
  • Combine with PDB β€” topology spread prevents placement issues, PDB prevents disruption

Key Takeaways

  • Topology spread constraints distribute pods across zones, nodes, or regions
  • maxSkew controls how uneven the distribution can be (1 = perfectly balanced)
  • DoNotSchedule enforces strictly; ScheduleAnyway is best-effort
  • Use matchLabelKeys for per-revision spreading during rolling updates
  • Combine zone spread + node spread + PDB for maximum availability
#topology #scheduling #high-availability #deployments #cka
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