Kubernetes Topology Spread Placement
Kubernetes Topology Spread Placement distributes workloads across nodes using constraints, improving resource efficiency and fault tolerance in clusters.
Kubernetes Topology Spread Placement is a scheduling mechanism, expressed through spec.topologySpreadConstraints, that evenly distributes a group of related Pods across a specified topology dimension — nodes, zones, regions, or any custom node label — with fine-grained, quantitative control over how much imbalance is tolerated. Where pod anti-affinity expresses spreading as a pairwise "avoid this other Pod" relationship, topology spread constraints express it as a direct, numerical balancing goal: keep the difference in Pod count between the most-loaded and least-loaded topology domain within a specified bound, making it the more precise and purpose-built tool specifically for even distribution.
Each constraint is defined by a topology key identifying the dimension to spread across, a maximum allowed skew, a label selector identifying which Pods are counted as part of the same spreading group, and a policy for what to do when the constraint cannot be satisfied.
The maxSkew Calculation
Defining Skew
Skew is the difference between the number of matching Pods in the topology domain with the most and the domain with the least, among domains that currently host at least one matching Pod (or all eligible domains, depending on minDomains configuration).
maxSkew sets the ceiling this calculated value must not exceed after the new Pod is placed — a maxSkew of 1 means no topology domain may end up with more than one more matching Pod than the least-loaded domain.
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: codartium-api
whenUnsatisfiable Policies
DoNotSchedule
A hard constraint: the scheduler will not place a Pod anywhere that would violate the maxSkew bound, potentially leaving the Pod unschedulable if every domain is already at or above the balanced threshold relative to the least-loaded one.
ScheduleAnyway
A soft constraint: the scheduler prefers placements that keep skew within bounds, scoring nodes accordingly, but will still schedule the Pod somewhere even if every option violates maxSkew, rather than leaving it Pending.
Multiple Constraints Combined
Layered Spreading Across Dimensions
Multiple topologySpreadConstraints entries can be specified together, each targeting a different topology key — for example, spreading across zones as a hard requirement while also spreading across individual nodes within each zone as a softer preference, giving layered control over both coarse and fine-grained distribution simultaneously.
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: codartium-api
- maxSkew: 2
topologyKey: "kubernetes.io/hostname"
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app: codartium-api
minDomains
Guarding Against Too Few Domains
minDomains specifies the minimum number of eligible topology domains that must exist for the constraint's balancing goal to be meaningfully satisfied — if fewer domains exist than minDomains (for example, a cluster with only two zones when three are expected), the scheduler treats the missing domains as having zero matching Pods for skew calculation purposes, which can influence placement to actively favor filling in the "missing" domains conceptually, though this only applies when using DoNotSchedule.
Topology Spread vs. Pod Anti-Affinity
When to Prefer Topology Spread Constraints
For a single workload's replicas needing even, quantitatively bounded distribution, topology spread constraints are generally the more precise tool, offering direct control over acceptable imbalance (maxSkew) that anti-affinity has no equivalent for — anti-affinity can express "avoid" but not "spread evenly within a tolerance of N."
When Anti-Affinity Remains More Suitable
For relationships between different workloads (this Pod should avoid that other, differently labeled Pod entirely) rather than even distribution within a single workload's own replica set, pod anti-affinity remains the more natural expression.
Example
apiVersion: apps/v1
kind: Deployment
metadata:
name: codartium-api
spec:
replicas: 6
selector:
matchLabels:
app: codartium-api
template:
metadata:
labels:
app: codartium-api
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: "topology.kubernetes.io/zone"
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: codartium-api
containers:
- name: api
image: codartium/api:latest