Every Kubernetes deployment can carry four numbers: a request and a limit, for CPU and for memory. I ran the Microsoft Learn module's example on its own one-node cluster, then pushed pods past their limits on purpose. Kubernetes did three different things, and only one of them showed up as an error.
This explainer shows what requests and limits really do, and what happens past each limit, with the real output from my own Azure Kubernetes Service (AKS) cluster.
Key points
- A request is a booking. The scheduler adds up the requests of the pods on a node and places a new pod only where its request still fits. It never looks at how busy the node really is, and it never adds up limits. A pod that doesn't fit stays Pending.
- A node gives pods less than it has. On a two-CPU node, AKS keeps 100 millicores for itself (1,900 are allocatable), and the system pods had already booked 927. The module's example asks for 1,000 per pod: neither of its two pods started.
- Past the CPU limit, a container waits. The kernel gives it its share of every 100 ms slice. The same work took 3.09 s instead of 1.64 s, and the pod stayed Running with zero restarts.
- Past the memory limit, the container is killed. OOMKilled, exit code 137, then Kubernetes starts it again. The reason is in the container's last state, not in the events, and its own log just stops. A limit without a request becomes the request.
The request is a booking
A request is what Kubernetes books for your container on a node: so much CPU, so much memory. The scheduler, the part of Kubernetes that picks a node for every new pod, adds up those bookings before it places anything. CPU is counted in millicores: 1,000m is one full CPU.
The module's example asks for two pods, each with 1 CPU and 2 GiB:
resources:requests: # booked by the schedulercpu: "1000m"memory: "2Gi"limits: # enforced by the kernelcpu: "2000m"memory: "4Gi"
The exercise's cluster has one node; mine was a Standard_D2s_v5 with two CPUs and 8 GB. I applied the example and watched both pods for a minute with a small script of mine (like kubectl get pods --watch, with the seconds):
> python watch.py pods api-big --secs 600.4s api-big-6dd546c879-ftqhh Pending 0/10.4s api-big-6dd546c879-q52gv Pending 0/160.3s time is up
Both Pending from the first second to the last: no node assigned, no restart, no crash. kubectl describe nodes shows why:
> kubectl describe nodesCapacity:cpu: 2Allocatable:cpu: 1900mResource Requests Limitscpu 927m (48%) 9642m (507%)
Allocatable is 1,900 millicores, not 2,000: AKS keeps 100 for itself on a node this size. The system pods that AKS runs on every cluster (DNS, networking, metrics) had already booked 927. That leaves 973, and each pod asks for 1,000. Not even the first pod gets a place. Memory had room for both; CPU had room for neither.
The same output shows the other half of the rule: the limits on that node added up to 507 % of its CPU. The scheduler only adds up requests. Limits can promise far more than the node has, and real usage doesn't count either.
A booking also stays as long as its pod exists, even if the pod never runs. While I recorded the previous video, my earlier test pods were still on the node, one of them stuck at its image and never started. One more pod asked for only 50 millicores and stayed Pending too:
> kubectl describe pod -l app=api-crashRequests:cpu: 50mFailedScheduling … 0/1 nodes are available: 1 Insufficient cpu. …
When a pod is Pending, describe the pod, then the node. Ask for less, delete pods you don't need, or add a node.
Past the CPU limit
A limit is the ceiling: the most your container may use. The module's hands-on exercise sets a CPU limit of 500m, half a CPU. My demo app has a /work endpoint, a loop that keeps one CPU busy for about a second and a half. I deployed it twice: with the exercise's limits (api-mem) and with a CPU limit of 2 (api-fast), and ran the same request inside each pod:
> kubectl exec deploy/api-fast -- python get.py http://localhost:8000/work200 {"pod": "api-fast-…", "wall_s": 1.64, "cpu_s": 1.63}> kubectl exec deploy/api-mem -- python get.py http://localhost:8000/work200 {"pod": "api-mem-…", "wall_s": 3.09, "cpu_s": 1.54}
With a limit of two, the work had a whole CPU the whole time: 1.64 seconds on the clock. With half a CPU, almost twice as long, for about the same CPU time. A second try gave 1.62 and 3.11.
That's throttling. Every scheduling period (100 ms by default) the kernel gives the container its share, 50 ms for half a CPU, and then makes it wait for the next slice. The work still gets done, in twice as many slices. And Kubernetes doesn't notice:
> kubectl get pods -l app=api-memNAME READY STATUS RESTARTS AGEapi-mem-666bbc8499-kdp82 1/1 Running 0 17s
Over its CPU limit, a container is never killed. It waits. Nothing in the status tells you; you see it as slower answers.
Past the memory limit
Same pod, same exercise settings: it requests 256Mi of memory and its limit is 512Mi (Mi = mebibytes, the binary kind of megabytes). A second endpoint takes memory in steps of 50 MB, holds on to it and prints a line after each step. I asked it for 700.
The module says a request set too low causes these kills. So where does it die, at the request or at the limit? At the limit, and it doesn't slow down first:
> kubectl exec deploy/api-mem -- python get.py "http://localhost:8000/alloc?mb=700"command terminated with exit code 137
137 is 128 + 9: signal 9, SIGKILL, a kill no program can catch or clean up after. The kernel can make a process wait for the CPU, but it can't take back memory the app is already holding, so when the container goes over its memory limit, the kernel's out-of-memory killer stops it. Where Kubernetes says so:
> kubectl describe pod -l app=api-memState: RunningLast State: TerminatedReason: OOMKilledExit Code: 137Restart Count: 1Limits:memory: 512MiRequests:memory: 256MiNormal Started 46s (x2 over 63s) … Container started
The events at the bottom said nothing about it. The reason sits in the container's last state, and Kubernetes had already started the container again, in the same second. What the app said before it died:
> kubectl logs deploy/api-mem --previousaks-api v1 listening on :8000holding 50 MBholding 100 MB…holding 400 MBholding 450 MB
Then nothing: no error, no goodbye. The next 50, plus the app's own memory, crossed 512. Here --previous worked because the new container was already running, so the previous one was the container that got killed (the previous explainer in this series shows when it fails).
And the request: 256Mi. The app held 450 far above it with no problem. A request is not a ceiling; the limit is.
| Past the… | What happens | Restarts | Where you see it |
|---|---|---|---|
| request (memory or CPU) | nothing, if the node has room | 0 | — |
| CPU limit | throttled: waits for its share of each 100 ms slice | 0 | slower answers |
| memory limit | OOMKilled, exit code 137, restarted | +1 | describe pod → Last State |
A limit alone
How can a limit alone keep a pod from ever starting? I gave a pod a CPU limit of 3 and no request at all, then asked Kubernetes which resources it had stored:
> kubectl get pod -l app=api-limits -o jsonpath='{.items[0].spec.containers[0].resources}'{"limits":{"cpu":"3"},"requests":{"cpu":"3"}}
I never wrote that request. Without a request, Kubernetes copies the limit and books it: three CPUs on a node that gives pods 1.9.
> kubectl describe pod -l app=api-limitsFailedScheduling … 0/1 nodes are available: 1 Insufficient cpu. …
Pending for the whole 30 seconds I watched. So set both: a request for what your pod needs to run, and a limit for what it must never pass.
What happened when I ran it
Tested on October 26, 2026 in my own Azure subscription (AKS 1.36 in westeurope, Azure CLI 2.88.0, kubectl 1.36); everything was deleted afterwards. One AKS cluster served four videos of this series; this page uses its resource steps. The cluster had one Standard_D2s_v5 node (2 vCPU, 8 GB) and the free tier; the app is a small Python API shaped like the module exercise's (port 8000), without the exercise's model in Microsoft Foundry, plus two endpoints for this test: /work (a CPU loop) and /alloc (holds memory in 50 MB steps; its MB are binary, like the Mi of the limit).
- The node: capacity 2 CPUs and 8,126,896 KiB of memory, allocatable 1,900m and 5,927,344 KiB; 14 system pods had requested 927m CPU (48 %) and 994Mi memory (17 %).
- The module's example: both pods Pending for 60 seconds,
Node: <none>,PodScheduled: False. - The OOM kill: the container started at 15:01:19 and was killed at 15:01:36; the new one started in the same second.
What the video leaves out
- QoS classes (both pods here were Burstable: requests below limits) and node-pressure eviction, where pods using more than their requests are evicted first.
- How to measure throttling directly (the container's CPU statistics),
kubectl top, LimitRange and ResourceQuota (defaults set at admission), the Vertical Pod Autoscaler.
Further reading
- Microsoft Learn module: Deploy applications to Azure Kubernetes Service
- Resource management for pods and containers (Kubernetes docs)
- Node resource reservations in AKS
- Troubleshoot OOMKilled errors in AKS



