Resources/SRE reference/Kubernetes incident debugging

Kubernetes incident debugging

NOFire AI

How do you debug a Kubernetes incident?

Identify which layer failed before running any command. A pod that is not serving can be blocked at the scheduler, the image pull, the container process, the probe configuration, or the Service endpoints, and each layer has a different first command. NOFire AI does this step automatically by tying the symptom to the change that caused it.

VerdictStart with the layer, not the logs. Reaching for kubectl logs on a pod that never started is the most common wasted first move in Kubernetes debugging.

At a glance

SymptomFailing layerFirst commandWhere the cause usually is
Pod stuck PendingSchedulerkubectl describe podResource requests, node capacity, taints, unbound volume claims
ImagePullBackOffKubelet and registrykubectl describe podWrong tag, missing pull secret, registry unreachable
CrashLoopBackOffContainer processkubectl logs --previousThe application failing during startup, often config or a missing dependency
OOMKilled, exit code 137Kernel and limitskubectl describe podMemory limit below real usage, or a leak reaching it
Running but never ReadyProbe configurationkubectl describe podReadiness probe path, port, or timing wrong for the application
502 or 503 at the ingressService and endpointskubectl get endpointsNo ready backends behind the Service, or a port mismatch

How it works

The single most useful move in a Kubernetes incident is not a command. It is deciding which layer failed, because the same visible symptom, users cannot reach the application, is produced by at least six distinct failures with nothing in common.

The layers run in order and each one gates the next. The scheduler has to place the pod. The kubelet has to pull the image. The container process has to start and stay running. The readiness probe has to pass. The Service has to have ready endpoints behind it. The ingress has to route to that Service. A failure at any layer produces the same user-visible outcome and a completely different fix.

This is why reaching for kubectl logs first is the most common wasted move. Logs only exist if the container process actually started, so on a Pending or ImagePullBackOff pod there is nothing to read, and the emptiness gets misread as an application problem. kubectl describe pod is the better opening move for anything below the process layer, because it prints the scheduler and kubelet events that explain why the pod is where it is.

Once the container has started at least once, kubectl logs --previous matters more than kubectl logs. After a crash the current container is a fresh instance, so the plain command shows a process that has not failed yet while the evidence sits in the previous one.

Two facts about events are worth knowing before an incident rather than during one. Events are namespaced, so kubectl get events in the wrong namespace looks reassuringly empty. And they expire: the API server keeps them for one hour by default, which means the events explaining the original failure are frequently gone by the time a long incident is being investigated.

That expiry is the structural limit of kubectl as a debugging surface. It shows current state well and keeps almost no history, so it can tell you a pod is CrashLoopBackOff and not that the deploy forty minutes ago changed the config map it reads at startup. Answering that means correlating cluster state with change history, which is the job NOFire AI does by tying a symptom back to the specific deploy or configuration event that caused it, with the dependency path attached.

How it is measured

Measure the layer identification, not the total.

Time to correct layer is the number that matters, because it is the part of a Kubernetes incident that varies most between responders. An engineer who recognises ImagePullBackOff as a registry problem is finished in a minute; one who starts reading application logs can lose twenty. Track it by asking, in the retrospective, which layer the first three commands were aimed at and whether that was the failing one.

First-hypothesis accuracy is the same discipline applied to the diagnosis rather than the tool, and it is the measurement automated root cause analysis is scored on. The version that matters here is narrower: how often was the first layer guessed correctly.

Two things are worth counting across incidents rather than within one. How many were caused by a change in the preceding hour, which tells you whether your real problem is deployment risk rather than debugging skill. And how many required information that had already expired, which is a direct measurement of whether your event retention and log aggregation are adequate.

Mean time to resolution is the wrong instrument at this level. It aggregates detection, layer identification, diagnosis and remediation into one number, and the whole point of the layer model is that those are separable.

Common misconceptions

That logs are the first place to look. They are the first place to look once you know the process started. Below that layer they are empty, and the emptiness is misleading rather than informative.

That CrashLoopBackOff is an error. It is a backoff state, not a cause. It says the container has exited repeatedly and the kubelet is now waiting longer between attempts. The cause is whatever made the process exit, which is in the previous container's logs.

That a Running pod is a working pod. Running means the container process exists. Ready means the readiness probe passes. A pod can sit Running and never Ready indefinitely, serving nothing, and the deployment will report it as unavailable while the pod looks alive.

That 503 means the application is down. It frequently means the Service has no ready endpoints, which is a routing and probe problem rather than an application one. The application may be running perfectly and simply not be in the endpoint list, which is why debugging 502 and 503 errors starts at the endpoints rather than the pods.

That raising the memory limit fixes OOMKilled. It stops the symptom and tells you nothing about whether usage is legitimate growth or a leak. A limit raised without that answer is a longer interval before the same incident.

That kubectl is a record. It is a view of current state. Events expire, replaced pods take their logs with them, and nothing in the cluster tells you what changed. That gap is where most of the time in a long Kubernetes incident actually goes.

Frequently asked questions

Why does kubectl logs return nothing during an incident?
Because the container never started, or it already restarted. A pod stuck Pending has no container to log from, and after a crash the current container is new. Use `kubectl logs --previous` to read the instance that actually failed.
What does exit code 137 mean in Kubernetes?
The container was killed with SIGKILL, and in almost every production case that means OOMKilled: it exceeded its memory limit. The pod status says `OOMKilled` explicitly, and 137 is 128 plus signal 9.
Why do Kubernetes events disappear before I can read them?
Events are stored with a time-to-live, one hour by default on the API server, and are not a permanent record. During a long incident the events explaining the original failure often expire before anyone looks.
What is the difference between CrashLoopBackOff and ImagePullBackOff?
CrashLoopBackOff means the container started and exited repeatedly, so the problem is in your application or its configuration. ImagePullBackOff means it never started, so the problem is the image tag, the registry, or the pull credentials.
Book a demo