• English icon
  • German icon
Erklärvideo

Mein Pod startet nicht. Welcher Befehl zeigt, warum?

· Reihe: Container auf Azure

azureakskuberneteskubectltroubleshootingdevops

Das Video ist auf Englisch.

Ich habe vier Pods auf Azure Kubernetes Service absichtlich kaputt gemacht. Zwei meldeten ImagePullBackOff, einer CreateContainerConfigError, und einer stürzte in anderthalb Minuten viermal ab. Jedes Mal dieselbe Frage: Welcher Befehl zeigt, warum?

Dieser Explainer folgt dem Weg, den jeder Pod geht, bevor Ihr Code läuft, zeigt, wo jeder Status ihn anhält, und mit welchem Befehl Sie an jeder Station den Grund finden, mit der echten Ausgabe aus meinem eigenen Cluster. Das Video ist auf Englisch.

Das Wichtigste

  • Der Status nennt die Station. Das Kubelet zieht das Image, füllt die Einstellungen des Containers aus (Umgebungsvariablen und Secrets) und startet dann den Container. ImagePullBackOff hält an der ersten Station, CreateContainerConfigError an der zweiten, CrashLoopBackOff nach der dritten: Ihre App lief und ist abgestürzt.
  • BackOff heißt warten, nicht aufgeben. Kubernetes versucht es weiter, und die Pause zwischen zwei Versuchen wächst bis auf fünf Minuten. Eine Korrektur (ein nachgeschobener Tag, Registry-Zugriff, ein angelegtes Secret) greift beim nächsten Versuch, ohne neues Deployment.
  • Bevor Ihre App läuft, kennt nur Kubernetes den Grund. kubectl describe pod endet mit den Events. Lesen Sie den ersten Fehler: Auch ein falscher Tag meldet 401 Unauthorized, weil AKS zusätzlich immer einen anonymen Pull versucht.
  • Nachdem Ihre App lief, haben ihre eigenen Logs die Antwort. In einer Crash-Schleife zeigt das einfache kubectl logs den gerade abgestürzten Container; --previous hilft erst, wenn der Container wieder läuft. Die Spalte RESTARTS wählt den Befehl: null und nicht laufend → describe, mehr als null → logs.

Hängt am Image

Ein Pod ist die kleine Hülle, in der Kubernetes Ihren Container ausführt. Bevor Ihr Code läuft, zieht das Kubelet, der Agent auf jeder Maschine des Clusters, das Container-Image aus der Registry, füllt die Einstellungen des Containers aus und startet ihn. Der Status in kubectl get pods nennt die Station, an der der Pod wartet.

Mein erster Fehler war ein Tippfehler: der Tag v1.0, der Tag aus dem Beispiel des Microsoft-Learn-Moduls, während meine Registry nur v1 hatte. Ein kleines Skript von mir zeigte jede Statusänderung mit der Sekunde, in der sie passierte, wie kubectl get pods --watch mit Zeitstempeln:

> python watch.py pods api-typo --secs 75
0.4s api-typo-… ContainerCreating 0/1
1.1s api-typo-… ErrImagePull 0/1
16.4s api-typo-… ImagePullBackOff 0/1
27.4s api-typo-… ErrImagePull 0/1
39.0s api-typo-… ImagePullBackOff 0/1

ErrImagePull heißt, ein Versuch ist gerade gescheitert; ImagePullBackOff heißt, Kubernetes wartet vor dem nächsten Versuch, und jede Pause ist länger als die letzte (bis zu fünf Minuten). Neuneinhalb Minuten später meldete der Pod immer noch ImagePullBackOff, mit null Neustarts. Die Versuche hören nie auf.

Ihre App lief nie, also hat sie nichts zu sagen. Kubernetes schon: kubectl describe pod endet mit den Events, was das Kubelet getan hat und was gescheitert ist:

> kubectl describe pod -l app=api-typo
Status: Pending
Reason: ImagePullBackOff
Restart Count: 0
Failed to pull image "…/aks-api:v1.0": … not found, … 401 Unauthorized

Unauthorized heißt hier nicht fehlende Berechtigung. Der erste Fehler ist der echte: not found. AKS versucht immer zusätzlich einen Pull ohne Anmeldedaten, und dieser Versuch bekommt die 401 (Microsofts Troubleshooting-Leitfaden beschreibt das für falsche Image-Namen). Prüfen Sie Image-Namen und Tag gegen Ihre Registry; korrigieren Sie den Tag im YAML und wenden Sie es erneut an, oder schieben Sie den fehlenden Tag nach, den Kubernetes beim nächsten Versuch übernimmt.

Mein zweiter Fehler nutzte dasselbe Image in einer zweiten Registry, auf die der Cluster nie Zugriff bekommen hatte. Jetzt hatte das Event die 401 und sonst nichts:

> kubectl describe pod -l app=api-private
Reason: ErrImagePull
Restart Count: 0
Failed to pull image "otherregistry…": … 401 Unauthorized

Das Modul nennt den Registry-Zugriff „meist automatisch“. Das ist er nicht: Der Cluster zieht Images mit seiner eigenen Identität (der Managed Identity des Kubelets), und diese Identität braucht die Rolle AcrPull auf jeder Registry. Meine erste Registry hatte sie nur, weil ich sie beim Anlegen des Clusters angehängt hatte. Für einen bestehenden Cluster:

az aks update -g rg-demo -n aks-demo --attach-acr otherregistry

Das dauerte 106 Sekunden. Ich habe weder neu deployt noch den Pod angefasst; 32 Sekunden nachdem ich mit dem Beobachten begann, lief die App.

Hängt an den Einstellungen

Das Manifest des Moduls liest einen API-Schlüssel aus einem Secret, einem Kubernetes-Objekt für Schlüssel und Passwörter:

env:
- name: MODEL_NAME
value: "gpt-5-mini"
- name: API_KEY
valueFrom:
secretKeyRef:
name: api-secrets # never created
key: api-key

Das Modul führt Secrets unter CrashLoopBackOff auf, als würde ein fehlendes Secret die App abstürzen lassen. Das tut es nicht: Das Kubelet füllt die Einstellungen aus, bevor der Container existiert, und ein fehlendes Pflicht-Secret hält es genau dort an. Der Status war nach einer halben Sekunde CreateContainerConfigError, und dann nichts mehr: kein Absturz, kein Neustart. Der Container wurde nie angelegt, also gibt es keine Logs:

> kubectl logs deploy/api-secret
Error from server (BadRequest): container "api" in pod "…"
is waiting to start: CreateContainerConfigError

Der Grund steht wieder in den Events:

> kubectl describe pod -l app=api-secret
Reason: CreateContainerConfigError
Restart Count: 0
API_KEY: <set to the key 'api-key' in secret 'api-secrets'>
Failed (x5 over 42s) Error: secret "api-secrets" not found

Die Lösung ist das Secret selbst (kubectl create secret generic api-secrets --from-literal=api-key=demo). Neun Sekunden später lief derselbe Pod, ohne neues Deployment und ohne Neustart.

Die App lief und ist abgestürzt

Meine App braucht eine Einstellung, MODEL_NAME; ohne sie gibt sie einen Fehler aus und beendet sich mit Code 1. Ich habe sie ohne diese Einstellung deployt und hundert Sekunden lang beobachtet:

> python watch.py pods api-crash --secs 100
1.2s api-crash-… Running 1/1
1.9s api-crash-… Error 0/1
2.6s api-crash-… Running 1/1 restarts 1
4.1s api-crash-… Error 0/1 restarts 1
18.0s api-crash-… CrashLoopBackOff 0/1 restarts 1
94.2s api-crash-… Running 1/1 restarts 4

Diesmal ist der Container gestartet, also startet Kubernetes ihn neu: Der erste Neustart kommt sofort, danach verdoppelt sich die Pause (10 s, 20 s, 40 s, bis zu fünf Minuten; nach zehn Minuten ohne Probleme beginnt sie von vorn). Meine Neustarts kamen etwas später, aber immer weiter auseinander. CrashLoopBackOff heißt: Kubernetes sitzt gerade eine dieser Pausen aus.

Diesmal lief Ihre App, also hat sie etwas zu sagen:

> kubectl logs deploy/api-crash
error: MODEL_NAME is not set

Die meisten Anleitungen, auch das Modul, sagen: Bei einer Crash-Schleife fragen Sie nach den Logs des vorherigen Containers. Direkt nach den einfachen Logs brachte mir das einen eigenen Fehler:

> kubectl logs deploy/api-crash --previous
unable to retrieve container logs for containerd://74a2e527a39d…

kubectl describe pod zeigt, warum: Der Pod hatte zwei beendete Container, den aktuellen und den davor.

State: Terminated
Exit Code: 1
Last State: Terminated
Exit Code: 1
Restart Count: 4

Solange ein abgestürzter Container auf seinen Neustart wartet, ist er noch der neueste Container des Pods, also zeigt ihn das einfache kubectl logs. --previous fragt nach dem davor, und den hatte das Kubelet schon aufgeräumt (es entfernt beendete Container per Garbage Collection). Nutzen Sie --previous, sobald der Container wieder läuft, wenn der Absturz hinter ihm liegt.

Die eine Spalte, die den Befehl wählt

kubectl get pods hat eine Spalte RESTARTS. Am Ende meines Laufs:

> kubectl get pods
NAME READY STATUS RESTARTS AGE
api-crash-565c6974d4-r5xzq 0/1 Error 4 (116s ago) 2m37s

Die Pods, deren App nie lief, hatten bei jeder Prüfung null Neustarts. Null Neustarts und nicht laufend: Ihre App lief nie, also fragen Sie Kubernetes mit kubectl describe pod. Mehr als null: Sie lief und ist abgestürzt, also fragen Sie Ihre App mit kubectl logs.

StatusWo er anhältNeustartsLesen
ErrImagePull / ImagePullBackOffbeim Ziehen des Images0describe → Events (der erste Fehler)
CreateContainerConfigErrorbeim Ausfüllen der Einstellungen (fehlendes Secret oder ConfigMap)0describe → Events
CrashLoopBackOffnach dem Start: die App hat sich beendet> 0logs (zuerst einfach, --previous, sobald er wieder läuft)

Was passiert ist, als ich es ausprobiert habe

Getestet am 26. Oktober 2026 in meinem eigenen Azure-Abonnement (AKS 1.36 in westeurope, Azure CLI 2.88.0, kubectl 1.36); danach wurde alles gelöscht. Ein AKS-Cluster diente vier Videos dieser Serie; diese Seite nutzt seine Schritte zu den Pod-Status. Der Cluster hatte einen Knoten Standard_D2s_v5 und den Free-Tier; die App ist eine kleine Python-API nach dem Vorbild der Übungs-API des Moduls (Port 8000, MODEL_NAME als Pflicht), ohne das Modell der Übung in Microsoft Foundry.

  • Während die zweite Registry angehängt wurde, ersetzte Kubernetes den wartenden Pod durch einen neuen desselben ReplicaSets (ein neuer Pod-Name; der Lauf zeigt nicht, warum). Ein neues Deployment war so oder so nicht nötig: Die App lief 31,7 Sekunden nach dem Start des Beobachters.
  • Mein erster Versuch der Crash- und Secret-Schritte kam gar nicht so weit: Beide Pods blieben Pending mit 0/1 nodes are available: 1 Insufficient cpu, weil meine eigenen früheren Deployments die CPU-Requests des Knotens gefüllt hatten. Das ist das Thema des nächsten Videos (Requests und Limits); nach dem Aufräumen zeigte der zweite Lauf die Status oben.
  • In der Crash-Schleife kamen die Neustarts bei 2,6, 18,7, 40,4 und 94,2 Sekunden des Beobachters.

Was das Video weglässt

  • Pending-Pods und Insufficient cpu (Requests und Limits: das nächste Video).
  • Image-Pull-Secrets (imagePullSecrets), ABAC-fähige Registries (bei denen --attach-acr nicht die passende Rolle vergibt) und falsche Image-Architekturen, die ebenfalls 401 Unauthorized melden (Microsofts Troubleshooting-Leitfaden behandelt sie).
  • Liveness-Probes als Ursache von Neustarts, kubectl get events und kubectl debug.

Weiterführende Links

Video-Transkript (Englisch)

I broke four pods on Azure Kubernetes Service, on purpose. Two said ImagePullBackOff. One said CreateContainerConfigError. And one crashed four times in a minute and a half. Three statuses, and the same question every time: which command shows you why?

In this video, you'll see the three places a pod's start can stop, and which command finds the reason at each. And at the end: the one column that picks the command for you.

What does ImagePullBackOff actually tell you? A pod is the small wrapper Kubernetes runs your container in, and every pod walks the same line before your code runs. The kubelet, the agent on every machine of the cluster, pulls the container image from the registry. It fills in the container's settings: environment variables, and Secrets for keys and passwords. Then it starts the container. An earlier video timed this line second by second; the card is up there now. The status column of kubectl get pods, the Kubernetes command line, names the stop where the pod is waiting. My first break was a typo. I gave the image the tag v1.0, the tag in the example of the Microsoft Learn module I followed. My registry only had v1. I watched the pod with a small script of mine. Like kubectl get pods in watch mode, it prints every status change, plus the second it happened. ErrImagePull after one second. ImagePullBackOff at sixteen. Then back and forth. ErrImagePull means a try just failed. BackOff means Kubernetes is waiting before the next try, and each wait is longer than the last. So will Kubernetes ever give up on this image? No. It keeps trying. Nine and a half minutes later, I listed my pods again, with their status and their restarts. Still ImagePullBackOff. The wait between two tries grows up to five minutes, but the tries never stop. So where is the reason? Your app never ran, so it has nothing to say. Kubernetes does: kubectl describe pod ends with the events, what the kubelet did and what failed. I asked Kubernetes to describe the pod, and went to the events at the bottom. Failed to pull image: not found. And in the same line: 401, Unauthorized. Is that 401 a permission problem? Here it isn't. AKS, Azure Kubernetes Service, always tries a pull without credentials as well, and that try gets the 401. The first error is the real one: not found. So check the image name and the tag against your registry. Fix the tag in your YAML and apply it again, or push the missing tag to the registry: Kubernetes picks that up on its next try, at most five minutes later. My second break: the same image, in a second registry the cluster was never given access to. I described that pod too, and went straight to the events at the bottom of its output. Four oh one, Unauthorized, and nothing else. No not found. Now it really is access. So why did the first registry work? The module says registry access is usually automatic. It isn't. The cluster pulls images with its own Azure identity, and that identity needs permission on each registry. My first registry had it only because I attached it when I created the cluster. So I attached the second one too: az aks update with the attach ACR option, ACR for Azure Container Registry. A hundred and six seconds, and the cluster could pull from it. Then I only watched with my script. I didn't redeploy, and I didn't touch the pod. Thirty-two seconds after I started watching, the app was running. Stuck at the image: your app never ran. Read the events, and trust the first error.

What happens when a Secret doesn't exist? The module's YAML for this app reads an API key from a Secret. The pod names the Secret, api-secrets, and the key inside it. The module lists missing Secrets under CrashLoopBackOff, the status of an app that keeps crashing. So I deployed it without creating the Secret. I watched this pod for forty seconds, waiting for the first crash. CreateContainerConfigError after half a second. Then nothing. No crash at all. So what does kubectl logs show? Nothing from my app. I asked for the pod's logs, the way you would for any app that misbehaves. Bad request: the container is waiting to start. Logs are what your app prints. The image was pulled just fine, but the kubelet fills in the settings before the container exists, and a missing Secret stops it right there. The container was never created, so the app never printed a thing. This series runs one Azure service at a time, for real. Subscribe to follow the series. So the reason is in the events again. I described the pod and went to the bottom. Secret api-secrets not found. Five times in forty-two seconds: the kubelet kept trying. The fix is the Secret itself. I created it with one kubectl command, with a demo value for the key. Secret created. No restart, no new deployment. Then I watched the same pod again, without touching anything else. Nine seconds later, it was running. Stuck at the settings: the app never started, so there are no logs. The reason is in the events.

And when the app itself is the problem? My app needs one setting, the model name. Without it, it prints an error and exits. So I deployed it without that setting. I watched this pod for a hundred seconds, and counted the restarts. Running after one second, then Error. Running, Error. CrashLoopBackOff at eighteen seconds, and the fourth restart at ninety-four. This time the container did start, so Kubernetes restarts it. The first restart comes at once. After that, the wait doubles: ten seconds, twenty, forty, up to five minutes. In my run, the restarts came a little later than that, but further and further apart. CrashLoopBackOff means Kubernetes is sitting out one of those waits. And this time your app ran, so it had something to say. I asked for its logs. Error: MODEL_NAME is not set. The reason, in the app's own words. For a crash loop, most guides, the module too, say: ask for the previous container's logs. So what did that give me? An error of its own. I ran kubectl logs with previous, on the same pod, right after the plain logs. Unable to retrieve container logs. Describe shows the two containers. I asked for the pod's container states: the current one, and the one before. State: terminated, exit code one. That's the crash I had just read. Last state: the one before it. While a crashed container waits for its restart, it's still the pod's latest container, so plain kubectl logs shows it. Previous asks for the one before, and the kubelet had already cleaned that one up. Use previous once the container is running again, and the crash is behind it. The app ran, and died: now its own logs have the answer.

To sum it up: the status names the stop. Stuck at the image or at the settings, your app never ran: describe the pod, and read the first error in the events. And if the app ran and crashed, read its own logs, plain logs first.

And the one column that picks the command for you? Restarts, in kubectl get pods. At the end of my run, I listed the pods one last time. The crash loop: four restarts. The pods that never got to run their app had zero, in every check I made. Zero restarts and not running: your app never ran, so ask Kubernetes, with describe. More than zero: it ran and died, so ask your app, with logs.

Every pod's start begins with its image. The video on screen now shows how tags work in Azure Container Registry, and why one tag can give you different code tomorrow. 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