The Triage Playbook — diagnose & remediate fast
Almost every task on a performance-based platform exam has something broken in it — sometimes that is the task, and sometimes it is a quiet trap inside a task that looks like plain configuration. The people who pass are not the ones who have memorised the most YAML; they are the ones who run the same diagnostic sequence every single time, so that all their thinking is left over for the actual fix. This page is the entry point: first that universal sequence, then a symptom index that takes whatever you are staring at and sends you to the page covering it in depth. Drill the sequence until the first six commands are muscle memory, and use the index as a lookup table, not a reading list.
Imagine the light in your bedroom won’t turn on. A panicky person immediately starts unscrewing the ceiling. A calm person always does the same four checks first: is the switch on? is the bulb dead? do the other lights work? did someone flip the big switch in the hallway box? Kubernetes has exactly the same kind of checks — and one of them, called Events, is a little note the cluster leaves you saying literally what went wrong. Most people forget to read the note. This page is about reading the note, then knowing which drawer to open next.
The universal triage order
☺ Like you’re 10: Before you guess what’s wrong, always look in the same six places, in the same order. Guessing first is how you waste ten minutes.
The single biggest time sink under exam pressure is forming a hypothesis before gathering evidence. You see CrashLoopBackOff, you decide it must be the config map, you spend six minutes on the config map, and the answer was in the Events the whole time. So: do not think. Run the sequence. The sequence takes ninety seconds and it very frequently hands you the literal answer in plain English.
The six steps, in order
Steps 1–4 are the general Kubernetes ladder; step 5 is the platform-engineering addition that matters enormously on this exam, because so much of the platform is expressed as custom resources whose status.conditions block is the real error message. Only at step 6 do you get to have an opinion.
| # | Step | Command | What you are looking for |
|---|---|---|---|
| 1 | Wide list | kubectl get <kind> -o wide | Phase, restarts, age, node, IP. Restarts > 0 and a recent age tell you a lot before you read anything else. |
| 2 | Describe | kubectl describe <kind> <name> | The Events block at the bottom. Scroll straight there first. Then Conditions, then the container spec. |
| 3 | Namespace events | kubectl get events --sort-by=.lastTimestamp | Failures on objects you did not think to describe — the ReplicaSet, the PVC, the webhook, the quota. |
| 4 | Logs | kubectl logs <pod> --previous | Why the app itself died. --previous is essential in a crash loop — the current container has no logs yet. |
| 5 | Controller / CR status | kubectl get <cr> -o yaml → status.conditions | What the operator thinks. Argo CD, Flux, Crossplane and cert-manager all write the true error here. |
| 6 | Hypothesis | — | Only now. And prefer the cause that explains all the evidence, not just the loudest bit. |
# The 90-second opener. Run this on every broken thing, every time. kubectl config current-context # am I even on the right cluster? kubectl get pods -n "$NS" -o wide # 1. wide list kubectl describe pod -n "$NS" "$POD" | tail -30 # 2. Events live at the BOTTOM kubectl get events -n "$NS" --sort-by=.lastTimestamp | tail -25 # 3. everything else that failed kubectl logs -n "$NS" "$POD" --previous --tail=50 # 4. why the process exited kubectl logs -n "$NS" "$POD" -c "$INIT" --previous # 4b. init containers need -c # Cluster-wide sweep when you do not yet know where the damage is: kubectl get pods -A --field-selector=status.phase!=Running kubectl get events -A --sort-by=.lastTimestamp | grep -i -E 'warn|fail|error' | tail -30
Events are the answer key. Kubernetes tells you, in near-English, things like 0/3 nodes are available: 3 Insufficient cpu, couldn't find key DB_HOST in ConfigMap app/config, or admission webhook "validate.kyverno.svc" denied the request: require-labels. Every one of those is the whole diagnosis. Train yourself to read the Events block before you read the spec — it is the difference between a two-minute task and a twelve-minute one.
The decision flow
The flow below is what the six steps look like as a shape. Notice that almost every branch terminates in “read Events / read Conditions” — because in a well-built control plane, every controller is contractually obliged to explain itself in one of those two places. Notice too that the three branches map exactly onto the three detailed playbooks this page routes you to.
“What made me fast wasn’t learning more Kubernetes — it was deleting a habit. I used to open the Deployment YAML first, because that felt like ‘real’ debugging. Now I read the last ten lines of Events before anything else, and two thirds of the time the fix is right there in a sentence.”
Symptom index
☺ Like you’re 10: Find the words you can actually see on your screen in the left column. The right column says which page to open.
Once the sequence has produced evidence, this is the lookup. Scan the left column for the string in front of you, read the middle column for the cause to test first, and follow the link for the full symptom → cause → command → fix treatment.
| You see… | Most likely | Where to look |
|---|---|---|
Pending / Unschedulable | Insufficient CPU/memory, taint or selector mismatch, unbound PVC, quota | Workloads |
ImagePullBackOff / ErrImagePull | Image name or tag typo; private registry, no imagePullSecrets | Workloads |
CrashLoopBackOff | App exits non-zero; wrong command; missing runtime config | Workloads |
OOMKilled (exit 137) | Memory limit below real usage, or a runtime heap above it | Workloads |
CreateContainerConfigError | ConfigMap/Secret missing — or the key inside it is | Workloads |
Init:Error / Init:CrashLoopBackOff | Init container failed: migration, dependency wait, or permissions | Workloads |
Running but 0/1 READY, no restarts | Readiness probe failing — wrong path, wrong port, slow start | Workloads |
| Restarts climbing, app looks healthy | Liveness probe too aggressive for a slow container | Workloads |
Evicted | Node memory/disk pressure or ephemeral-storage overrun | Workloads |
Stuck Terminating | Finalizer never removed, long grace period, or node gone | Workloads |
PVC stuck Pending | No matching or default StorageClass; no provisioner running | Workloads |
| Multi-attach error on a volume | A ReadWriteOnce volume wanted by a second node | Workloads |
| StatefulSet pod never binds its volume | volumeClaimTemplates mismatch, or a zone-bound PV | Workloads |
endpoints returns <none> | Selector matches no pods — or matching pods are not Ready | Networking |
| DNS lookups fail inside a pod | CoreDNS down, wrong FQDN, or DNS egress blocked | Networking |
| Pod-to-pod connection times out | A NetworkPolicy: default-deny with no matching allow | Networking |
| Ingress or Gateway returns 404 / 503 | Wrong host/path rule, missing class, or backend without endpoints | Networking |
| mTLS handshake failure in the mesh | STRICT mode with a sidecar missing, or an authorization policy | Networking |
Error from server (Forbidden) | No Role grants that verb, or the binding names the wrong subject | Networking |
admission webhook … denied the request | A Kyverno/Gatekeeper rule, or a webhook failing closed | Networking |
Argo CD app OutOfSync | Drift, auto-sync off, or a path that renders nothing | Delivery |
Argo CD app Synced but Degraded | Git applied cleanly; the manifests themselves are wrong | Delivery |
| Sync fails or a hook never completes | Failing hook Job, wrong sync wave, or an RBAC gap | Delivery |
| Argo Rollout stuck mid-canary | Paused awaiting promotion, or the new ReplicaSet never available | Delivery |
| Rollout AnalysisRun failed | Metric query returns nothing — bad provider address or selector | Delivery |
| Flux Kustomization not reconciling | Source not fetched, object suspended, or a build error | Delivery |
| Tekton PipelineRun / TaskRun failed | Failing step, missing workspace, or ServiceAccount lacking registry access | Delivery |
| Prometheus target down or missing | ServiceMonitor labels, port name mismatch, or CR not selecting it | Delivery |
| Alert never fires | Rule not loaded, expression never true, or routing swallows it | Delivery |
| Traces missing or fragmented | Wrong collector endpoint, heavy sampling, or context not propagated | Delivery |
| Crossplane claim never Ready | No matching Composition, or missing provider credentials | Delivery |
| Custom resource accepted but ignored | No controller watching that kind; CRD version not served | Delivery |
The three playbooks
☺ Like you’re 10: The detail lives in three pages: things that won’t start, things that can’t talk, and things that won’t deliver.
Each page recaps the six-step order, then goes deep with matrices, commands and worked fixes. Between them they cover every row of the index above.
Workloads & Storage
Everything that stops a pod starting or staying up — Pending, image pulls, crash loops, OOM kills, probes, evictions, stuck deletions — plus PVCs, access modes and StatefulSet volumes.
Playbook 2Networking, RBAC & Admission
Everything that stops a healthy pod being reached or permitted — Services and endpoints, DNS, NetworkPolicy, Ingress and Gateway, mesh mTLS, “forbidden” errors and webhook denials.
Playbook 3Delivery, Observability & Platform APIs
Everything that stops the platform delivering or reporting — Argo CD, Flux, Tekton and Argo Rollouts, Prometheus and OpenTelemetry pipelines, and Crossplane claims and custom resources.
If the symptom spans two of them — a rollout stuck because the new pods fail a readiness probe, say — start at the layer closest to the evidence you already have, and follow the signals outward.
When you are stuck
☺ Like you’re 10: If you’ve been stuck a while, stop and check the boring things: are you in the right room, and did you read the question properly?
Every experienced operator has lost twenty minutes to a problem that did not exist. The checks below cost fifteen seconds and resolve a surprising share of “impossible” failures — and being in the wrong namespace is one of the most common ways to fail a task you knew how to do.
# 1. Am I where I think I am?
kubectl config current-context
kubectl config view --minify -o jsonpath='{..namespace}{"\n"}'
kubectl config use-context cluster-prod
kubectl config set-context --current --namespace=app
# 2. Does the object exist, spelled that way?
kubectl get <kind> -A | grep -i <name>
# 3. Is the control plane healthy? (rules out a class of ghosts)
kubectl get nodes
kubectl get apiservices | grep -v True # a broken aggregated API breaks unrelated commands- Re-read the task wording. Verbatim. People solve the problem they remember rather than the one on screen — fixing
stagingwhen the task namedprod, or creating a ClusterRoleBinding where namespace-scoped access was asked for. The wording names the exact resource, namespace and context you are meant to use. - Verify cluster and namespace. Multi-cluster environments switch context between tasks, so a task beginning “on cluster staging” means the previous task’s context is now wrong.
- Time-box hard. Past five to seven minutes with no visible progress, flag it and move on — partial credit is real, and three finished easy tasks beat one heroically half-finished hard one. Drill that against Practice Tasks and the Exam Guide, with the Command Reference within reach.
Details about the CNPE such as task counts, the pass mark, time limits and which resources you may open can and do change, and community write-ups describe one person’s sitting rather than a specification. The authoritative sources are the official CNCF certification page and the Linux Foundation handbook. Use this page for technique; confirm the rules before you book. Our reading of the published accounts is in Field Notes.
Practise routing, not fixing. On a kind or minikube cluster, deploy a small app and break it five ways, one at a time: point the Service selector at a label no pod has; set resources.limits.memory: 16Mi on a JVM app; reference a ConfigMap key that does not exist; apply a default-deny-egress NetworkPolicy with no DNS exception; point an Argo CD Application at a path that isn’t there. Run only the six-step opener each time, then name the symptom-index row and the playbook you would open. If you can name the row before the fix, the index is doing its job.
Dot: My deployment’s been rolling for ten minutes and the pods just say Pending. I think the cluster’s broken.
Ellie: Maybe! But did you describe the pod and read the bottom?
Dot: …it says 0/3 nodes are available: 3 Insufficient cpu. Oh. It literally just told me.
Ellie: Events almost always do — and now you know which page to open, because “Pending” is a workloads row.
Gizmo: Easy fix — strip the resource requests off entirely! No requests, no “insufficient”, no problem. 😈
Timmy: And then the scheduler packs six noisy pods onto one node and evicts your database at 3am. Also, Dot: which cluster are you on?
Dot: …staging. The task said prod. I’ll see myself out.
Triage is a habit, not a talent. The order never changes and the commands never change; all that changes is which drawer you open once the evidence is in.
1. Name the first four steps of the universal triage order, in order. 2. Why is --previous essential when reading logs from a crash-looping pod? 3. A pod is Running with 0/1 READY and zero restarts — which probe is failing, and how do you know it isn’t the other one? 4. Which playbook covers an admission webhook denied the request error, and which covers a Crossplane claim with no status at all?
Check your answers
kubectl get -o wide→kubectl describe(read the Events at the bottom) →kubectl get events --sort-by=.lastTimestamp→kubectl logs --previous. Then the controller/CRstatus.conditions, and only then a hypothesis.- The container that failed has already been replaced, so the current one may have no logs yet.
--previousreads the dead container’s output, where the real error is. - The readiness probe. A failing liveness probe restarts the container, so restarts would be climbing; zero restarts rules it out. The pod runs fine but has been dropped from the Service’s endpoints.
- Networking, RBAC & Admission for the webhook denial; Delivery, Observability & Platform APIs for the claim — and no
statusat all implies no controller is watching that kind.