| 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 CPU1m— 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— mebibyteGi— 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:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 256MiPod-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.
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:
kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memorySample output:
NAME CPU MEM
k8s-cp 3 6729672Ki
worker01 1 1905896KiA 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:
kubectl create namespace qos-labSample output:
namespace/qos-lab createdBestEffort Pod
A Pod with no resources block is BestEffort:
apiVersion: v1
kind: Pod
metadata:
name: qos-besteffort
namespace: qos-lab
spec:
containers:
- name: app
image: busybox:1.36
command: ["sleep", "3600"]
restartPolicy: NeverApply the manifest:
kubectl apply -f qos-besteffort.yamlWait until the Pod reports Ready so status.qosClass is populated:
kubectl wait --for=condition=Ready pod/qos-besteffort -n qos-lab --timeout=60sRead the assigned QoS class:
kubectl get pod qos-besteffort -n qos-lab -o jsonpath='{.status.qosClass}{"\n"}'Sample output:
BestEffortThis 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:
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: NeverApply the manifest:
kubectl apply -f qos-burstable.yamlWait until the Pod is Ready:
kubectl wait --for=condition=Ready pod/qos-burstable -n qos-lab --timeout=60sCheck the QoS label:
kubectl get pod qos-burstable -n qos-lab -o jsonpath='{.status.qosClass}{"\n"}'Sample output:
BurstableUnequal request and limit pairs always produce Burstable, even when both resources are set.
Guaranteed Pod
Match requests to limits on both CPU and memory:
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: NeverApply the manifest:
kubectl apply -f qos-guaranteed.yamlWait until the Pod is Ready:
kubectl wait --for=condition=Ready pod/qos-guaranteed -n qos-lab --timeout=60sRead the QoS class from status:
kubectl get pod qos-guaranteed -n qos-lab -o jsonpath='{.status.qosClass}{"\n"}'Sample output:
GuaranteedWith all three Pods running, list their classes side by side:
kubectl get pods -n qos-lab -o custom-columns=NAME:.metadata.name,QOS:.status.qosClassSample output:
NAME QOS
qos-besteffort BestEffort
qos-burstable Burstable
qos-guaranteed GuaranteedMulti-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:
kubectl explain pod.spec.containers.resourcesRepresentative relevant 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:
kubectl explain pod.spec.resourcesThe 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:
kubectl describe pod qos-burstable -n qos-labThe container section lists Limits and Requests explicitly:
Limits:
cpu: 500m
memory: 256Mi
Requests:
cpu: 100m
memory: 128Mikubectl 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:
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
fiSample output (usage and metric rounding vary):
POD NAME CPU(cores) MEMORY(bytes)
qos-besteffort app 0m 0Mi
qos-burstable app 0m 0Mi
qos-guaranteed app 4m 0MiIdle 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:
- Add
resources.requestsandresources.limitsunder a Deployment's container template. - 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.resourceswhen the feature is enabled. - Explain why a two-container Pod is Burstable when the app container is Guaranteed but a logging sidecar has only a memory limit.
- Fix
memory: 400mtomemory: 400Miwhen the question means four hundred mebibytes. - When an explicit memory request already exists, raise only
limits.memoryif 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. - Remove the CPU limit only when the prompt explicitly allows unbounded CPU; otherwise leave limits in place.
What's Next
- Kubernetes ResourceQuota and LimitRange with Examples
- Kubernetes Taints and Tolerations with Examples
- Kubernetes Scheduling, nodeSelector and Affinity
References
- Manage resources for containers — Kubernetes documentation
- Assign Pod-level resources — official Pod-level resource task guide
- Resource quotas — namespace-level caps (out of scope for YAML in this lesson)
- Configure Quality of Service for Pods — official QoS task guide
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.

