Scheduler

The Scheduler and Bin-Packing Decisions

kube-scheduler watches for Pods with no spec.nodeName and picks a node in two phases. Filtering drops nodes that cannot run the Pod (too little unreserved CPU or memory, an untolerated taint, a failed affinity rule). Scoring ranks the rest with weighted plugins, and the choice is written back as a binding. The scheduler counts requests, not real usage, so a Pod asking for more than any node has left stays Pending:

Two replicas requesting 3 CPUs each on a 4-CPU workerShell
kubectl create deployment hungry --image=localhost:33500/booknest-web:1.3 --replicas=2 \
  --dry-run=client -o yaml | kubectl set resources -f - --local --requests=cpu=3 -o yaml \
  | kubectl apply -f -
sleep 10
kubectl get pods -l app=hungry \
  -o custom-columns=NAME:.metadata.name,STATUS:.status.phase,NODE:.spec.nodeName
kubectl get events --field-selector reason=FailedScheduling -o jsonpath='{.items[-1:].message}' \
  | fold -s -w 90
Output
deployment.apps/hungry created
NAME                     STATUS    NODE
hungry-66f6c7c98-2m6pm   Pending   <none>
hungry-66f6c7c98-ccl6l   Running   l3-booknest-worker
0/2 nodes are available: 1 Insufficient cpu, 1 node(s) had untolerated taint(s). 
preemption: 0/2 nodes are available: 1 No preemption victims found for incoming pod, 1 
Preemption is not helpful for scheduling.

That is the filtering verdict node by node: the worker lacks CPU, and the control-plane node carries a NoSchedule taint. By default the NodeResourcesFit plugin scores with LeastAllocated, spreading Pods onto the emptiest nodes; configure MostAllocated to bin-pack instead, so the cluster autoscaler (Cluster Autoscaler) can remove idle nodes.