Reading Pod Status and Container State
Six broken Pods at once. One reports phase Running while restarting on a loop, one reports Failed with exit 137, and two sit Pending with no container status at all - because no container was ever created. Four statuses, and four different places the actual reason is written.
Troubleshooting and Debugging Guide 27 of 46 Beginner
- Kubernetes1.36.4
- Cluster4 nodes
- Runtimecontainerd 2.2.6
- CNICalico v3.32.1
- TimeAbout 18 min
- Reviewed22 August 2026
Written against the versions above. Status strings and exit codes here are core Kubernetes and stable across versions. The scheduler's FailedScheduling wording changes between releases - the shape (how many nodes, and why each was rejected) does not.
| Server Name | IP Address | OS | Roles | CPU | RAM | HDD |
|---|---|---|---|---|---|---|
| CKA1001 | 192.168.0.175 | Ubuntu 26.04 LTS | Control Plane Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE01 | 192.168.0.176 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE02 | 192.168.0.177 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
| CKA1001-NODE03 | 192.168.0.178 | Ubuntu 26.04 LTS | Worker Node | 2 Core | 4 GB | 50 GB |
Before you start
- A running cluster and kubectl. Nothing here needs node access - every reason is readable through the API.
- A namespace to break things in. This session uses
tsh, and the six Pods are created deliberately broken: a bad image tag, an unreachable registry, a container that exits 3, one that exceeds its memory limit, one that asks for more CPU than any node has, and one with a nodeSelector nothing matches.
-
Six broken Pods, and what the status column does not tell you
One table, six failures. Read it carefully, because three things in it surprise almost everyone:
badimage Pending ImagePullBackOff <none> 0 crasher Running CrashLoopBackOff <none> 6 hungry Failed <none> OOMKilled 0 noauth Pending ImagePullBackOff <none> 0 nonode Pending <none> <none> <none> toobig Pending <none> <none> <none>crasheris phaseRunning. It has restarted six times and is failing every time. Phase is not health - a Pod is Running once a container has been started, and it stays Running while the kubelet keeps restarting a container that exits.hungryis phaseFailedand its reason is underterminated, notwaiting. A container that ran and stopped reports where it stopped; a container that never started reports what it is waiting for. Two different fields, and looking in the wrong one returns.nonodeandtoobighave no container status at all - not even a restart count. That is the important one. Both are Pending, likebadimage, but for a completely different reason: the scheduler never placed them, so no container was ever created, so there is no container status to read. Their reason lives somewhere else entirely, which is the last step of this guide.So
Pendingcovers two unrelated situations, andRunningdoes not mean working. The status is the category. The cause is always one level down.bash Example session kubectl get pods -n tsh -o custom-columns=NAME:.metadata.name,PHASE:.status.phase,REASON:.status.containerStatuses[0].state.waiting.reason,TERM:.status.containerStatuses[0].state.terminated.reason,RESTARTS:.status.containerStatuses[0].restartCount --no-headersbadimage Pending ImagePullBackOff <none> 0crasher Running CrashLoopBackOff <none> 6hungry Failed <none> OOMKilled 0noauth Pending ImagePullBackOff <none> 0nonode Pending <none> <none> <none>toobig Pending <none> <none> <none>Expected resultSix Pods, four distinct statuses, and two rows with no container status.
Success conditionYou can say why two Pending Pods have nothing in their container status.
-
ImagePullBackOff: same status, different sentence
ImagePullBackOffmeans the kubelet tried to pull an image, failed, and is waiting before trying again. It does not say why the pull failed - the message does.First Pod, a tag that does not exist:
... failed to resolve image: docker.io/library/nginx:this-tag-does-not-exist: not foundSecond Pod, same status, entirely different problem - a registry hostname that does not resolve:
... dial tcp: lookup registry.example.invalid on 127.0.0.53:53: no such hostOne is a typo in a tag, the other is DNS or a firewall. Same status, and the fix has nothing in common. Note also the second is still reporting
ErrImagePull- the first attempt - where the first has already moved toImagePullBackOff. They are the same failure at different points in the retry cycle, which is why treating them as different problems is a mistake.And
kubectl logscannot help here, which is worth seeing once:Error from server (BadRequest): container "badimage" in pod "badimage" is waiting to start: trying and failing to pull imageThere are no logs because there is no container. Reaching for
logsfirst is the commonest wasted step in Kubernetes troubleshooting.bash Example session sleep 25; kubectl get pod badimage -n tshNAME READY STATUS RESTARTS AGEbadimage 0/1 ImagePullBackOff 0 25skubectl get pod badimage -n tsh -o jsonpath="{.status.containerStatuses[0].state.waiting.reason}{\": \"}{.status.containerStatuses[0].state.waiting.message}{\"\n\"}"ImagePullBackOff: Back-off pulling image "nginx:this-tag-does-not-exist": ErrImagePull: rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/library/nginx:this-tag-does-not-exist": failed to resolve image: docker.io/library/nginx:this-tag-does-not-exist: not foundsleep 30; kubectl get pod noauth -n tsh -o jsonpath="{.status.containerStatuses[0].state.waiting.reason}{\": \"}{.status.containerStatuses[0].state.waiting.message}{\"\n\"}"ErrImagePull: failed to pull and unpack image "registry.example.invalid/private/app:1.0": failed to resolve image: failed to do request: Head "https://registry.example.invalid/v2/private/app/manifests/1.0": dial tcp: lookup registry.example.invalid on 127.0.0.53:53: no such hostkubectl logs badimage -n tsh 2>&1 | tail -2Error from server (BadRequest): container "badimage" in pod "badimage" is waiting to start: trying and failing to pull imageExpected resultTwo Pods with the same status class and two unrelated causes, and logs refusing to help.
Success conditionYou read the message, not just the status.
-
A container that exits: the logs are the app's own words
This container prints a line, prints an error, and exits 3. Nine minutes later:
crasher 0/1 CrashLoopBackOff 6 (3m45s ago) 9m33sCrashLoopBackOffis not a cause either - it is the kubelet saying "I have stopped retrying this quickly". Earlier in its life the same Pod showedError; the status changes as the backoff grows. What actually happened is in the previous container's termination record:Error exit=3lastState, notstate- the container currently instateis the one waiting to start again. And this is the one failure class wherekubectl logsis exactly the right tool, because a container did run and it said why it gave up:starting up config missing, giving upThe events add nothing about the cause -
Pulled,Created,Started,BackOff, over and over. That loop is the symptom. The application's own stderr is the diagnosis.bash Example session kubectl get pod crasher -n tshNAME READY STATUS RESTARTS AGEcrasher 0/1 CrashLoopBackOff 6 (3m45s ago) 9m33skubectl get pod crasher -n tsh -o jsonpath="{.status.containerStatuses[0].lastState.terminated.reason}{\" exit=\"}{.status.containerStatuses[0].lastState.terminated.exitCode}{\"\n\"}"Error exit=3kubectl logs crasher -n tshstarting upconfig missing, giving upkubectl describe pod crasher -n tsh | sed -n '/Events:/,$p' | tail -6 Normal Scheduled 9m33s default-scheduler Successfully assigned tsh/crasher to cka1001-node03 Normal Started 3m45s (x7 over 9m32s) kubelet spec.containers{app}: Container started Warning BackOff 2s (x13 over 9m31s) kubelet spec.containers{app}: Back-off restarting failed container app in pod crasher_tshExpected resultCrashLoopBackOff with a non-zero exit code in lastState, and the application's reason in the logs.
Success conditionYou found the cause in the container's own output, not in the events.
-
OOMKilled is exit 137, and it is not a crash
This container asks for 32Mi, is limited to 64Mi, and then writes 200Mi. It does not crash - it is killed:
OOMKilled exit=137137 is 128 + 9: killed by signal 9. The kernel's OOM killer stopped the process because the container exceeded its memory *limit*. The application did nothing wrong and its logs will usually show nothing useful - it was terminated mid-work, with no chance to report anything.
The fix is a number, and it is a specific one:
limit=64Mi request=32MiThe limit is what gets you killed. The request is only what the scheduler used to place the Pod. Raise the limit, or make the workload use less - but know which of the two numbers you are changing, because raising the request changes where the Pod is scheduled and not whether it is killed.
bash Example session sleep 30; kubectl get pod hungry -n tshNAME READY STATUS RESTARTS AGEhungry 0/1 OOMKilled 0 30skubectl get pod hungry -n tsh -o jsonpath="{.status.containerStatuses[0].state.terminated.reason}{\" exit=\"}{.status.containerStatuses[0].state.terminated.exitCode}{\"\n\"}"OOMKilled exit=137kubectl get pod hungry -n tsh -o jsonpath="limit={.spec.containers[0].resources.limits.memory}{\" request=\"}{.spec.containers[0].resources.requests.memory}{\"\n\"}"limit=64Mi request=32MiExpected resultReason OOMKilled, exit code 137, and the limit that caused it.
Success conditionYou can name which of request and limit does the killing.
-
Pending with no container: ask the scheduler instead
Back to the two rows with no container status. Nothing is wrong with their images and no container has crashed, because the scheduler has not placed them on a node. There is nothing for the kubelet to report.
The reason is on the Pod's own condition:
PodScheduled=False Unschedulable: 0/4 nodes are available: 1 node(s) had untolerated taint(s), 3 Insufficient cpu, 3 Insufficient memory.Read it as arithmetic. Four nodes; one is the control plane and carries a taint this Pod does not tolerate; the other three each lack the CPU and the memory. The Pod asked for 8 CPU and 32Gi, and every node has 2 CPU allocatable - so this is not a cluster problem, it is a request no node in this cluster could ever satisfy.
The second Pending Pod produces the same status from a different sentence:
0/4 nodes are available: 1 node(s) had untolerated taint(s), 3 node(s) didn't match Pod's node affinity/selector.No resource problem at all - a
nodeSelectorasking for a label no node carries. Same status, same empty container status, and the scheduler names both causes precisely if you ask it. For anything Pending, read thePodScheduledcondition or theFailedSchedulingevent first;kubectl logsandkubectl describe's container section have nothing to offer yet.bash Example session kubectl get pod toobig -n tsh -o jsonpath="{range .status.conditions[*]}{.type}={.status}{\" \"}{.reason}{\": \"}{.message}{\"\n\"}{end}"PodScheduled=False Unschedulable: 0/4 nodes are available: 1 node(s) had untolerated taint(s), 3 Insufficient cpu, 3 Insufficient memory. no new claims to deallocate, preemption: 0/4 nodes are available: 4 Preemption is not helpful for scheduling.kubectl get nodes -o custom-columns=NAME:.metadata.name,CPU:.status.allocatable.cpu,MEM:.status.allocatable.memory --no-headerscka1001 2 3377972Kicka1001-node01 2 3377980Kicka1001-node02 2 3377980Kicka1001-node03 2 3377980Kikubectl describe pod nonode -n tsh | sed -n '/Events:/,$p' | tail -3 Type Reason Age From Message ---- ------ ---- ---- ------- Warning FailedScheduling 12s default-scheduler 0/4 nodes are available: 1 node(s) had untolerated taint(s), 3 node(s) didn't match Pod's node affinity/selector. no new claims to deallocate, preemption: 0/4 nodes are available: 4 Preemption is not helpful for scheduling.Expected resultTwo Unschedulable Pods, one short of resources and one short of a matching label, both named by the scheduler.
Success conditionYou asked the scheduler rather than the kubelet.
Troubleshooting
kubectl logsreturns "is waiting to start" and nothing else.Why: No container has run yet - the image is still failing to pull, or the Pod is not scheduled.
Fix:Read the status instead:
kubectl get pod <p> -o jsonpath='{.status.containerStatuses[0].state.waiting.message}', orkubectl describe pod <p>and read the Events.A Pod says Running but the application is not working.
Why: Phase Running only means a container was started. A container restarting on a loop keeps the Pod in Running.
Fix:Check the restart count and the previous container's exit:
kubectl get pod <p> -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'. Thenkubectl logs <p> --previous.The reason field is empty even though the Pod is clearly broken.
Why: You are reading
state.waiting.reasonon a container that has terminated, or reading container status at all on a Pod that was never scheduled.Fix:Terminated containers report under
state.terminatedorlastState.terminated. Unscheduled Pods report under.status.conditionswhere type is PodScheduled.A Pod is OOMKilled and raising the memory request does not help.
Why: The limit does the killing; the request only influences scheduling.
Fix:Raise
resources.limits.memory, or reduce what the workload allocates. Confirm which value you changed by reading both back.