KubernetesLXXXIV · Resource Capacity PlanningCapacity planning
Resource requests and limits — the resource budgeting primitives
What you'll learn
- Explain resource requests and limits
- Identify the QoS classes
- Configure the requests and limits for production
- Plan the resource budgeting
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
Resource requests and limits are Kubernetes’ resource budgeting primitives. The request is the scheduler’s budget; the limit is the cgroup ceiling. The QoS classes are derived from the requests and limits. This lesson walks the primitives, the scheduler integration, the QoS classes, and the production discipline.
The request
The request is the resource guarantee:
spec:
containers:
- name: nginx
resources:
requests:
cpu: 500m
memory: 1Gi
The scheduler uses the request to fit the pod on a node:
flowchart LR
A[Pod with request 500m CPU] --> B[Scheduler]
B --> C[Node with 4 vCPU available]
C --> D[Pod scheduled]
The request is the scheduler’s budget.
The limit
The limit is the resource ceiling:
spec:
containers:
- name: nginx
resources:
limits:
cpu: 1
memory: 2Gi
The limit is enforced by the cgroup:
flowchart LR
A[Pod with limit 1 CPU] --> B[cgroup]
B --> C[CPU limited to 1]
C --> D[CPU throttled if exceeded]
The limit is the cgroup ceiling.
The QoS classes
The QoS classes are derived from the requests and limits:
| QoS | Request | Limit |
|---|---|---|
| Guaranteed | Set on every container, CPU and memory | Set on every container and equal to the request |
| Burstable | At least one container has a CPU or memory request | Absent, higher than the request, or set on only some containers |
| BestEffort | Not set on any container | Not set on any container |
Read the Burstable row carefully: it is the catch-all. A Pod is Burstable whenever it has at least one request and does not meet the Guaranteed criteria. Requests with no limits at all is Burstable, not BestEffort — this is the single most common misreading of the table, and it matters because the two classes sit at opposite ends of the eviction order. BestEffort means the Pod declares nothing.
flowchart LR
A[Pod] --> B{Request set?}
B -->|yes| C{Limit equals request?}
B -->|no| D[BestEffort]
C -->|yes| E[Guaranteed]
C -->|no| F[Burstable]
The QoS class determines the pod’s eviction priority.
The Guaranteed QoS
The Guaranteed QoS means the request equals the limit:
resources:
requests:
cpu: 1
memory: 1Gi
limits:
cpu: 1
memory: 1Gi
The pod is guaranteed to have the resources. The pod’s eviction priority is the lowest; the pod is the last to be evicted under node pressure.
The Burstable QoS
The Burstable QoS means the request is set but the limit is higher:
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 1
memory: 2Gi
The pod has a guaranteed minimum (the request) and may use up to the limit. The pod’s eviction priority is between Guaranteed and BestEffort.
The BestEffort QoS
The BestEffort QoS means no request and no limit:
# No resources configured
spec:
containers:
- name: nginx
image: nginx:1.25
The pod has no guaranteed resources. The pod’s eviction priority is the highest; the pod is the first to be evicted under node pressure.
The CPU throttling
The CPU limit causes throttling:
Pod limit: 1 CPU
Pod usage: 1.5 CPU
Cgroup: throttle the pod to 1 CPU
The throttling is at the cgroup level. The pod’s CPU usage is capped at the limit, even if the node has spare CPU.
The enforcement is not a smooth cap, and this is what
surprises people. The CFS bandwidth controller writes the
limit into cpu.max as a quota per 100 ms period
(cpu.cfs_period_us on cgroup v1). A container with a
1 CPU limit gets 100 ms of CPU time per 100 ms period. If
it burns that in the first 40 ms — which a multi-threaded
runtime easily does — it is descheduled for the remaining
60 ms. The average utilisation looks fine on a dashboard;
the p99 latency carries a 60 ms cliff.
Read the throttling directly rather than inferring it from CPU utilisation:
# per-container throttling, from cAdvisor via kubelet
container_cpu_cfs_throttled_periods_total
/ container_cpu_cfs_periods_total
Anything sustained above a few percent on a latency-sensitive service is worth acting on.
The CPU throttling is the cost of having a limit, and the only way to remove the cost is to remove or raise the limit. Setting the limit equal to the request does not help — see the callout below.
The memory OOM
The memory limit causes OOM:
Pod limit: 1Gi memory
Pod usage: 1.2Gi memory
Cgroup: OOM kill the pod
The OOM kill is at the cgroup level. The pod is killed when the usage exceeds the limit.
The memory OOM is unavoidable for memory-bounded workloads. The mitigation is to set the limit higher than the actual usage, or to use the Guaranteed QoS.
The production configuration
The production configuration:
spec:
containers:
- name: nginx
resources:
requests:
cpu: 500m
memory: 1Gi
limits:
cpu: 1
memory: 2Gi
The configuration is per workload:
- Latency-sensitive workloads: set a memory request = memory limit, and set a CPU request with no CPU limit. This is Burstable, and it is deliberate: a CPU limit is the thing that introduces throttling, and a latency-sensitive service is precisely what you do not want throttled. Accept that the Pod is Burstable rather than buy Guaranteed at the price of a CFS quota.
- Batch workloads: CPU request only, no CPU limit. This is Burstable (a Pod with requests and no limits is never BestEffort). Batch work absorbs idle CPU and is the right thing to evict first when the node is under pressure.
- Memory-sensitive workloads: memory request = memory limit. Memory has no equivalent of throttling — the cgroup either fits or the container is OOM-killed — so a memory limit costs nothing in latency and bounds the blast radius of a leak.
The scheduler behavior
The scheduler fits the pod on a node:
Node capacity: 8 vCPU, 32Gi memory
Request sum: 4 vCPU, 16Gi memory
Pod to schedule: 1 vCPU, 2Gi memory
Node has: 8 - 4 = 4 vCPU available, 32 - 16 = 16Gi memory available
Pod fits: 1 vCPU <= 4 vCPU, 2Gi <= 16Gi → OK
The scheduler fits the pod if the request fits.
The VPA integration
The VPA right-sizes the requests:
# VPA recommends:
target:
cpu: 600m
memory: 1.2Gi
The recommended requests become the pod’s requests (via the VPA admission controller).
Cross-course references
- The VPA course (Part LXXXIII) covers the right-sizing.
- The HPA course (Part LXXXII) covers the scaling.
- The Capacity Sizing course (Part LXXV) covers the node sizing.
Quiz
Knowledge check · 4 questions
Q1. What is the QoS class of a pod with requests but no limits?
Q2. Production workloads should not be BestEffort.
Q3. Walk the configuration of the resource requests and limits for a production workload.
Deployment nginx with 5 replicas. The workload is a web service. The team is configuring the resource requests and limits for production.
Q4. What is CPU throttling, and how does it affect latency-sensitive workloads?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Always set the requests. Avoid BestEffort.
- Set the limits thoughtfully. Consider the workload.
- Use the VPA for right-sizing. Set the requests automatically.
- Monitor the QoS classes. kubectl describe pod shows the QoS.
- Document the resource budgeting. The requests, the limits, the rationale.
- Test the resource budgeting. Use load generation to verify.
The resource requests and limits are the resource budgeting primitives. Operating it well is setting them per workload, monitoring the QoS, and using the VPA.