Skip to main content

Set affinity and anti-affinity

Inspect 1.36

Affinity is the more expressive cousin of the node selector. Node affinity places a pod relative to node labels, with both hard and soft rules; pod affinity places a pod relative to other pods, to pull them together or push them apart. Use it when a plain cannot say what you mean.

Anti-affinity and spread exist for one blunt reason: nodes get drained and replaced. If three replicas of a service all landed on the same host, a single node going away takes all three with it. So you spread them on purpose. One caveat before the recipes: all of these rules read IgnoredDuringExecution, meaning they are evaluated once, at scheduling time. A pod already running is never re-placed when labels change underneath it.

Node affinity: require or prefer#

Two forms, and the whole difference is what happens when nothing matches:

yaml
		# Hard rule: only bare-metal nodes, or stay Pending
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - matchExpressions:
              - key: instance.hetzner.cloud/is-root-server
                operator: In
                values: ["true"]
	
yaml
		# Soft rule: prefer a zone, run anywhere if it is full
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          preference:
            matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values: [hel1-dc2]
	

required... is a filter: no matching node means the pod sits Pending. preferred... is a nudge carrying a weight, so the scheduler tries to honor it but places the pod elsewhere rather than leave it stuck. matchExpressions accepts In, NotIn, Exists, DoesNotExist, Gt, and Lt.

These rules read the labels a node already carries. For which system labels you can select on and the empty-string role-label gotcha, see .

Pod affinity and anti-affinity#

Pod affinity places a pod relative to other pods that carry a label, pulling them together or pushing them apart:

yaml
		# Co-locate with a cache to cut latency (soft)
spec:
  affinity:
    podAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          podAffinityTerm:
            labelSelector:
              matchLabels: {app: cache}
            topologyKey: kubernetes.io/hostname
	
yaml
		# One replica per node (hard), for quorum members
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        - labelSelector:
            matchLabels: {app: my-database}
          topologyKey: kubernetes.io/hostname
	
Warning

Required host anti-affinity allows at most one matching pod per node. Ask for more replicas than you have nodes and the extras stay Pending forever.

Pod affinity rules are not free. The scheduler weighs each candidate pod against every already-placed pod, so heavy use across a large cluster gets expensive fast. Keep the selectors tight, and do not spray these rules on workloads that do not need them.

When required anti-affinity strands replicas#

The Pending behavior above is correct for a quorum-based database that must never put two members on one machine, and wrong for a stateless service you only wanted spread for availability.

For plain availability spread, prefer : they keep replicas balanced as the node count moves, without stranding pods when replicas briefly outnumber nodes. Reserve required host anti-affinity for the strict one-per-node case where a collision is genuinely unacceptable.