KubernetesXXIII · nodeSelector and Node AffinityNode affinity
nodeSelector — the simplest node-placement mechanism
What you'll learn
- Configure nodeSelector with key:value pairs
- List well-known node labels (kubernetes.io/hostname, topology.kubernetes.io/zone)
- Identify the limitations that lead to Node Affinity
- Apply nodeSelector to a real production manifest
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
nodeSelector is the simplest node-placement mechanism in
Kubernetes: a Pod specifies one or more label key:value
pairs; the scheduler places the Pod on a node with all of
those labels. It is widely understood, easy to reason about,
but limited to equality. This lesson covers the syntax, the
well-known labels, and the migration path to Node Affinity
for more expressive rules.
What nodeSelector does
spec:
nodeSelector:
disk: ssd
tier: production
The scheduler’s filter phase checks each node’s labels
against the Pod’s nodeSelector. A node with both labels
disk: ssd and tier: production survives; a node with
only one of them is eliminated.
flowchart LR
A["Pod with<br/>nodeSelector"] --> B[Filter phase]
B --> C{Node has<br/>all labels?}
C -->|yes| D[Survives]
C -->|no| E[Eliminated]
D --> F[Score phase]
E --> G[Not scheduled here]
Inspecting node labels
kubectl get nodes --show-labels
# NAME STATUS ROLES LABELS
# node-01 Ready worker beta.kubernetes.io/arch=amd64,kubernetes.io/hostname=node-01,disk=ssd
# node-02 Ready worker beta.kubernetes.io/arch=amd64,kubernetes.io/hostname=node-02,disk=hdd
# node-03 Ready worker beta.kubernetes.io/arch=arm64,kubernetes.io/hostname=node-03,disk=ssd
The kubernetes.io/hostname label is automatically set on
every node. Other labels are set by the kubelet
(beta.kubernetes.io/arch, beta.kubernetes.io/os) or by
the operator (custom labels).
Adding labels to a node
kubectl label node node-01 disk=ssd
kubectl label node node-01 tier=production --overwrite
The label is stored on the Node object. The change is visible to the scheduler immediately (the kubelet watches Node updates).
flowchart LR
A[kubectl label] --> B[Node object updated]
B --> C["Scheduler watches<br/>node events"]
C --> D[Cache updates]
D --> E["Next scheduling<br/>uses new labels"]
Well-known labels
Kubernetes reserves a kubernetes.io and
k8s.io prefix for labels set by the system:
| Label | Set by | Value |
|---|---|---|
kubernetes.io/hostname | kubelet | node’s hostname |
kubernetes.io/arch | kubelet | amd64, arm64, etc. |
kubernetes.io/os | kubelet | linux, windows |
topology.kubernetes.io/zone | cloud provider | us-east-1a, etc. |
topology.kubernetes.io/region | cloud provider | us-east-1, etc. |
topology.kubernetes.io/zone (legacy) | kubelet | (same as above) |
node.kubernetes.io/instance-type | cloud provider | m5.large, etc. |
kubernetes.io/role | kubeadm | control-plane, worker |
The full list is documented in the Kubernetes reference under “Well-Known Labels, Annotations and Taints.”
The Pod spec
spec:
nodeSelector:
kubernetes.io/os: linux
disk: ssd
The Pod must match all keys and values. If a node has
disk=ssd but no kubernetes.io/os label, the Pod is
eliminated (the label exists implicitly as linux only if
the kubelet set it).
Limitations
nodeSelector has three limitations:
- Equality only. It can match
key=valueorkey!=value(withaffinity, notnodeSelector). Set-based match (In,NotIn) is not supported. - No preferred. nodeSelector is hard; if no node matches, the Pod is unschedulable. There is no “prefer but allow fallback.”
- No composition. Multiple selectors are ANDed but not ORed. There is no way to say “either zone us-east-1a or us-east-1b.”
flowchart TB
A[nodeSelector features] --> B[Equality only]
A --> C[Hard constraint]
A --> D[AND composition]
B --> E["Limitation:<br/>no set-based match"]
C --> F["Limitation:<br/>no preferred"]
D --> G["Limitation:<br/>no OR composition"]
For “either zone us-east-1a or us-east-1b,” use Node
Affinity with matchExpressions.
Migration to Node Affinity
flowchart LR
A[nodeSelector] --> B["Node Affinity<br/>required"]
A --> C["Node Affinity<br/>preferred"]
B --> D["Hard: must satisfy"]
C --> E["Soft: prefer but allow fallback"]
# nodeSelector
spec:
nodeSelector:
disk: ssd
# Equivalent Node Affinity (required)
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: disk
operator: In
values: ["ssd"]
Node Affinity supports set-based operators (In, NotIn,
Exists, DoesNotExist, Gt, Lt) and preferred
variants. The migration is mechanical.
Production patterns
Pattern 1: SSD-only
spec:
nodeSelector:
disk: ssd
Database workloads on SSD nodes; reporting on HDD is fine.
Pattern 2: zone pinning
spec:
nodeSelector:
topology.kubernetes.io/zone: us-east-1a
A workload that must stay in a specific zone for latency or compliance.
Pattern 3: instance type
spec:
nodeSelector:
node.kubernetes.io/instance-type: m5.4xlarge
A workload that needs a specific instance type (large memory, GPU, etc.).
Pattern 4: arch
spec:
nodeSelector:
kubernetes.io/arch: arm64
A workload built for ARM (the image must also be ARM); the scheduler only considers ARM nodes.
Common mistakes
Mistake 1: typo in the label
spec:
nodeSelector:
dissk: ssd # typo
The Pod never schedules; the events show “didn’t match Pod’s node selector.” Fix the typo.
Mistake 2: label not on any node
spec:
nodeSelector:
disk: nvme # no nodes have this label
The Pod never schedules; add the label or remove the selector.
Mistake 3: nodeSelector on a Deployment with multiple labels
spec:
nodeSelector:
disk: ssd
tier: production
arch: amd64
Three labels must match. If the cluster has nodes that match two of three, the Pod is unschedulable. Reduce the selector or add the missing label.
Quiz
Knowledge check · 4 questions
Q1. What does nodeSelector match against?
Q2. nodeSelector supports set-based operators (In, NotIn, Exists, DoesNotExist).
Q3. Your Pod has nodeSelector disk ssd but no nodes have the disk ssd label. Diagnose.
Cluster has nodes with various labels. No node has disk ssd. The Pod is Pending.
Q4. Name the standard node labels Kubernetes and cloud providers set automatically.
Passing score: 75%. Answers are checked in this browser.
Production discipline
- nodeSelector is the simplest tool. Reach for it first; move to Node Affinity when set-based match or preferred is needed.
- Document the labels the cluster uses. Custom labels
(
disk,tier) must be added by an operator or automation; verify they exist. - Audit nodeSelector usage. A Pod with a nodeSelector that matches no nodes is a Pending Pod; an alert catches it.
- Validate in CI. A Pod’s
nodeSelectorshould be checked against the cluster’s node labels before applying. - Consider the operator pattern. Labels are declarative; an operator (e.g., the cloud provider’s node controller) sets them automatically. Custom labels need automation.
nodeSelector is a foundational Kubernetes concept. Operators who understand its limitations reach for Node Affinity when the workload needs more.