Diagnosing Pod Failure States🔗
Part of Essentials: Troubleshooting
These are the statuses you'll see in the STATUS column of kubectl get pods when something's wrong. Each maps to a small set of causes and a quick way to confirm which one you're hitting. For the Pod lifecycle phases themselves (Pending → Running → Succeeded/Failed), see Pods Deep Dive. This article is also the closing step of the How Modern Software Really Runs on a CPU pathway on bradpenney.io: every status below is one of this pathway's mechanisms failing loudly enough to show up in kubectl get pods.
Most Pod problems announce themselves as one of a handful of statuses. Learn what each one is actually telling you, and the fix usually follows.
| Status | What Actually Failed | First Command |
|---|---|---|
CrashLoopBackOff |
The container starts, then exits repeatedly | kubectl logs <pod> --previous |
Pending |
The scheduler couldn't find a Node that qualifies | kubectl describe pod <pod> (Events) |
ImagePullBackOff |
The container image couldn't be pulled | kubectl describe pod <pod> (Events) |
OOMKilled |
The container exceeded its memory limit | kubectl describe pod <pod> (Last State) |
| Running, but unresponsive | The container is alive; your app inside it isn't | kubectl logs / kubectl exec |
CrashLoopBackOff🔗
CrashLoopBackOff is not a Pod phase. It's a container status that appears in kubectl get pods when a container starts, immediately crashes, and Kubernetes keeps trying to restart it.
What it tells you:
- The container started (image pulled correctly)
- The container crashed almost immediately (application error)
- Kubernetes is backing off between restart attempts (the "Backoff" part)
What to do:
kubectl get pods # (1)!
kubectl describe pod <pod-name> # (2)!
kubectl logs <pod-name> # (3)!
kubectl logs <pod-name> --previous # (4)!
- See the crash status.
- Check events: often explains why.
- Read the container's output before it crashed.
- If the container is too fast to catch, read the previous run's logs.
The most common causes: missing environment variables, wrong command, misconfigured entrypoint, or a configuration file the app expected that isn't mounted.
Pending: The Pod Won't Schedule🔗
A Pod stuck in Pending hasn't been placed on a Node yet: the scheduler looked at every Node in the cluster and found none that qualified. The Kubernetes Scheduler covers the two-phase filtering/scoring decision behind this; here, the diagnosis is simpler than the mechanism.
Possible causes:
- No Node has enough allocatable CPU or memory (
Insufficient cpu/Insufficient memory) - A taint on every eligible Node isn't tolerated by this Pod (
untolerated taint) - A
nodeSelectoror required affinity rule doesn't match any Node - The container image name is wrong or the registry is inaccessible
- A required PersistentVolumeClaim doesn't exist
- Look at the
Events:section at the bottom: for scheduling failures specifically, it lists exactly how many Nodes were rejected and why (e.g.0/6 nodes are available: 4 Insufficient memory, 2 node(s) had untolerated taint).
ImagePullBackOff🔗
The container image couldn't be pulled.
Possible causes: wrong image name, wrong tag, private registry without credentials, network issue.
- The
Eventssection shows the pull error (image not found, unauthorized, etc.).
OOMKilled🔗
kubectl get pods shows a container with RESTARTS climbing, and kubectl describe pod shows Reason: OOMKilled, Exit Code: 137 under the container's Last State. The container tried to use more memory than its resources.limits.memory allows, and the kernel's OOM killer (the exact mechanism from Swap and the OOM Killer, here scoped to one cgroup instead of the whole Node) terminated it.
kubectl describe pod <pod-name> # (1)!
kubectl top pod <pod-name> # (2)!
- Look at
Last Stateunder the specific container, not just the Pod-level Events:OOMKilledis a container-level termination reason. - Compare current usage against the configured limit, but remember
kubectl topshows a point-in-time average; a short, sharp spike that triggered the kill can be invisible in that snapshot. This is the same "average looks fine, but it wasn't" trap covered in depth in Resource Requests and Limits, applied to memory instead of CPU.
Possible causes: the limit is set too low for genuine peak usage; a memory leak causing usage to climb unbounded over the container's lifetime; a batch or startup process that briefly needs far more memory than steady-state suggests. The fix is rarely "just raise the limit" without first confirming which of these it is: raising the limit on a genuine leak just delays the same kill.
Running, but the App Doesn't Respond🔗
kubectl get pods shows Running and 1/1 Ready, but HTTP requests fail.
This means: the container is alive, but your application inside it isn't listening on the expected port, or is returning errors.
- Look for application startup errors.
- Check if the process is running.
Why This Matters for Platform Work🔗
- The
STATUScolumn is a starting point, not a diagnosis.PendingandOOMKilledlook like two words in a table, but they point to entirely different subsystems: the scheduler's placement decision versus the kernel's memory enforcement, with entirely different fixes. kubectl describe pod's Events section is almost always faster than guessing. Every status in this article has a specific, readable explanation waiting in Events or Last State; reaching for it first beats reasoning from first principles every time.- "Just increase the limit/resources" is a fix for exactly one of these symptoms (a genuinely undersized memory limit), and a band-aid or a mistake for the others. A CrashLoopBackOff from a missing environment variable, or a Pending from an untolerated taint, won't move an inch no matter how much CPU or memory you throw at them.
Practice Exercises🔗
Exercise 1: OOMKilled or CrashLoopBackOff?
A Pod shows RESTARTS: 12 and status CrashLoopBackOff. kubectl describe pod shows Last State: Terminated, Reason: OOMKilled, Exit Code: 137. Which failure is this, actually?
Solution
Both, in sequence. And this is a common point of confusion. OOMKilled is why the container terminated; CrashLoopBackOff is the status describing the pattern of it terminating repeatedly, regardless of the reason. The container is being OOM-killed, restarting, immediately hitting the same memory ceiling, and getting killed again: CrashLoopBackOff is true of any repeatedly-dying container, whether the cause is an application bug or a memory limit. Always check Last State's Reason to find the actual root cause underneath the loop.
Exercise 2: Reading a Scheduling Failure vs. an OOM Kill
Two Pods are unhealthy. Pod A: STATUS: Pending. Pod B: STATUS: CrashLoopBackOff, Last State: OOMKilled. A teammate proposes raising memory requests and limits for both. Is that the right fix for either one?
Solution
For Pod B, maybe: if kubectl top and recent history show genuine peak usage exceeding the current limit, raising it is legitimate. For Pod A, almost certainly not by itself: Pending means the scheduler rejected every Node, and raising a resource request makes that harder to satisfy, not easier. It shrinks the set of Nodes with enough allocatable capacity left. If Pod A's Events show Insufficient memory, the real fix is more/bigger Nodes or a smaller request; if they show untolerated taint, memory sizing is irrelevant entirely.
Quick Recap🔗
CrashLoopBackOffdescribes a repeating pattern, not a root cause: always checkLast State'sReasonunderneath it.Pendingis the scheduler reporting zero feasible Nodes: Events names exactly which filter rejected each one.ImagePullBackOffis a registry/image problem, confirmed in Events.OOMKilled(Exit Code: 137) is the kernel's OOM killer enforcing a memory limit. The same mechanism as a Linux host running out of memory, scoped to one container.Runningwith no response means the failure moved from Kubernetes' visibility into your application's:kubectl logs/kubectl execfrom here.
What's Next🔗
This closes the loop on the How Modern Software Really Runs on a CPU pathway's Kubernetes steps. Your Flux Workflow covers how a Pod's spec (including the resource limits this article keeps referencing) gets onto the cluster declaratively in the first place.
Further Reading🔗
Official Documentation🔗
- Debug Pods. The official troubleshooting walkthrough this article summarizes for common cases.
- Determine the Reason for Pod Failure: container termination reasons, including
OOMKilled, in full.
Related Articles🔗
- The Kubernetes Scheduler: the filtering/scoring decision behind every
PendingPod. - Resource Requests and Limits: what a memory limit actually promises, and the CPU-throttling equivalent of the OOMKilled trap.
- Swap and the OOM Killer: the same kernel mechanism behind
OOMKilled, seen at the Linux host level instead of one container's cgroup.