🔴 CrashLoopBackOff
CrashLoopBackOff means the container started, crashed (non-zero exit), and Kubernetes is retrying with exponential backoff (10s → 20s → 40s → … → 5min cap). The pod is not stuck permanently — it just keeps failing.
Diagnostic workflow
# Step 1: confirm the status and restart count
kubectl get pod my-app-xyz
# NAME READY STATUS RESTARTS AGE
# my-app-xyz 0/1 CrashLoopBackOff 8 12m
# Step 2: read the PREVIOUS container's logs (current may be empty)
kubectl logs my-app-xyz --previous
# Step 3: check events for extra context
kubectl describe pod my-app-xyz | grep -A 20 Events
# Step 4: check exit code in lastState
kubectl get pod my-app-xyz -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'
# exitCode: 1 → app error
# exitCode: 137 → OOMKilled (128 + SIGKILL)
# exitCode: 139 → Segfault (128 + SIGSEGV)
# exitCode: 143 → SIGTERM not handled gracefully
Common root causes by exit code
| Exit Code | Signal | Likely cause | Fix |
|---|---|---|---|
1 | — | App startup error (bad config, missing file, DB unreachable) | Read logs, fix config/env vars |
2 | — | Shell/bash syntax error or misuse of command | Fix entrypoint command |
137 | SIGKILL | OOMKilled — container exceeded memory limit | Increase memory limit or fix leak |
139 | SIGSEGV | Segmentation fault — application bug | Debug core dump, update app |
143 | SIGTERM | Pod evicted or SIGTERM not handled — exit took too long, got SIGKILL | Implement graceful shutdown handler |
255 | — | Generic unhandled error | Check application logs carefully |
--previous log means the container crashed before writing anything — bad entrypoint, missing binary, or permission error. Use kubectl debug --copy-to=debug-pod -- sleep infinity to keep the pod alive and inspect its filesystem interactively.
🟡 ImagePullBackOff / ErrImagePull
ImagePullBackOff means the kubelet could not pull the container image. ErrImagePull is the immediate error; ImagePullBackOff is the same error after backoff kicks in.
Diagnostic workflow
# Check the exact error in events
kubectl describe pod my-app-xyz | grep -A 5 "Failed to pull"
# Failed to pull image "ghcr.io/myorg/myapp:v99": rpc error: code = NotFound
# Common error messages and meanings:
# "not found" → wrong image name or tag doesn't exist
# "unauthorized" → missing or wrong imagePullSecret
# "connection refused" → registry unreachable (network issue, private registry)
# "manifest unknown" → tag exists but wrong architecture (amd64 vs arm64)
Fix: missing imagePullSecret
# Create the secret from registry credentials
kubectl create secret docker-registry ghcr-secret \
--docker-server=ghcr.io \
--docker-username=myuser \
--docker-password=ghp_token123 \
--namespace=production
# Reference it in the pod spec
spec:
imagePullSecrets:
- name: ghcr-secret
containers:
- name: app
image: ghcr.io/myorg/myapp:v1.2.3
# Or attach to a ServiceAccount so all pods in the namespace get it
kubectl patch serviceaccount default \
-p '{"imagePullSecrets": [{"name": "ghcr-secret"}]}'
🗑️ Eviction Diagnosis
Pods are evicted when the kubelet detects node pressure — low memory, low disk, or too many processes. Eviction is a normal protective mechanism but becomes a problem when it's continuous or targets critical workloads.
Identifying evictions
# Evicted pods stay in the namespace with STATUS=Evicted
kubectl get pods -A | grep Evicted
# See why a pod was evicted
kubectl describe pod evicted-pod-xyz | grep -A 5 "Status:"
# Status: Failed
# Reason: Evicted
# Message: The node was low on resource: memory.
# Threshold quantity: 100Mi, available: 47Mi
# Check current node conditions
kubectl describe node worker-1 | grep -A 15 Conditions
# MemoryPressure True → node actively evicting
# DiskPressure True → disk full, kubelet will evict
# PIDPressure True → too many processes
# Clean up completed/evicted pods (they accumulate)
kubectl delete pods -A --field-selector='status.phase=Failed'
Eviction prevention strategies
Set memory requests accurately
BestEffort pods (no requests) are evicted first. Guaranteed QoS pods (requests=limits) are evicted last.
Use PodDisruptionBudgets
PDBs limit voluntary disruptions but don't block kubelet evictions under pressure. They do protect against scheduler evictions during upgrades.
Watch imagefs + nodefs
Unused images accumulate. kubectl debug node + crictl rmi --prune frees disk. Or increase imageGCHighThresholdPercent in kubelet config.
Tune eviction thresholds
Kubelet eviction thresholds: memory.available<100Mi, nodefs.available<10%. Increase node size or adjust in kubelet config.
💥 OOMKilled
OOMKilled (exit code 137) means the Linux kernel's OOM killer terminated the container because it exceeded its memory limit. Unlike eviction (node-level), OOMKill is container-level — enforced by cgroups.
# Identify OOMKilled containers
kubectl get pods -A -o json | jq '.items[] | select(
.status.containerStatuses[]?.lastState.terminated.reason == "OOMKilled"
) | {name: .metadata.name, ns: .metadata.namespace}'
# Check memory usage vs limit for a running pod
kubectl top pod my-app-xyz --containers
# See the limit that was exceeded
kubectl get pod my-app-xyz -o jsonpath='{.spec.containers[0].resources.limits.memory}'
# Check container memory usage in the describe output
kubectl describe pod my-app-xyz | grep -A 10 "Last State"
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
🧭 Universal Troubleshooting Methodology
Every Kubernetes problem follows the same diagnostic ladder. Work top-down, stop when you find the cause:
- kubectl get — confirm the resource exists and note its phase/status/age/restarts
- kubectl describe — read the Events section at the bottom; this is the most useful signal
- kubectl logs [--previous] — read application output; always check previous on restarts
- kubectl get endpoints / pvc / configmap — verify dependencies exist and are populated
- kubectl exec — test connectivity — nslookup, curl, nc from inside the affected pod
- kubectl debug / node — ephemeral container or node-level inspection for deep issues
- Metrics + logs — check Prometheus for resource saturation, Loki for correlated log spikes
kubectl describe pod always ends with an Events section. Kubernetes components (scheduler, kubelet, image puller) write human-readable messages here. In 80% of cases, the Events section contains the exact error message you need. Always read it before diving deeper.
Quick-reference: status → most likely cause
| Status | READY | Most likely cause | First command |
|---|---|---|---|
| Pending | 0/1 | Scheduler can't place pod | kubectl describe pod → Events |
| ImagePullBackOff | 0/1 | Can't pull image | kubectl describe pod → Failed to pull |
| CrashLoopBackOff | 0/1 | Container crashes on start | kubectl logs --previous |
| Running | 0/1 | Readiness probe failing | kubectl describe pod → Unhealthy events |
| Running | 1/1 | App error (check with logs) | kubectl logs -f |
| OOMKilled | 0/1 | Memory limit exceeded | kubectl describe pod → lastState + kubectl top |
| Evicted | 0/1 | Node pressure (disk/mem) | kubectl describe node → Conditions |
🔵 Pending Pods
Pending means the scheduler could not find a suitable node. The pod exists in etcd but no node has been assigned. Always check the Events section of kubectl describe pod first — the scheduler emits a clear reason.
Common Pending causes and fixes
| Scheduler event message | Root cause | Fix |
|---|---|---|
Insufficient cpu / Insufficient memory |
No node has enough allocatable CPU/memory to satisfy requests | Scale cluster, reduce requests, or check for resource hogging pods |
0/3 nodes are available: 3 node(s) had untolerated taint |
All nodes have taints the pod doesn't tolerate | Add toleration to pod spec or remove taint from a node |
didn't match Pod's node affinity/selector |
nodeSelector or nodeAffinity rules exclude all available nodes | Fix affinity rules or label the target nodes correctly |
pod has unbound immediate PersistentVolumeClaims |
PVC is Pending — no PV matched or StorageClass provisioner failed | Check kubectl describe pvc — wrong StorageClass? Provisioner running? |
Unschedulable: exceeds max pod count |
Node has hit its maxPods limit (default 110) |
Add more nodes or increase maxPods in kubelet config |
PodTopologySpread constraints not satisfiable |
TopologySpreadConstraint with whenUnsatisfiable: DoNotSchedule |
Add nodes in missing zones or relax maxSkew / change to ScheduleAnyway |
# Full diagnostic sequence for a Pending pod
# 1. Read the scheduler reason
kubectl describe pod my-app-xyz | grep -A 10 Events
# 2. Check allocatable resources on all nodes
kubectl describe nodes | grep -A 5 "Allocated resources"
# 3. Check if the PVC is bound (if storage-related)
kubectl get pvc -n production
kubectl describe pvc my-pvc -n production
# 4. Test scheduling manually with a dry-run
kubectl run test-sched --image=nginx --dry-run=server -o yaml \
--overrides='{"spec":{"nodeSelector":{"disktype":"ssd"}}}' 2>&1
🌐 DNS Failures
DNS failures are subtle — they appear as connection timeouts or "host not found" errors in application logs, often misdiagnosed as a service being down when the service is perfectly healthy.
DNS resolution hierarchy in Kubernetes
# A pod's /etc/resolv.conf looks like:
nameserver 10.96.0.10 # CoreDNS ClusterIP
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
# Resolution order for "payments-svc":
# 1. payments-svc.default.svc.cluster.local ← found! stops here
# 2. payments-svc.svc.cluster.local
# 3. payments-svc.cluster.local
# 4. payments-svc. (external DNS)
Diagnostic workflow
# Step 1: test DNS from inside a pod
kubectl exec -it my-app-xyz -- nslookup kubernetes.default.svc.cluster.local
# If this fails → CoreDNS is the problem
# If this works but service DNS fails → check the service/endpoints
# Step 2: check CoreDNS is healthy
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
# Step 3: verify the Service and its Endpoints exist
kubectl get svc payments-svc -n production
kubectl get endpoints payments-svc -n production
# Empty Endpoints → no pods matching the Service selector are Ready
# Step 4: check ndots — external hostnames with few dots get search suffixes appended
# "api.stripe.com" has 2 dots < ndots:5, so 5 failed local lookups happen first!
# Fix: use trailing dot "api.stripe.com." OR set dnsConfig.options.ndots: 2
spec:
dnsConfig:
options:
- name: ndots
value: "2" # reduces unnecessary local DNS queries
kubectl get endpoints <svc>. If it shows <none>, no pods match the selector OR no pods have passed their readiness probe. Fix the selector mismatch or the readiness issue — DNS itself is not the problem.