KubernetesXXV · Topology Spread ConstraintsTopology spread
Topology keys — kubernetes.io/hostname, topology.kubernetes.io/zone, and beyond
What you'll learn
- List the standard topology keys and their domains
- Reason about the cost of each (counting domains)
- Apply layered topology keys (hostname + zone)
- Verify the cluster has the right labels
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
The topologyKey field in a topology spread constraint
defines the placement domain. The most common keys are
kubernetes.io/hostname (per node) and
topology.kubernetes.io/zone (per zone). This lesson
covers the standard keys, their domains, and how to
verify the cluster has the right labels.
The standard keys
| Topology key | Domain | Cost |
|---|---|---|
kubernetes.io/hostname | Per node | Many domains (one per node) |
topology.kubernetes.io/zone | Per zone | Few domains (one per zone) |
topology.kubernetes.io/region | Per region | Fewest domains (one per region) |
kubernetes.io/hostname (per node)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
Each domain is one node. The constraint distributes Pods across nodes; the count is the number of matching Pods per node.
flowchart TB
A[6 replicas, 6 nodes] --> B["Each node: 1 Pod"]
B --> C["Skew: 0"]
For a 6-replica Deployment on 6 nodes with maxSkew: 1,
each node hosts 1 Pod. With maxSkew: 1 and 3 nodes,
distribution [2, 2, 2]; skew 0.
topology.kubernetes.io/zone (per zone)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
Each domain is one zone. The constraint distributes Pods across zones.
flowchart TB
subgraph "us-east-1a"
N1[node-01] --> P1[db-1]
end
subgraph "us-east-1b"
N2[node-02] --> P2[db-2]
end
subgraph "us-east-1c"
N3[node-03] --> P3[db-3]
end
For a 3-replica Deployment with maxSkew: 1 across 3
zones, each zone gets 1 Pod. A zone failure loses 1
replica.
topology.kubernetes.io/region (per region)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/region
Each domain is one region. Useful for cluster-spanning workloads (rare in production; most clusters are single-region).
Layered constraints
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: db
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: db
Two constraints: one per node (hard) and balanced across zones (soft). The Pod must satisfy both.
flowchart TB
A[Pod arrives] --> B{Constraint 1:<br/>one per node?}
B -->|yes| C[Continue]
B -->|no| D[Pending]
C --> E{Constraint 2:<br/>balanced zones?}
E -->|yes| F[Schedule]
E -->|no| G[Schedule with skew]
The cost
flowchart LR
A[Topology key] -->|hostname| B["Many domains<br/>fast counting"]
A -->|zone| C["Few domains<br/>very fast"]
A -->|region| D["Very few domains<br/>fastest"]
B --> E[Walk per node]
C --> F[Walk per zone]
D --> G[Walk per region]
Hostname walks every node; zone walks every zone (typically
3-5); region walks every region (typically 1-3). The
scheduler counts matching Pods per domain. The cost is
O(P) where P is the total Pod count (cached).
For a 1000-Pod cluster, the count is a single walk through the cache. Topology spread is cheaper than anti-affinity because the cost is counting, not selector evaluation.
Custom topology keys
topologySpreadConstraints:
- maxSkew: 1
topologyKey: rack.example.com/rack-id
A custom topology key requires the cluster’s nodes to have the label. The scheduler groups nodes by label value.
kubectl label node node-01 rack.example.com/rack-id=rack-1
kubectl label node node-02 rack.example.com/rack-id=rack-2
Custom topology keys are useful for:
- Rack-level distribution (data centre topology).
- Custom hardware tiers (e.g.,
tier=premium). - Software-defined domains (e.g., per-application boundaries).
Verifying the cluster has the labels
# Verify zone labels exist
kubectl get nodes -o custom-columns=NAME:.metadata.name,ZONE:.metadata.labels.topology\.kubernetes\.io/zone
# NAME ZONE
# node-01 us-east-1a
# node-02 us-east-1b
# Verify hostname labels (always set)
kubectl get nodes -o custom-columns=NAME:.metadata.name,HOSTNAME:.metadata.labels.kubernetes\.io/hostname
# NAME HOSTNAME
# node-01 node-01
# node-02 node-02
# Verify custom labels
kubectl get nodes -o custom-columns=NAME:.metadata.name,RACK:.metadata.labels.rack\.example\.com/rack-id
# NAME RACK
# node-01 rack-1
# node-02 rack-2
If the cluster’s nodes do not have the labels, the topology spread constraint has no effect (every node is in the same “missing” domain).
Failure modes
Failure 1: missing zone label
A constraint with topologyKey: topology.kubernetes.io/zone
on a cluster where no nodes have the zone label:
flowchart TB
A[Topology key] --> B{All nodes have<br/>zone label?}
B -->|no| C["All nodes in same<br/>missing-label domain"]
B -->|yes| D[Distribution works]
C --> E[Constraint has no effect]
The fix: add the zone label, or use a topology key that exists.
Failure 2: empty domain
A topology key whose value is empty (""):
kubectl get nodes -o custom-columns=NAME:.metadata.name,ZONE:.metadata.labels.topology\.kubernetes\.io/zone
# NAME ZONE
# node-01
# node-02
Empty values are treated as the same domain. The distribution is unbalanced. The fix: set the label correctly.
Failure 3: single domain
A topology key with only one domain (e.g., single-zone cluster):
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
With one zone, the skew is always 0; the constraint is
satisfied trivially. The constraint does not spread
across nodes; for that, use kubernetes.io/hostname.
Production patterns
Pattern 1: layered (hostname + zone)
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
One per node, balanced across zones. A node failure loses one Pod; a zone failure loses one per node in the zone.
Pattern 2: hostname only (small cluster)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
A single-zone cluster. Hostname is the only meaningful domain.
Pattern 3: zone + custom rack
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
- maxSkew: 1
topologyKey: rack.example.com/rack-id
Zone-level balance and rack-level balance. A multi-rack zone distributes Pods across racks; a single-rack zone distributes across the zone’s nodes.
Quiz
Knowledge check · 4 questions
Q1. What is the difference between kubernetes.io/hostname and topology.kubernetes.io/zone as topology keys?
Q2. Topology keys with few domains (e.g., 3 zones) are slower than keys with many domains (e.g., 1000 hostnames).
Q3. Your team uses topology spread with topologyKey topology.kubernetes.io/zone but the cluster has only one zone. The constraint has no effect. Diagnose.
Topology spread with topologyKey topology.kubernetes.io/zone. Cluster has 10 nodes all in one zone.
Q4. Explain layered topology constraints (hostname + zone) and how they combine.
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Verify the cluster has the labels. A topology key with missing labels has no effect.
- Layer constraints for layered protection. Hostname
- zone gives node-level and zone-level distribution.
- Use ScheduleAnyway for flexibility. A workload that
prefers balance but accepts some imbalance uses
ScheduleAnywayto avoid Pending Pods. - Custom keys require operator setup. A custom topology key is only useful if the cluster’s nodes have the label.
- Audit the resulting distribution. A dashboard that surfaces per-domain Pod counts catches imbalance.
Topology keys define the placement domain. Operators who choose deliberately have workloads that distribute across the right failure domains.