Skip to main content

Set resource requests and limits

Inspect 1.36

Most resource advice says to match both limits to the request on every container and call the pod Guaranteed. Follow it here and you introduce latency you never needed. The right default is narrower: cap memory at its request, leave CPU uncapped, land as Burstable. A request is what the scheduler reserves, a limit the ceiling. Treat them the same and you ignore that CPU and memory fail differently.

Tip

Memory limit equal to the request, no CPU limit, Burstable. Right for most workloads. Go beyond it only when you can name why.

Set a CPU and memory request on every container. A request-less container looks free to the scheduler, so it packs onto a full node and gets evicted under pressure, and free to the , so a pod that should trigger a scale-up silently never does.

The shape those rules produce, on every container:

yaml
		resources:
  requests:
    cpu: "250m" # the scheduler reserves this; your guaranteed floor
    memory: "256Mi"
  limits:
    memory: "256Mi" # equal to the request; deliberately no cpu limit
	

Why the CPU limit stays off

CPU is throttled, not killed. A CPU limit is an absolute CFS quota: hit it and the kernel slows the container even when the node has idle cores, latency in exchange for nothing. The request already sets a guaranteed floor, so let the pod burst into spare CPU. Add a CPU limit only to fence a proven noisy neighbour, and "proven" means you have watched it: kubectl top pod or a CPU dashboard shows the container pinning cores that its co-tenants then wait behind. Fence it just above its real peak, not at its request.

Memory is the opposite. It cannot be reclaimed by slowing a container down, so a container that grows past its request with no limit starves the node and gets its neighbours OOM-killed. Cap the limit at the request to hold it to what it reserved; if it needs more, raise both numbers rather than dropping the limit.

QoS follows from your requests

Kubernetes derives each pod's QoS class from its requests and limits, and that class decides who the kubelet evicts first under memory pressure:

Class When Under pressure
Guaranteed every container has requests equal to limits, CPU and memory evicted last
Burstable at least one request set, but not Guaranteed evicted after BestEffort
BestEffort no requests or limits at all evicted first

That combination lands you in Burstable, protected on memory and free on CPU. Use Guaranteed only when a workload demands it, such as a database that pins cores with whole-integer CPU requests equal to limits. The reason there is not the eviction ranking: the CPU Manager only hands a pod dedicated, unshared cores when it is Guaranteed with whole-integer CPU, so pinning is simply impossible in Burstable. That requirement, not eviction order, is what pulls those workloads up.

Node capacity is not the whole machine

The scheduler places against a node's allocatable capacity, not its physical size. The platform reserves CPU and memory for system daemons, holds back an eviction buffer, and caps pods per node. See for the figures. Never size a pool as if every byte were schedulable.

Right-size from real usage

Guessing requests wastes money when you aim high and triggers evictions when you aim low. Measure instead. The watches real usage and recommends requests from history. Run it in recommendation mode so it advises without moving your pods:

yaml
		apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  updatePolicy:
    updateMode: "Off" # recommend only, never evict
	

After a day or so of history, read the recommendation and copy the target numbers into your requests:

		$ kubectl get vpa web -o jsonpath='{.status.recommendation.containerRecommendations[0].target}'
{"cpu":"180m","memory":"210Mi"}
	

Verify the QoS class

Requests and limits only matter through the QoS class they produce, so confirm it instead of assuming it:

		$ kubectl get pod <pod> -o jsonpath='{.status.qosClass}'
Burstable
	

Burstable is the target for most workloads. BestEffort means the requests are missing and the pod is first in line for eviction. Guaranteed means every request equals its limit for both CPU and memory; since the shape here already matches memory, adding a CPU limit is all it takes to land there, which reintroduces the throttling this page tells you to avoid, unless you set out to pin cores.