• English icon
  • German icon
Erklärvideo

Was passiert, wenn ein Pod mehr braucht als sein Limit?

· Reihe: Container auf Azure

azureakskubernetesresourceskubectldevops

Das Video ist auf Englisch.

Jedes Kubernetes-Deployment kann vier Zahlen tragen: einen Request und ein Limit, für CPU und für Arbeitsspeicher. Ich habe das Beispiel aus dem Microsoft-Learn-Modul auf seinem eigenen Cluster mit einem Node laufen lassen und dann Pods absichtlich über ihre Limits getrieben. Kubernetes hat drei verschiedene Dinge getan, und nur eines davon erschien als Fehler.

Dieser Explainer zeigt, was Requests und Limits wirklich tun und was hinter jedem Limit passiert, mit der echten Ausgabe aus meinem eigenen Azure-Kubernetes-Service-Cluster (AKS). Das Video ist auf Englisch.

Das Wichtigste

  • Ein Request ist eine Buchung. Der Scheduler addiert die Requests der Pods auf einem Node und setzt einen neuen Pod nur dort hin, wo sein Request noch passt. Wie ausgelastet der Node wirklich ist, schaut er nie an, und Limits addiert er nie. Ein Pod, der nicht passt, bleibt Pending.
  • Ein Node gibt Pods weniger, als er hat. Auf einem Node mit zwei CPUs behält AKS 100 Millicores für sich (1.900 sind allocatable), und die System-Pods hatten bereits 927 gebucht. Das Beispiel des Moduls verlangt 1.000 pro Pod: Keiner seiner beiden Pods ist gestartet.
  • Über dem CPU-Limit wartet ein Container. Der Kernel gibt ihm seinen Anteil an jedem 100-ms-Abschnitt. Dieselbe Arbeit dauerte 3,09 s statt 1,64 s, und der Pod blieb Running, ohne einen einzigen Neustart.
  • Über dem Speicher-Limit wird der Container beendet. OOMKilled, Exit-Code 137, danach startet Kubernetes ihn neu. Der Grund steht im letzten Zustand des Containers, nicht in den Events, und sein eigenes Log bricht einfach ab. Ein Limit ohne Request wird zum Request.

Der Request ist eine Buchung

Ein Request ist das, was Kubernetes für Ihren Container auf einem Node bucht: so viel CPU, so viel Speicher. Der Scheduler, der Teil von Kubernetes, der für jeden neuen Pod einen Node auswählt, addiert diese Buchungen, bevor er etwas platziert. CPU wird in Millicores gezählt: 1.000m sind eine ganze CPU.

Das Beispiel des Moduls verlangt zwei Pods mit je 1 CPU und 2 GiB:

resources:
requests: # booked by the scheduler
cpu: "1000m"
memory: "2Gi"
limits: # enforced by the kernel
cpu: "2000m"
memory: "4Gi"

Der Cluster der Übung hat einen Node; meiner war ein Standard_D2s_v5 mit zwei CPUs und 8 GB. Ich habe das Beispiel angewendet und beide Pods eine Minute lang mit einem kleinen eigenen Skript beobachtet (wie kubectl get pods --watch, mit den Sekunden):

> python watch.py pods api-big --secs 60
0.4s api-big-6dd546c879-ftqhh Pending 0/1
0.4s api-big-6dd546c879-q52gv Pending 0/1
60.3s time is up

Beide Pending von der ersten bis zur letzten Sekunde: kein Node zugewiesen, kein Neustart, kein Absturz. kubectl describe nodes zeigt, warum:

> kubectl describe nodes
Capacity:
cpu: 2
Allocatable:
cpu: 1900m
Resource Requests Limits
cpu 927m (48%) 9642m (507%)

Allocatable sind 1.900 Millicores, nicht 2.000: AKS behält auf einem Node dieser Größe 100 für sich. Die System-Pods, die AKS auf jedem Cluster betreibt (DNS, Netzwerk, Metriken), hatten bereits 927 gebucht. Bleiben 973, und jeder Pod verlangt 1.000. Nicht einmal der erste Pod bekommt einen Platz. Arbeitsspeicher hätte für beide gereicht, CPU für keinen.

Dieselbe Ausgabe zeigt die andere Hälfte der Regel: Die Limits auf diesem Node ergaben zusammen 507 % seiner CPU. Der Scheduler addiert nur Requests. Limits können weit mehr versprechen, als der Node hat, und die tatsächliche Auslastung zählt ebenfalls nicht.

Eine Buchung bleibt außerdem bestehen, solange ihr Pod existiert, auch wenn der Pod nie läuft. Während ich das vorige Video aufgenommen habe, waren meine früheren Test-Pods noch auf dem Node, einer davon hing an seinem Image fest und ist nie gestartet. Ein weiterer Pod verlangte nur 50 Millicores und blieb ebenfalls Pending:

> kubectl describe pod -l app=api-crash
Requests:
cpu: 50m
FailedScheduling … 0/1 nodes are available: 1 Insufficient cpu. …

Wenn ein Pod Pending ist, beschreiben Sie zuerst den Pod und dann den Node. Fordern Sie weniger an, löschen Sie Pods, die Sie nicht brauchen, oder fügen Sie einen Node hinzu.

Über dem CPU-Limit

Ein Limit ist die Obergrenze: das Maximum, das Ihr Container nutzen darf. Die praktische Übung des Moduls setzt ein CPU-Limit von 500m, also eine halbe CPU. Meine Demo-App hat einen /work-Endpunkt, eine Schleife, die eine CPU etwa anderthalb Sekunden lang beschäftigt. Ich habe sie zweimal bereitgestellt: mit den Limits der Übung (api-mem) und mit einem CPU-Limit von 2 (api-fast), und in jedem Pod dieselbe Anfrage ausgeführt:

> kubectl exec deploy/api-fast -- python get.py http://localhost:8000/work
200 {"pod": "api-fast-…", "wall_s": 1.64, "cpu_s": 1.63}
> kubectl exec deploy/api-mem -- python get.py http://localhost:8000/work
200 {"pod": "api-mem-…", "wall_s": 3.09, "cpu_s": 1.54}

Mit einem Limit von zwei hatte die Arbeit die ganze Zeit eine ganze CPU: 1,64 Sekunden auf der Uhr. Mit einer halben CPU fast doppelt so lange, bei etwa derselben CPU-Zeit. Ein zweiter Versuch ergab 1,62 und 3,11.

Das ist Throttling. In jeder Scheduling-Periode (standardmäßig 100 ms) gibt der Kernel dem Container seinen Anteil, 50 ms bei einer halben CPU, und lässt ihn dann auf den nächsten Abschnitt warten. Die Arbeit wird trotzdem erledigt, nur in doppelt so vielen Abschnitten. Und Kubernetes bemerkt es nicht:

> kubectl get pods -l app=api-mem
NAME READY STATUS RESTARTS AGE
api-mem-666bbc8499-kdp82 1/1 Running 0 17s

Über seinem CPU-Limit wird ein Container nie beendet. Er wartet. Nichts im Status verrät es; Sie sehen es nur an langsameren Antworten.

Über dem Speicher-Limit

Derselbe Pod, dieselben Einstellungen der Übung: Er fordert 256Mi Arbeitsspeicher an, sein Limit liegt bei 512Mi (Mi = Mebibyte, die binäre Art von Megabyte). Ein zweiter Endpunkt belegt Speicher in Schritten von 50 MB, hält ihn fest und schreibt nach jedem Schritt eine Zeile. Ich habe 700 angefordert.

Das Modul sagt, ein zu niedrig gesetzter Request verursache diese Abbrüche. Wo stirbt der Container also, am Request oder am Limit? Am Limit, und er wird vorher nicht langsamer:

> kubectl exec deploy/api-mem -- python get.py "http://localhost:8000/alloc?mb=700"
command terminated with exit code 137

137 ist 128 + 9: Signal 9, SIGKILL, ein Abbruch, den kein Programm abfangen oder hinter sich aufräumen kann. Der Kernel kann einen Prozess auf die CPU warten lassen, aber er kann keinen Speicher zurücknehmen, den die App bereits hält. Geht der Container über sein Speicher-Limit, beendet ihn der Out-of-Memory-Killer des Kernels. Wo Kubernetes das sagt:

> kubectl describe pod -l app=api-mem
State: Running
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Restart Count: 1
Limits:
memory: 512Mi
Requests:
memory: 256Mi
Normal Started 46s (x2 over 63s) … Container started

Die Events am Ende der Ausgabe sagten nichts dazu. Der Grund steht im letzten Zustand des Containers, und Kubernetes hatte den Container bereits in derselben Sekunde neu gestartet. Was die App vor ihrem Ende ausgegeben hat:

> kubectl logs deploy/api-mem --previous
aks-api v1 listening on :8000
holding 50 MB
holding 100 MB
…
holding 400 MB
holding 450 MB

Dann nichts mehr: kein Fehler, kein Abschied. Die nächsten 50 plus der eigene Speicher der App überschritten 512. Hier funktionierte --previous, weil der neue Container schon lief; der vorige war also der beendete (der vorige Explainer dieser Reihe zeigt, wann es scheitert).

Und der Request: 256Mi. Die App hielt 450, weit darüber, ohne Probleme. Ein Request ist keine Obergrenze; das Limit ist es.

Über dem …Was passiertNeustartsWo Sie es sehen
Request (Speicher oder CPU)nichts, solange der Node Platz hat0—
CPU-LimitThrottling: wartet auf seinen Anteil an jedem 100-ms-Abschnitt0langsamere Antworten
Speicher-LimitOOMKilled, Exit-Code 137, Neustart+1describe pod → Last State

Ein Limit allein

Wie kann ein Limit allein einen Pod am Starten hindern? Ich habe einem Pod ein CPU-Limit von 3 gegeben und gar keinen Request, dann Kubernetes gefragt, welche Ressourcen es gespeichert hat:

> kubectl get pod -l app=api-limits -o jsonpath='{.items[0].spec.containers[0].resources}'
{"limits":{"cpu":"3"},"requests":{"cpu":"3"}}

Diesen Request habe ich nie geschrieben. Ohne Request kopiert Kubernetes das Limit und bucht es: drei CPUs auf einem Node, der Pods 1,9 gibt.

> kubectl describe pod -l app=api-limits
FailedScheduling … 0/1 nodes are available: 1 Insufficient cpu. …

Pending für die ganzen 30 Sekunden, die ich zugesehen habe. Setzen Sie also beides: einen Request für das, was Ihr Pod zum Laufen braucht, und ein Limit für das, was er nie überschreiten darf.

Was passierte, als ich es ausgeführt habe

Getestet am 26. Oktober 2026 in meinem eigenen Azure-Abonnement (AKS 1.36 in westeurope, Azure CLI 2.88.0, kubectl 1.36); anschließend wurde alles gelöscht. Ein AKS-Cluster diente vier Videos dieser Reihe; diese Seite nutzt seine Ressourcen-Schritte. Der Cluster hatte einen Standard_D2s_v5-Node (2 vCPU, 8 GB) und den Free-Tarif; die App ist eine kleine Python-API nach dem Vorbild der Übung (Port 8000), ohne das Modell der Übung in Microsoft Foundry, plus zwei Endpunkte für diesen Test: /work (eine CPU-Schleife) und /alloc (hält Speicher in 50-MB-Schritten; ihre MB sind binär, wie die Mi des Limits).

  • Der Node: Kapazität 2 CPUs und 8.126.896 KiB Speicher, allocatable 1.900m und 5.927.344 KiB; 14 System-Pods hatten 927m CPU (48 %) und 994Mi Speicher (17 %) angefordert.
  • Das Beispiel des Moduls: beide Pods 60 Sekunden lang Pending, Node: <none>, PodScheduled: False.
  • Der OOM-Abbruch: Der Container startete um 15:01:19 und wurde um 15:01:36 beendet; der neue startete in derselben Sekunde.

Was das Video weglässt

  • QoS-Klassen (beide Pods hier waren Burstable: Requests unter den Limits) und Eviction bei Ressourcendruck auf dem Node, bei der Pods, die mehr als ihre Requests nutzen, zuerst verdrängt werden.
  • Wie man Throttling direkt misst (die CPU-Statistiken des Containers), kubectl top, LimitRange und ResourceQuota (Standardwerte bei der Zulassung), der Vertical Pod Autoscaler.

Weiterlesen

Video-Transkript (Englisch)

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. Only one of them showed up as an error.

In this video, you'll see what requests and limits really do, and what happens past each limit. And at the end: how a limit alone can keep a pod from ever starting.

What does a request actually do? 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. A thousand millicores are one full CPU. The module's example asks for two pods, one CPU and two gigabytes each. The exercise's cluster has one node, and mine had two CPUs and eight gigabytes of memory. So how many of the two pods start? Neither of them. I applied the example and watched both pods for a minute, with a small script of mine: like kubectl get pods in watch mode, with the seconds. Both Pending, from the first second to the last. No node assigned, no restart, no crash. So where did two CPUs go? I asked Kubernetes to describe the node, to see what it can give, and what is already booked. Allocatable: nineteen hundred millicores, not two thousand. AKS keeps a hundred for itself on a node this size. And the system pods that AKS runs on every cluster, for DNS, networking and metrics, had already booked nine hundred twenty-seven. That leaves nine hundred seventy-three. Each pod asks for a thousand. It doesn't fit, so not even the first pod gets a place. Memory had room for both. CPU had room for neither. The scheduler only adds up these bookings. It doesn't care how busy the node really is. And a booking stays as long as its pod exists, even if the pod never runs. While I recorded the previous video, about pods that never start, my earlier test pods were still on the node. One more pod asked for only fifty millicores, a twentieth of a CPU, and stayed Pending too. I described it. Insufficient CPU. The earlier pods had booked the rest, even one that never got past pulling its image. The request is a booking. If it doesn't fit, the pod waits in Pending, however idle the node is. Ask for less, delete pods you don't need, or add a node.

And what happens at the CPU limit? A limit is the ceiling: the most your container may use. Limits aren't booked at all: on my node, the limits of the system pods added up to five hundred and seven percent of its CPU. The module's hands-on exercise sets a CPU limit of five hundred millicores, 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: once with the exercise's limits, as api-mem, the pod I'll fill with memory later, and once with a CPU limit of two, as api-fast. The half-CPU pod gets work that wants a whole CPU. Is that pod killed, or slowed down? Slowed down. Nothing was killed. First, the pod with the limit of two. I ran the work from inside the pod and asked for the time on the clock, and the CPU time it used. One point six four seconds on the clock, and one point six three of CPU. It had a whole CPU, the whole time. Then exactly the same request, to the pod with the exercise's limit of half a CPU. Three point zero nine seconds. Almost twice as long, for about the same CPU time, one and a half seconds. A second try gave almost the same numbers. That's throttling. Every hundred milliseconds, the kernel gives the container its share: fifty milliseconds for half a CPU. Then it has to wait for the next slice. The work still gets done, in twice as many slices. This series runs one Azure service at a time, for real. Subscribe to follow the series. And did Kubernetes even notice? I listed the pod after both runs. Running, zero restarts. Nothing in the status tells you the pod is throttled. You only see it as slower answers. Over its CPU limit, a container is never killed. It waits.

And when a container needs more memory than its limit? Same pod, same exercise settings: it requests two hundred fifty-six Mi of memory, and its limit is five hundred twelve. Mi means mebibytes, the binary kind of megabytes. My app has a second endpoint that takes memory in steps of fifty megabytes, holds on to it, and prints a line after each step. I asked it for seven hundred. 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: it's killed. I sent the request from inside the pod, the same way as the work, only to the memory endpoint. Command terminated, exit code one three seven, after one and a half seconds. One three seven is a hundred twenty-eight plus nine: signal nine, a kill that no program can catch or clean up after. Memory isn't like CPU time. 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 limit, the kernel's out-of-memory killer stops it. So where does Kubernetes say what happened? I described the pod and went to its container. Last state: terminated. Reason: OOMKilled, out of memory. Exit code one three seven. Restart count: one. The events at the bottom of the output 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. And what did the app say before it died? I asked for the logs of the previous container. Holding fifty megabytes, a hundred, up to four hundred fifty. Then nothing. No error, no goodbye. The next fifty, plus the app's own memory, crossed five hundred twelve. Previous means the container before the current one, and here that was the one that got killed. The previous video shows when that fails. And notice the request: two hundred fifty-six. The app held four hundred fifty, far above it, with no problem. A request is not a ceiling. The limit is. Memory can't wait. Over its limit, the container is killed, and Kubernetes starts it again.

To sum it up: a request is a booking. The scheduler adds them up, and a pod that doesn't fit waits in Pending. Over the CPU limit, the container waits for its share, and gets slower. Over the memory limit, it's killed and restarted: OOMKilled, exit code one three seven.

And how can a limit alone keep a pod from ever starting? I gave a pod a CPU limit of three, and no request at all. Then I asked Kubernetes which resources it had actually stored for that pod, straight from the pod's definition. Limits: CPU three. Requests: CPU three. I never wrote that request. Without a request, Kubernetes copies the limit and books it. Three CPUs, on a node that gives pods one point nine. I described the pod, to see what the scheduler said. Insufficient CPU, and Pending for the whole thirty seconds I watched. So set both: a request for what your pod needs to run, and a limit for what it must never pass.

Pending is only the first stop. The video on screen follows a pod from kubectl apply to its first answer, second by second. If this was useful, subscribe for more practical .NET and Azure.

Brauchen Sie das in Ihrer eigenen Azure-Umgebung?

Ich entwickle, migriere und betreibe .NET-Plattformen auf Azure, mit Azure DevOps und Terraform. Wenn Ihr Team das sauber aufsetzen möchte, sehen Sie sich mein Angebot an.

Zu den Leistungen