Skip to main content
RunBook Academy

KubernetesXXIII · nodeSelector and Node AffinityNode affinity

nodeSelector — the simplest node-placement mechanism

Advanced⏱ ~16 minkubectlkubeadm

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

Not yet marked complete on this device.

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:

LabelSet byValue
kubernetes.io/hostnamekubeletnode’s hostname
kubernetes.io/archkubeletamd64, arm64, etc.
kubernetes.io/oskubeletlinux, windows
topology.kubernetes.io/zonecloud providerus-east-1a, etc.
topology.kubernetes.io/regioncloud providerus-east-1, etc.
topology.kubernetes.io/zone (legacy)kubelet(same as above)
node.kubernetes.io/instance-typecloud providerm5.large, etc.
kubernetes.io/rolekubeadmcontrol-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:

  1. Equality only. It can match key=value or key!=value (with affinity, not nodeSelector). Set-based match (In, NotIn) is not supported.
  2. No preferred. nodeSelector is hard; if no node matches, the Pod is unschedulable. There is no “prefer but allow fallback.”
  3. 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

  1. Q1. What does nodeSelector match against?

  2. Q2. nodeSelector supports set-based operators (In, NotIn, Exists, DoesNotExist).

  3. 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.

  4. 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 nodeSelector should 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.