Node Affinity and Pod Affinity in Kubernetes: Complete Guide
Learn Kubernetes node affinity and pod affinity with YAML examples: required vs preferred rules, topologyKey, anti-affinity, HA and troubleshooting.
Kubernetes Node Affinity and Pod Affinity Explained
bitcodematrix.com | Kubernetes Series
1. Introduction
The nodeSelector, covered in the previous article, lets you say "run this Pod only on nodes with this label". That is enough for simple needs, but real clusters ask for more. You may want a Pod to prefer SSD nodes but still run elsewhere if none is free. You may want replicas of a web service spread across different nodes and zones so that one failure does not take them all down. You may want an application to run on the same node, or in the same zone, as its cache to cut network latency. Kubernetes answers these needs with affinity and anti-affinity.
This article explains node affinity (rules based on node labels), Pod affinity and Pod anti-affinity (rules based on where other Pods run), the difference between required and preferred rules, how the topologyKey field works, how to write each with YAML, how they compare with topology spread constraints, and how to troubleshoot Pods that stay Pending because of them.
After reading it you should be able to:
Write node affinity rules with required and preferred terms and the available operators.
Use Pod affinity to co-locate Pods and Pod anti-affinity to spread them.
Explain topologyKey, labelSelector and namespaces in Pod affinity terms.
Choose between nodeSelector, node affinity, Pod affinity, anti-affinity and topology spread.
Diagnose Pending Pods caused by affinity rules and avoid common pitfalls.
2. Overview of Affinity
Figure 1: Node affinity, Pod affinity and Pod anti-affinity
All affinity rules live under spec.affinity in the Pod specification (in the Pod template for Deployments and other controllers). There are three groups:
2.1 Required and preferred rules
Each type supports two kinds of rules, distinguished by long but descriptive field names.
The "IgnoredDuringExecution" part of the names means that the rules are checked only when the Pod is scheduled. If labels change later, or if the Pods that satisfied a Pod affinity rule leave, already running Pods are not evicted or moved. A stricter variant that would evict Pods is described in the Kubernetes design but is not available at the time of writing, so do not rely on it. Tools such as the descheduler can evict Pods that violate their rules so that they are placed again.
3. Node Affinity
Node affinity is a more expressive replacement for nodeSelector. It matches node labels with operators and supports preferences.
3.1 Structure
apiVersion: v1
kind: Pod
metadata:
name: affinity-demo
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
- key: spot
operator: DoesNotExist
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 80
preference:
matchExpressions:
- key: disktype
operator: In
values: ["ssd"]
- weight: 20
preference:
matchExpressions:
- key: topology.kubernetes.io/zone
operator: In
values: ["zone-a"]
containers:
- name: app
image: nginx:1.27
In this example the node must be amd64 or arm64 and must not have a label named spot. Among those nodes, the scheduler adds 80 points for disktype=ssd and 20 points for zone-a. Zone names are placeholders that depend on your provider.
3.2 How terms combine
Several matchExpressions inside one nodeSelectorTerm are combined with AND.
Several nodeSelectorTerms in the list are combined with OR: it is enough that one term matches.
Several preferred entries each add their weight when matched; the node with the highest total score wins, together with other scoring plugins.
If a Pod has both a nodeSelector and node affinity, a node must satisfy both.
3.3 Operators
The NotIn and DoesNotExist operators are the basis of node anti-affinity: you can keep Pods away from nodes with certain labels. There is also matchFields, which matches fields of the node object such as metadata.name, but labels are the usual and more portable choice.
4. Pod Affinity and Pod Anti-Affinity
Pod affinity rules are about relationships between Pods. Instead of asking what labels a node has, they ask which Pods are already running in the same topology domain as the node, and use the labels of those Pods.
4.1 The anatomy of a Pod affinity term
4.2 Understanding topologyKey
The topologyKey decides how broadly "together" is defined. It must be a label key present on the nodes.
Nodes that do not have the topologyKey label are not part of any domain and cannot satisfy the rule, which is a common source of unexpected Pending Pods.
4.3 Pod affinity: co-locate Pods
Use Pod affinity when two components benefit from being close, for example a web application and the cache it calls often.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
podAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: topology.kubernetes.io/zone
labelSelector:
matchLabels:
app: cache
containers:
- name: app
image: nginx:1.27
This asks the scheduler to prefer nodes in a zone where Pods labelled app=cache already run. Using a preferred rule keeps the workload schedulable if the cache is not yet present or its zone is full. A required Pod affinity rule would leave the Pods Pending until a matching Pod exists in a suitable domain. The scheduler has a special case that lets the very first Pod of a group be placed when the rule refers to its own labels, so that self-affinity does not deadlock.
4.4 Pod anti-affinity: spread Pods
Anti-affinity is most often used to keep replicas of one service apart, so that a node or zone failure does not remove all of them.
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: web
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: topology.kubernetes.io/zone
labelSelector:
matchLabels:
app: web
This example says: never place two app=web Pods on the same node (hard rule), and prefer to place them in different zones (soft rule). The hard rule has a consequence: if the Deployment has more replicas than there are eligible nodes, the extra replicas stay Pending. Use required anti-affinity only when it is truly necessary, and make sure that the cluster has enough nodes, or use the preferred form.
4.5 Namespaces
By default a Pod affinity term considers only Pods in the same namespace as the Pod being scheduled. To refer to Pods in other namespaces, list them in namespaces or select them with namespaceSelector. An empty namespaceSelector ({}) matches all namespaces.
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: cache
namespaceSelector:
matchLabels:
team: platform
4.6 Rolling updates and matchLabelKeys
During a rolling update, old and new Pods of a Deployment coexist and carry the same app label, so a required anti-affinity rule against app=web can make new Pods wait for old ones to disappear. Newer Kubernetes versions add matchLabelKeys and mismatchLabelKeys to Pod affinity terms, which let a rule consider the pod-template-hash so that each revision is handled separately. These fields are version dependent, so check the documentation for your release before using them.
5. Common Use Cases
6. Comparison with Other Placement Tools
Kubernetes documentation notes that inter-Pod affinity and anti-affinity require a significant amount of processing and are not recommended in clusters of several hundred nodes. For simply spreading replicas across zones and nodes, topology spread constraints are usually the better choice, as described in the article on resource management and scheduling. Use Pod anti-affinity when you need specific "never together" relationships between different workloads.
7. How the Scheduler Evaluates Affinity
Filtering: required node affinity, required Pod affinity and required Pod anti-affinity remove nodes that do not satisfy them, together with the checks for resources, taints and volumes.
Scoring: preferred rules add weighted scores to the nodes that satisfy them. The final choice combines these with other scoring plugins, such as resource balance and image locality.
Binding: the Pod is bound to the best node. From then on, the rules are not re-evaluated for this Pod.
Pod affinity rules are symmetric in one important way: when you schedule a new Pod, the scheduler also checks the required anti-affinity rules of Pods that are already running. If an existing Pod says "no app=web Pod in my zone", a new web Pod cannot enter that zone, even though the new Pod itself has no rule. This can surprise teams who look only at the manifest of the Pending Pod.
8. Interaction with Other Features
9. Hands-On Walkthrough
This exercise demonstrates anti-affinity spreading and the effect of a hard rule when there are not enough nodes. Use a test cluster with at least two nodes and no taints that block ordinary Pods. Replace the replica numbers if your cluster has a different node count.
Note the number of nodes.
Deploy a Deployment with soft anti-affinity and observe spreading.
Switch to a hard rule with more replicas than nodes and observe Pending Pods.
Read the scheduler event to see how the rule is reported.
kubectl get nodes
# 1. Soft anti-affinity: replicas spread across nodes where possible
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: spread-demo
spec:
replicas: 4
selector:
matchLabels:
app: spread-demo
template:
metadata:
labels:
app: spread-demo
spec:
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
topologyKey: kubernetes.io/hostname
labelSelector:
matchLabels:
app: spread-demo
containers:
- name: app
image: nginx:1.27
resources:
requests:
cpu: 50m
memory: 32Mi
EOF
kubectl get pods -l app=spread-demo -o wide
Expected result: the four Pods are distributed across the available nodes as evenly as the scores allow, and all of them run. Now make the rule hard:
# 2. Change to a required rule and scale beyond the node count
kubectl patch deployment spread-demo --type=json -p='[
{"op":"remove","path":"/spec/template/spec/affinity/podAntiAffinity/preferredDuringSchedulingIgnoredDuringExecution"},
{"op":"add","path":"/spec/template/spec/affinity/podAntiAffinity/requiredDuringSchedulingIgnoredDuringExecution","value":[{"topologyKey":"kubernetes.io/hostname","labelSelector":{"matchLabels":{"app":"spread-demo"}}}]}
]'
kubectl scale deployment spread-demo --replicas=10
kubectl get pods -l app=spread-demo
kubectl describe pod <pending-pod> | sed -n '/Events:/,$p'
# Clean up
kubectl delete deployment spread-demo
Expected result (illustrative): at most one Pod per node is Running and the remaining Pods are Pending with an event similar to "0/3 nodes are available: 3 node(s) didn't match pod anti-affinity rules". The exact wording varies by version. This shows why required anti-affinity needs enough nodes, or a preferred rule or a topology spread constraint instead.
10. Troubleshooting
# Useful diagnostics
kubectl get nodes --show-labels
kubectl get nodes -L topology.kubernetes.io/zone,kubernetes.io/hostname
kubectl get pods -A -l app=cache -o wide
kubectl describe pod <pending-pod> | sed -n '/Events:/,$p'
11. Best Practices
Start with the simplest tool that works: nodeSelector for one hard label, node affinity for preferences and alternatives.
Prefer soft (preferred) rules unless a hard rule is a real requirement, to avoid unschedulable Pods.
Use topology spread constraints rather than Pod anti-affinity for generic spreading across zones and nodes, especially in large clusters.
Choose the topologyKey carefully and make sure all nodes carry that label.
Use meaningful, consistent labels on Pods and nodes, and document them.
For required anti-affinity, make sure the number of eligible domains is at least the number of replicas, including during rolling updates.
Combine affinity with taints and tolerations when nodes must be reserved, since affinity alone does not keep other Pods away.
Remember the symmetry: existing Pods' required anti-affinity rules can block new Pods.
Test rules in staging with a realistic node count, and watch for Pending Pods after changes.
Keep the number and complexity of rules small; complex rule sets are hard to reason about and expensive to evaluate.
12. Conclusion
Node affinity and Pod affinity give Kubernetes users fine control over placement. Node affinity extends the nodeSelector with operators, alternatives and weighted preferences, all based on node labels. Pod affinity and anti-affinity add relationships between workloads, using topologyKey to define whether "together" means the same node, zone or any other domain you label. The key decisions are simple: choose required rules only when a placement is mandatory, prefer soft rules for everything else, make sure there is enough capacity in the domains you require, and switch to topology spread constraints when your real goal is even distribution. With these guidelines, affinity rules improve performance and availability without turning into a source of Pending Pods.