Node affinity is the expressive successor to nodeSelector. While nodeSelector uses simple key=value label matching, node affinity supports operators (In, NotIn, Exists, DoesNotExist, Gt, Lt), soft preferences with weights, and the distinction between hard requirements and soft preferences.

1. nodeSelector vs Node Affinity

# nodeSelector — simple, limited:
spec:
  nodeSelector:
    disktype: ssd
    zone: us-east-1a
# Pod MUST go to a node with BOTH labels. No "or", no "prefer", no operators.
# Node affinity — powerful, expressive:
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution: ...   # Hard
      preferredDuringSchedulingIgnoredDuringExecution: ...  # Soft
FeaturenodeSelectorNode Affinity
OperatorsOnly exact match (=)In, NotIn, Exists, DoesNotExist, Gt, Lt
Hard/SoftHard only (must match)Both hard (required) and soft (preferred)
WeightNoYes — weighted scoring for soft rules
OR logicNo (all must match)Yes (multiple nodeSelectorTerms = OR)
Use caseSimple constraintsComplex placement logic

2. Required Node Affinity (Hard)

requiredDuringSchedulingIgnoredDuringExecution — the Pod must schedule on a matching node, or stays Pending.

spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:                # OR between terms
          - matchExpressions:             # AND between expressions in same term
              - key: topology.kubernetes.io/zone
                operator: In
                values:
                  - us-east-1a
                  - us-east-1b
              - key: node.kubernetes.io/instance-type
                operator: In
                values:
                  - m5.xlarge
                  - m5.2xlarge

Logic Rules

  • Multiple nodeSelectorTerms → OR (match any one term)
  • Multiple matchExpressions within one term → AND (all must match)
# This means: Node must be in (us-east-1a OR us-east-1b) AND (m5.xlarge OR m5.2xlarge)
# If you want "zone a with m5" OR "zone c with c5", use two separate nodeSelectorTerms

Available Operators

OperatorMeaningExample
InLabel value is in the listzone In [us-east-1a, us-east-1b]
NotInLabel value is NOT in the listinstance-type NotIn [t3.micro] (avoid small)
ExistsLabel key exists (any value)gpu Exists (has a GPU label)
DoesNotExistLabel key doesn't existdedicated DoesNotExist (not a dedicated node)
GtLabel value > given integergpu-count Gt 2
LtLabel value < given integerage Lt 30
NotIn is how you express anti-affinity to nodes. There's no separate "node anti-affinity" resource — you use NotIn or DoesNotExist operators within node affinity to repel Pods from certain nodes. Example: operator: NotIn, values: [spot] keeps the Pod off spot instances.

3. Preferred Node Affinity (Soft)

preferredDuringSchedulingIgnoredDuringExecution — the scheduler tries to match, but schedules elsewhere if no matching node is available. Uses weights to express preference strength.

spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 80                     # 1-100: higher = stronger preference
          preference:
            matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values: ["us-east-1a"]    # Strongly prefer zone a
        - weight: 20
          preference:
            matchExpressions:
              - key: node.kubernetes.io/instance-type
                operator: In
                values: ["m5.xlarge"]     # Weakly prefer m5.xlarge

How Weights Work

# The scheduler scores each node:
# For each preference that matches → add its weight to the node's score
#
# Node A: zone=us-east-1a, type=m5.xlarge → score += 80 + 20 = 100
# Node B: zone=us-east-1a, type=c5.xlarge → score += 80 + 0  = 80
# Node C: zone=us-east-1b, type=m5.xlarge → score += 0  + 20 = 20
# Node D: zone=us-east-1b, type=c5.xlarge → score += 0  + 0  = 0
#
# Scheduler picks Node A (highest score)
# But if Node A is full, it can still use B, C, or D (soft, not hard)
Weights let you express priorities. "I really want zone A (weight 80) and slightly prefer m5 instances (weight 20)." The scheduler optimizes for the highest total score. If constraints conflict, higher weights win. This enables nuanced placement without creating unschedulable Pods.

Combining Required + Preferred

spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:     # MUST
        nodeSelectorTerms:
          - matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values: [us-east-1a, us-east-1b, us-east-1c]  # Must be in these zones
      preferredDuringSchedulingIgnoredDuringExecution:    # PREFER
        - weight: 70
          preference:
            matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values: [us-east-1a]     # Prefer zone a among the allowed zones
# Result: Must be in zone a/b/c. Strongly prefers a. Falls back to b/c if a is full.
This combined pattern is the most common in production: use required to enforce hard constraints (correct region, correct instance family) and preferred to optimize within those bounds (prefer the cheaper zone, prefer nodes with image cache). Pods always schedule (within the hard constraints) but land in the best spot when possible.

4. "IgnoredDuringExecution" — What It Means

The full name requiredDuringSchedulingIgnoredDuringExecution means:

  • During scheduling: The rule is enforced (required or preferred)
  • During execution: If node labels change after scheduling, the Pod stays — it's NOT evicted
# Example: Pod requires zone=us-east-1a
# Pod schedules on node in us-east-1a ✓
# Admin removes the zone label from the node
# Pod STAYS — the rule is only checked at scheduling time

A future K8s feature (requiredDuringSchedulingRequiredDuringExecution) would evict Pods when labels change — but it doesn't exist yet.

5. Common Node Labels for Affinity

LabelExample ValueUse Case
topology.kubernetes.io/zoneus-east-1aZone placement, HA distribution
topology.kubernetes.io/regionus-east-1Regional constraints
node.kubernetes.io/instance-typem5.2xlargeInstance family selection
kubernetes.io/archamd64, arm64Architecture-specific images
kubernetes.io/oslinux, windowsOS-specific workloads
node.kubernetes.io/disk-typessd, hddStorage-sensitive workloads
Custom: teamplatformDedicated team nodes
Custom: gpunvidia-a100ML workloads
# See all labels on a node:
kubectl get node worker-1 --show-labels

# Add a custom label:
kubectl label node worker-3 gpu=nvidia-a100
CKA exam: the most commonly tested labels are topology.kubernetes.io/zone for zone-aware scheduling and custom labels for nodeSelector/affinity. Know how to label nodes (kubectl label node) and write both required and preferred affinity rules. The YAML is verbose — practice until you can write it from memory.

6. nodeSelector vs Affinity — When to Use Which

ScenarioUseWhy
Simple: "must be on SSD nodes"nodeSelector: {disktype: ssd}Cleaner, shorter YAML
"Must be in zone a OR b" (not c)Required affinity with InnodeSelector can't do OR
"Prefer zone a, fall back to others"Preferred affinity with weightnodeSelector is hard only
"Must NOT be on spot instances"Required affinity with NotInnodeSelector can't negate
"Prefer large instances, accept small"Preferred affinitySoft preference impossible with nodeSelector

Summary

ConceptKey Point
nodeSelectorSimple label matching (AND logic, exact match only)
Required affinityMust match — Pod stays Pending if no node qualifies
Preferred affinityBest-effort — scheduler scores nodes, picks highest weight match
Weights (1-100)Higher = stronger preference. Scores are summed across preferences.
nodeSelectorTermsMultiple terms = OR logic
matchExpressionsMultiple in same term = AND logic
OperatorsIn, NotIn, Exists, DoesNotExist, Gt, Lt
IgnoredDuringExecutionRules only enforced at scheduling time — label changes don't evict
NotIn = anti-affinityNo separate "node anti-affinity" — use NotIn/DoesNotExist operators

📝 Quiz: Node Affinity

Q1: A Pod has requiredDuringSchedulingIgnoredDuringExecution with zone In [us-east-1a]. No node has that zone label. What happens?

Pod stays Pending. Required affinity is a hard constraint. If no node matches, the Pod cannot be scheduled. It remains Pending with an event: "0/N nodes are available: N node(s) didn't match Pod's node affinity/selector."

Q2: A Pod has preferred affinity: weight 90 for zone=a, weight 10 for type=m5. Node X is in zone a with type c5. Node Y is in zone b with type m5. Which is chosen?

Node X (score 90). Node X matches zone=a (weight 90) but not type=m5 (score: 90+0=90). Node Y matches type=m5 (weight 10) but not zone=a (score: 0+10=10). The scheduler picks the highest total score.

Q3: What's the difference between two matchExpressions in one nodeSelectorTerm vs two separate nodeSelectorTerms?

Same term (AND): The node must match ALL expressions. E.g., zone=us-east-1a AND type=m5.xlarge.
Separate terms (OR): The node must match ANY one complete term. E.g., (zone=us-east-1a AND type=m5) OR (zone=us-west-2a AND type=c5).

Q4: How do you express "don't schedule on spot instances" using node affinity?

Use NotIn or DoesNotExist operator:
requiredDuringSchedulingIgnoredDuringExecution:
  nodeSelectorTerms:
    - matchExpressions:
        - key: node.kubernetes.io/lifecycle
          operator: NotIn
          values: ["spot"]
Or if spot nodes have a label key spot:
operator: DoesNotExist, key: spot

Q5: A Pod is running on a node labeled zone=us-east-1a. The admin removes this label. The Pod has required affinity for zone In [us-east-1a]. Is the Pod evicted?

No. The full name is requiredDuringSchedulingIgnoredDuringExecution. Once the Pod is running, affinity rules are not re-evaluated. The Pod stays on the node even if labels change. Only NoExecute taints can evict running Pods based on changed conditions.

Q6: When should you use nodeSelector instead of node affinity?

When your constraint is simple exact-match AND logic: "this Pod must go to a node with label X=Y." nodeSelector is shorter, easier to read, and less error-prone for simple cases. Use node affinity when you need: OR logic, NotIn/Exists operators, soft preferences with weights, or multiple terms with different logic.