Skip to main content
RunBook Academy

KubernetesXII · Resource Requests and LimitsResource requests and limits

CPU and memory requests — scheduling and the resource model

Advanced⏱ ~18 minkubectl

What you'll learn

  • Distinguish CPU and memory as resource types (compressible vs incompressible)
  • Configure resource requests for scheduling
  • Reason about request sizing and its impact on placement
  • Understand how requests and limits interact with the kubelet

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 the foundation of Kubernetes scheduling and enforcement. CPU and memory behave differently under contention; requests drive scheduling, limits drive cgroup enforcement. This lesson covers the resource model, how the scheduler uses requests, and how the kubelet enforces limits.

CPU and memory as resource types

Two fundamentally different resource types:

  • CPU: compressible. A container that exceeds its CPU share is throttled — the kernel slows it down. The container can continue running; it just runs slower.
  • Memory: incompressible. A container that exceeds its memory limit is OOMKilled — the kernel kills the process. The container must restart.
flowchart LR
    CPU["CPU usage > limit"] --> Throttle[Throttled; runs slower]
    Mem["Memory usage > limit"] --> OOM[OOMKilled; restarts]

This asymmetry shapes everything about resource management:

  • CPU pressure is gradual: latency goes up, throughput goes down. The application has time to react.
  • Memory pressure is immediate: the process dies. There is no graceful response.

Production discipline: every container must have a memory limit. CPU limits are debated (some teams omit them to avoid throttling); memory limits are mandatory.

Resource requests and limits

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

Two fields per resource:

  • requests: the resources reserved for the container. The scheduler uses this for placement.
  • limits: the maximum the container can use. The kubelet enforces this via cgroups.

The semantics:

  • CPU request: the scheduler reserves 100 millicores of CPU on the chosen node. If the node cannot fulfil this request, the Pod is not scheduled there.
  • CPU limit: the container can use up to 500 millicores. If it tries to use more, the kernel throttles it.
  • Memory request: the scheduler reserves 128 MiB of memory on the chosen node.
  • Memory limit: the container can use up to 512 MiB. If it tries to use more, the kernel OOMKills it.

The relationship between requests and limits determines the QoS class (Part XIII):

  • Guaranteed: requests == limits for both CPU and memory.
  • Burstable: requests < limits for at least one resource.
  • BestEffort: no requests or limits set.

CPU units

CPU is specified in cores or millicores:

  • 1 = 1 core.
  • 100m = 100 millicores = 0.1 core.
  • 0.5 = 500 millicores = half a core.

A node with 4 cores has 4000 millicores of CPU. A Pod requesting 500m reserves 1/8 of the node’s CPU.

Memory units

Memory is specified in bytes with IEC prefixes:

  • 128Mi = 128 × 1024 × 1024 bytes = 128 mebibytes.
  • 1Gi = 1024 MiB = 1 gibibyte.
  • 1G = 10^9 bytes = 1 gigabyte (slightly different from Gi).

The Mi and Gi suffixes are exact powers of 2; M and G are powers of 10. For memory limits, prefer Mi/Gi to avoid confusion.

How requests drive scheduling

The scheduler’s job: place each Pod on a node that can satisfy its requests. The scheduler subtracts the Pod’s requests from each candidate node’s allocatable; if the result is non-negative for CPU and memory, the Pod can be scheduled.

flowchart LR
    Pod[Pod: requests cpu=500m, mem=1Gi] --> Sched[Scheduler]
    Sched --> Node1[Node 1: allocatable cpu=2, mem=4Gi]
    Sched --> Node2[Node 2: allocatable cpu=4, mem=8Gi]
    Node1 --> Check1[2 - 0.5 >= 0 and 4 - 1 >= 0]
    Check1 -- yes --> Fit1[Pod fits]
    Node2 --> Check2[4 - 0.5 >= 0 and 8 - 1 >= 0]
    Check2 -- yes --> Fit2[Pod fits]

Both nodes can host the Pod. The scheduler picks one based on other criteria (affinity, taints, spread).

If no node can satisfy the Pod’s requests, the Pod stays in Pending with FailedScheduling events.

How the kubelet enforces limits

The kubelet configures cgroups for each container:

  • cpu cgroup: contains the CFS quota and period. The container can use up to its CPU limit; beyond that, the kernel throttles it.
  • memory cgroup: contains the memory limit. If the container’s memory usage exceeds the limit, the kernel triggers the cgroup OOM killer.
flowchart LR
    Kubelet[Kubelet] --> CGroup["cgroup: cpu"]
    Kubelet --> MGroup["cgroup: memory"]
    CGroup --> Throttle[Throttled]
    MGroup --> OOM[OOMKilled]

The enforcement is at the kernel level, not the kubelet. The kubelet sets up the cgroups; the kernel enforces them.

The CPU request and CFS shares

CPU request determines CFS shares, not just the scheduler. A container’s CFS shares are proportional to its CPU request. Under contention, the kernel allocates CPU proportionally to shares.

containers:
- name: low-priority
  resources:
    requests:
      cpu: 100m
- name: high-priority
  resources:
    requests:
      cpu: 1000m

Under CPU contention, the high-priority container gets 10x the CPU of the low-priority container. This is CFS shares behaviour.

Request sizing in production

The discipline:

  • CPU request: typical usage. The scheduler reserves this; under-utilisation wastes node capacity, over- utilisation causes scheduling failures.
  • CPU limit: peak usage plus headroom. Throttling at peak is better than starving other containers.
  • Memory request: typical usage. Same logic as CPU request.
  • Memory limit: peak usage plus headroom. OOMKill at peak is worse than scheduling failure, but a memory leak that grows unboundedly is worse than OOMKill.

For a typical web service:

resources:
  requests:
    cpu: 100m      # typical: 50-100m
    memory: 128Mi  # typical: 80-128Mi
  limits:
    cpu: 500m      # peak: 300-500m
    memory: 512Mi  # peak: 300-512Mi

Production discipline: measure before sizing. Use kubectl top pod or Prometheus metrics to understand actual usage; size requests and limits accordingly.

Production patterns

VPA (Vertical Pod Autoscaler) for sizing:

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web
  updatePolicy:
    updateMode: Auto

VPA observes the workload’s actual usage and recommends (or applies) resource requests. Part LXXXIII-Kubernetes-VPA covers VPA in depth.

ResourceQuota for namespace limits:

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-a-prod-quota
  namespace: team-a-prod
spec:
  hard:
    requests.cpu: "32"
    requests.memory: 64Gi
    limits.cpu: "64"
    limits.memory: 128Gi

The quota caps the total requests and limits in the namespace. Part CVIII-Kubernetes-ResourceQuota covers quotas in depth.

LimitRange for default requests:

apiVersion: v1
kind: LimitRange
metadata:
  name: default
  namespace: team-a-prod
spec:
  limits:
  - type: Container
    default:
      cpu: 500m
      memory: 512Mi
    defaultRequest:
      cpu: 100m
      memory: 128Mi

The LimitRange sets default requests and limits for containers that don’t declare them. Part CIX-Kubernetes-LimitRange covers LimitRange in depth.

Cross-course references

  • The Linux course part XXXVII-Linux-Resources covers cgroups, CFS quotas, and memory limits; the kubelet exposes these primitives to containers.
  • The Ansible course part XXXVII-Ansible-Drift covers configuration drift; resource sizing is the cluster-level equivalent.
  • The Observability course part LXXXVII-Kubernetes-MetricsServer covers kubectl top and metrics that inform resource sizing.

Quiz

Knowledge check · 4 questions

  1. Q1. What is the key difference between CPU and memory as resource types in Kubernetes?

  2. Q2. It is acceptable to omit memory limits on production containers if memory usage is well-understood.

  3. Q3. A team deploys 100 Pods on a 3-node cluster. Each Pod requests 1 CPU. The cluster becomes slow under load. Walk through the diagnosis.

    Cluster: 3 nodes, each with 4 CPU capacity. After kubelet reservations, each node has ~3.6 CPU allocatable. Total cluster allocatable: ~10.8 CPU. 100 Pods each requesting 1 CPU = 100 CPU requested. 100 Pods each with a limit of 2 CPU = 200 CPU limit.

  4. Q4. What happens when a container exceeds its CPU limit, and what is the production implication?

Passing score: 75%. Answers are checked in this browser.

Production discipline

  • Set memory limits on every container. Memory is incompressible; OOMKill at the limit is better than node-wide eviction.
  • Set CPU requests based on typical usage. Requests drive scheduling; over-sized requests waste node capacity.
  • Measure before sizing. Use kubectl top and Prometheus to understand actual usage; size requests and limits from data, not guesses.
  • Distinguish CPU behaviour from memory behaviour. Throttling is gradual; OOMKill is immediate.
  • Pair resources with QoS and quotas. Requests determine the QoS class (Part XIII); namespaces enforce ResourceQuotas (Part CVIII).