🎉 DevOps Interview Prep Bundle is live — 1000+ Q&A across 20 topicsGet it →
All Articles

What is Node Affinity in Kubernetes? Pod Scheduling Explained Simply

Node affinity lets you control which nodes your pods run on. Here's a plain-English explanation of nodeSelector, nodeAffinity, podAffinity, and taints/tolerations — when to use each.

DevOpsBoysMay 15, 20264 min read
Share:Tweet

By default, Kubernetes schedules pods on any available node. Sometimes that's not what you want. Your GPU workloads should run on GPU nodes. Your database shouldn't share a node with untrusted workloads.

Node affinity is how you control this.


The Simple Version First: nodeSelector

The simplest way to pin a pod to specific nodes — requires a matching label on the node.

yaml
# Step 1: Label your node
kubectl label node node-1 disk=ssd
 
# Step 2: Tell your pod to only run on SSD nodes
apiVersion: v1
kind: Pod
spec:
  nodeSelector:
    disk: ssd
  containers:
    - name: my-app
      image: my-app:latest

If no node has disk=ssd, the pod stays Pending. Simple and works well for most cases.


Node Affinity — More Expressive Rules

Node affinity gives you more control than nodeSelector. You can define required rules and preferred rules.

Required (Hard Constraint)

Pod will NOT schedule unless this is satisfied:

yaml
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: kubernetes.io/arch
                operator: In
                values:
                  - amd64
                  - arm64

This pod only runs on amd64 or arm64 nodes.

Preferred (Soft Constraint)

Kubernetes tries to place pod here, but places it elsewhere if no match:

yaml
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 80        # Higher weight = stronger preference
          preference:
            matchExpressions:
              - key: node-type
                operator: In
                values:
                  - spot
        - weight: 20
          preference:
            matchExpressions:
              - key: node-type
                operator: In
                values:
                  - on-demand

This pod prefers spot instances but falls back to on-demand if no spot is available.


Operators in Node Affinity

OperatorMeaningExample
InValue in listzone In [us-east-1a, us-east-1b]
NotInValue NOT in listenv NotIn [test, dev]
ExistsLabel exists (any value)gpu Exists
DoesNotExistLabel absentspot DoesNotExist
GtGreater thancpus Gt 4
LtLess thanmemory Lt 16

Real-World Examples

Run GPU workloads on GPU nodes only

yaml
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: nvidia.com/gpu
                operator: Exists
  containers:
    - name: ml-training
      image: pytorch:latest
      resources:
        limits:
          nvidia.com/gpu: "1"

Prefer spot instances, tolerate on-demand

yaml
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          preference:
            matchExpressions:
              - key: eks.amazonaws.com/capacityType
                operator: In
                values: [SPOT]

Spread across availability zones

yaml
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values:
                  - ap-south-1a
                  - ap-south-1b
                  - ap-south-1c

Pod Affinity and Anti-Affinity

Node affinity = "schedule on this type of node."
Pod affinity = "schedule near/away from this type of pod."

Co-locate pods on the same node (affinity)

Cache pods should be on the same node as app pods (lower latency):

yaml
spec:
  affinity:
    podAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          podAffinityTerm:
            labelSelector:
              matchLabels:
                app: redis-cache
            topologyKey: kubernetes.io/hostname   # "same node"

Spread pods across nodes (anti-affinity)

Don't run two replicas of the same app on the same node:

yaml
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels:
              app: my-api
          topologyKey: kubernetes.io/hostname   # Different node
 
# Or spread across AZs:
        - labelSelector:
            matchLabels:
              app: my-api
          topologyKey: topology.kubernetes.io/zone   # Different AZ

Taints and Tolerations (The Opposite Direction)

Node affinity is "pods choosing nodes." Taints/tolerations is "nodes repelling pods."

bash
# Taint a node — only pods that tolerate this can schedule here
kubectl taint nodes node-1 gpu=true:NoSchedule
 
# Remove taint
kubectl taint nodes node-1 gpu=true:NoSchedule-
yaml
# Pod must have this toleration to schedule on tainted node
spec:
  tolerations:
    - key: gpu
      operator: Equal
      value: "true"
      effect: NoSchedule

Taint effects:

  • NoSchedule — New pods without toleration won't schedule here
  • PreferNoSchedule — Soft version — tries to avoid, but schedules if necessary
  • NoExecute — Existing pods without toleration are evicted

When to Use What

ScenarioSolution
Run only on SSD nodesnodeSelector or nodeAffinity
Prefer spot instancesnodeAffinity preferred
Dedicated GPU nodesTaint GPU nodes + toleration in pod
HA across AZspodAntiAffinity with zone topologyKey
Cache near apppodAffinity with hostname topologyKey
Isolate untrusted workloadsTaint + toleration

Topology Spread Constraints (Modern Alternative)

For spreading pods across nodes/zones without complex affinity rules:

yaml
spec:
  topologySpreadConstraints:
    - maxSkew: 1                    # Max difference between zones
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: DoNotSchedule
      labelSelector:
        matchLabels:
          app: my-api

This is the recommended modern way to spread pods across AZs — simpler than pod anti-affinity.


Node affinity sounds complex but boils down to: "I want this pod to run on nodes that have these labels." Once you understand that, the rest is just syntax.

For CKA exam prep and Kubernetes scheduling deep dives, KodeKloud has hands-on labs covering affinity, taints, tolerations, and topology spread constraints.

Newsletter

Stay ahead of the curve

Get the latest DevOps, Kubernetes, AWS, and AI/ML guides delivered straight to your inbox. No spam — just practical engineering content.

Related Articles

Comments