Troubleshoot Python Pod Crash Loops
Learn to diagnose and fix Python pod crash loops in Kubernetes—check logs, events, and restart policies, then apply fixes through hands-on steps.
Focus: troubleshoot python pod crash loops
Your Python service is scaling beautifully in Kubernetes, but suddenly every pod shows CrashLoopBackOff and the deployment is bouncing faster than a rubber ball. You're not alone—crash loops are one of the most common and frustrating issues Python developers face when moving to Kubernetes, especially when your app worked perfectly in local Docker. This lesson gives you a battle-tested, step-by-step approach to troubleshoot python pod crash loops: how to inspect logs, events, and restart policies, and how to apply targeted fixes so your pods stay healthy and your users stay happy.
The Problem This Lesson Solves
When a pod crashes and restarts repeatedly, Kubernetes doesn't just give up—it enters a CrashLoopBackOff state, backing off with increasing delays between restarts. For a Python developer, this usually means a code-level issue: an unhandled exception on startup, a missing dependency, a bad environment variable, or a resource limit that's too tight. Left unchecked, crash loops can cause prolonged downtime, especially if your Deployment has a low replicas count.
What makes crash loops particularly tricky is that Kubernetes hides the root cause behind layers of abstraction: the pod spec, the container runtime, the logs, and the cluster events. Without a systematic method, you'll waste hours guessing. This lesson arms you with a repeatable diagnostic process—starting from the health of the pod up, down to your Python code—so you can find and fix the issue quickly, even under pressure.
Core Concept / Mental Model
Think of a crash loop like a car that won't start: the symptoms (engine clicking) are obvious, but the cause could be a dead battery, a jammed starter, or an empty fuel tank. In Kubernetes, the pod is the car, the container is the engine, and the restart policy is the ignition system that keeps trying to turn the key.
Here’s the mental model you need:
- Liveness probe: Kubernetes checks if the app is still alive by hitting an endpoint (e.g.,
/health). If it fails, the kubelet kills the container and restarts it. - Readiness probe: This only affects service routing, not restarts. A failing readiness probe removes the pod from service endpoints but does not restart it.
- Restart policy: The default for
DeploymentsisAlways. So any container exit (even a normal one) triggers a restart—which is often the root of the loop. - Exit codes: Each container exit has a numeric code:
0means success,1means generic error,137means killed by SIGKILL (often OOM),143means SIGTERM.
The crash loop is not a single failure—it’s a cycle: start → crash → backoff → restart → start again. Your goal is to break that cycle by addressing the root cause.
To remember this, use the WHEEL mnemonic: Watch logs, Help (diagnose) events, Examine exit codes, Evaluate probes, Look at resources (limits).
How It Works Step by Step
Diagnosing a crash loop is a structured exercise. Follow these steps in order—don’t skip ahead.
Step 1: Identify the Pod and Container Name
Use kubectl get pods to list all pods in the namespace. Look for one in CrashLoopBackOff. Note the pod name and the container name (usually the same if you have a single container).
Step 2: Inspect Logs for the Last Crash
Logs are your best friend. Use kubectl logs <pod> --previous to see the logs from the container that crashed—the current container may not have written anything yet. If you don’t see the --previous flag, you’ll often get logs from the crashing attempt, but mixing them up can mislead you.
Step 3: Check Events and Exit Codes
Run kubectl describe pod <pod> and look for the Events section. Pay attention to messages like Back-off restarting failed container, OOMKilled, or Failed to pull image. Also note the Last State and its Reason—e.g., Error with exit code 1, or OOMKilled with code 137.
Step 4: Validate Resource Limits
Check the pod spec for CPU/memory limits. A container that exceeds its memory limit gets killed by the kernel (OOMKilled). Use kubectl get pod <pod> -o yaml to see the resources section.
Step 5: Review Probes
If liveness probe is defined, verify the endpoint path and port. A probe that’s misconfigured (e.g., pointing to /healthz when your app only has /health) will cause repeated kills, even if your app is perfectly healthy.
Step 6: Reproduce Locally
Once you suspect code-level issues, run the same container image locally with docker run. Test with the same environment variables and startup commands. This isolates whether it’s a code problem or a cluster-configuration problem.
Step 7: Apply a Fix and Roll Out
Once you’ve identified the root cause, patch the Deployment—either by editing the YAML or using kubectl set env. Then roll out with kubectl rollout restart deployment <name> and verify.
Hands-On Walkthrough
Let’s apply the steps with a real example. Suppose you have a Python Flask app that crashes because a required environment variable is missing.
Example 1: Missing Environment Variable
Create a simple pod manifest that references an undefined env var:
apiVersion: v1
kind: Pod
metadata:
name: crash-python
spec:
containers:
- name: app
image: python:3.11-slim
command: ["/bin/sh", "-c"]
args:
- |
python -c "import os; print(os.environ['REQUIRED_VAR'])"
restartPolicy: OnFailure
Apply it and watch the loop:
kubectl apply -f crash-pod.yaml
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# crash-python 0/1 CrashLoopBackOff 3 45s
Now check logs:
kubectl logs crash-python --previous
# Traceback (most recent call last):
# File "<string>", line 1, in <module>
# File "/usr/local/lib/python3.11/os.py", line 679, in __getitem__
# raise KeyError(key) from None
# KeyError: 'REQUIRED_VAR'
The log clearly shows a KeyError. Fix it by setting the env var in the manifest.
Example 2: Memory Limit Exceeded
Suppose your Python service uses a lot of memory. Create a Deployment with a memory limit of 64Mi:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mem-hog
spec:
replicas: 1
selector:
matchLabels:
app: mem-hog
template:
metadata:
labels:
app: mem-hog
spec:
containers:
- name: app
image: python:3.11-slim
command: ["python", "-c", "x = ' ' * (100*1024*1024); import time; time.sleep(60)"]
resources:
limits:
memory: 64Mi
Apply and check events:
kubectl apply -f mem-hog-deploy.yaml
kubectl get pods
kubectl describe pod mem-hog-xxx
# Last State: Terminated
# Reason: OOMKilled
# Exit Code: 137
The exit code 137 and OOMKilled tell you the limit is too low. Increase the memory limit or optimize your Python code.
Example 3: Liveness Probe Misconfiguration
Here’s a Flask app where the probe hits the wrong path:
from flask import Flask
app = Flask(__name__)
@app.route("/health")
def health():
return "OK"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)
The manifest sets the liveness probe to /healthz:
livenessProbe:
httpGet:
path: /healthz
port: 5000
initialDelaySeconds: 5
periodSeconds: 5
The probe fails, so Kubernetes kills the container. Logs look fine, but describe shows liveness probe failures. Fix the path to /health—problem solved.
Compare Options / When to Choose What
There are different ways to diagnose and resolve crash loops. Here’s a comparison of common approaches:
| Approach | Best For | Pros | Cons |
|---|---|---|---|
kubectl logs --previous |
Quick diagnosis of code errors | Fast, no extra setup | Requires container to have written logs |
kubectl describe pod |
Understanding events and exit codes | Shows OOMKilled, probe failures, image pull errors | Verbose output can be overwhelming |
| Accessing container interactively | Exploring runtime state | Direct debugging (shell, Python) | Risk of modifying state; requires exec permission |
Local docker run reproduction |
Isolating code vs. cluster config | Full control over environment | Doesn’t simulate kubernetes probes/limits |
| Centralized logging (e.g., ELK, Loki) | Long-term monitoring | Aggregates logs across restarts | Requires setup and processing delay |
When to choose what: For a first look, use kubectl logs --previous to catch stack traces. If the logs are empty or unclear, describe will reveal OOM or probe issues. For persistent issues, adopt centralized logging so you don’t lose crash logs between restarts.
Troubleshooting & Edge Cases
Even with a systematic approach, you’ll hit edge cases. Here are the most common and how to fix them.
Python-specific pitfalls
- Unhandled exceptions at import time: If a dependency isn’t installed or a config error occurs at import, the app crashes before you can log anything. Wrap your startup in try/except and log the traceback early.
- Missing
if __name__ == '__main__'guard: If you use multiprocessing, the child processes re-import the module and might execute startup code repeatedly, causing loops. - Wrong entrypoint: Ensure your Dockerfile’s
CMDmatches your Python executable. A typo likepython app.pyinstead ofpython ./app.pycan cause immediate exits.
Kubernetes configuration errors
- Readiness vs liveness probe mix-up: A failing readiness probe doesn’t restart, but causes the pod to be removed from service. It might look like a crash if you have zero replicas ready. Double-check which probe you changed.
- Image pull issues: A wrong image tag or a private registry without credentials causes
ErrImagePull/ImagePullBackOff, which is often confused with a crash loop. Check the events forFailed to pull image. - Restart policy on Pods: If you’re using a standalone
Pod(not a Deployment), the defaultrestartPolicyisNever, so you may see aFailedstate instead of a loop. Deployments always set it toAlways.
Fix common issues
- OOMKilled: Increase
resources.limits.memory, or reduce memory usage in Python (e.g., usegc, stream large data). Monitor withkubectl top pod. - Probe failure: Fix the endpoint path or adjust
initialDelaySeconds—your app might need more time to start. - Env var missing: Use ConfigMaps/Secrets and validate them in code with a clear error message instead of a
KeyError.
What You Learned & What's Next
You now have a reliable, step-by-step method to troubleshoot python pod crash loops: check --previous logs, inspect events and exit codes, validate resource limits and probes, reproduce locally, and apply targeted fixes. You learned how to interpret exit codes like 137 (OOMKilled) and why the restart policy matters in Kubernetes. You’ve practiced real exercises with missing env vars, memory overruns, and probe misconfigurations.
Next, you’ll build on this by exploring how to automate crash-loop detection with monitoring and alerts—so you’re not manually watching pods. That lesson will cover tools like Prometheus, custom liveness probes, and Python-based operators that can watch and recover from failures automatically.
Practice recap
Pick one of your own Python services and intentionally break it—remove a required environment variable, set a too-small memory limit, or misconfigure its liveness probe. Deploy it to a test cluster and go through the troubleshooting process step by step: check logs with --previous, examine events, and fix the issue. Then write a short runbook documenting what you observed and how you resolved it.
Common mistakes
- Ignoring
--previousonkubectl logs— you see the live (often empty) logs instead of the crash logs. - Confusing a failing readiness probe with a crash loop — readiness only removes the pod from service, not restart it.
- Setting memory limits too low for Python apps without testing, leading to silent OOMKilled exits (code 137).
- Forgetting to wrap startup code in try/except, so a missing imports or env var kills the container before any useful log appears.
Variations
- Use
kubectl debugto start a temporary container with extra debugging tools (e.g., Python REPL orcurl) attached to the crashed pod. - Adopt ephemeral containers for troubleshooting production pods without altering the original container.
- Implement a health-check module in your Python app (e.g., using Flask or FastAPI) that logs the exception and exits with a distinct code to aid automated diagnosis.
Real-world use cases
- A Flask API starts crash-looping after a config change; logging reveals a missing env var, fixed by updating the ConfigMap.
- A Celery worker in Kubernetes gets OOMKilled under high traffic; increasing the memory limit and optimizing tasks resolves the loop.
- A Python microservice with a liveness probe pointed to the wrong endpoint restarts every 5 seconds; correcting the path eliminates the crash loop.
Key takeaways
- Use
kubectl logs <pod> --previousto see the logs from the crashing container. - Check
kubectl describe podfor events—OOMKilled,Back-off, and exit codes like137tell the real story. - Match your liveness probe to a real health endpoint; a false probe causes infinite restarts.
- Set realistic memory limits and test locally with the same constraints to avoid OOM kills.
- Always reproduce the issue locally with
docker runto isolate code issues from cluster config. - The restart policy
Alwayson Deployments means any exit triggers a restart—so focus on the root cause, not the loop itself.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.