Scheduling and Resources

Kubernetes has to answer a question every time a pod is created: which machine should run it? The scheduler answers it, and the quality of that answer depends entirely on information you provide — resource requests and limits. Get those right and the cluster packs efficiently and stays stable; get them wrong and you get waste, evictions, and mysterious outages.

The controllers create pods; something must decide which node each pod runs on. That’s the scheduler, and its decisions hinge on resource requests and limits you declare. This post covers how scheduling works, requests vs limits (a crucial and often-misunderstood distinction), quality of service and eviction, and autoscaling. It’s how Kubernetes places pods and manages the cluster’s finite resources — and where small configuration mistakes cause real production problems.

The scheduler: placing pods

When a controller creates a pod, the pod initially has no node — it’s Pending. The scheduler watches for unscheduled pods and assigns each to a suitable node, via a two-step process:

Pending pod → scheduler:
   filter feasible nodes (enough resources? constraints met?)
   → score them (spread / pack / affinity)
   → bind pod to the best node → kubelet runs it

The critical input to filtering is the pod’s resource requests — the scheduler can only place a pod on a node with enough requested resources free. This is why requests matter so much: they’re how the scheduler knows what a pod needs and what a node has left. You can also influence placement with node selectors/affinity (run on nodes with certain labels — e.g. GPU nodes), taints and tolerations (reserve nodes for certain workloads), and pod affinity/anti-affinity (co-locate or spread pods). But the foundation is resources, which brings us to the most important and most misunderstood concept: requests vs limits.

Requests vs limits: the crucial distinction

Every container can declare two things for CPU and memory, and confusing them causes real problems:

request: guaranteed/reserved amount, used for SCHEDULING (find a node with this free)
limit:   maximum allowed, ENFORCED at runtime (cgroups cap the container)

The distinction matters, and the two resources behave differently at their limits — a key gotcha:

This is why setting these wrong causes outages: too-low a memory limit → your container gets OOM-killed under load (a mysterious crash); too-low a CPU limit → your app is throttled and slow; no requests → the scheduler can’t place pods well and nodes get overcommitted; requests far above actual use → wasted, reserved-but-idle capacity (paying for resources nobody uses). Setting requests and limits correctly — requests near actual typical usage (so scheduling and packing are accurate), limits high enough to handle spikes without OOM-kills — is one of the highest-impact Kubernetes tuning tasks, and getting it wrong is a leading cause of both waste and instability.

Quality of service and eviction

Requests and limits also determine a pod’s Quality of Service (QoS) class, which decides who gets evicted when a node runs low on resources (memory pressure):

When a node runs out of memory, Kubernetes evicts pods to reclaim resources, and it evicts BestEffort first, then Burstable, protecting Guaranteed. This is a crucial practical consequence: a pod with no resource requests (BestEffort) is the first to be killed under pressure — so critical workloads should set requests/limits (Burstable or Guaranteed) to avoid being evicted first. This ties directly to the previous point: setting requests/limits isn’t just about scheduling and OOM-kills, it also determines your pod’s survival priority when a node is stressed. Not setting them means your pod is both poorly scheduled and first to die — which is why “always set resource requests and limits on production workloads” is standard advice.

Autoscaling: adjusting to load

Beyond placing a fixed set of pods, Kubernetes can automatically adjust capacity to load — the reconciliation idea applied to scale:

Together: HPA scales pods to load, Cluster Autoscaler scales nodes to fit the pods, and VPA right-sizes pod resources — so the cluster automatically adapts capacity to demand (and cost, connecting to the FinOps theme: scale down when idle). Autoscaling is the reconciliation model applied to capacity: declare a target (e.g. keep CPU utilization ~60%), and the autoscaler continuously adjusts replicas/nodes to hit it.

Scheduling and resources, in practice

The takeaway: the scheduler places each pod on a suitable node by filtering (feasible nodes, based on resource requests and constraints) and scoring (best choice), so accurate requests are what make placement work. Requests (guaranteed, used for scheduling) and limits (maximum, cgroup-enforced) are the crucial, often-misunderstood distinction — and getting them wrong causes real problems: too-low memory limits OOM-kill your app, too-low CPU limits throttle it, missing requests break scheduling and make your pod BestEffort (evicted first under pressure), and inflated requests waste money. Setting them correctly (and using autoscaling — HPA for pods, Cluster Autoscaler for nodes, VPA for right-sizing) is how the cluster runs efficiently and stably. This is where much Kubernetes operational pain — and its resolution — lives. The final post covers extending and operating Kubernetes in production.

Key takeaways

Further reading

Sources & References