$ skip to content
devresurge

11 — Scheduler: who goes where

ELI5 hook

The scheduler is a seat assigner at a theater. Pods without seats (nodeName empty) wait in line. The scheduler checks which seats fit (filters), scores the comfy ones (prioritize), then writes your ticket (nodeName). It does not start the show — kubelet does once the ticket exists.

Mental model

flowchart LR
  pending[Pending Pod] --> filter[Filter plugins]
  filter --> score[Score plugins]
  score --> bind[Bind: set nodeName]
  bind --> kubelet[kubelet sync]
PhaseExamples
FilterEnough CPU/memory? Node selector? Taints? Affinity? Ports? PVC zone?
ScoreSpread, least requested, affinity preference
BindAtomic API update assigning the node

If no node passes filters → Pod stays Pending with events explaining why.

What the scheduler considers

Resources

Uses requests (not limits) for packing math against node allocatable.

Taints & tolerations

Node: “no un-tolerated Pods.” Pod: “I tolerate this taint.” Control-plane nodes usually taint themselves.

Affinity / topology

  • nodeAffinity — prefer/require certain node labels
  • podAffinity / antiAffinity — sit with / away from other Pods
  • topologySpreadConstraints — even spread across zones/hosts

Preemption

Higher PriorityClass Pods can evict lower ones to make room — still subject to PDBs and graceful termination.

How Kubernetes uses it

ObjectEffect
nodeSelector / affinityHard/soft placement rules
tolerationsAllow tainted nodes
PriorityClassScheduling order + preemption power
RuntimeClassMay constrain eligible nodes
PVC + storage topologySchedule where volume can attach

Multiple schedulers can coexist (schedulerName); default is default-scheduler.

Lab

# Pending due to impossible request (adjust if your node is huge)
kubectl apply -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: too-fat
spec:
  containers:
    - name: pause
      image: registry.k8s.io/pause:3.9
      resources:
        requests:
          memory: "16Ti"
EOF

kubectl describe pod too-fat | tail -20
kubectl delete pod too-fat

# Taint experiment (kind single-node — untaint when done!)
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
kubectl taint node "$NODE" course=demo:NoSchedule
kubectl run stuck --image=registry.k8s.io/pause:3.9
kubectl describe pod stuck | grep -A5 Events || true
kubectl delete pod stuck
kubectl taint node "$NODE" course=demo:NoSchedule-

Gotchas

  • Limits don’t reserve — A node can overcommit limits; requests drive scheduling.
  • Local PV / hostPath — Pins you; scheduling must match.
  • Pending forever — Read Events; don’t restart the Pod as a first fix.
  • Custom schedulers — Batch/ML systems often bring their own; Pods must set schedulerName.

Checkpoint

Scheduler assigns; kubelet runs. Pending means “no legal seat,” not “Linux failed.”

Next: 12 — Networking.


Quiz: /quizzes/k8s-node-control-plane/