KubernetesXXV · Topology Spread ConstraintsTopology spread
Topology spread vs anti-affinity — choosing the right tool
What you'll learn
- Compare topology spread and pod anti-affinity in detail
- Choose the right tool for a workload class
- Combine both for layered protection
- Identify when each is the wrong tool
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
Topology spread and pod anti-affinity both place Pods across domains, but they have different goals and failure modes. Topology spread is for balanced distribution; anti-affinity is for strict separation. This lesson covers the comparison, the trade-offs, and the patterns that combine both.
The comparison
flowchart TB
A{Distribution goal}
A -->|Balanced distribution<br/>tolerate some skew| B[Topology spread]
A -->|Strict separation<br/>no co-location| C[Pod anti-affinity]
B --> D["Cheaper<br/>counts Pods"]
C --> E["Expensive<br/>evaluates selectors"]
| Aspect | Topology spread | Pod anti-affinity |
|---|---|---|
| Goal | Balanced distribution | Strict separation |
| Mechanism | Count Pods per domain | Evaluate selector per Pod |
| Cost | O(P) counting | O(N × P) selector walk |
| Skew | Bounded by maxSkew | None (no co-location) |
| Failure mode | ScheduleAnyway continues | Pending Pod |
| Eviction | None | None (standard variant) |
Cost comparison
flowchart LR
A[Topology spread] --> B["Walk all Pods<br/>once per scheduling<br/>count per domain"]
C[Pod anti-affinity] --> D["Walk all Pods<br/>on every node<br/>evaluate selector"]
B --> E["Cost: O(P)"]
D --> F["Cost: O(N × P)"]
For a cluster with 1000 Pods and 100 nodes:
- Topology spread: ~1000 operations (one walk through Pod cache).
- Anti-affinity: ~100,000 operations (100 nodes × ~1000 Pods each).
The difference is significant for large clusters.
Failure modes
flowchart TB
A[Constraint cannot be satisfied] --> B{Topology spread}
A --> C{Pod anti-affinity}
B -->|ScheduleAnyway| D[Schedule with skew]
B -->|DoNotSchedule| E[Pending]
C -->|required| E
C -->|preferred| D
Topology spread with ScheduleAnyway always succeeds.
Anti-affinity with required is hard; if no node satisfies,
the Pod is Pending.
The decision matrix
| Requirement | Use topology spread | Use anti-affinity |
|---|---|---|
| Spread replicas evenly | Yes | No |
| Strict separation (no co-location) | No | Yes |
| Tolerate some imbalance | Yes | No |
| Cost-sensitive scheduling | Yes | No |
| Per-version distribution | Yes (with matchLabelKeys) | No |
flowchart TB
A{Strict or balanced?}
A -->|Strict| B[Anti-affinity]
A -->|Balanced| C[Topology spread]
B --> D{Always avoid co-location?}
D -->|yes| E[Required anti-affinity]
D -->|no, prefer separation| F[Preferred anti-affinity]
C --> G{ScheduleAnyway OK?}
G -->|yes| H["Topology spread<br/>ScheduleAnyway"]
G -->|no, must balance| I["Topology spread<br/>DoNotSchedule"]
Pattern: combine both
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: db
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: db
The Pod must not co-locate with another db Pod on the same node (strict, hard). The Pod’s zone distribution must be balanced (soft, maxSkew 1).
flowchart TB
A[Pod arrives] --> B{Anti-affinity:<br/>same node?}
B -->|yes| C[Eliminated]
B -->|no| D{Topology spread:<br/>zone skew?}
D -->|yes| E["Eliminated or<br/>ScheduleAnyway"]
D -->|no| F[Schedule]
When to reach for each
Use topology spread when:
- Spreading replicas evenly. The standard pattern for HA workloads.
- Tolerating some imbalance. A
ScheduleAnywayconstraint withmaxSkew: 2tolerates imbalance. - Per-version distribution during rollouts. With
matchLabelKeys, each ReplicaSet distributes independently. - Cost-sensitive scheduling. Topology spread is cheaper than anti-affinity.
Use anti-affinity when:
- Strict separation. Even one co-located Pod is unacceptable (e.g., a primary and a replica that must not share a node).
- Compliance. Regulatory requirements that mandate separation.
- Hardware-specific. A workload that requires different hardware from another workload (e.g., a primary on fast disk and a replica on slow disk).
The anti-patterns
Anti-pattern 1: anti-affinity for “spread replicas”
# WRONG: too expensive for the goal
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: web
The goal is “spread replicas.” Use topology spread:
# RIGHT: cheaper, configurable skew
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: web
Anti-pattern 2: topology spread for strict separation
# WRONG: topology spread tolerates skew
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
The goal is “no two Pods on the same node.” Topology
spread with maxSkew: 1 allows one extra Pod per domain.
Use anti-affinity:
# RIGHT: strict separation
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: db
Anti-pattern 3: both at once
# WRONG: redundant
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: web
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: web
Topology spread with maxSkew: 1, DoNotSchedule is
equivalent to required anti-affinity at the same domain.
Pick one, not both.
Production patterns
Pattern 1: HA web (topology spread)
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: web
Standard spread across nodes. Allows some imbalance via
ScheduleAnyway.
Pattern 2: HA database (anti-affinity)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: db
A database with strict separation. Each replica on its own node.
Pattern 3: HA layered (both)
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: db
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: db
Each replica on its own node; replicas balanced across zones.
Quiz
Knowledge check · 4 questions
Q1. For most spread-replicas use cases, which is the right tool?
Q2. Topology spread and required pod anti-affinity are equivalent for spreading replicas.
Q3. Your team uses topology spread with maxSkew 1 and ScheduleAnyway for a 100-replica Deployment on a 10-node cluster. The distribution is imbalanced (some nodes have 12 Pods, others have 8). Diagnose.
Deployment with topology spread maxSkew 1 ScheduleAnyway. Cluster has 10 nodes. Some nodes have 12 Pods; others have 8.
Q4. When is required pod anti-affinity the right tool, and when is topology spread better?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Default to topology spread. It’s cheaper and more flexible than anti-affinity.
- Use anti-affinity for hard separation. Compliance, hardware, correctness.
- Combine for layered protection. Anti-affinity for node-level; topology spread for zone-level.
- Don’t use both for the same domain. Redundant and confusing.
- Audit the resulting distribution. A dashboard that surfaces per-domain Pod counts catches imbalance.
The choice between topology spread and anti-affinity is the choice between balanced and strict. Operators who choose deliberately have workloads that distribute predictably.