Scheduling: Affinity, Taints & Topology Spread

Control where Pods land so a single node or zone failure can't take your whole service down — using taints, affinity, and topology spread constraints.

By default the scheduler packs Pods wherever they fit. In production you want more say: keep replicas apart, steer workloads onto the right hardware, and survive a zone outage. Four tools cover it.

Taints and tolerations (repel)

A taint on a node repels Pods that do not tolerate it. Use it to reserve GPU nodes, spot instances, or control-plane nodes for specific workloads. The node says "stay away unless you have a pass"; the toleration is that pass.

Node affinity (attract)

Node affinity pulls Pods toward nodes with matching labels — requiredDuringScheduling for a hard rule, preferredDuringScheduling for a soft one. Great for "run on nodes with SSDs" or "prefer the compute pool."

Pod anti-affinity (spread replicas)

Pod anti-affinity keeps copies of the same app off the same node, so losing one node cannot take out every replica.

Topology spread constraints (the modern default)

topologySpreadConstraints is the cleaner, newer way to say "spread my Pods evenly across zones (or nodes)." A maxSkew of 1 across the topology.kubernetes.io/zone key means no zone ever holds more than one extra replica than another — so a full zone outage costs you at most a fraction of your capacity.

Example

Example · yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 6
  selector:
    matchLabels: { app: web }
  template:
    metadata:
      labels: { app: web }
    spec:
      # Spread the 6 replicas evenly across availability zones
      topologySpreadConstraints:
        - maxSkew: 1
          topologyKey: topology.kubernetes.io/zone
          whenUnsatisfiable: ScheduleAnyway
          labelSelector:
            matchLabels: { app: web }
      # Prefer the general compute pool, tolerate spot nodes
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
            - weight: 100
              preference:
                matchExpressions:
                  - key: node-pool
                    operator: In
                    values: ["compute"]
      tolerations:
        - key: "spot"
          operator: "Equal"
          value: "true"
          effect: "NoSchedule"
      containers:
        - name: web
          image: nginx:1.27

When to use it

  • A team uses topologySpreadConstraints to distribute pods evenly across three availability zones, replacing the more brittle podAntiAffinity approach.
  • A platform team uses PriorityClasses to ensure critical system pods are never evicted in favour of batch workloads when nodes run low on resources.
  • An operator uses node affinity with requiredDuringScheduling to pin GPU workloads to nodes with the nvidia.com/gpu resource without hard-coding node names.

More examples

TopologySpreadConstraints for AZ spread

Spreads web pods so no availability zone has more than 1 extra pod compared to other zones, ensuring balanced HA placement.

Example · yaml
spec:
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: topology.kubernetes.io/zone
      whenUnsatisfiable: DoNotSchedule
      labelSelector:
        matchLabels:
          app: web

PriorityClass for critical workloads

Creates a PriorityClass with value 1,000,000 and assigns it to a pod so it can preempt lower-priority pods when resources are scarce.

Example · yaml
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: high-priority
value: 1000000
preemptionPolicy: PreemptLowerPriority
globalDefault: false
description: "Critical production services"
---
spec:
  priorityClassName: high-priority

Multiple scheduling constraints combined

Combines priority class, topology spread, and node affinity preference so pods land on the best available nodes with soft constraints.

Example · yaml
spec:
  priorityClassName: high-priority
  topologySpreadConstraints:
    - maxSkew: 1
      topologyKey: kubernetes.io/hostname
      whenUnsatisfiable: ScheduleAnyway
      labelSelector:
        matchLabels:
          app: api
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          preference:
            matchExpressions:
              - key: node-type
                operator: In
                values: [compute-optimized]

Discussion

  • Be the first to comment on this lesson.