Kubernetes Dashboards That Matter
"The cluster feels slow" is an accusation without coordinates, and Kubernetes gives it a specific shape: is the workload starved — throttled, OOM-killed, unschedulable — or is the application actually slow? The two look identical from a latency graph, and they have opposite fixes.
The dashboards that matter answer that question in order — resource pressure first, application blame second — built from the requests/limits/usage triangle that kube-state-metrics and cAdvisor together make visible. Mara builds three views on top of it: capacity, saturation, and failure signatures.
The Requests/Limits/Usage Triangle
Three numbers exist per container: what it asked for (kube_pod_container_resource_requests), what it may not exceed (kube_pod_container_resource_limits), and what it actually uses — cAdvisor's CPU rates and container_memory_working_set_bytes. The relationships carry the meaning, not any single line: usage far below requests is cluster money burning quietly, and usage pressed against the limit is an incident forming. Panel all three on one graph per service, because the gaps are the story.
The Throttling Ratio
A pod at its CPU limit is not killed — it is stalled. The CFS quota inserts pauses into request handling, and the tell is the ratio of throttled periods to total periods, not the usage graph: sustained above roughly 25%, throttling is adding latency to checkout while every CPU panel stays comfortably green. The query below draws that ratio per bookings pod.
# share of CFS periods in which the container was throttled
sum by (pod) (rate(container_cpu_cfs_throttled_periods_total{container="bookings"}[5m]))
/
sum by (pod) (rate(container_cpu_cfs_periods_total{container="bookings"}[5m]))
OOM Kills and Restart Signatures
Working set against the limit is the memory headroom gauge, because working set is the non-reclaimable estimate Kubernetes' own eviction logic — and, in practice, an OOM kill — both key off. The failure signature is a pair: kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} plus a rising kube_pod_container_status_restarts_total. A restart count that increments every few hours while probes keep the Service green is how a memory leak hides on Kubernetes — the platform keeps repairing the symptom, and every repair drops the requests in flight.
Pending Pods and Headroom by Requests
kube_pod_status_phase{phase="Pending"} held for more than a couple of minutes means the scheduler cannot place workloads, and that is a capacity fact, not a glitch. It belongs on the board as a stat panel that is normally zero.
Its companion panel is per-node allocatable minus the sum of requests, because the scheduler places on requests, not on usage. A cluster can idle at 40% CPU and still refuse to schedule anything — the requests are spoken for even where the cycles are not. Headroom by requests is the only headroom that predicts whether the cluster can take Saturday's traffic.
Mara's Order of Interrogation
The "cluster feels slow" runbook is one dashboard walk in a fixed order: node saturation first (load, memory pressure), then throttling ratios per service, then OOM kills and restart rates, then pending pods — and only then the Chapter 6 checkout dashboard. Ten minutes in that order prevents the hour spent blaming bookings for a starved node. The ordering works because resource pressure mimics application slowness perfectly, and an application cannot prove its innocence from inside a throttled container.
Mixin Boards and Your Own
The bundled kubernetes-mixin boards already draw the triangle well — compute resources by cluster, namespace, and pod — so Mara adopts them for capacity and builds only the Harborline-specific saturation view: throttling ratio, OOMKilled terminations, restart rate, and pending pods for the five services. Adopt the commodity views, own the specific ones — the Chapter 6 dashboard economy, applied to a platform that ships its own commodity.
- Watching CPU usage and never the throttling ratio — a service pinned at its quota shows moderate usage and terrible p95, and the dashboard swears the pod is fine while requests queue behind CFS pauses.
- Averaging cluster CPU to judge capacity — the scheduler places by per-node requests, so "cluster at 40%" coexists with Pending pods; capacity panels must show requests against allocatable per node.
- Treating restarts as harmless because the Deployment self-heals — a
paymentspod OOM-killed every 3 hours drops every in-flight charge each time; restart rate is a failure signal, not a liveness feature. - Setting no memory limits so OOM kills "can't happen" — the node's OOM killer then picks victims across all pods by its own score, and the blast radius becomes whichever neighbor was biggest.
- Building one dashboard that mixes node, pod, and application panels — during an incident nobody can walk it in order; the interrogation sequence needs one view per question, linked in sequence.
- Panel the triangle — requests, limits, usage — on one graph per service, because the relationships between the lines carry the meaning, not any single one.
- Alert on a throttled-period ratio above 25% sustained for latency-sensitive services, since throttling degrades p95 long before usage panels look alarming.
- Track restart counts and OOMKilled terminations as first-class panels next to error rate — on Kubernetes they are the failure signatures that precede user-visible symptoms.
- Keep a pending-pods stat and per-node requests-vs-allocatable headroom on the capacity board, so "can the cluster take Saturday's traffic" is a glance, not an experiment.
Knowledge Check
Checkout p95 doubles while every CPU usage panel for bookings stays comfortably under the limit. What does Mara check first?
- Node network saturation, since CPU is proven healthy
- The throttling ratio — CFS quota stalls a pod that never looks busy
- The Grafana query, because healthy usage with bad latency means a broken panel
- Working-set memory, the other corner of the resource triangle
The cluster averages 40% CPU usage, yet new pods sit Pending for ten minutes. Why?
- metrics-server is lagging behind actual usage, so the scheduler sees stale numbers
- 40% is above the scheduler's default packing threshold
- Per-node requests already exhaust allocatable — the scheduler never looks at usage
- Pending pods indicate an image-pull failure, not a capacity problem
payments restarts 8 times a day with last-terminated reason OOMKilled, but probes stay green and the Service keeps routing. What is the correct reading?
- Normal behavior; self-healing is working as designed
- A probe misconfiguration, since green probes and OOM kills cannot coexist
- A failure signal — each kill drops in-flight charges even though the Deployment recovers
- A capacity win, since the pod frees memory for its neighbors between restarts and eases pressure
Why does the runbook check node saturation and throttling before opening the checkout dashboard?
- Resource pressure mimics application slowness and must be excluded first
- The checkout dashboard is the least reliable view during an incident
- Node-level queries are cheaper to run than application queries
- Platform metrics belong to Mara while application metrics belong to the bookings team
You got correct