Pod Anti Affinity
Kubernetes
Node Scheduling
Cluster Management
Resource Optimization

Using pod Anti Affinity to force only 1 pod per node

Master System Design with Codemia

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

Introduction

Pod anti-affinity is a Kubernetes scheduling constraint that prevents pods from being placed on the same node as other pods matching certain criteria. The most common use case is ensuring that only one instance of a particular pod runs per node — critical for stateful workloads, data-locality requirements, and high-availability deployments where co-located replicas would create a single point of failure.

Required vs Preferred Anti-Affinity

Kubernetes offers two types of anti-affinity:

TypeBehaviorWhat happens if no node is available
requiredDuringSchedulingIgnoredDuringExecutionHard constraint — must be satisfiedPod stays Pending
preferredDuringSchedulingIgnoredDuringExecutionSoft constraint — best effortPod is scheduled on a shared node

One Pod Per Node: Required Anti-Affinity

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

Key elements:

  • labelSelector: Matches pods with app: my-service — the same label as this deployment's pods
  • topologyKey: kubernetes.io/hostname: The anti-affinity applies per node (hostname). Each node is a separate topology domain
  • Effect: No two pods with app: my-service can be scheduled on the same node

One Pod Per Node: Preferred Anti-Affinity

Use preferred anti-affinity when you want spreading but do not want pods stuck in Pending:

yaml
1spec:
2  affinity:
3    podAntiAffinity:
4      preferredDuringSchedulingIgnoredDuringExecution:
5        - weight: 100
6          podAffinityTerm:
7            labelSelector:
8              matchExpressions:
9                - key: app
10                  operator: In
11                  values:
12                    - my-service
13            topologyKey: kubernetes.io/hostname

The weight (1-100) determines how strongly the scheduler prefers this constraint relative to other scheduling factors.

One Pod Per Zone

Spread pods across availability zones instead of nodes:

yaml
1spec:
2  affinity:
3    podAntiAffinity:
4      requiredDuringSchedulingIgnoredDuringExecution:
5        - labelSelector:
6            matchExpressions:
7              - key: app
8                operator: In
9                values:
10                  - my-service
11          topologyKey: topology.kubernetes.io/zone

This ensures at most one pod per availability zone — useful for cross-zone redundancy.

Topology Spread Constraints (Alternative)

Kubernetes 1.19+ introduced topologySpreadConstraints as a more flexible alternative:

yaml
1spec:
2  topologySpreadConstraints:
3    - maxSkew: 1
4      topologyKey: kubernetes.io/hostname
5      whenUnsatisfiable: DoNotSchedule
6      labelSelector:
7        matchLabels:
8          app: my-service
9  containers:
10    - name: my-service
11      image: my-service:latest
ParameterMeaning
maxSkew: 1Maximum difference in pod count between any two nodes
whenUnsatisfiable: DoNotScheduleHard constraint (like required anti-affinity)
whenUnsatisfiable: ScheduleAnywaySoft constraint (like preferred anti-affinity)

topologySpreadConstraints is more powerful than anti-affinity for controlling distribution because it considers the overall balance, not just pairwise exclusion.

DaemonSet Alternative

If you truly need exactly one pod per node, a DaemonSet may be simpler:

yaml
1apiVersion: apps/v1
2kind: DaemonSet
3metadata:
4  name: my-service
5spec:
6  selector:
7    matchLabels:
8      app: my-service
9  template:
10    metadata:
11      labels:
12        app: my-service
13    spec:
14      containers:
15        - name: my-service
16          image: my-service:latest

A DaemonSet automatically schedules exactly one pod on every node (or a subset selected by nodeSelector/nodeAffinity). Use this when you want full coverage, not just uniqueness.

StatefulSet with Anti-Affinity

For stateful workloads with persistent volumes:

yaml
1apiVersion: apps/v1
2kind: StatefulSet
3metadata:
4  name: database
5spec:
6  replicas: 3
7  selector:
8    matchLabels:
9      app: database
10  template:
11    metadata:
12      labels:
13        app: database
14    spec:
15      affinity:
16        podAntiAffinity:
17          requiredDuringSchedulingIgnoredDuringExecution:
18            - labelSelector:
19                matchExpressions:
20                  - key: app
21                    operator: In
22                    values:
23                      - database
24              topologyKey: kubernetes.io/hostname
25      containers:
26        - name: database
27          image: postgres:15
28          volumeMounts:
29            - name: data
30              mountPath: /var/lib/postgresql/data
31  volumeClaimTemplates:
32    - metadata:
33        name: data
34      spec:
35        accessModes: ["ReadWriteOnce"]
36        resources:
37          requests:
38            storage: 10Gi

Verifying Anti-Affinity

bash
1# Check which node each pod is on
2kubectl get pods -l app=my-service -o wide
3# NAME              READY   NODE
4# my-service-abc    1/1     node-1
5# my-service-def    1/1     node-2
6# my-service-ghi    1/1     node-3
7
8# Check if a pod is Pending due to anti-affinity
9kubectl describe pod my-service-jkl
10# Events:
11#   Warning  FailedScheduling  no nodes available to schedule pods
12#   (all nodes have pods matching the anti-affinity rules)
13
14# Check node labels
15kubectl get nodes --show-labels | grep hostname

Common Pitfalls

  • More replicas than nodes: With required anti-affinity and 3 nodes but 5 replicas, 2 pods stay Pending forever. Use preferred anti-affinity or add more nodes.
  • Wrong topologyKey: kubernetes.io/hostname means one pod per node. If you use topology.kubernetes.io/zone, it means one pod per availability zone (not per node). Choose the right key for your spreading goal.
  • Label selector mismatch: The labelSelector in the anti-affinity rule must match the pod's own labels. If the labels do not match, the anti-affinity has no effect and pods are scheduled freely.
  • IgnoredDuringExecution means no eviction: If a node later gets a second matching pod (e.g., through manual scheduling), the first pod is not evicted. Anti-affinity only affects scheduling, not running pods.
  • Performance with many pods: Anti-affinity with large numbers of pods (hundreds) can slow down the scheduler because it must check all existing pods. Use topologySpreadConstraints for better performance in large clusters.

Summary

  • Use podAntiAffinity with requiredDuringSchedulingIgnoredDuringExecution to guarantee at most one pod per node
  • Set topologyKey: kubernetes.io/hostname for per-node spreading
  • Use preferredDuringSchedulingIgnoredDuringExecution when you prefer spreading but cannot guarantee enough nodes
  • Consider topologySpreadConstraints (Kubernetes 1.19+) for more flexible distribution control
  • Use a DaemonSet if you need exactly one pod on every node, not just uniqueness
  • Ensure your cluster has at least as many nodes as replicas when using required anti-affinity

Course illustration
Course illustration

All Rights Reserved.