Kubernetes resource requests en limits

Requests vertellen de scheduler hoeveel CPU en geheugen een pod nodig heeft om op een node te landen. Limits vertellen de kernel wanneer hij moet throttlen of killen. Door ze door elkaar te halen krijg je pods die in Pending blijven hangen, onverwachte OOMKills, of latency op nodes die amper belast zijn. Dit artikel legt de mechanica achter beide uit, hoe Kubernetes er QoS-klassen aan toewijst, en hoe je startwaarden kiest die bij je workload passen.

Requests vs. limits: scheduling vs. enforcement

Requests en limits regelen twee verschillende dingen op twee verschillende momenten. Ze door elkaar halen is de directe oorzaak van de meeste resource-gerelateerde incidenten.

Requests zijn een scheduling-input. De kube-scheduler leest ze om te bepalen welke node genoeg ruimte heeft voor een pod. Een pod die 500m CPU en 256Mi geheugen vraagt, landt alleen op een node die minimaal zoveel vrij heeft. Eenmaal geplaatst mag de container meer verbruiken als de node ruimte over heeft.

Limits zijn een runtime-grens. De kubelet en de Linux-kernel handhaven ze terwijl de pod draait. Overschrijdt een container zijn CPU-limit, dan wordt hij gethrottled. Overschrijdt hij zijn memory-limit, dan wordt het proces gekilld.

Aspect Requests Limits
Gebruikt door kube-scheduler (plaatsing) kubelet + kernel (runtime)
Timing Voordat de pod start Terwijl de pod draait
Mag overschreden worden? Ja, als de node ruimte heeft CPU: nee (throttled). Memory: nee (gekilld)
Als weggelaten Geen scheduling-garantie Geen harde cap op verbruik
Beïnvloedt QoS-klasse Ja Ja

Een regel die mensen verrast: als je wel een limit zet maar de request weglaat, kopieert Kubernetes de limit als request. Dat maakt de pod Guaranteed QoS, wat vaak onbedoeld is en scheduling onnodig beperkt.

CPU requests en limits

CPU is een compressible resource. Raakt een container zijn CPU-limit, dan vertraagt de kernel hem. Het proces overleeft het; het krijgt gewoon minder cycles.

Hoe CPU requests werken

Bij contention verdeelt de Linux Completely Fair Scheduler (CFS) CPU-tijd evenredig op basis van requests. Als Pod A 200m vraagt en Pod B 600m op dezelfde node, krijgt A 25% en B 75% van de beschikbare CPU wanneer ze allebei concurreren.

Eenheden: 1 CPU = 1000 millicores. 250m betekent 25% van één core's schedulingtijd per periode.

Hoe CPU limits werken

Limits worden afgedwongen via CFS-quota. Op cgroup v1 is dat cpu.cfs_quota_us en cpu.cfs_period_us. Op cgroup v2 is het cpu.max. De standaardperiode is 100ms. Een limit van 500m betekent dat de container 50ms mag draaien per 100ms-venster. Is dat budget op, dan throttlet de kernel tot de volgende periode, ook als de node 100% idle CPU heeft.

Dat laatste punt is belangrijk. CPU-throttling is geen signaal dat de node overbelast is. Het is een signaal dat de limit van de container te laag is voor zijn burstpatroon.

Het "moet ik CPU limits zetten?"-debat

De Kubernetes-community is verdeeld. De officiële Kubernetes-blog betoogt dat limits waardevol zijn voor voorspelbare capaciteitsplanning, vooral in multi-tenant clusters. Anderen, waaronder Sysdig en learnk8s, stellen dat CPU-limits vooral latency-gevoelige services schaden, omdat CFS-proportionele sharing via requests al bescherming biedt tegen starvation.

Mijn positie: zet altijd CPU requests. Zet CPU limits alleen als je een concrete reden hebt (multi-tenant isolatie, kostentoerekening, of batchworkloads die je wilt begrenzen). Bij latency-gevoelige services heb ik te vaak gezien dat CPU-throttling latency-pieken veroorzaakte op nodes die amper belast waren.

Go runtime-valkuil

Voor Go 1.25 zet de Go-runtime GOMAXPROCS op het totale aantal CPU's van de node, niet op de limit van de container. Op een 32-core node met een 1-core CPU-limit maakt Go 32 threads aan. Die verbranden het 100ms CFS-quota bijna direct, wat ernstige throttling en versterkte GC-pauzes veroorzaakt. Oplossing: gebruik uber-go/automaxprocs voor Go < 1.25. Go 1.25+ regelt dit zelf.

Memory requests en limits

Memory is een incompressible resource. Overschrijdt een container zijn memory-limit, dan vertraagt de kernel hem niet. Hij killt het proces. Geen geleidelijke degradatie, geen waarschuwing. De pod herstart met exit code 137 en reden OOMKilled.

Hoe memory requests werken

Requests reserveren capaciteit voor scheduling: de node moet minstens dit bedrag aan geheugen vrij hebben. Op cgroup v2 clusters met de MemoryQoS feature gate (alpha, vereist cgroup v2) mapt de request naar memory.min, wat garandeert dat dat bedrag nooit teruggevorderd wordt door de kernel, zelfs niet onder geheugendruk op de node.

Eenheden: bytes met binaire suffixen. Gebruik Mi (mebibytes) en Gi (gibibytes), niet M en G. Het verschil is zo'n 5%, en Kubernetes interpreteert ze letterlijk.

Hoe memory limits werken

Limits zetten het harde plafond via memory.limit_in_bytes (cgroup v1) of memory.max (cgroup v2). Overschrijd het met één byte, en de kernel OOM killer beëindigt het containerproces. De kubelet detecteert de kill, logt het OOMKilled-event, en herstart de container volgens het restart-beleid.

Twee verschillende OOM-scenario's

  1. Container OOM. De container overschrijdt zijn eigen cgroup memory-limit. De kernel killt die container. De pod herstart. Dit is de gebruikelijke situatie en verschijnt als OOMKilled in kubectl describe pod.
  2. Node OOM. De node raakt door zijn geheugen heen voordat de kubelet's eviction manager kan ingrijpen. De kernel OOM killer kiest slachtoffers over de hele node. De QoS-klasse bepaalt welke pods eerst sterven. Dit is zeldzamer maar veel ernstiger.

Waarom je (bijna) altijd memory limits moet zetten

Anders dan bij CPU, waar proportionele sharing onder contention natuurlijke bescherming biedt, heeft memory geen terugvaloptie. Een container zonder memory-limit kan groeien totdat hij node-brede OOM triggert en buren meeneemt. Zet memory limits voor elke productie-workload.

QoS-klassen en eviction-prioriteit

Kubernetes wijst een QoS-klasse toe aan elke pod op basis van hoe requests en limits geconfigureerd zijn. De QoS-klasse bepaalt de eviction-volgorde wanneer de node door zijn resources heen raakt.

Guaranteed

Elke container in de pod heeft CPU- en memory-requests die gelijk zijn aan de limits, en beide zijn ingesteld. Dit is de best beschermde klasse. Guaranteed pods worden pas geëvict als alle Burstable en BestEffort pods weg zijn. Ze komen ook in aanmerking voor exclusieve CPU-coretoewijzing wanneer het CPU Manager static-beleid actief is.

De afweging: geen burstcapaciteit. De pod betaalt altijd de volledige scheduling-kosten van zijn resources, ook als hij maar een fractie verbruikt.

# Guaranteed QoS voorbeeld
resources:
  requests:
    cpu: "500m"
    memory: "256Mi"
  limits:
    cpu: "500m"      # gelijk aan request
    memory: "256Mi"  # gelijk aan request

Burstable

Minimaal één container heeft een request of limit, maar de pod voldoet niet aan de Guaranteed-criteria. Dit is de meest voorkomende QoS-klasse in productie. Burstable pods kunnen boven hun requests uit consumeren als de node ruimte heeft, en ze worden geëvict na BestEffort pods maar voor Guaranteed.

# Burstable QoS voorbeeld
resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "1000m"     # hoger dan request
    memory: "512Mi"  # hoger dan request

BestEffort

Geen enkele container in de pod heeft een request of limit voor CPU of memory. Eerste die geëvict wordt bij elke vorm van nodedruk. Acceptabel voor niet-kritieke batchjobs of dev-omgevingen. Niet voor productieservices.

Eviction-volgorde binnen dezelfde QoS-klasse

Wanneer de kubelet pods moet evicten en meerdere pods dezelfde QoS-klasse delen, rangschikt hij ze op (huidig_verbruik - request) / request. De pod die het meest verbruikt ten opzichte van zijn request wordt als eerste geëvict. Accurate requests zijn dus belangrijk: een pod met lage requests en hoog daadwerkelijk verbruik wordt het eerste eviction-doelwit in zijn klasse.

Wat requests en limits niet zijn

Deze sectie voorkomt de meest voorkomende misverstanden die ik tegenkom.

Limits beïnvloeden scheduling niet. De kube-scheduler leest geen limits. Een pod met request: 100m en limit: 8000m wordt gewoon gescheduled op een node met maar 100m vrij, en kan vervolgens tot 8 cores bursten en buren verhongeren. De officiële documentatie is expliciet: "the kube-scheduler uses [requests] to decide which node to place the Pod on."

CPU limits beschermen de node niet tegen starvation. Bij contention verdeelt CFS CPU evenredig op basis van requests. Een pod zonder limits hogt de node niet wanneer er daadwerkelijk contention is. Limits cappen alleen individuele containers, ongeacht of er contention is.

"OOMKilled" is niet hetzelfde als "evicted." Eviction is een kubelet-initiatief dat pod disruption budgets respecteert (soft eviction) of direct vuurt (hard eviction). OOMKill is een kernel-signaal dat een proces beëindigt omdat het zijn cgroup memory-limit overschreed. Het zijn afzonderlijke mechanismen met verschillende oorzaken en verschillende oplossingsrichtingen.

Memory wordt niet "gethrottled." Alleen CPU wordt gethrottled (vertraagd). Memory wordt gekilld. Als iemand zegt "de pod werd gethrottled op memory," bedoelen ze waarschijnlijk dat hij OOMKilled was, en dat is een fundamenteel ander faalscenario.

Startwaarden bepalen

Vanaf nul beginnen zonder productiedata? Drie methodes, op volgorde van voorkeur.

VPA recommendation-modus

Deploy de Vertical Pod Autoscaler met UpdateMode: Off naast je workload. Laat hem 7 tot 14 dagen usage-data verzamelen. Lees de aanbevelingen met kubectl describe vpa <naam>. Valideer de cijfers tegen daadwerkelijke loadpatronen voordat je ze toepast.

Stel minAllowed en maxAllowed in om weglopende aanbevelingen te voorkomen. En gebruik VPA en HPA niet samen op dezelfde resource-dimensie (allebei op CPU veroorzaakt conflicterende schaalbeslissingen).

Loadtesting met profiling

Deploy met ruime initiële limits. Draai representatieve loadtests (k6, Gatling, Locust) tegen de pod. Monitor container_memory_working_set_bytes en container_cpu_usage_seconds_total in Prometheus. Neem P95 geheugengebruik onder piekbelasting als memory request. Voeg 20% buffer toe voor de memory limit. Neem P95 CPU-gebruik als CPU request. Beslis over een CPU-limit op basis van het workloadtype (latency-gevoelig: geen limit of veel headroom; batch: strakke limit).

kubectl top als startpunt

Voor draaiende workloads zonder observability-stack geeft kubectl top pods realtime verbruik. Vergelijk met huidige requests en limits. Een ruwe startregel: request = 1,1 tot 1,2x gemiddeld geobserveerd verbruik.

Workloadtype CPU request CPU limit Memory request Memory limit
Stateless web-API 100–250m Geen of 2–4x request 128–512Mi 1,5–2x request
Achtergrondworker 100–500m 2x request 256Mi–1Gi 1,2–1,5x request
Database-sidecar 50–100m 2x request 64–128Mi 2x request
Kritieke stateful service Verwacht verbruik = request (Guaranteed) Verwacht verbruik = request (Guaranteed)
Batchjob 100–500m 2x request 256Mi–2Gi = request

Dit zijn startwaarden. Vervang ze door geobserveerde data zodra je die hebt.

Overcommitstrategie

Overcommit treedt op wanneer de som van containerlimits op een node de fysieke capaciteit overschrijdt. De som van requests kan nooit de nodecapaciteit overschrijden, want de scheduler voorkomt dat. Maar limits wel.

Waarom overcommit werkt

Applicaties vertonen bursty gebruikspatronen. Webservices verbruiken typisch 10 tot 30% van gealloceerde resources tijdens normaal gebruik, met pieken tot 60 tot 80%. Als elke pod tegelijk op zijn limit zou draaien, zou de node overbelast raken. In de praktijk gebeurt dat zelden, en overcommit verbetert de clusterdichtheid.

CPU vs. memory overcommit

CPU overcommit is laag risico. Bij contention beschermen CFS-shares pods evenredig op basis van hun requests. Een node met 200% CPU-overcommit (limits opgeteld = 2x node-CPU) handelt contention netjes af via throttling.

Memory overcommit is hoog risico. Er is geen geleidelijke degradatie. Als pods gezamenlijk het beschikbare geheugen overschrijden, begint de kernel processen te killen. Conservatieve memory-overcommit (limits = 1,0 tot 1,5x requests) is de norm.

Het fixed-fraction headroom-patroon

De officiële Kubernetes-blog beveelt limits = requests x (1 + klein_percentage) aan. Limits op 1,1x tot 1,2x requests geven pods een klein burstbudget terwijl de totale overcommit begrensd blijft. Dit levert Burstable QoS op (niet Guaranteed), wat de juiste afweging is voor de meeste workloads.

Verder lezen

Dit artikel behandelt de kernmechanica. Voor specifieke faalscenario's en operationele taken die hierop bouwen:

  • Wanneer een pod herstart met exit code 137, loopt de OOMKilled-gids door diagnose en memory limit-sizing
  • Wanneer latency piekt zonder zichtbare load, behandelt de CPU-throttling gids CFS-mechanica, hoe je throttling-metrics leest, en argumenten voor het verwijderen van CPU limits
  • Wanneer de clusterfactuur te hoog is maar de workloads gezond zijn, loopt de kostenoptimalisatiegids door rightsizing, namespace-quota's en spot-instance integratie

Terugkerende server- of deploymentproblemen?

Ik help teams productie betrouwbaar maken met CI/CD, Kubernetes en cloud—zodat fixes blijven en deploys geen stress meer zijn.

Bekijk DevOps consultancy

Doorzoek deze site

Begin met typen om te zoeken, of blader door de kennisbank en blog.