Node affinity controls which nodes a Pod lands on. Pod affinity controls whether a Pod should be co-located with or separated from other Pods. It answers: "Place me near Pods running X" (affinity) or "Keep me away from Pods running Y" (anti-affinity).
1. The Topology Key Concept
Pod affinity doesn't just say "near another Pod" — it defines what "near" means using a topology key. The topology key is a node label that groups nodes into domains.
Common Topology Keys
| topologyKey | "Same" Means | Use Case |
|---|---|---|
kubernetes.io/hostname | Same node | Co-locate on exact same machine (shared memory, local comms) |
topology.kubernetes.io/zone | Same availability zone | Reduce cross-zone latency/cost |
topology.kubernetes.io/region | Same region | Data sovereignty, compliance |
•
hostname = "co-locate on the exact same node" (tightest)•
zone = "co-locate in the same zone" (medium — nodes in same AZ)•
region = "co-locate in the same region" (loosest)Anti-affinity works the same: "keep apart per-node" vs "keep apart per-zone."
2. Pod Affinity — "Schedule Near"
Required (Hard)
# "I MUST be in the same zone as a Pod labeled app=cache"
spec:
affinity:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["cache"]
topologyKey: topology.kubernetes.io/zone
Preferred (Soft)
# "I'd LIKE to be on the same node as a Pod labeled app=cache"
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values: ["cache"]
topologyKey: kubernetes.io/hostname
Use Cases for Pod Affinity
| Scenario | topologyKey | Rationale |
|---|---|---|
| Web server near its Redis cache | zone | Reduce cache latency (same AZ) |
| App Pod near its database | zone | Reduce cross-AZ data transfer cost |
| Sidecar-style co-location | hostname | Must be on same node (shared volume, localhost comms) |
| ML training near data | hostname | GPU + data locality for performance |
zone topology to colocate services that communicate frequently. In AWS/GCP, cross-AZ traffic costs ~$0.01/GB. A web service making 1000 req/s to a cache in another AZ generates significant cost. Pod affinity with zone topology eliminates this.
3. Pod Anti-Affinity — "Schedule Away From"
Required — Spread Across Zones
# "I MUST NOT be in the same zone as another Pod with app=web"
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values: ["web"]
topologyKey: topology.kubernetes.io/zone
# If you have 3 replicas and 3 zones, each replica lands in a different zone
# If you have 4 replicas and 3 zones → 4th Pod is UNSCHEDULABLE (Pending!)
Required — One Per Node
# "Only one instance of me per node" (like a DaemonSet but with replicas control)
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname
# Ensures at most 1 "app=web" Pod per node
# If replicas > nodes → extra Pods stay Pending
Preferred — Spread Across Nodes (Best Effort)
# "Try to spread across nodes, but don't fail if you can't"
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname
# Scheduler spreads Pods across nodes but will colocate if not enough nodes
preferred to degrade gracefully instead of failing.
Common Anti-Affinity Patterns
| Pattern | topologyKey | Required/Preferred | Why |
|---|---|---|---|
| HA across zones | zone | Required (if replicas ≤ zones) | Survive zone failure |
| Spread across nodes | hostname | Preferred (safe) | Survive node failure |
| Separate competing workloads | hostname | Required | Prevent resource contention |
| Keep replicas of same DB apart | zone | Required | Prevent correlated failure |
zone topology for databases/stateful apps (must survive zone failure) + preferred anti-affinity with hostname for stateless services (spread across nodes but don't block scheduling if nodes are limited).
4. Scalability Warning
Pod affinity/anti-affinity is expensive for the scheduler. For every Pod being scheduled, the scheduler must check ALL other Pods in the cluster (or namespace) to see which topology domains contain matching Pods.
# Complexity: O(Pods × Nodes) # With 1000 Pods and 100 nodes, the scheduler evaluates 100,000 combinations # This is why large clusters see scheduling latency increase with pod affinity
Mitigation Strategies
- Use
namespacesfield — restrict the search to specific namespaces (instead of all) - Use
namespaceSelector— limit which namespaces are checked - Prefer Topology Spread Constraints — lighter-weight for spreading (next lesson)
- Use preferred over required — fails gracefully instead of blocking
# Restrict affinity search to specific namespaces:
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: cache
topologyKey: topology.kubernetes.io/zone
namespaces: ["caching"] # Only look at Pods in this namespace
# OR:
namespaceSelector:
matchLabels:
team: platform # Only namespaces with this label
5. Pod Affinity vs Topology Spread Constraints
| Feature | Pod Anti-Affinity | Topology Spread Constraints |
|---|---|---|
| Goal | Binary: "not on same topology domain" | Even distribution: "max skew of N across domains" |
| Scheduler cost | High (checks all Pods) | Lower (built-in optimization) |
| Flexibility | All-or-nothing (required) or best-effort (preferred) | Tunable maxSkew (allow some imbalance) |
| Use case | "Exactly 1 per zone" / "never colocate with X" | "Spread evenly across zones (some imbalance OK)" |
| Recommended for | Small deployments, strict requirements | Large deployments, general HA spreading |
Summary
| Concept | Key Point |
|---|---|
| Pod Affinity | "Schedule near Pods matching label X" (attract) |
| Pod Anti-Affinity | "Schedule away from Pods matching label X" (repel) |
| topologyKey | Defines "same domain": hostname (node), zone, region |
| Required | Hard — Pod stays Pending if no valid domain. Risk of unschedulable. |
| Preferred | Soft — scheduler optimizes but doesn't block |
| Anti-affinity to self | Common pattern: use own labels to spread replicas across zones/nodes |
| Scalability | Expensive (O(Pods×Nodes)). Use namespaces field to limit scope. |
| vs Topology Spread | Spread Constraints are lighter-weight for "spread evenly" patterns |
📝 Quiz: Pod Affinity & Anti-Affinity
Q1: A Deployment has 3 replicas with required pod anti-affinity (topologyKey: zone). The cluster has 2 zones. What happens?
Q2: What's the difference between topologyKey: kubernetes.io/hostname and topologyKey: topology.kubernetes.io/zone in anti-affinity?
zone: "Don't place two matching Pods in the same zone." This is stricter — even if there are many nodes in a zone, only one matching Pod is allowed per zone.
Q3: You want a web Pod to be in the same zone as its Redis cache. How do you configure this?
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchLabels:
app: redis
topologyKey: topology.kubernetes.io/zoneThe web Pod will only schedule in a zone that already has a Pod labeled app=redis.Q4: Why is Pod affinity/anti-affinity computationally expensive for the scheduler?
Q5: A Pod has anti-affinity against itself (labelSelector matches its own labels). It's already running on node-1. A second replica is being scheduled. Can it go on node-1?
•
hostname: No — the existing Pod is on node-1, so anti-affinity blocks the new Pod from node-1.•
zone: No — if node-1 is in zone-a, the new Pod can't go to ANY node in zone-a (including node-1 and other nodes in that zone).Q6: When should you use Pod anti-affinity vs Topology Spread Constraints?
Topology Spread: When you want "spread my replicas evenly" (e.g., 10 replicas across 3 zones with max 1 difference). Better for large-scale spreading, lower scheduler cost, and allows tunable imbalance (maxSkew).