4.2 Advanced Scheduling: Affinity, Topology Spread, Priority & Disruption Budgets

Key Takeaways

  • Pod affinity attracts Pods to nodes already running matching Pods, while pod anti-affinity repels them — both are evaluated against a topologyKey such as kubernetes.io/hostname or topology.kubernetes.io/zone.
  • topologySpreadConstraints distribute replicas evenly across failure domains using maxSkew, and are the modern replacement for hand-written anti-affinity rules.
  • A PriorityClass assigns a numeric priority to Pods; when the cluster is full, the scheduler may preempt lower-priority Pods to make room for a higher-priority pending Pod.
  • A PodDisruptionBudget limits how many replicas may be voluntarily disrupted at once, protecting availability during node drains and cluster upgrades.
  • PodDisruptionBudgets constrain only voluntary disruptions such as kubectl drain — they cannot prevent involuntary disruptions like hardware failure or an OOM kill.
Last updated: August 2026

4.2 Advanced Scheduling: Affinity, Topology Spread, Priority & Disruption Budgets

Quick Answer: nodeSelector and node affinity decide which node a Pod can land on. The controls in this section decide how Pods relate to each other: pod affinity/anti-affinity co-locate or separate workloads relative to a topologyKey; topology spread constraints enforce an even distribution across zones or nodes using maxSkew; PriorityClasses let an urgent Pod preempt a less important one when capacity runs out; and PodDisruptionBudgets cap how many replicas may be taken down voluntarily at once.

Section 4.1 covered placement against node properties. Real availability engineering needs placement against other Pods — three replicas that all land on one node are not highly available, no matter what the replica count says.


1. Pod Affinity and Anti-Affinity

Where node affinity matches node labels, pod affinity matches the labels of Pods already running, evaluated within a topology domain.

  • podAffinity — schedule this Pod near Pods matching a selector. Used for latency-sensitive pairs, for example a web tier co-located with its cache in the same zone.
  • podAntiAffinity — schedule this Pod away from Pods matching a selector. Used to spread replicas so a single node or zone failure cannot take out the whole service.

topologyKey Is the Whole Idea

The topologyKey names the node label that defines the boundary of "near" and "away":

topologyKeyDomain it definesTypical use
kubernetes.io/hostnameA single nodeNever put two replicas on one machine
topology.kubernetes.io/zoneAn availability zoneSurvive an AZ outage
topology.kubernetes.io/regionA cloud regionMulti-region distribution
spec:
  affinity:
    podAntiAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
      - labelSelector:
          matchLabels:
            app: payment-api
        topologyKey: kubernetes.io/hostname

This says: do not place this Pod on a node that already runs a Pod labelled app: payment-api. With required…, a 4-replica Deployment on a 3-node cluster will leave one Pod Pending forever — the rule is hard. Switching to preferredDuringSchedulingIgnoredDuringExecution with a weight makes it a soft preference that the scheduler honours when it can.

Cost warning: pod affinity rules are evaluated against every candidate node for every pending Pod, which is computationally expensive. On large clusters, prefer topology spread constraints.


2. Topology Spread Constraints

topologySpreadConstraints express the common intent — spread these replicas evenly — declaratively, and far more cheaply than anti-affinity.

spec:
  topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app: payment-api
FieldMeaning
maxSkewThe maximum permitted difference between the most-populated and least-populated domain. maxSkew: 1 across three zones means counts like 2/2/1 are fine but 3/1/1 is not.
topologyKeyThe node label that defines a domain (zone, hostname, rack).
whenUnsatisfiableDoNotSchedule (hard — Pod stays Pending) or ScheduleAnyway (soft — scheduler prefers a balanced placement but will not block).
labelSelectorWhich existing Pods are counted when measuring skew.

Anti-affinity says "not here"; spread constraints say "keep the distribution flat". For a 9-replica service across 3 zones, spread constraints give you 3/3/3, which hostname anti-affinity alone cannot express.


3. Priority and Preemption

A PriorityClass is a cluster-scoped object mapping a name to an integer. Higher numbers mean higher priority.

apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
  name: mission-critical
value: 1000000
globalDefault: false
preemptionPolicy: PreemptLowerPriority
description: "Payment and auth services only."

A Pod references it with spec.priorityClassName: mission-critical. Priority does two things:

  1. Scheduling order — pending Pods are dequeued highest-priority first.
  2. Preemption — if a high-priority Pod cannot be scheduled anywhere, the scheduler looks for a node where evicting one or more lower-priority Pods would make room, and evicts them. Preempted Pods are terminated gracefully and go back to the scheduling queue.

Setting preemptionPolicy: Never gives a Pod scheduling precedence in the queue without letting it evict anything — the right setting for batch work that should be prioritised but must never disturb serving traffic.

Kubernetes ships two built-in classes, system-cluster-critical and system-node-critical, used by control plane and node add-ons so that CNI and DNS Pods are never evicted to make room for application workloads.


4. PodDisruptionBudgets

Kubernetes distinguishes two kinds of disruption, and only one of them can be budgeted:

KindExamplesCan a PDB stop it?
Voluntarykubectl drain, node upgrade, cluster autoscaler scale-down, preemption, deliberate Pod deletion by a controllerYes
InvoluntaryHardware failure, kernel panic, node running out of memory, someone unplugging a cableNo

A PodDisruptionBudget declares the availability floor the cluster must respect while performing voluntary disruptions:

apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: payment-api-pdb
spec:
  minAvailable: 2          # or: maxUnavailable: 1
  selector:
    matchLabels:
      app: payment-api

With minAvailable: 2 and three replicas, kubectl drain on the node holding one replica succeeds; a drain that would take the count to 1 blocks until a replacement Pod is Ready elsewhere. This is precisely what turns a node-by-node cluster upgrade from an outage into a non-event.

Exam trap: a PDB set to minAvailable: 3 on a Deployment with replicas: 3 makes every node drain block forever — there is no slack. Budgets need headroom.


5. Choosing the Right Control

GoalMechanism
Run only on GPU nodesnodeSelector or node affinity
Keep application Pods off control plane nodesTaints on the node, tolerations on the Pod
Never put two replicas on one nodepodAntiAffinity with topologyKey: kubernetes.io/hostname
Keep replicas balanced across three zonestopologySpreadConstraints with maxSkew: 1
Guarantee the payment service gets capacity firstPriorityClass with a high value
Keep two replicas alive throughout an upgradePodDisruptionBudget with minAvailable: 2
Co-locate a cache with its consumerpodAffinity with a zone topologyKey
Test Your Knowledge

A three-replica Deployment uses podAntiAffinity with requiredDuringSchedulingIgnoredDuringExecution and topologyKey: kubernetes.io/hostname. What happens if the cluster has only two schedulable nodes?

A
B
C
D
Test Your Knowledge

A PodDisruptionBudget specifies minAvailable: 2 for a service with three replicas. Which situation does it actually protect against?

A
B
C
D
Test Your Knowledge

What does maxSkew: 1 mean in a topologySpreadConstraint using topology.kubernetes.io/zone?

A
B
C
D