How it works
The explanation, with examples you can run.
Filtering and scoring
The scheduler works in two steps for each pod:
- Filtering removes nodes where the pod cannot run: not enough unrequested CPU or memory for the pod's requests, a
nodeSelectoror required affinity that does not match, a taint the pod does not tolerate, a volume in another zone, a port already used on the host. - Scoring ranks the remaining nodes: spreading pods of the same app, preferred affinities, image already present, balanced resource use.
The pod is bound to the highest-scoring node. If no node passes filtering, the pod stays Pending, and kubectl describe pod shows the reason in its events, for example 0/5 nodes are available: 5 Insufficient memory.
Telling the scheduler what you need
spec: nodeSelector: kubernetes.io/arch: arm64 # only arm64 nodes tolerations: - key: dedicated # allowed onto nodes tainted dedicated=gpu:NoSchedule operator: Equal value: gpu effect: NoSchedule topologySpreadConstraints: - maxSkew: 1 # keep zones within one pod of each other topologyKey: topology.kubernetes.io/zone whenUnsatisfiable: ScheduleAnyway labelSelector: matchLabels: app: trainer containers: - name: trainer image: ghcr.io/acme/trainer:2.0.0 resources: requests: cpu: "2" memory: 4Gi- Requests are the most important input: the scheduler only places a pod where the sum of requests fits the node's allocatable capacity. It never looks at live usage.
- nodeSelector / node affinity attract pods to labelled nodes (architecture, instance type, zone).
- Taints and tolerations work the other way: a tainted node repels pods unless they tolerate the taint. Dedicated or special nodes (GPUs, system pools) are tainted so ordinary pods stay off.
- Topology spread and pod anti-affinity keep replicas apart, so one node or one zone failing does not take them all.
Nodes and the kubelet
Each node runs a kubelet and a container runtime (containerd or CRI-O, through the Container Runtime Interface; Docker Engine itself has not been a built-in runtime since dockershim was removed in 1.24, though images built with Docker run unchanged). The kubelet reports the node's capacity and conditions. If it stops reporting, the node becomes NotReady, and after a timeout its pods are evicted and replaced elsewhere by their controllers.
Useful views:
kubectl get nodes -o widekubectl describe node <name> # allocatable, requests already placed, conditions, taintskubectl get pods -o wide # which node each pod landed onWhen a node must be emptied for maintenance, kubectl drain evicts its pods (respecting PodDisruptionBudgets) and marks it unschedulable, so the scheduler places the replacements elsewhere.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.