K8s Topology Spread: Distribute Pods
Configure Kubernetes topology spread constraints to distribute pods across zones, nodes, and regions. maxSkew, whenUnsatisfiable, and scheduling strategies.
π‘ Quick Answer:
topologySpreadConstraintsdistributes pods evenly across failure domains. SetmaxSkew: 1withtopologyKey: topology.kubernetes.io/zoneto spread pods across availability zones. UsewhenUnsatisfiable: DoNotSchedule(strict) orScheduleAnyway(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 distributedSpread 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: webmaxSkew 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 = 5whenUnsatisfiable 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 placementCombined 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/hostnameCluster-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: ScheduleAnywayCommon 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: 1for even distribution - Use
matchLabelKeysfor 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
maxSkewcontrols how uneven the distribution can be (1 = perfectly balanced)DoNotScheduleenforces strictly;ScheduleAnywayis best-effort- Use
matchLabelKeysfor per-revision spreading during rolling updates - Combine zone spread + node spread + PDB for maximum availability

Recommended
Kubernetes Recipes β The Complete Book100+ production-ready patterns with detailed explanations, best practices, and copy-paste YAML. Everything in one place.
Get the Book βLearn by Doing
CopyPasteLearn β Hands-on Cloud & DevOps CoursesMaster Kubernetes, Ansible, Terraform, and MLOps with interactive, copy-paste-run lessons. Start free.
Browse Courses βπ Deepen Your Skills β Hands-on Courses
Courses by CopyPasteLearn.com β Learn IT by Doing
