Skip to content

Resource Requests and Limits

Resource settings are one of the most important workload controls in Kubernetes.

They determine scheduling quality, runtime stability, and autoscaling behavior.

Core model

  • requests: minimum resources reserved for scheduling
  • limits: maximum runtime resources a container may use

The most common misconception: requests are a scheduling number, not a runtime guarantee or cap. The scheduler subtracts requests from a node's allocatable capacity to decide placement -- actual usage is irrelevant to scheduling. A container that requests 100m CPU can happily use 2 cores at runtime if the node has slack (and no limit stops it). Conversely, a node can be "full" by requests while sitting 80% idle.

If requests are too low, pods get packed too tightly and become unstable under load. If they are too high, cluster capacity is wasted -- this gap between requested and used capacity is where most Kubernetes overspend lives.

CPU and memory behavior

CPU and memory limits fail differently, because they are enforced by different kernel mechanisms:

  • CPU limit → throttling. CPU limits are enforced by the kernel's CFS quota: for every 100ms window, the container gets limit × 100ms of CPU time. A container with a 500m limit that burns its 50ms budget in the first half of a window simply stops running for the remaining 50ms. For a multi-threaded service this can happen far below "100% CPU" as seen on a dashboard -- 4 threads exhaust a 500m budget in 12.5ms of wall time. The symptom is latency spikes with modest average utilization. Check the container_cpu_cfs_throttled_periods_total metric.
  • Memory limit → OOM kill. Memory is not compressible. When the container's cgroup exceeds its limit, the kernel OOM killer terminates the process, the kubelet restarts the container, and you see OOMKilled with exit code 137.

That is why memory sizing errors are more disruptive (crash) while CPU sizing errors are more insidious (silent latency). A widely used production pattern follows from this: set memory requests equal to memory limits, set CPU requests honestly, and consider omitting CPU limits for latency-sensitive services -- throttling protection is rarely worth the tail-latency cost on well-isolated nodes. Treat that as a starting point, not a law; multi-tenant platforms may still want CPU caps.

Example

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api
  template:
    metadata:
      labels:
        app: api
    spec:
      containers:
        - name: api
          image: ghcr.io/example/api:v5.2.1
          resources:
            requests:
              cpu: 250m
              memory: 512Mi
            limits:
              cpu: 1000m
              memory: 1Gi

QoS classes

Pod QoS class is derived from resource configuration and determines eviction priority under node pressure:

graph TD
    BE[BestEffort\nno requests or limits\nevicted first]
    BU[Burstable\nrequests set, not equal to limits\nevicted second]
    GU[Guaranteed\nrequests == limits for all containers\nevicted last]

    BE -->|evict before| BU
    BU -->|evict before| GU
  • Guaranteed: all containers have requests equal to limits. Maximum scheduling predictability. Recommended for latency-sensitive services.
  • Burstable: at least one request set, but requests do not equal limits. Can use slack capacity when available.
  • BestEffort: no requests or limits at all. First to be evicted under memory pressure. Avoid for production workloads.

In node memory pressure events, the kubelet evicts BestEffort first, then Burstable pods that exceed their requests, then Guaranteed pods only as a last resort.

Sizing guidance

  • Start from observed p50 and p95 usage, not guesses
  • Keep requests close to realistic baseline load
  • Set memory limits with enough headroom for peak behavior
  • Avoid setting very low CPU limits on latency-sensitive services

Relationship to autoscaling

HPA resource targets depend on requests. Bad request values produce bad scaling decisions.

Always tune requests before tuning autoscaler thresholds.

Operational checks

kubectl top pods -A
kubectl describe pod <pod-name>
kubectl get pod <pod-name> -o jsonpath='{.status.containerStatuses[*].lastState}'

Look for OOMKilled events and sustained CPU throttling signals in metrics.

Certification notes

  • Know the QoS class derivation rules exactly -- "which pod is evicted first?" is a standard question. Guaranteed requires requests == limits for every container, for both CPU and memory.
  • kubectl describe node shows allocated requests vs capacity; kubectl top shows actual usage. Exam tasks distinguish these.
  • CPU units: 1000m = 1 core; memory: Mi/Gi are binary (1Mi = 1024Ki), M/G are decimal.

Summary

Requests drive placement. Limits enforce runtime caps (throttling for CPU, OOM kill for memory). Correct values improve stability, cost efficiency, and autoscaling quality.

Further Reading