Skip to content

The Scheduler and Resource Requests

The scheduler places a Pod onto a Node that has enough free capacity to cover its requests, while limits cap how much of a resource the Pod can actually use once it is running.

Requests set the floor, limits set the ceiling

Section titled “Requests set the floor, limits set the ceiling”

Every container in a Pod can declare resources.requests and resources.limits for CPU and memory, and the two numbers do completely different jobs.

  • requests is the guaranteed minimum a container needs. The scheduler only considers a Node a candidate if that Node has enough unreserved capacity to cover every container’s requests — it never looks at limits when deciding where a Pod can go.
  • limits is the hard ceiling enforced at runtime, after the Pod is already running. CPU is a compressible resource, so usage above the CPU limit is throttled, not killed. Memory is not compressible, so a container that exceeds its memory limit gets OOMKilled and restarted.
apiVersion: v1
kind: Pod
metadata:
name: payments-worker
spec:
containers:
- name: worker
image: payments-worker:1.4.0
resources:
requests:
cpu: "500m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "256Mi"
Terminal window
# See which node a Pod landed on, and check the node's spare capacity
kubectl get pod payments-worker -o jsonpath='{.spec.nodeName}{"\n"}'
kubectl describe node <node-name> | grep -A5 "Allocated resources"

Because the scheduler reasons only about requests, a Pod with no requests set is effectively invisible to capacity planning — it can be scheduled anywhere, and it can also starve neighbors that did the right thing and declared theirs.

Kubernetes derives a Quality of Service (QoS) class for every Pod automatically from its requests and limits — you never set this field yourself.

  • Guaranteed — every container has requests equal to limits, for both CPU and memory.
  • Burstable — at least one container has requests set, but they differ from limits, or only some resources have both set.
  • BestEffort — no container has any requests or limits set at all.
Terminal window
# QoS class is computed and reported on the Pod status
kubectl get pod payments-worker -o jsonpath='{.status.qosClass}{"\n"}'

QoS class only matters once a Node runs low on a resource. When the kubelet needs to reclaim memory under pressure, it evicts BestEffort Pods first, then Burstable Pods, and only reaches for Guaranteed Pods as an absolute last resort. This is one of the few places where setting requests and limits carefully has a direct, visible effect on which of your workloads survive a noisy neighbor.

How the scheduler picks a Node: filter, score, bind

Section titled “How the scheduler picks a Node: filter, score, bind”

For every unscheduled Pod, the scheduler runs a two-phase decision before it commits:

  1. Filtering — throw out every Node that flatly cannot run this Pod: not enough free CPU/memory to cover the requests, a taint the Pod does not tolerate, or an affinity/anti-affinity rule that rules it out. What is left is the set of feasible Nodes.
  2. Scoring — rank the feasible Nodes by a set of scoring plugins (spread across zones, bin-pack density, and more) and pick the highest-scoring one.

Once a Node wins, the scheduler binds the Pod to it by writing spec.nodeName, and the kubelet on that Node takes over and starts the containers.

flowchart LR
  pod["Pod spec\n(requests + limits)"] --> filter["Filter:\nwhich Nodes CAN run it"]
  filter --> score["Score:\nrank the feasible Nodes"]
  score --> bind["Bind:\nPod assigned to winning Node"]
  bind --> node["Node"]
A Pod's requests and limits flowing through the scheduler's filter, score, and bind steps
What is the scheduler actually reasoning about when it decides where a Pod can run?
A container exceeds its memory limit. What happens?
Under memory pressure on a Node, which QoS class is evicted first?
What are the two main phases the scheduler runs before binding a Pod to a Node?