Topology spread vs pod anti-affinity is not a choice between modern and obsolete syntax. The mechanisms express different placement ideas. Anti-affinity says that a pod should or must avoid a topology domain containing another selected pod. Topology spread says that matching pods should remain within a permitted skew across eligible domains. One models separation; the other models distribution. The correct choice follows from the service failure invariant and what should happen when a zone or node disappears.
A manifest that looks highly available can reduce availability if it becomes impossible to schedule replacement pods. Required anti-affinity across hostnames limits one matching pod per node. That may be exactly right for quorum members, but it blocks a fourth replica in a three-node cluster and can stall a rolling update. A hard maxSkew: 1 zone spread can also block when labels or eligible domains are wrong. Design ordinary and degraded states together, then test scheduler decisions rather than reading intent from YAML.
Define the placement invariant in six steps
Write the failure statement first: for example, loss of any one zone must leave two ready replicas, or loss of one node must not remove both copies of a shard. Name the selected pod set, required surviving capacity, recognized domains, and acceptable skew. Then calculate replica counts for normal operation, rolling surge, scale-out, one unavailable domain, and a partially drained cluster. If the arithmetic cannot satisfy the invariant, no scheduler feature will repair it; add replicas, nodes, zones, or a controlled relaxation.
| Requirement | Topology spread | Pod anti-affinity | Recommended expression |
|---|---|---|---|
| Keep replicas roughly even across zones | Direct fit through maxSkew | Indirect and often too rigid | Zone spread constraint |
| Never co-locate two quorum members on a node | Can express skew but needs domain math | Direct required separation | Required node anti-affinity |
| Prefer separation but schedule during shortage | ScheduleAnyway with scoring | Preferred anti-affinity | Choose based on evenness need |
| Support many replicas per domain | Designed for this case | Required rule prevents it | Topology spread |
| Place near or away from another application | Selector can count it but intent is awkward | Direct pod affinity or anti-affinity | Affinity mechanism |
Model distribution with topology spread constraints
A topology spread constraint combines maxSkew, topologyKey, whenUnsatisfiable, and a label selector. The official constraint reference explains how eligible domains and global minimum affect skew. Use DoNotSchedule only for a true invariant; use ScheduleAnyway when evenness is preferred but service capacity is more important during shortage. Multiple constraints are combined, so a pod may need to satisfy both zone and hostname distribution alongside resource, volume, affinity, and taint filters.
The selector must match the intended population and normally the incoming pod itself. Otherwise, pods can accumulate without being counted as expected. minDomains can prevent a hard constraint from treating one surviving zone as a satisfactorily balanced universe, but it may leave pods pending until enough domains exist. Review nodeAffinityPolicy and nodeTaintsPolicy where available because they change which domains participate in calculations. Use stable labels such as topology.kubernetes.io/zone only after verifying every eligible node carries accurate values.
Use pod anti-affinity for explicit separation
Inter-pod anti-affinity selects existing pods and a topology key, then either requires or prefers separation. The Kubernetes pod assignment guide cautions that inter-pod affinity and anti-affinity can slow scheduling substantially in large clusters. Required rules are appropriate when co-location violates fault tolerance, security, licensing, or quorum design. Preferred rules provide scoring influence while allowing progress. Keep selectors narrow and namespace scope deliberate; a broad selector can couple unrelated deployments and make scheduling behavior difficult to explain.
requiredDuringSchedulingIgnoredDuringExecution is evaluated when scheduling; it does not continuously evict pods if labels later change or topology becomes invalid. Anti-affinity therefore prevents new bad placements but is not a rebalancer. Required hostname anti-affinity is common for stateful replicas, yet the workload also needs enough eligible nodes for upgrades and failures. Include rolling-update surge, PodDisruptionBudget, volume zone, and node maintenance in the capacity calculation before calling the rule safe.
Govern labels, taints, and eligible domains
Placement policy is only as trustworthy as node metadata. Restrict who can set isolation labels and use protected label prefixes where security depends on them. Audit missing, unexpected, or changing zone, region, rack, accelerator, and node-pool labels. Taints filter candidates only when pods lack matching tolerations; they do not guarantee that tolerated pods select the intended pool. The taints and tolerations documentation recommends combining dedicated-node taints with affinity when exclusivity matters.
Autoscaled-to-zero domains are a subtle case. The scheduler discovers topology domains from existing eligible nodes and may not account for a zone with no nodes. A topology-aware node autoscaler can compensate, but support must be proven. Storage adds another hidden filter: a bound zonal volume may make only one domain eligible. Capture scheduler events and enable a controlled pending-pod test for each failure domain. The result should explain not merely that a pod is Pending, but which filter eliminated every node.
| Scenario | Expected scheduler result | Availability evidence | Misconfiguration signal |
|---|---|---|---|
| One node cordoned | Replacement uses another node | Ready replicas stay above objective | Required rule leaves no candidate |
| One zone unavailable | Survivors run in remaining zones per policy | Service and quorum remain healthy | minDomains or volume locality deadlock |
| Rolling update with surge | New revision schedules before old removal | No availability dip | Hard anti-affinity blocks surge |
| Scale from two to many replicas | Skew remains bounded | Counts by domain converge | Selector omits new revision |
| Node pool scaled to zero | Autoscaler creates eligible capacity | Pending duration within objective | Domain invisible to autoscaler |
Observe placement and plan rebalancing
Export pod counts by workload revision, node, zone, and readiness. Alert on an invariant violation, not simply any skew. After scale-down or a repaired zone, Kubernetes does not automatically move healthy pods solely to improve topology spread. Decide whether natural rollout is sufficient or a descheduler and eviction policy is justified. Any rebalancing mechanism must respect disruption budgets and stateful recovery time; a cosmetically perfect distribution is not worth avoidable churn.
Use scheduler profiles cautiously. The scheduler configuration reference shows PodTopologySpread, InterPodAffinity, NodeAffinity, TaintToleration, and NodeResourcesFit as separate plugins in the scheduling path. Cluster defaults can provide a baseline, but workload-specific constraints should remain visible when they encode a service invariant. Document who owns global defaults and how application teams can detect that a default, rather than their manifest, changed placement.
Review placement changes with concrete arithmetic
For a six-replica web tier across three zones, a zone spread constraint with maxSkew: 1 should normally produce counts such as two, two, and two. If one zone disappears and only four replicas fit in the others, a hard minDomains: 3 policy can intentionally leave replacements Pending, while a softer policy may allow two and two to preserve service. The choice depends on whether running in fewer domains is safer than reduced capacity. Write that degraded decision in the workload record instead of discovering it during an outage.
For a three-member consensus system, required hostname anti-affinity prevents two members sharing one node, but zone policy needs different arithmetic. Three zones provide one member each; a temporary fourth member during replacement requires a fourth eligible node and may share a zone even though it must not share a hostname. If every rule requires unique zones, replacement cannot begin before old-member removal, increasing risk. Test the controller's actual membership and rollout sequence alongside scheduler constraints.
Make constraint review part of every replica, node-pool, and topology change. A scale decision can cross a satisfiability boundary even when the manifest is unchanged. Admission checks can catch selectors that do not match pod labels, unknown topology keys, and hard anti-affinity with replicas exceeding current domains. Runtime alerts should identify skew, but also the more urgent state: desired capacity that cannot schedule while a permitted relaxation or infrastructure repair is available.
Key takeaways
- Translate availability objectives into replica and domain arithmetic before writing selectors.
- Use topology spread for bounded distribution and anti-affinity for direct separation.
- Reserve hard constraints for invariants that justify pending pods during shortage.
- Treat node labels, taints, volume locality, and autoscaler awareness as part of the policy.
- Test rollout, scale-out, node loss, zone loss, and recovery; placement does not automatically rebalance afterward.
Frequently asked questions
Can topology spread and anti-affinity be combined?
Yes. A stateful service might require members on different nodes while preferring even zone distribution. Model satisfiability because every additional hard filter shrinks the candidate set.
Does maxSkew one guarantee a pod in every zone?
Not by itself. Eligible domains, replica count, minDomains, node filters, taints, and unavailable zones all affect the calculation. Verify the actual domain set in the target cluster.
Will the scheduler rebalance existing pods?
No. These rules guide new scheduling. Rebalancing generally occurs through rollout, scaling, eviction, or a separately governed descheduler process.
Conclusion
Failure-domain placement is a service design expressed through scheduler constraints. Choose spread when the invariant concerns relative distribution and anti-affinity when it concerns direct separation. Ground either choice in accurate topology data, satisfiable degraded states, and scenario tests. The strongest manifest is the one that protects availability without turning a recoverable infrastructure shortage into a scheduling deadlock.