Your application is crashing. The pod restarts. You check the logs, and there is nothing there. No stack trace, no error code, no obvious reason why a perfectly healthy container suddenly died. This is the reality of silent failures in Kubernetes, and if you manage clusters in production, you have probably lost hours, maybe days, to this exact problem. You are not alone, and more importantly, there is a repeatable way to catch these ghosts.
Silent failures in Kubernetes pods rarely leave obvious clues, but they always leave traces. By combining structured logging, resource monitoring, and systematic correlation of pod events with container exit codes, you can turn invisible crashes into trackable incidents. This article walks through a three-layer debugging method that works for platform engineers and SREs who need answers now.
The Anatomy of a Silent Pod Crash
Silent failures feel like magic, but they are mechanical. A pod exits without writing an error because the application process terminates in a way that bypasses your logging framework. Common causes include out-of-memory (OOM) kills from the kernel, SIGTERM signals that your app does not handle gracefully, or segmentation faults that happen before the logger flushes a buffer.
Understanding the exit code is your first clue. When a container stops, Kubernetes records the exit code from the main process. An exit code of 137 usually means the pod was killed by an external signal, often the Linux OOM killer. Exit code 143 indicates a SIGTERM that was not caught. Exit code 139 points to a segmentation fault. These numbers are not errors in your application logs; they are signals from the operating system. You need to look for them where Kubernetes stores them.
Layer 1: Correlate Pod Events with Exit Codes
Your first step is to stop treating pod logs as the only source of truth. Kubernetes keeps its own event history for every pod. You can access it through kubectl.
- Run
kubectl get pod <pod-name> -o yamland inspect thestatus.containerStatus.exitCodefield. This tells you the raw exit code of the main process. - Use
kubectl describe pod <pod-name>and look at theContainerssection. TheLast Statefield often reveals if the container was killed or if it exited normally. - Check the node-level logs on the worker node using
journalctl -u kubeletordmesg. An OOM kill will appear in the kernel logs, not in your application logs.
The most common mistake is jumping straight into application-level debugging without checking these three sources first. If the exit code is 137, you are not looking for a bug in your code; you are looking for a memory leak or an undersized resource allocation. This distinction saves hours of wasted effort.
Layer 2: Enable Structured Logging and Capture Stdout
Many silent failures happen because the application writes errors to stderr, but the logging configuration in Kubernetes only captures stdout. Or worse, the application buffers logs in memory and crashes before the buffer is flushed.
Here is a practical checklist to audit your setup:
- Ensure your application writes logs to stdout and stderr, not to a file inside the container. Kubernetes captures stdout, but it does not read files.
- Use a logging library that supports immediate flushing. In Python, set
flush=Trueon your print statements or configure your logger with aStreamHandler. In Node.js, avoidconsole.logbuffering by using a logger that writes toprocess.stdout. - Configure your container’s entrypoint to run the application with
exec. This ensures the application process receives signals directly and that signal handling works as expected. Withoutexec, your shell script becomes the parent process, and signals may not reach your app.
| Technique | What It Catches | Common Mistake |
|---|---|---|
| Check exit code via kubectl | OOM kills, SIGTERM, segfaults | Skipping this step and blaming the code |
| Flush stdout buffers | Crashes before log flush | Using file-based logging inside the container |
| Use exec in entrypoint | Lost signal handling | Running app as a background process in a shell script |
| Monitor node kernel logs | Kernel-level OOM events | Only checking pod logs in the dashboard |
Layer 3: Correlate Resource Pressure with Restart Patterns
Silent failures often cluster around specific conditions. A pod that crashes every day at 2:00 PM might be hitting a garbage collection cycle that spikes memory usage. A pod that fails only when another service on the same node is under load might be competing for CPU.
Use the following approach to find the pattern:
- Pull the restart history for the pod using
kubectl get pod <pod-name> -o yaml | grep -A 10 'lastState'. Look for a repeating time interval. - Graph the resource usage of the node during those windows. Tools like
htoporkubectl topare useful, but you should also pull metrics from your monitoring stack. If your node’s free memory drops below 10% right before the crash, you have found your culprit. - Check the resource limits on your pod. A pod with
resources.limits.memoryset too low will be killed by the kernel when the application requests more memory, even if the node has plenty of RAM available.
Expert advice: If your pod exits with code 137 and you cannot find an OOM entry in the kernel logs, check the cgroup memory limits. Kubernetes uses cgroups to enforce resource limits, and a cgroup eviction looks identical to an OOM kill in the pod’s exit code but does not always appear in the system logs. Run
cat /sys/fs/cgroup/memory/<pod-id>/memory.limit_in_byteson the node to verify.
Building a Systematic Debugging Workflow
You need a repeatable process so you do not start from scratch every time a pod dies silently. Here is a numbered workflow that covers the most common scenarios.
- Capture the exit code immediately. Before you do anything else, run
kubectl get podand record the exit code. Do not let the pod restart and overwrite the state. If your pod is set to restart automatically, usekubectl logs --tail=50 <pod-name>and look for the last few lines before the restart. - Check the node kernel logs. SSH into the worker node and run
dmesg -T | grep -i "killed\|oom\|segfault". Filter by the timestamp of the crash. This step finds OOM kills and segfaults that your application never logged. - Review the pod’s resource trends. Use your monitoring tool to look at memory, CPU, and disk I/O for the pod over the last hour. A sudden spike in memory usage right before the crash points to a memory leak. A gradual increase over days points to a different class of bug.
- Test with increased resources. If the exit code was 137 and the kernel logs show an OOM event, increase the pod’s memory limit by 50% and redeploy. If the crash stops, you have confirmed a resource constraint issue. If it continues, the problem is elsewhere.
Common Pitfalls That Make Silent Failures Worse
Some habits make these crashes harder to debug. Avoid them if you want to keep your sanity.
- Relying solely on
kubectl logs. Pod logs only show what your application writes to stdout. They do not show kernel events, signal handling, or cgroup evictions. - Ignoring the node’s overall health. A pod that crashes on one node but runs fine on another is a node problem, not an application problem. Always check if the crash is reproducible on a different node.
- Setting
restart: alwayswithout a backoff. If your pod crashes instantly on startup, Kubernetes will restart it in a tight loop, flooding your logs with restart events and making it harder to isolate the original failure. Userestart: on-failurewith a delay.
If you want to see how these debugging principles apply to other environments, check out our guide on common JavaScript debugging tricks every developer should know. The concept of checking the environment before the code applies everywhere.
When the Logs Are Truly Silent
There are cases where every technique above returns nothing. The pod exits with code 0, the kernel logs are clean, and the node has plenty of resources. This usually means the application shut down gracefully but did not log the reason. This can happen when an external health check decides the pod is unhealthy and sends a termination signal, or when a dependency like a database connection drops in a way that the application treats as a normal shutdown.
For these edge cases, you need to instrument the shutdown path itself. Add a signal handler in your application that logs the reason for termination. In most languages, you can catch SIGTERM and SIGINT and write a final log entry before the process exits. This simple change turns a silent exit into a documented event.
If you are dealing with a similar situation where errors appear in one environment but not another, our article on why your production errors don’t show up in local development explains the environmental differences that cause these mismatches.
Turning Invisible Crashes into Actionable Data
Silent failures in Kubernetes pod logs do not have to stay silent. By shifting your focus from application logs to system-level signals, you can catch the vast majority of these crashes within minutes. The exit code is your starting point. The kernel logs are your evidence. Resource monitoring is your context. Together, they form a debugging methodology that works even when your application has nothing to say.
Start with the exit code today. Add a signal handler to your application tomorrow. And the next time a pod dies without a word, you will know exactly where to look.
