This post is also available in: Spanish
When an application fails in Kubernetes, seeing a Pod in CrashLoopBackOff, ImagePullBackOff, or Pending does not necessarily tell us what the actual problem is.
It tells us where to start investigating.
One of the most common mistakes during troubleshooting is trying to immediately fix the status shown by kubectl.
For example:
CrashLoopBackOff
is not the root cause.
It means that a container is repeatedly failing and Kubernetes is applying a progressive delay before trying to start it again.
The actual cause could be related to the application, configuration, resources, health checks, or an external dependency.
In this article, we are going to build a systematic process to investigate four of the most common Pod states:
CrashLoopBackOffImagePullBackOffPods stuck in
PendingPods stuck in
Terminating
Before you start: do not restart the Pod immediately
When an incident occurs, it can be tempting to run:
kubectl delete pod <pod>
and wait for Kubernetes to create a new one.
In some cases, this can temporarily restore the service.
But it can also remove useful evidence that could help us understand the root cause.
Before changing anything, review:
Pod State
|
+--- Events
|
+--- Logs
|
+--- Previous container state
|
+--- Resources
|
+--- Configuration
A good investigation starts by preserving evidence.
A basic troubleshooting flow
Before investigating a specific error, we can get a general view of the cluster.
kubectl get pods -A -o wide
This allows us to quickly identify:
namespace;
status;
restart count;
IP address;
node where the Pod is running;
Pod age.
Then we can review recent Events:
kubectl get events -A \
--sort-by='.lastTimestamp'
Events are particularly useful when Kubernetes is having problems with:
Scheduling
Image pulls
Volumes
Health checks
Nodes
Evictions
If we already know which Pod is affected:
kubectl describe pod <pod> -n <namespace>
kubectl describe combines much of the information we need for an initial investigation:
Containers
State
Last State
Restart Count
Conditions
Volumes
Node
Events
Then we can inspect the logs:
kubectl logs <pod> -n <namespace>
If the container has already restarted, there is another very important command:
kubectl logs <pod> \
-n <namespace> \
--previous
This retrieves logs from the previous instance of the container.
1. CrashLoopBackOff
Suppose we find:
NAME READY STATUS RESTARTS
api-xyz 0/1 CrashLoopBackOff 7
CrashLoopBackOff means that the container is entering a cycle similar to this:
Container starts
|
v
Container fails
|
v
Kubernetes restarts it
|
v
Container fails again
|
v
Backoff
|
+----------------+
|
v
Retry again
Kubernetes progressively increases the time between restart attempts to avoid continuously restarting a container that keeps failing.
The important question is:
Why is the process inside the container terminating?
Step 1: inspect the Pod
kubectl describe pod api-xyz -n production
Pay particular attention to:
State:
Last State:
Reason:
Exit Code:
Restart Count:
Events:
We might find something like:
Last State: Terminated
Reason: Error
Exit Code: 1
This tells us that the process exited with an error.
But we could also find:
Reason: OOMKilled
Exit Code: 137
In that case, the investigation changes completely and should focus on memory usage.
Step 2: inspect previous logs
Start with:
kubectl logs api-xyz -n production
But if the container restarts quickly:
kubectl logs api-xyz \
-n production \
--previous
This command often contains the most useful clue.
For example:
ERROR: DATABASE_URL environment variable is missing
Now we have a much more specific hypothesis.
Step 3: inspect configuration
We can inspect the complete Pod specification:
kubectl get pod api-xyz \
-n production \
-o yaml
And if it belongs to a Deployment:
kubectl get deployment api \
-n production \
-o yaml
Check:
environment variables
Secrets
ConfigMaps
command
args
resources
volume mounts
probes
image
An incorrect configuration may allow the container to be created successfully but cause the application to fail immediately after startup.
Step 4: inspect health checks
We may also find Events such as:
Liveness probe failed
or:
Startup probe failed
In this case, review:
livenessProbe:
startupProbe:
readinessProbe:
An application that needs 40 seconds to start can end up in a restart loop if a health check starts too early.
Common causes of CrashLoopBackOff
CrashLoopBackOff
|
+--- Application crash
|
+--- Missing environment variable
|
+--- Invalid Secret / ConfigMap
|
+--- Dependency unavailable
|
+--- OOMKilled
|
+--- Liveness probe
|
+--- Startup probe
|
+--- Invalid command / arguments
This is why CrashLoopBackOff should be treated as a symptom, not the root cause.
2. ImagePullBackOff
Now suppose we find:
NAME READY STATUS RESTARTS
api-abc 0/1 ImagePullBackOff 0
In this case, the container has not started running our application yet.
The failure happens earlier:
Pod
|
v
kubelet
|
v
Container runtime
|
v
Registry
|
+--- Image
+--- Authentication
+--- DNS
+--- Network
+--- TLS
Again, start with:
kubectl describe pod api-abc \
-n production
The Events may show errors such as:
manifest unknown
pull access denied
unauthorized
x509: certificate signed by unknown authority
or:
no such host
Each error points us in a different direction.
Confirm the image
kubectl get pod api-abc \
-n production \
-o jsonpath='{.spec.containers[*].image}'
For example:
registry.example.com/platform/api:2.4.1
Confirm that the following are correct:
registry;
repository;
image name;
tag.
A simple typo can be enough to cause ImagePullBackOff.
Private registries
If we use a private registry, inspect available Secrets:
kubectl get secrets -n production
And check which imagePullSecrets the Pod is using:
kubectl get pod api-abc \
-n production \
-o jsonpath='{.spec.imagePullSecrets}'
There is also an important point to remember:
Just because your laptop can run
docker pulldoes not mean that the Kubernetes node can do the same.
The kubelet and container runtime on the node need access to the registry.
That means the issue could also involve:
DNS
Routing
Firewall
Proxy
TLS
Registry availability
3. Pods stuck in Pending
Another common scenario:
NAME READY STATUS RESTARTS
api-123 0/1 Pending 0
With Pending, the first question is:
Was the scheduler able to assign the Pod to a node?
The fastest way to find out is:
kubectl describe pod api-123 \
-n production
We might find:
Warning FailedScheduling
followed by something like:
0/3 nodes are available:
2 Insufficient cpu,
1 node(s) had untolerated taint
This information is much more useful than only seeing STATUS=Pending.
Insufficient resources
Check the Pod resource requests:
kubectl get pod api-123 \
-n production \
-o yaml
For example:
resources:
requests:
cpu: "2"
memory: "4Gi"
Then inspect the nodes:
kubectl describe nodes
and check current metrics:
kubectl top nodes
But there is an important difference to understand.
The scheduler primarily makes placement decisions using resource requests, not simply the real-time consumption shown by kubectl top.
We could have:
Actual Node CPU: 30%
Requested CPU: 95%
and new Pods may still be unable to schedule because there is not enough unallocated requested capacity.
Other FailedScheduling causes
CPU and memory are not the only possible causes.
We should also investigate:
nodeSelector
nodeAffinity
podAffinity
podAntiAffinity
taints
tolerations
topologySpreadConstraints
PVC
ResourceQuota
For example:
kubectl get nodes --show-labels
and:
kubectl describe node <node>
can help identify problems related to labels and taints.
4. Pods stuck in Terminating
Now suppose we find:
NAME READY STATUS AGE
api-456 1/1 Terminating 2d
Pods do not disappear instantly when we delete them.
Kubernetes allows them to perform a graceful shutdown before removing them.
Start with:
kubectl describe pod api-456 \
-n production
and:
kubectl get pod api-456 \
-n production \
-o yaml
Look specifically for:
deletionTimestamp:
finalizers:
terminationGracePeriodSeconds:
Common causes
A Pod can remain in Terminating because of:
Application not handling SIGTERM
|
preStop hook blocked
|
Volume cannot unmount
|
CSI problem
|
Finalizer
|
Node unavailable
We can identify the node with:
kubectl get pod api-456 \
-n production \
-o wide
Then check it:
kubectl get node <node>
If the Pod uses volumes:
kubectl describe pod api-456 \
-n production
and:
kubectl get pvc -n production
can help us identify storage-related issues.
What about force deletion?
We can run:
kubectl delete pod api-456 \
-n production \
--grace-period=0 \
--force
But this should be a deliberate action, not the first troubleshooting step.
Force deleting a Pod removes its Kubernetes API representation without necessarily guaranteeing that every underlying process or resource has already been terminated cleanly.
This requires even more caution with stateful workloads.
The first objective should be understanding why the Pod is not terminating normally.
When kubectl exec is not enough
Many modern container images are intentionally minimal or even distroless.
You may try:
kubectl exec -it api-xyz -- sh
and discover that the container does not include:
sh
curl
dig
tcpdump
ps
That does not mean we should permanently install debugging tools inside our production images.
Instead, we can use an ephemeral debugging container:
kubectl debug api-xyz \
-n production \
-it \
--image=ubuntu
kubectl debug allows us to add troubleshooting tools without modifying the original application image.
It can also be used to investigate nodes:
kubectl debug node/worker-01 \
-it \
--image=ubuntu
This creates a debugging environment that can help us inspect the node.
Metrics, Logs, and Events answer different questions
One of the most important parts of Kubernetes troubleshooting is understanding that a single source of information is usually not enough.
Problem
|
+-----------+-----------+
| | |
Events Metrics Logs
| | |
v v v
What did K8s? How much? What happened?
Events
Useful for:
FailedScheduling
FailedMount
FailedAttachVolume
FailedPull
BackOff
Unhealthy
Evicted
Command:
kubectl get events -A \
--sort-by='.lastTimestamp'
Metrics
Useful for:
CPU
Memory
Node utilization
Container utilization
For example:
kubectl top pods -A
kubectl top nodes
In environments with Prometheus, we can also inspect historical behavior and correlate incidents with changes in traffic or resource utilization.
Logs
Useful for understanding what happened inside the application:
kubectl logs <pod>
or:
kubectl logs <pod> --previous
In production, these logs should usually also be centralized using platforms such as Loki, OpenSearch, Elasticsearch, Splunk, Datadog, or similar solutions.
A troubleshooting process we can reuse
For these and many other Kubernetes problems, we can use the same general flow:
Symptom
|
v
Define blast radius
|
v
Check Pod state
|
v
Events
|
v
Logs / Metrics
|
v
Configuration
|
v
Dependencies / Infrastructure
|
v
Root cause
|
v
Fix
|
v
Validate
|
v
Prevent
Before changing anything, try to answer:
What is failing?
When did it start?
What changed?
Does it affect one Pod or several?
Does it affect one specific node?
What do the Events say?
What do the logs show?
What do the metrics show?
This helps reduce trial-and-error troubleshooting.
Command reference
# Pods
kubectl get pods -A -o wide
# Details and Events
kubectl describe pod <pod> -n <namespace>
# Logs
kubectl logs <pod> -n <namespace>
kubectl logs <pod> -n <namespace> --previous
# Events
kubectl get events -A --sort-by='.lastTimestamp'
# Resources
kubectl top pods -A
kubectl top nodes
# Configuration
kubectl get pod <pod> -n <namespace> -o yaml
kubectl get deployment <deployment> -n <namespace> -o yaml
# Nodes
kubectl get nodes -o wide
kubectl describe node <node>
# Debug container
kubectl debug <pod> -n <namespace> -it --image=ubuntu
# Node debugging
kubectl debug node/<node> -it --image=ubuntu
Conclusion
States such as CrashLoopBackOff, ImagePullBackOff, Pending, and Terminating are starting points for troubleshooting, not complete diagnoses.
The key is combining different signals:
Kubernetes State
+
Events
+
Logs
+
Metrics
|
v
Root Cause
In the next article, we will continue with another common group of Kubernetes problems:
CPU, memory, OOMKilled, throttling, and Pod evictions.
We will look at why a Pod can experience performance problems even when the Kubernetes node appears to have available resources.
Comments