Kubernetes Requests, Limits and QoS Classes

Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; any Kubernetes cluster. Pod-level resource examples require the PodLevelResources feature.
Cert prep CKA · CKAD
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Optional dependency Metrics Server is required only for the kubectl top examples
Scope Container-level and beta Pod-level CPU and memory requests and limits, scheduler behaviour, runtime throttling and OOM, Pod QoS classes, multi-container and init-container accounting, and kubectl inspection. Does not cover ResourceQuota, LimitRange, HPA, VPA, detailed in-place resizing, node capacity planning, HugePages, or full eviction policy.

Requests and limits look similar in YAML, but they answer different questions. A request helps the scheduler find a node with enough unreserved capacity; a limit tells the kernel how hard to enforce usage once the container is running. This walkthrough writes resource YAML at both the container and Pod level, reads CPU and memory units, classifies Pods into Guaranteed, Burstable, and BestEffort QoS, and verifies each class on a live cluster.


Understand Requests, Limits, and Units

Requests vs Limits

Think of requests as scheduling reservations and limits as runtime caps. A request does not lock a dedicated block of idle CPU or memory on the node; it is counted against allocatable capacity so the scheduler can place the Pod safely.

Setting Main consumer Main effect
CPU request Scheduler and CPU weighting Reserves scheduling capacity and influences CPU share
Memory request Scheduler Reserves scheduling capacity
CPU limit Kernel CPU control Throttles CPU usage
Memory limit Kernel memory control Can result in OOM termination

If you set a limit without a request, and no admission-time mechanism has already assigned a request for that resource, Kubernetes copies the limit and uses it as the request. A LimitRange or another admission mechanism can establish a different default request, so the limit is not always copied unchanged. That default helps scheduling, but it does not change what limits do at runtime.

CPU Units

CPU values in Kubernetes are absolute compute units, not percentages of a node.

Common forms you will see in YAML and on exams:

  • 1 — one full CPU (one vCPU/core on cloud VMs, one hyperthread on many bare-metal hosts)
  • 500m — half a CPU (500 millicores)
  • 100m — one tenth of a CPU
  • 1m — smallest practical precision (0.001 CPU)

You can write half a core as 0.5 or as 500m; they mean the same thing. Pick millicores when you need fine steps without long decimals.

Memory Units

Memory requests and limits use byte quantities. Kubernetes accepts plain integers or suffixes.

Binary suffixes (power-of-two, usual choice in examples):

  • Ki — kibibyte (1024 bytes)
  • Mi — mebibyte
  • Gi — gibibyte

Decimal suffixes (K, M, G, …) use powers of 1000 instead. Both are valid; stick to Mi and Gi in exam YAML unless the question specifies otherwise.

A frequent mistake is copying the CPU m suffix to memory. memory: 400m means 400 millibytes, not 400Mi. For hundreds of megabytes, write 400Mi.

Kubernetes also supports ephemeral-storage requests and limits for local emptyDir and container writable layers. The same request-versus-limit split applies, but this walkthrough focuses on CPU and memory because those fields dominate CKAD and CKA tasks.


Configure Container and Pod-Level Resources

Container-Level Resources

Container-level resource requirements belong under each container's resources field. Kubernetes 1.36 can also accept CPU, memory, and huge page requirements at spec.resources when the beta PodLevelResources feature is enabled. This article demonstrates both locations because they affect scheduling and QoS differently.

Every container in a multi-container Pod can carry its own resources block. This exam-friendly pattern sets both requests and limits on one container:

yaml
resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 256Mi

Pod-Level Resources

Pod-level resources define an overall Pod budget. Containers can share unused capacity within that budget. When both Pod-level and container-level values exist, Pod-level values take precedence for Pod allocation and QoS classification.

yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-budget
spec:
  resources:
    requests:
      cpu: 500m
      memory: 256Mi
    limits:
      cpu: "1"
      memory: 512Mi
  containers:
    - name: app
      image: nginx:1.27.0
    - name: helper
      image: busybox:1.36
      command: ["sleep", "3600"]

This creates an overall Pod resource budget. The containers can share unused capacity within that Pod-level budget. Clusters can disable the beta feature gate, so verify support with kubectl explain pod.spec.resources.

Limit-to-Request Defaulting

When you omit a request but set a limit for the same resource, Kubernetes copies the limit into the request only if no admission-time mechanism has already assigned a request. LimitRange objects and other admission controllers can inject different defaults, so always read the admitted Pod spec rather than assuming the copy rule applied.

In-Place Resize Note

Ordinary Pod updates do not directly change container or Pod-level resource requirements. Kubernetes 1.35 and later support changing container CPU and memory through the Pod /resize subresource. Kubernetes 1.36 also supports resizing Pod-level CPU and memory under spec.resources through the same subresource when the beta InPlacePodLevelResourcesVerticalScaling feature is enabled; it is enabled by default. An in-place resize must preserve the Pod's original QoS class, so changing from Burstable to Guaranteed still requires a replacement Pod.

Kubernetes 1.36 added beta, default-enabled in-place resizing for Pod-level resources, while QoS class remains fixed for the Pod lifetime.


How Kubernetes Schedules and Enforces Resources

Scheduler Accounting

For container-level resources, the scheduler calculates an effective Pod request. It compares the sum of concurrently running application and restartable sidecar requests with the effective request of the regular init containers and uses the larger value for each resource. Pod overhead is added when configured. When Pod-level resources are present, the Pod-level request takes precedence.

Imagine a Pod that requests 500m CPU and 256Mi memory. The scheduler subtracts existing Pod requests from each node's allocatable capacity and only places the Pod where enough unrequested CPU and memory remain.

Current live usage is not the primary scheduling signal. kubectl top can show a nearly idle node while scheduling still fails because requests from other Pods already reserved the allocatable slice.

On my lab cluster, allocatable capacity looks like this:

bash
kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memory

Sample output:

output
NAME       CPU   MEM
k8s-cp     3     6729672Ki
worker01   1     1905896Ki

A Pod stays Pending when no node has 500m CPU and 256Mi memory free in the request ledger, even if actual CPU use is low. For events and volume mount issues that also block scheduling, see Pod Pending and ContainerCreating.

CPU Throttling

Once the kubelet starts a container, limits are passed to the container runtime and enforced through cgroups. When a container tries to use more CPU than its limit, the kernel throttles it. The process keeps running but slows down; Kubernetes does not kill the container for exceeding a CPU limit.

Memory Limits and OOM

Memory charged to the container or Pod cgroup is subject to the configured limit. Memory-limit enforcement is reactive: when usage exceeds the limit and the kernel detects memory pressure, it can terminate one or more processes with an OOM kill. The kill is not necessarily instantaneous at the exact byte where usage crosses the limit. The Pod may restart depending on restartPolicy. For exit code 137 and log patterns, see OOMKilled troubleshooting.

Usage Above Requests

A container may consume more than its request when the node has spare capacity, as long as it stays within its limit (if a limit is set) and the node is not under memory pressure. Requests shape scheduling and relative share; they are not a hard runtime ceiling.


Understand QoS Classes

Kubernetes assigns one QoS class to the whole Pod when it is created. The class is not shown in kubectl get pods columns—you read it from status. An in-place resize cannot change the class later.

These examples omit spec.resources and demonstrate traditional container-level QoS classification.

Guaranteed

A Pod is Guaranteed through either of these paths:

Container-level resources: every application and init container has non-zero CPU and memory requests and limits, and each request equals its corresponding limit.

Pod-level resources: the Pod has non-zero CPU and memory requests and limits under spec.resources, with request equal to limit for each resource.

Ephemeral containers cannot declare resources and do not affect these criteria.

Burstable

A Pod is Burstable when:

  • It does not meet the Guaranteed criteria; and
  • At least one application/init container or the Pod itself declares a CPU or memory request or limit.

Unequal request/limit pairs land here, as do Pods where only some resources are set.

BestEffort

A Pod is BestEffort only when neither the Pod nor any application/init container declares CPU or memory requests or limits.

QoS and Node-Pressure Eviction

QoS class helps describe a Pod's resource guarantees and can indicate its likely eviction risk. During node-pressure eviction, the kubelet primarily considers whether usage exceeds requests, Pod Priority, and usage relative to requests rather than sorting directly by the QoS label. BestEffort Pods often face higher risk because they declare no requests, but QoS alone does not determine eviction order.


Verify QoS Classes in a Lab

I use namespace qos-lab and three small busybox Pods so you can see each class on a running cluster.

Create the namespace:

bash
kubectl create namespace qos-lab

Sample output:

output
namespace/qos-lab created

BestEffort Pod

A Pod with no resources block is BestEffort:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: qos-besteffort
  namespace: qos-lab
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
  restartPolicy: Never

Apply the manifest:

bash
kubectl apply -f qos-besteffort.yaml

Wait until the Pod reports Ready so status.qosClass is populated:

bash
kubectl wait --for=condition=Ready pod/qos-besteffort -n qos-lab --timeout=60s

Read the assigned QoS class:

bash
kubectl get pod qos-besteffort -n qos-lab -o jsonpath='{.status.qosClass}{"\n"}'

Sample output:

output
BestEffort

This assumes no LimitRange or admission controller injects default resource values. Always read status.qosClass from the admitted Pod rather than assuming the class from the submitted YAML.

Burstable Pod

Unequal requests and limits produce Burstable:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: qos-burstable
  namespace: qos-lab
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
    resources:
      requests:
        cpu: 100m
        memory: 128Mi
      limits:
        cpu: 500m
        memory: 256Mi
  restartPolicy: Never

Apply the manifest:

bash
kubectl apply -f qos-burstable.yaml

Wait until the Pod is Ready:

bash
kubectl wait --for=condition=Ready pod/qos-burstable -n qos-lab --timeout=60s

Check the QoS label:

bash
kubectl get pod qos-burstable -n qos-lab -o jsonpath='{.status.qosClass}{"\n"}'

Sample output:

output
Burstable

Unequal request and limit pairs always produce Burstable, even when both resources are set.

Guaranteed Pod

Match requests to limits on both CPU and memory:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: qos-guaranteed
  namespace: qos-lab
spec:
  containers:
  - name: app
    image: busybox:1.36
    command: ["sleep", "3600"]
    resources:
      requests:
        cpu: 200m
        memory: 128Mi
      limits:
        cpu: 200m
        memory: 128Mi
  restartPolicy: Never

Apply the manifest:

bash
kubectl apply -f qos-guaranteed.yaml

Wait until the Pod is Ready:

bash
kubectl wait --for=condition=Ready pod/qos-guaranteed -n qos-lab --timeout=60s

Read the QoS class from status:

bash
kubectl get pod qos-guaranteed -n qos-lab -o jsonpath='{.status.qosClass}{"\n"}'

Sample output:

output
Guaranteed

With all three Pods running, list their classes side by side:

bash
kubectl get pods -n qos-lab -o custom-columns=NAME:.metadata.name,QOS:.status.qosClass

Sample output:

output
NAME             QOS
qos-besteffort   BestEffort
qos-burstable    Burstable
qos-guaranteed   Guaranteed

Multi-Container and Init-Container Accounting

Application containers and restartable sidecars can run together, so their requests contribute to the concurrent total. Regular init containers run sequentially; Kubernetes uses the highest effective init requirement rather than adding all regular init-container requests together. A large init request can therefore control scheduling even though that init container exits before the application starts.

One sidecar without matching Guaranteed fields prevents the Pod from reaching Guaranteed QoS through container-level criteria even when the main app container is perfect. Pod-level resources can determine Guaranteed QoS independently when equal requests and limits are set under spec.resources. Before an exam answer, check every container in spec.containers, any Pod-level spec.resources, and init containers—not only the primary application container. For init-container lifecycle details, see Kubernetes init containers.


Inspect and Troubleshoot Resources

kubectl explain and describe

Confirm field paths in your cluster version:

bash
kubectl explain pod.spec.containers.resources

Representative relevant output:

output
FIELD: resources <ResourceRequirements>

DESCRIPTION:
    Compute Resources required by this container.

FIELDS:
  claims    <[]ResourceClaim>
  limits    <map[string]Quantity>
  requests  <map[string]Quantity>

For Pod-level fields:

bash
kubectl explain pod.spec.resources

The exact schema comes from the API server's OpenAPI data and can vary with Kubernetes version and enabled feature gates.

kubectl describe pod is the fastest way to read configured requests and limits alongside events:

bash
kubectl describe pod qos-burstable -n qos-lab

The container section lists Limits and Requests explicitly:

output
Limits:
      cpu:     500m
      memory:  256Mi
    Requests:
      cpu:        100m
      memory:     128Mi

kubectl get pod -o yaml shows the same values under spec.containers[].resources if you need the raw manifest.

kubectl top

kubectl top reports recent CPU and memory usage, not configured requests or limits. Pod readiness does not guarantee that Metrics Server has already collected a sample. Wait for metrics before comparing usage with configured limits:

bash
METRICS_READY=false

for attempt in {1..24}; do
  if kubectl top pod -n qos-lab --containers; then
    METRICS_READY=true
    break
  fi
  sleep 5
done

if [[ "$METRICS_READY" != "true" ]]; then
  echo "Pod metrics are not available; verify Metrics Server before continuing." >&2
fi

Sample output (usage and metric rounding vary):

output
POD              NAME   CPU(cores)   MEMORY(bytes)
qos-besteffort   app    0m           0Mi
qos-burstable    app    0m           0Mi
qos-guaranteed   app    4m           0Mi

Idle sleep containers show near-zero usage even when limits are high. For Metrics Server setup and sorting options, see monitor Pod and container resources.

Common Problems

Symptom / mistake Likely cause Fix
Pod stays Pending with Insufficient cpu or Insufficient memory Node allocatable minus existing requests cannot fit this Pod's requests Lower requests, add nodes, or delete workloads that reserve capacity
Container OOMKilled Container or Pod memory limit exceeded, or process selected during node-wide OOM Check termination reason, configured limits, node MemoryPressure, and other affected workloads before changing the limit
Application slow while CPU is constrained CPU throttling, low CPU share under contention, or a non-CPU bottleneck kubectl top shows recent usage but does not prove throttling; compare the CPU request and limit and inspect throttling metrics before raising the limit
Expected Guaranteed, got Burstable Unequal request/limit on any container, missing field on a sidecar, or unequal Pod-level resources Align all four fields per container, set equal Pod-level requests and limits, or accept Burstable
kubectl top errors on a new Pod Metrics sample not ready yet or Metrics Server missing Wait and retry with the bounded loop above; install Metrics Server if the API is missing
Placing only resources under the Pod spec root on unsupported clusters PodLevelResources feature gate disabled Use container-level resources or enable the feature gate; verify with kubectl explain pod.spec.resources
Setting a limit and assuming the request stays unset Limit-to-request defaulting or LimitRange admission Read the admitted Pod spec; admission may inject a different request
Using 400m for memory Means millibytes, not 400Mi Write 400Mi for four hundred mebibytes
Treating requests as a runtime cap Usage can exceed the request until the limit or node pressure applies Compare describe output with kubectl top before changing limits
Expecting CPU-limit breaches to kill the container CPU is throttled, not OOMKilled Raise the limit only when throttling metrics justify it
Ignoring sidecars in QoS calculations One container without equal request/limit pairs drops container-level Guaranteed Check every container and any Pod-level spec.resources

Exit reason OOMKilled does not by itself prove that only the container limit was responsible.


Exam-Focused YAML Tasks

Practice these short edits until they feel automatic:

  1. Add resources.requests and resources.limits under a Deployment's container template.
  2. Make a Pod Guaranteed by setting equal CPU and memory requests and limits on every container, or equal Pod-level requests and limits under spec.resources when the feature is enabled.
  3. Explain why a two-container Pod is Burstable when the app container is Guaranteed but a logging sidecar has only a memory limit.
  4. Fix memory: 400m to memory: 400Mi when the question means four hundred mebibytes.
  5. When an explicit memory request already exists, raise only limits.memory if the task asks for more peak headroom without changing scheduling reservation. If the manifest omits the request, changing the limit can also change the defaulted request.
  6. Remove the CPU limit only when the prompt explicitly allows unbounded CPU; otherwise leave limits in place.

What's Next


References


Summary

You set CPU and memory requests and limits under each container's resources block, or at spec.resources when PodLevelResources is enabled. Pod-level values take precedence for allocation and QoS when both are present. Requests reserve scheduling capacity on a node; limits throttle CPU and can trigger reactive memory OOM at runtime. CPU uses cores or millicores (500m equals half a core); memory uses binary suffixes such as Mi, and the m suffix on memory means millibytes, not mebibytes.

The scheduler places Pods using effective requests—not live kubectl top numbers—so a node can look idle while remaining unschedulable. At runtime, CPU over the limit slows the container; memory over the limit can kill it after the kernel detects pressure. QoS is a Pod-wide label assigned at creation: Guaranteed needs equal requests and limits on every relevant container or at the Pod level, Burstable covers partial or unequal settings, and BestEffort means no resources at all. Verify with kubectl get pod -o jsonpath='{.status.qosClass}' after you apply manifests.

On exams, read every container—including sidecars—and any Pod-level spec.resources before you name the QoS class. Measure usage with kubectl top and compare against describe output before you change limits. Namespace-level quotas and autoscalers build on these fields; this lesson stops at the container and Pod YAML that those policies consume.


Frequently Asked Questions

1. What is the difference between a Kubernetes request and a limit?

A request supplies the scheduler resource reservation and influences runtime weighting or protection. A CPU limit is enforced through throttling. A memory limit is enforced reactively and can lead to an OOM kill when memory usage exceeds the allowed cgroup budget.

2. Does setting a CPU limit kill the container?

No. When a container exceeds its CPU limit, the kernel throttles it so the process slows down but keeps running. Memory limits are different: crossing the cgroup memory limit can cause the container to be OOMKilled.

3. What QoS class does a Pod get when requests equal limits?

A Pod is Guaranteed when its admitted resource configuration has equal, non-zero CPU and memory requests and limits. With container-level resources, this must hold for every application and init container. With Pod-level resources enabled, equal CPU and memory requests and limits under spec.resources can determine Guaranteed QoS. If only a limit is submitted and no admission-time mechanism supplies another request, Kubernetes copies that limit into the request.

4. If I set only a memory limit, what is the request?

If you set a limit without a request, and no admission-time mechanism has already assigned a request for that resource, Kubernetes copies the limit and uses it as the request. The same default applies to CPU limits without a CPU request. A LimitRange or another admission mechanism can establish a different default request instead.

5. Can a container use more CPU than its request?

Yes, when the node has spare capacity and the container has not hit its CPU limit. Requests influence scheduling and relative CPU share; they do not hard-cap usage the way limits do.

6. Why is my Pod Pending when kubectl top shows low node usage?

Scheduling is based on unrequested allocatable capacity, not current usage. A node can look idle in kubectl top while every allocatable CPU or memory slice is already reserved by other Pods requests.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)