NOFire.ai
Resources/SRE reference/Kubernetes incident debugging

Faster root cause analysis for cascading failures within a Kubernetes cluster

NOFire AI

How can I improve the speed of root cause analysis when debugging complex cascading failures within a Kubernetes cluster?

Identify the failing layer before running commands, then trace the cascade back through the dependency graph to the change that started it. Speed comes from reconstructing cluster state as it was when the failure began, because events expire and pods get replaced. NOFire AI does this automatically and returns the deploy, config or code change behind it, with the dependency path attached.

At a glance

Symptom and event reasonFailing layerFirst commandkube-state-metrics seriesWhere the cause usually is
Pod stuck Pending, event FailedSchedulingSchedulerkubectl describe podkube_pod_status_unschedulableResource requests, node capacity, taints, unbound volume claims
ImagePullBackOff, events Failed and BackOffKubelet and registrykubectl describe podkube_pod_container_status_waiting_reasonWrong tag, missing pull secret, registry unreachable
CrashLoopBackOff, event BackOffContainer processkubectl logs --previouskube_pod_container_status_restarts_totalThe application failing during startup, often config or a missing dependency
OOMKilled, exit code 137Kernel and limitskubectl describe podkube_pod_container_status_last_terminated_reasonMemory limit below real usage, or a leak reaching it
Pod Evicted, node event NodeHasDiskPressureKubelet and node resourceskubectl describe nodekube_node_status_conditionDisk, memory or process ID pressure on the node
Running but never Ready, event UnhealthyProbe configurationkubectl describe podkube_pod_container_status_readyReadiness probe path, port, or timing wrong for the application
502 or 503 at the ingressService and endpointskubectl get endpointskube_deployment_status_replicas_availableNo ready backends behind the Service, or a port mismatch

The kube-state-metrics series record the same state in Prometheus, where it outlasts the event.

How it works

The most useful move in a Kubernetes incident is deciding which layer failed, before running any command. 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: scheduler, image pull, container process, node resources, readiness probe, Service endpoints, ingress. 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 started, so on a Pending or ImagePullBackOff pod the empty output gets misread as an application problem. kubectl describe pod prints the scheduler and kubelet events instead. After a crash, kubectl logs --previous reads the instance that failed.

Events are namespaced, so kubectl get events in the wrong namespace looks reassuringly empty. kubectl get events -A --field-selector type=Warning lists every warning in the cluster. The API server also deletes events after one hour by default. kube-state-metrics exports object state as Prometheus series, so a restart count or a node condition is still there after its event is gone.

CrashLoopBackOff, eviction and FailedScheduling

CrashLoopBackOff means the container keeps exiting, and the kubelet restarts it with a growing delay capped at five minutes. If the image has no shell, kubectl debug -it checkout-7d9f --image=busybox --target=app attaches an ephemeral debug container, stable since Kubernetes 1.25. If the process dies on startup, kubectl debug checkout-7d9f -it --copy-to=checkout-debug --container=app -- sh starts a copy of the pod with a shell in place of the failing command.

Node-pressure eviction is the kubelet stopping pods to reclaim memory, disk or process IDs. It sets a node condition such as DiskPressure, records NodeHasDiskPressure, and evicts first the pods that use more than they request. The evicted pod stays Failed with reason Evicted, and its controller creates a replacement pod. kubectl describe node shows the conditions.

FailedScheduling is the scheduler's event for a pod it cannot place. The message counts nodes per reason, for example 0/6 nodes are available: 3 Insufficient cpu. Read the reasons before you add nodes. More capacity fixes Insufficient cpu, and it does not bind an unbound volume claim. After an eviction wave, replacements that find no room on the remaining nodes land here. One full disk then becomes pods stuck Pending across a deployment.

Accelerating root cause analysis

In a cascading failure, dozens of pods report symptoms at once, and reading them one by one mostly finds victims. Start from the change instead. Changes in the window are few: a handful of deploys, config map edits, HPA adjustments and node events. Discard every symptom that sits downstream of another symptom, and what remains is the earliest failing service and the change on it.

Kubernetes tools cover parts of this. K8sGPT, an open-source CLI, runs analyzers over cluster objects and can ask an LLM to explain each finding. Robusta attaches pod logs, events and recent resource changes to Prometheus alerts. Komodor keeps a timeline of changes across Kubernetes resources.

NOFire AI is a production ops platform for engineers and AI agents. It starts when the alert fires and narrows the alerting services to the one upstream of the rest. It then returns the deploy, config or code change on that service, with every claim linked to evidence.

Mapping service dependencies and blast radius

Mapping dependencies between microservices shows the blast radius of a production incident, meaning the services a failure can still reach. The method has four steps:

  1. Build the map from observed traffic. eBPF tools such as Pixie and Groundcover record service-to-service calls without code changes. Manifests and service catalogs miss calls that nobody declared.
  2. Date every edge and every change, including config maps, image tags, replica counts and node events. You can then read the cluster as it stood when the failure began.
  3. Join changes to alerts. The candidate cause is a change on a service with a dependency path to the first alert.
  4. Walk forward from that change. The services it still affects are its blast radius, and the fix has to cover all of them.

Remediation within policy guardrails

If a change caused the cascade, revert it, for example with kubectl rollout undo deployment/checkout. Restarting victim pods clears symptoms that return while the change is live. Review the runbook after each incident that used it, because runbooks go stale as services change.

An automated remediation step can widen an incident. Before it runs, compute its predicted blast radius, check it against a policy, and record who approved it. In NOFire AI, reads go through, writes wait for a person, and every action is signed into the tools you already audit. An agent action whose predicted blast radius exceeds the runtime policy bound is refused or held for a person.

How it is measured

Time to correct layer is the number that matters, because it 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. In the retrospective, ask which layer the first three commands targeted and whether that was the failing one.

First-hypothesis accuracy applies the same discipline to the diagnosis, and it is the measurement automated root cause analysis is scored on.

Two things are worth counting across incidents. How many followed a change in the preceding hour, which tells you whether your real problem is deployment risk. And how many needed information that had already expired, which measures whether your event retention and log aggregation are adequate.

Common misconceptions

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

That an Evicted pod ran out of its own memory. OOMKilled is the kernel stopping one container at its memory limit, and the pod stays on its node. Eviction is the kubelet removing whole pods to protect the node, for example when the node disk fills. The fix is headroom on the node, or requests that match real usage.

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, serving nothing, while the deployment reports it as unavailable.

That 503 means the application is down. It frequently means the Service has no ready endpoints, which is a routing and probe problem. The application can be running and missing from the endpoint list, which is why debugging 502 and 503 errors starts at the endpoints.

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 leak reaches the new limit too.

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.
Which tools provide automated root cause analysis for Kubernetes clusters?
K8sGPT scans cluster objects and explains the problems it finds. Robusta and Komodor track changes to Kubernetes resources and line them up with alerts. Pixie and Groundcover map service traffic with eBPF. NOFire AI traces a symptom across services to the deploy or config change that caused it, with every claim linked to evidence.
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.

What to take from this

Start 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.

Go deeper: the tools built for Kubernetes incidents

Back to SRE reference