nodeSelector, Node Affinity & Anti-Affinity
Summary
- Definition: These fields steer Pods toward or away from nodes (node affinity) and toward or away from other Pods (inter-pod affinity and anti-affinity).
- Insight:
nodeSelectoris the blunt tool; node affinity is its expressive superset; pod affinity/anti-affinity reason about where other Pods already run. - Key Concepts:
nodeSelector,requiredDuringSchedulingIgnoredDuringExecution,preferredDuringSchedulingIgnoredDuringExecution,topologyKey, andmatchExpressions. - When to Use: pin workloads to hardware pools, co-locate chatty services, or spread replicas so one node loss does not take the service down.
- Limitations: required rules can make Pods unschedulable, and pod anti-affinity is computationally expensive at large scale.
Recipe
Choose the weakest tool that expresses your intent.
Use nodeSelector for a simple exact-label match.
Use node affinity when you need operators (In, NotIn, Exists), multiple values, or soft preferences.
Use pod anti-affinity to keep replicas of the same app on different nodes or zones.
Prefer preferred... (soft) rules for availability, and reserve required... (hard) rules for genuine constraints like CPU architecture.
Working Example
This Deployment prefers the general pool, requires amd64, and spreads its replicas across nodes with soft anti-affinity.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/arch
operator: In
values: ["amd64"]
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 70
preference:
matchExpressions:
- key: node.acme.io/pool
operator: In
values: ["general"]
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: web
topologyKey: kubernetes.io/hostname
containers:
- name: web
image: ghcr.io/acme/web:2.1.0
resources:
requests:
cpu: "200m"
memory: "256Mi"Apply and observe the spread across nodes.
kubectl apply -f web.yaml
kubectl get pods -l app=web -o wideDeep Dive
nodeSelector versus node affinity
nodeSelector is a flat map of label key/value pairs; every pair must match, and only equality is supported.
Node affinity replaces it with nodeSelectorTerms containing matchExpressions, which add operators and value lists.
Within one term, all matchExpressions must match (logical AND).
Across multiple nodeSelectorTerms, any one matching is enough (logical OR).
required versus preferred
requiredDuringSchedulingIgnoredDuringExecution is a hard filter; a node that fails it is infeasible.
preferredDuringSchedulingIgnoredDuringExecution adds score through a weight from 1 to 100, but never blocks scheduling.
The IgnoredDuringExecution suffix means the rule is checked only at scheduling time; if node labels change later, a running Pod is not evicted.
topologyKey is the heart of pod affinity
Pod affinity and anti-affinity do not name nodes; they name a topology domain through topologyKey.
kubernetes.io/hostname treats each node as a domain, so anti-affinity spreads Pods across nodes.
topology.kubernetes.io/zone treats each zone as a domain, so anti-affinity spreads Pods across zones.
The labelSelector inside the term picks which Pods the rule reasons about, usually the app's own label.
matchLabels and matchExpressions
Selectors accept matchLabels (equality map) and matchExpressions (operator-based), and both may appear together.
For node affinity the numeric operators Gt and Lt also exist, comparing single integer label values.
Namespace scope of pod affinity
Pod affinity and anti-affinity reason about other Pods, so namespace scope matters.
By default a term matches Pods in the same namespace as the Pod being scheduled.
The namespaces field lists explicit namespaces to consider instead.
The namespaceSelector field selects namespaces by label, and an empty selector ({}) means all namespaces.
This lets a shared platform team, for example, keep one tenant's Pods away from another's across namespace boundaries.
matchLabelKeys for rollouts
Both pod affinity and topology spread accept matchLabelKeys, which narrows the set of counted Pods to those sharing the given label values as the incoming Pod.
Adding pod-template-hash keeps a rolling update's old and new ReplicaSets from being counted together, so anti-affinity does not fight itself mid-rollout.
Gotchas
Required pod anti-affinity with topologyKey: kubernetes.io/hostname caps replicas at the node count; a 4th replica on a 3-node cluster stays Pending.
The fix is to use preferred anti-affinity, or move to topology spread constraints which express skew more precisely.
Node affinity uses requests, not labels, for capacity, so a matching node can still fail on Insufficient cpu; read the events.
Pod anti-affinity is evaluated against all matching Pods cluster-wide and is expensive; avoid it on very large, high-churn deployments and prefer topology spread.
An empty topologyKey is invalid; every affinity term must set one.
Changing labels on a node does not reschedule already-running Pods, because these rules are IgnoredDuringExecution.
Alternatives
Topology spread constraints are usually the better tool for even distribution; they express maxSkew directly and scale better than anti-affinity.
Taints and tolerations are the right choice when you want to reserve nodes and repel Pods by default, rather than attract Pods that opt in.
nodeName hard-pins a Pod to one node but skips all scheduler checks, so use it only for debugging.
Pick node affinity when you must match node attributes, and pod affinity/anti-affinity when placement depends on where other Pods already run.
FAQs
Is nodeSelector deprecated? No. It is still supported and is the simplest option for a single exact-match label. Node affinity is just more expressive.
Can I combine nodeSelector and node affinity? Yes. Both are applied, and both must be satisfied for a node to be feasible.
Why is my 4th replica Pending? Likely required pod anti-affinity on hostname with only 3 nodes. Switch to preferred anti-affinity or topology spread.
Does anti-affinity work across zones? Yes, set topologyKey: topology.kubernetes.io/zone so each zone is one domain.
What weight should preferred rules use? Any value 1 to 100; the number only matters relative to other preferred terms competing for the same Pod.
Related
- Scheduling Basics
- How the Scheduler Decides
- Taints & Tolerations
- Topology Spread Constraints
- Scheduling Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).