KubernetesXXIV · Pod Affinity and Anti-AffinityPod affinity
Pod affinity and anti-affinity — co-locate or separate Pods
What you'll learn
- Describe Pod affinity and Pod anti-affinity in Kubernetes
- Configure the topologyKey and labelSelector fields
- Distinguish required and preferred variants
- Reason about the performance cost of large topology domains
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
Node Affinity is “this Pod wants this node.” Pod affinity and anti-affinity are “this Pod wants (or doesn’t want) Pods with these labels on the same (or different) node.” This is the placement tool for relationships between Pods: co-locate a cache with the workload that uses it; separate replicas across nodes for availability. This lesson covers the syntax, the topologyKey, and the required/preferred variants.
The shape
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: cache
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: web
The structure:
podAffinity— co-locate with Pods matching the selector.podAntiAffinity— separate from Pods matching the selector.topologyKey— the placement domain.kubernetes.io/ hostnamemeans “same node”;topology.kubernetes.io/ zonemeans “same zone.”labelSelector— the labels of the Pods to consider.
flowchart TB
A["Pod with affinity/anti-affinity"] --> B["topologyKey:<br/>kubernetes.io/hostname"]
B --> C{Label Selector:<br/>match?}
C -->|yes| D{Anti-affinity?<br/>co-locate?}
D -->|yes| E[Co-locate]
D -->|no| F[Separate]
Pod affinity — co-locate
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: cache
The Pod must land on a node that already hosts a Pod
labelled app: cache. Used for workloads that benefit
from local communication (cache, sidecar, agent).
flowchart LR
A[Web Pod] -->|podAffinity:<br/>app=cache| B[Node with cache Pod]
A --> C[Node without cache Pod]
C -.->|rejected| A
B --> D[Co-located]
Pod anti-affinity — separate
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: web
The Pod must not land on a node that already hosts a Pod
labelled app: web. Used for spreading replicas across
nodes for availability.
flowchart LR
A[Web replica] -->|podAntiAffinity:<br/>app=web| B[Node without web Pod]
A --> C[Node with web Pod]
C -.->|rejected| A
B --> D[Spread]
Topology key
The topologyKey field is a node label that defines the
placement domain:
| Topology key | Domain |
|---|---|
kubernetes.io/hostname | Same node |
topology.kubernetes.io/zone | Same zone |
topology.kubernetes.io/region | Same region |
topology.kubernetes.io/zone (legacy) | Same zone (legacy) |
For kubernetes.io/hostname, the affinity is “co-locate on
the same node.” For topology.kubernetes.io/zone, the
affinity is “co-locate in the same zone (any node in the
zone).”
flowchart LR
A[Pod with affinity] --> B{topologyKey}
B -->|hostname| C[Same node]
B -->|zone| D[Same zone]
B -->|region| E[Same region]
Required vs preferred
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: cache
Required: if no node satisfies, the Pod is unschedulable.
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: cache
Preferred: if no node satisfies, the Pod schedules anyway. The weight (1-100) is added to the score for nodes that do satisfy.
flowchart TB
A[Affinity type] -->|required| B["If no match: Pending"]
A -->|preferred| C["If no match: schedule anywhere"]
C --> D["Match: score += weight"]
Naming spaces
Pod affinity operates within the same namespace by default. The selector only matches Pods in the same namespace. For cross-namespace affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
namespaces: ["prod",,"staging"] # list of namespaces
labelSelector:
matchLabels:
app: cache
The namespaces field (an array) lists the namespaces to
consider. namespaces: [] (empty list with the
explicit-namespace form) means “all namespaces”; but the
default behaviour is to consider only the Pod’s own
namespace.
The performance cost
flowchart LR
A[Scheduler receives Pod] --> B[Filter phase]
B --> C[Affinity filter]
C --> D{For each node...}
D --> E{For each Pod on node...}
E --> F{Selector matches?}
F --> G{Constraint satisfied?}
Pod affinity requires the scheduler to walk every Pod on every candidate node, evaluating the selector. For large clusters (thousands of Pods per node), the cost is significant.
The scheduler caches Pods; the affinity evaluation is amortised. But the first scheduling of a Pod with broad affinity can be slow.
Production patterns
Pattern 1: co-locate with cache
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: cache
A web Pod that depends on a local cache. Co-location reduces network latency.
Pattern 2: spread replicas
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: web
A Deployment’s Pods spread across nodes. With
replicas: 6 and 6 nodes, each node gets one Pod.
Pattern 3: spread across zones
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: topology.kubernetes.io/zone
labelSelector:
matchLabels:
app: db
A database’s replicas spread across zones. A zone failure loses one replica; the others continue.
Failure modes
Failure 1: no node satisfies required
flowchart TB
A[Required affinity] --> B{Any node<br/>satisfies?}
B -->|no| C[Pod Pending]
B -->|yes| D[Scheduled]
The fix: change to preferred, add more nodes, or remove the constraint.
Failure 2: broad selector
A selector that matches every Pod requires walking every Pod. Performance degrades. The fix: narrow the selector.
Failure 3: cross-namespace without RBAC
A Pod with namespaces: ["kube-system"] requires the
scheduler to read Pods in kube-system. By default, the
scheduler has access; in some restricted setups, it does
not. The fix: verify the scheduler’s RBAC.
Quiz
Knowledge check · 4 questions
Q1. What does topologyKey define in Pod affinity?
Q2. Pod affinity operates across namespaces by default.
Q3. Your team uses Pod affinity with a selector matching all web Pods across 1000 nodes. Scheduling is slow. Diagnose.
Pod affinity with labelSelector app web. Cluster has 1000 nodes and 5000 web Pods. Scheduling each Pod takes seconds.
Q4. Explain the performance cost of Pod affinity and the patterns that mitigate it.
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Keep selectors tight. A selector matching one or two labels is fast; a selector matching all Pods is slow.
- Prefer hostname over zone. Hostname affinity is cheaper; zone affinity walks Pods across many nodes.
- Use required for hard separation. A Deployment’s replicas must spread across nodes; required anti-affinity enforces.
- Use preferred for soft preference. Co-location with a cache is preferred, not required; the workload runs anywhere.
- Audit the resulting placement. A dashboard that surfaces per-node Pod counts catches affinity failures (Pods clustered on one node).
Pod affinity and anti-affinity are the placement tools for relationships between Pods. Operators who understand the topologyKey and selector semantics have workloads that land predictably.