A troubleshooting method
Working outside-in, and stopping at the first no.
A pod in your prod namespace just flipped to CrashLoopBackOff, and the Slack channel is filling up. The instinct is to start changing things: restart it, redeploy, bump the memory limit. Resist that. Change something at random and you might get lucky, the pod goes green, and now you've buried the real break instead of fixing it. Next week it comes back and nobody remembers what you touched. Troubleshooting a cluster is more like tracing a water leak from the street back into the house. You don't rip open a wall first. You walk the pipe. Is the water even on, does it reach the building, then the right room, then the tap. You stop at the first place it runs dry, because that's where the break is. Every dry spot past that point is just a symptom of the first one.
Follow the pipe, stop at the first dry spot
Kubernetes hands you four commands, and they line up with those four checks. You run them with kubectl, the command-line tool that talks to the cluster's single front desk, the API server (Application Programming Interface server, the one door in and out of the cluster). First, kubectl get: is the object even there, and what state is it in? A Pod, the smallest thing Kubernetes runs, usually a single container, reports a status like Running, Pending, or CrashLoopBackOff. Second, kubectl describe: why is it in that state? Every object keeps a running diary at the bottom of describe called Events, and the answer is usually sitting right there. Third, kubectl logs: what did the app itself say before it fell over? Fourth, kubectl exec: open a shell inside the running container and test by hand, the way you'd walk into the room and turn the tap yourself. From in there you can check whether a config file is really where the app expects it, or curl a database the app swears it can't reach. You go in that order, and the moment one command says no, you stop and fix that. Don't read logs for a pod that never started.
kubectl get pods -n prod
NAME READY STATUS RESTARTS AGEweb-7d9f8c6b4-xl2kq 0/1 CrashLoopBackOff 6 (2m14s ago) 11mweb-7d9f8c6b4-p8m4t 1/1 Running 0 11mapi-5c8b0d9f77-k2wsz 0/1 Pending 0 4m
get tells you the state, not the reason. The web pod is crash-looping and the api pod is stuck Pending, but neither line says why. The READY column does say a little: 0/1 means the container isn't ready, either not running at all or up but failing its readiness check, and 1/1 means it's passing. That still won't tell you the cause, though, and guessing it from the status name is how you lose an afternoon. For the real answer, describe the object and read the Events at the bottom.
kubectl describe pod web-7d9f8c6b4-xl2kq -n prod
Events:Type Reason Age From Message---- ------ ---- ---- -------Normal Pulled 11m kubelet Successfully pulled image "web:1.8.2" in 812ms (812ms including waiting). Image size: 24917331 bytes.Normal Created 11m (x7 over 11m) kubelet Created container webNormal Started 11m (x7 over 11m) kubelet Started container webWarning BackOff 92s (x24 over 11m) kubelet Back-off restarting failed container web in pod web-7d9f8c6b4-xl2kq_prod
Look at what describe does and doesn't tell you. It says the kubelet, the agent running on every node that starts and watches containers, keeps restarting the container after it fails. That's the symptom, not the cause. The container that failed is already gone, replaced by a fresh one, so its error isn't in the current logs. That's what --previous is for. Show me the logs of the container that died, not the one that just booted.
kubectl logs web-7d9f8c6b4-xl2kq -n prod --previous
2026-07-16T09:14:02.118Z INFO starting web v1.8.22026-07-16T09:14:02.140Z FATAL config: environment variable DATABASE_URL is not setpanic: missing required configurationgoroutine 1 [running]:main.mustEnv(...)
There's the first no. The app never got its DATABASE_URL, so it panics the instant it boots. That's a config problem, not a networking problem and not a Kubernetes bug. The fix lives in that missing environment variable, usually a bad or absent reference to a ConfigMap or a Secret (the small objects that hold config values and passwords for the pod to read). Set it correctly and the crash loop ends. You never had to touch the network, the node, or the control plane (the cluster's brain, the components that decide what runs where).
The symptom already names the layer
After a while you stop walking every hop, because the state alone points at the layer. Pending means the pod was accepted but never placed on a node. The scheduler, the component that works like a host seating diners, couldn't find a table that fits, so the pod waits. describe or the events give the real reason: not enough CPU, a taint the pod doesn't tolerate (a node fenced off unless the pod carries a matching pass), or a storage volume that won't bind. CrashLoopBackOff means it started and then died, again and again, which is almost always the app's own fault, so go straight to logs --previous. Running but unreachable is a Service problem before it's ever a network one. Check whether the Service has endpoints first. Empty endpoints mean no ready pod matches its selector, not a broken router. And weirdness across the whole cluster, everything timing out at once, usually points at DNS (Domain Name System, the cluster's phone book) or the control plane, not any single pod.
kubectl get events -n prod --sort-by=.lastTimestamp
LAST SEEN TYPE REASON OBJECT MESSAGE5m Warning FailedScheduling pod/api-5c8b0d9f77-k2wsz 0/3 nodes are available: 3 Insufficient cpu. preemption: 0/3 nodes are available: 3 No preemption victims found.2m Warning BackOff pod/web-7d9f8c6b4-xl2kq Back-off restarting failed container web in pod web-7d9f8c6b4-xl2kq_prod
Sorting events by time gives you the whole cluster's story on one screen. It's how you spot that the Pending api pod is really a capacity problem: three nodes, none of them with a spare core to give. Same method, wider lens.
One last place the pipe can run dry is the node itself. If a pod looks fine in describe and its logs are clean but it still won't come up, check the machine it landed on with kubectl get nodes. A node marked NotReady, or one flagged with disk or memory pressure, will quietly wreck every pod on it while each pod looks blameless on its own. Same walk, one hop further out toward the street.
Write the hypothesis before you change anything. Blind deletes destroy evidence.
Time-box each layer. If DNS is unclear after ten minutes, you are probably not in a DNS failure.
Capture commands and outputs in the incident doc as you go. Memory lies under stress. Blind deletes destroy evidence.
Try this
Pick a broken lab app and force yourself to work outside-in: Service, endpoints, pod, events, node. Stop at the first failing layer and fix only that before moving on.
$ kubectl get pods -n prod$ kubectl describe pod web-7d9f8c6b4-xl2kq -n prod$ kubectl logs web-7d9f8c6b4-xl2kq -n prod --previous$ kubectl get events -n prod --sort-by=.lastTimestamp
Takeaway
Troubleshooting is a funnel. Outside-in, one no at a time. Skipping layers is how you restart healthy pods for an hour.