Kubernetes
pods
podAntiAffinity
topologySpreadConstraints
node distribution

Kubernetes spread pods across nodes using podAntiAffinity vs topologySpreadConstraints

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Both podAntiAffinity and topologySpreadConstraints can keep replicas from piling onto one node, but they express different intents. podAntiAffinity says "do not place me near matching pods," while topologySpreadConstraints says "keep the replica counts balanced across topology domains." If you treat them as interchangeable, scheduling becomes harder to predict.

What podAntiAffinity Is Good At

podAntiAffinity is useful when the rule is primarily relational. You want a pod to avoid nodes or zones that already contain matching pods.

yaml
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: api
5spec:
6  replicas: 3
7  selector:
8    matchLabels:
9      app: api
10  template:
11    metadata:
12      labels:
13        app: api
14    spec:
15      affinity:
16        podAntiAffinity:
17          requiredDuringSchedulingIgnoredDuringExecution:
18            - labelSelector:
19                matchExpressions:
20                  - key: app
21                    operator: In
22                    values:
23                      - api
24              topologyKey: kubernetes.io/hostname
25      containers:
26        - name: api
27          image: nginx:1.27

This says that matching api pods must not share the same node. It is a strong rule and can leave pods pending if the cluster does not have enough distinct nodes.

What topologySpreadConstraints Is Good At

topologySpreadConstraints focuses on balance, not just separation. It lets you say how uneven replica counts are allowed to become across nodes, zones, or other topology domains.

yaml
1apiVersion: apps/v1
2kind: Deployment
3metadata:
4  name: api
5spec:
6  replicas: 6
7  selector:
8    matchLabels:
9      app: api
10  template:
11    metadata:
12      labels:
13        app: api
14    spec:
15      topologySpreadConstraints:
16        - maxSkew: 1
17          topologyKey: kubernetes.io/hostname
18          whenUnsatisfiable: DoNotSchedule
19          labelSelector:
20            matchLabels:
21              app: api
22      containers:
23        - name: api
24          image: nginx:1.27

With maxSkew: 1, Kubernetes tries to keep the difference between the busiest and least busy eligible topology domains within one pod. That is usually a better mental model when you care about even spreading rather than strict one-per-node exclusion.

The Practical Difference

Here is the most important distinction:

  • 'podAntiAffinity answers "may these pods coexist in the same domain?"'
  • 'topologySpreadConstraints answers "how evenly should these pods be distributed?"'

That difference matters in larger clusters. Anti-affinity can prevent co-location without guaranteeing good balance. Spread constraints actively manage skew.

Which One To Prefer

If the goal is "make sure replicas do not all land together," modern Kubernetes setups often benefit more from topologySpreadConstraints. The scheduler can reason about balance directly, and the intent is clearer to readers.

If the goal is a strict rule such as "never put two replicas of this workload on the same node," anti-affinity is still appropriate.

In other words:

  • use anti-affinity for exclusion rules,
  • use spread constraints for balancing rules.

Zones Versus Nodes

Both mechanisms can operate across different topology keys. Common values include:

  • 'kubernetes.io/hostname for node-level spreading,'
  • 'topology.kubernetes.io/zone for zone-level spreading.'

You can even combine them if the workload needs balance across zones and sensible placement within each zone.

yaml
1topologySpreadConstraints:
2  - maxSkew: 1
3    topologyKey: topology.kubernetes.io/zone
4    whenUnsatisfiable: DoNotSchedule
5    labelSelector:
6      matchLabels:
7        app: api
8  - maxSkew: 1
9    topologyKey: kubernetes.io/hostname
10    whenUnsatisfiable: ScheduleAnyway
11    labelSelector:
12      matchLabels:
13        app: api

That kind of setup lets you express hard zone balance and softer node-level preferences.

Beware Of Hard Constraints

Both features can make pods unschedulable if the cluster lacks enough eligible domains.

  • hard anti-affinity can require more nodes than you actually have,
  • 'DoNotSchedule spread constraints can block placement if balance is impossible,'
  • label selectors that match too broadly can accidentally constrain unrelated workloads.

So the correctness question is not just "does the YAML look valid?" It is "can this cluster satisfy the policy under failure and scale conditions?"

Common Pitfalls

  • Using podAntiAffinity when the real goal is even distribution rather than strict exclusion.
  • Using a hard requiredDuringSchedulingIgnoredDuringExecution rule in a small cluster and then wondering why replicas stay pending.
  • Forgetting that topology keys can target zones as well as individual nodes.
  • Matching too many pods with a broad selector and creating unnecessary scheduling pressure.
  • Assuming spread constraints and anti-affinity are identical because both influence pod placement.

Summary

  • 'podAntiAffinity is about preventing co-location with matching pods.'
  • 'topologySpreadConstraints is about keeping replica counts balanced across topology domains.'
  • Prefer anti-affinity for strict exclusion rules.
  • Prefer spread constraints when the real goal is even distribution.
  • Always validate the policy against actual cluster size and failure scenarios.

Course illustration
Course illustration

All Rights Reserved.