Kubernetes QoS Classes: How Requests and Limits Shape Eviction and Scheduling
A precise guide to Guaranteed, Burstable, and BestEffort Pods, including request math, cgroup enforcement, OOM behavior, and node-pressure eviction.
Kubernetes assigns every Pod one of three quality-of-service classes—Guaranteed, Burstable, or BestEffort—from its container resource requests and limits. The label sounds like a performance promise, but it is more specific: it influences node-pressure eviction and interacts with cgroup controls. It does not reserve an SLA, prevent application-level overload, or make a badly sized workload reliable.
Requests drive placement; limits constrain use
The scheduler compares Pod requests with allocatable node resources. A container may use more CPU than its request when capacity is available, but a CPU limit can throttle it. Exceeding a memory limit can trigger an OOM kill because memory cannot be throttled in the same simple way.
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: 1
memory: 512Mi
Requests describe the capacity the scheduler accounts for, not a measurement of recent use. Limits are enforcement inputs, not recommendations. Namespace LimitRange objects may default values, and ResourceQuota can reject objects that do not satisfy policy, so inspect the admitted Pod rather than only the submitted manifest.
The three classes are mechanically derived
A Pod is Guaranteed only when every container has both CPU and memory requests and limits, and each request equals its corresponding limit. Init containers and supported Pod-level resource declarations participate according to the current Kubernetes rules. One container with a mismatched or missing value prevents the whole Pod from being Guaranteed.
BestEffort means no container declares any CPU or memory request or limit. Everything between those cases is Burstable, including a Pod with requests but no limits or one container sized correctly beside another with no resources.
kubectl get pod api -o jsonpath='{.status.qosClass}{"\n"}'
Do not infer the class from a Helm values file. Admission mutation and generated sidecars can change the final result.
QoS influences eviction, but priority and usage still matter
Under node pressure, kubelet ranks candidates using whether a Pod exceeds requests, Pod Priority, and resource usage relative to requests. BestEffort and over-request Burstable workloads are generally exposed earlier than Guaranteed workloads, but the class is not the only ordering key. A high-priority Pod can outrank a lower-priority one, and eviction decisions depend on the pressured resource and observed state.
Kubelet’s node-pressure eviction is different from a container exceeding its cgroup memory limit. A limit breach can produce OOMKilled within a Pod even when the node is healthy. Under global memory exhaustion, Linux’s OOM selection and Kubernetes-adjusted oom_score_adj values interact; neither mechanism guarantees that the operator’s intuitively “least important” process dies first.
CPU limits can create invisible latency
CPU requests affect scheduling weight, while CPU limits are commonly implemented through CFS bandwidth controls. A service can show average CPU below its limit and still be periodically throttled after using its quota within a control period. For latency-sensitive services, compare container_cpu_cfs_throttled_* metrics, request utilization, queueing, and tail latency before interpreting low average CPU as spare headroom.
Removing every CPU limit is not a universal fix. It can reduce throttling for one workload while allowing noisy-neighbor behavior elsewhere. Decide through measured service objectives, node isolation, requests, autoscaling, and capacity policy.
Memory requests are a risk statement
Set memory requests from representative working sets plus known overhead, then validate under realistic concurrency. A request far below steady-state use overcommits the node and places the Pod above its request during pressure. A limit too close to normal peaks creates restart loops; a limit far above any tested requirement weakens containment.
Sidecars, init behavior, emptyDir memory volumes, kernel memory accounting, and runtime overhead all affect the node. Dashboard only at deployment totals can hide a single Pod or container with an unsafe distribution.
Build a resource acceptance test
For each workload, capture admitted requests/limits, resulting QoS class, PriorityClass, autoscaler inputs, node allocatable capacity, throttling, working-set peaks, OOM events, and node-pressure signals. Test startup, steady load, burst, dependency slowdown, rolling update, and node drain.
The goal is not to maximize the number of Guaranteed Pods. It is to make scheduling intent, failure isolation, and capacity economics explicit—and to verify that the chosen controls produce the expected behavior.
Related:
- Fixing a Kubernetes Namespace Stuck in Terminating Without Hiding the Root Cause
- Kubernetes RBAC Impersonation: Testing Authorization Without Sharing Credentials
Sources: