How it works
The explanation, with examples you can run.
Writing them down
containers: - name: app image: ghcr.io/acme/web:1.4.2 resources: requests: cpu: 250m # what the scheduler reserves: a quarter of a core memory: 256Mi limits: memory: 256Mi # above this the container is OOM-killed # no CPU limit: use spare CPU instead of being throttledcpu: 250m means 250 millicores, a quarter of one core. Memory uses binary units (Mi, Gi).
CPU: shared by requests, throttled by limits
When the node is busy, CPU time is divided between containers in proportion to their requests. A CPU limit is a hard quota per scheduling period (by default 100 ms). With limits.cpu: 500m, the container gets 50 ms of CPU time per 100 ms, across all its threads. A request that needs 80 ms of CPU in a burst uses the 50 ms, is paused for the rest of the period, and finishes in the next one. Average utilisation can look low while p99 latency suffers. Multi-threaded runtimes (JVM, Go, Node worker pools) burn through a quota quickly.
Many teams therefore set CPU requests on everything but leave CPU limits off for latency-sensitive services, so they can use idle CPU, and rely on requests for fairness. If you do set CPU limits, watch the throttling metrics (container_cpu_cfs_throttled_periods_total).
Memory: no slowing down, only killing
Memory cannot be throttled. When a container goes over its memory limit, the kernel's OOM killer terminates a process in it, and the kubelet restarts the container:
kubectl describe pod web-6d4c...# Last State: Terminated# Reason: OOMKilled# Exit Code: 137Set memory requests equal to limits for predictable services; a memory request lower than the limit lets the pod overcommit the node, and if the node runs short, the kubelet evicts pods to protect itself. Make sure the runtime knows its limit: modern JVMs and Go (GOMEMLIMIT) read or accept it; Node's heap size may need --max-old-space-size.
QoS classes and eviction
Kubernetes derives a QoS class from these settings:
| Class | Rule | Evicted under node pressure |
|---|---|---|
| Guaranteed | Every container has CPU and memory requests equal to limits | Last |
| Burstable | At least one request or limit, not Guaranteed | Middle, roughly by how far over their requests they are |
| BestEffort | No requests or limits at all | First |
Production workloads should never be BestEffort: they are scheduled without any reservation and are the first to go.
Getting the numbers right, and changing them live
Start from measured usage (for example the p95 of memory and CPU over a week, plus headroom), not guesses; Vertical Pod Autoscaler in recommendation mode can suggest values. Namespace ResourceQuota and LimitRange objects enforce totals and defaults per team.
Changing resources used to mean replacing pods. In-place pod resize is stable since Kubernetes 1.35: you patch a running pod's resize subresource and the kubelet adjusts the cgroups, with a per-resource resizePolicy saying whether that resource can change without restarting the container. Controllers such as VPA can use it to right-size workloads with less disruption.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.