Skip to main content
RunBook Academy

KubernetesLXXXIV · Resource Capacity PlanningCapacity planning

Resource requests and limits — the resource budgeting primitives

Advanced⏱ ~13 minkubectl

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

Not yet marked complete on this device.

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:

QoSRequestLimit
GuaranteedSet on every container, CPU and memorySet on every container and equal to the request
BurstableAt least one container has a CPU or memory requestAbsent, higher than the request, or set on only some containers
BestEffortNot set on any containerNot 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

  1. Q1. What is the QoS class of a pod with requests but no limits?

  2. Q2. Production workloads should not be BestEffort.

  3. 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.

  4. 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.