58

OOM-Killed Blog: Incident Anatomy

Follow Ziba's failed request from physical RAM to Kubernetes evidence, then prove the smallest safe recovery.

The enterprise problem and today’s slice

Enterprise problem: Ziba's blog disappears during a traffic spike because its container is killed for exceeding a memory boundary, and a blind restart or larger node can hide the cause while readers keep seeing errors. Whole-course context: The incoming evidence is the physical-to-pod memory model: requests reserve scheduler capacity, limits become Linux control-group ceilings, and neither creates RAM. Today’s slice: We trace one failed HTTP request through the application, container, pod, node, and Linux kernel, then separate immediate restoration from a justified resource change. End-of-day evidence: An incident record links the OOMKilled termination, limit, node condition, traffic replay, rollout revision, and unaffected control request. Still unsolved: Fleet autoscaling, cloud-provider capacity, and GitOps-controlled promotion are deferred to later days.

The thesis is simple: OOMKilled is evidence of a boundary crossing, not a diagnosis of why memory grew. The useful response follows observe → locate the enforcing boundary → change one thing → replay → compare.

Customer use cases

An outage becomes longer when responders cannot distinguish a container limit breach from whole-node exhaustion. These use cases make customer restoration and safe diagnosis independently observable.

Use case IDActorCustomer jobSuccess outcomeDenial or recovery evidence
D58-UC-01Blog operatorRestore Ziba for readers without discarding the original failure evidenceHealthy replicas serve the traffic replay and no replacement is OOM-killedRollout is stopped if node headroom is absent; the old termination and a healthy control request remain recorded
D58-UC-02Incident reviewerProve whether a leak, legitimate peak, or node pressure caused the failureTimeline correlates request volume, process memory, cgroup ceiling, and node conditionUnsupported root-cause claims are rejected; missing measurements are named as gaps

Actor-centred user stories

Recovery without falsifiable acceptance conditions can merely move the outage to another node. Each story therefore requires both positive customer evidence and a negative or unaffected control.

Story IDUse case IDsUser storyObservable acceptance conditions
D58-US-01D58-UC-01As a blog operator, I want to restore Ziba with measured headroom, so that readers regain service without creating a second memory incidentNew revision is Ready, replay error rate is acceptable, restart count stays flat, and an unrelated pod remains healthy
D58-US-02D58-UC-02As an incident reviewer, I want one timeline across application and kernel evidence, so that the declared root cause can be disprovedTimeline includes actor, pod UID, container, node, limit, peak usage, termination reason, node pressure, environment, timestamp, and immutable incident ID

End-to-end product flows

The customer-visible symptom begins outside Kubernetes, so starting with a manifest edit loses the causal chain. The smallest flow follows the request to its failed process and back to a verified reader response before expanding into internal evidence.

Flow IDUse case IDsPathTriggerNumbered stepsTerminal evidence
D58-FLOW-01D58-UC-01, D58-UC-02RecoveryReader receives an error from Ziba1. Capture the failed request and time.
2. Identify the serving pod and previous container.
3. Read termination, resource, node, and traffic evidence.
4. Choose rollback, traffic reduction, or measured resize.
5. Deploy one controlled revision.
6. Replay traffic and compare controls.
Incident ID, deployment revision, request trace, old termination, resource diff, replay result, and unaffected pod probe
D58-FLOW-02D58-UC-01, D58-UC-02DeniedOperator proposes raising the limit before checking node capacity1. Admission or reviewer reads requested resources.
2. Compare them with allocatable headroom and policy.
3. Reject the unsafe revision.
4. Preserve the running recovery option.
5. Record the missing evidence.
Denied revision with actor, reason, expected headroom, observed headroom, environment, timestamp, and immutable review ID

System design derived from the flows

A restart reason alone cannot show whether the node was also unhealthy, so the incident system must join Kubernetes desired state with kernel and customer observations. A control group, or cgroup, is the Linux accounting and enforcement boundary used for the container's processes.

Use case IDEntry pointResponsible servicesAuthoritative storeFailure evidence
D58-UC-01Incident console and Deployment revisionIngress, Deployment controller, scheduler, kubelet, container runtime, Linux cgroup controller, synthetic probeKubernetes API desired/status state plus probe result storeOOMKilled, replacement Pending, failed readiness, elevated customer error rate, or node headroom denial
D58-UC-02Incident evidence reviewMetrics, logs, events, kubelet status, application profiler, incident recorderTime-series/log stores and immutable incident record keyed to pod UIDMissing interval, mismatched pod UID, absent node condition, or hypothesis contradicted by replay

Kubernetes documents that memory limits are enforced reactively by the kernel and that a container may be killed when it exceeds its limit; scheduling, by contrast, uses requests (Kubernetes resource management). Node-pressure eviction is a separate kubelet mechanism that protects the node (Kubernetes node-pressure eviction).

Data model and ownership

An incident cannot be audited if mutable pod names are the only join key, because replacements reuse labels but receive new identities. The records below preserve desired state, observed runtime state, and customer proof without pretending they share one owner.

Generated-application database: Not created in this slice — the blog's domain data is unchanged; Kubernetes, observability, deployment, and incident stores already own the evidence required to recover and review the outage.

Record or entityStore and ownerPrimary keyForeign key or opaque referenceTenant keyMaterial constraintLifecycle and deletionUse case IDs
WorkloadRevisionKubernetes API store, owned by Deployment controller and platform teamDeployment UID plus revisionPod-template hash references created podsNamespaceRequests and limits pass policy and the revision is immutable once observedCreated per rollout, retained in history, expired by revision policyD58-UC-01, D58-UC-02
ContainerTerminationKubernetes pod status and runtime evidence, owned by kubeletPod UID plus container name plus restart ordinalOpaque node UID and runtime container IDNamespaceReason, exit code, timestamps, and resource revision describe the same container lifetimeLives with pod status; exported incident copy follows evidence retentionD58-UC-01, D58-UC-02
IncidentEvidenceIncident store, owned by operationsIncident ID plus evidence IDOpaque pod UID, deployment revision, trace ID, and metrics query IDService ownership keyClaim must link positive, negative, and unaffected-control observations from one environment and intervalSealed at review, retained by audit policy, then deleted or legally heldD58-UC-01, D58-UC-02

Diagnose from symptom to enforcing boundary

The same outage can present as a 502, a restart, or a Pending replacement, so one signal cannot identify the failed boundary. Use a short evidence ladder and stop when a competing explanation remains possible.

ObservationMeaningWhat it does not proveNext discriminating check
Reader sees 502/503No healthy backend answered through the edge pathMemory was the causePod readiness, endpoints, and request trace
Previous container shows OOMKilledKernel ended that container lifetime after an out-of-memory conditionLeak versus legitimate peak; node healthLimit, usage curve, application profile, node conditions
Node has MemoryPressureKubelet observed a configured pressure thresholdThis container crossed its own limitEviction events and node available-memory series
Replacement is PendingScheduler found no eligible placementRuntime memory exhaustionScheduler events, requests, taints, affinity, allocatable remainder

Commands and manifests versus physical effects

A command is useful only when the operator can say which state it reads or changes underneath. This mapping prevents a YAML edit from being mistaken for new hardware.

kubectl get pod ziba-blog-abc -n blog -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'
kubectl describe pod ziba-blog-abc -n blog
kubectl top pod ziba-blog-abc -n blog --containers
kubectl describe node worker-3
kubectl rollout status deployment/ziba-blog -n blog
resources:
  requests:
    memory: 384Mi
  limits:
    memory: 768Mi
Code or actionKubernetes or software effectHardware/runtime effectCustomer proof
Read previous terminationAPI server returns kubelet-reported status for the prior container lifetimeNo memory changes; evidence is readCorrelate failure time with reader error
Read kubectl topMetrics API returns recent usage samples if a metrics pipeline existsSamples kernel accounting; it does not reserve RAMCompare measured peak with limit and replay
Change requests.memoryNew pod template asks scheduler to reserve a different node budgetNo DRAM is allocated immediatelyReplacement either schedules or stays Pending with evidence
Change limits.memoryRuntime configures a different cgroup ceiling for replacement containersNo RAM is added; kernel permits charges only up to the new boundaryReplay completes below limit with stable latency and restarts
Roll back DeploymentController creates pods from an earlier template revisionProcesses are replaced on eligible nodesHealthy endpoints and customer probes recover

Recovery sequence and decision rule

Pressure to restore service can erase evidence or introduce an unsafe resize, so recovery and diagnosis must interleave. Preserve the old pod status and interval first, restore through the least invasive reversible action, then test the hypothesis.

  1. Capture the failed request time, pod UID, previous termination, events, resource spec, node condition, and traffic/usage interval.
  2. Restore availability with a known-good rollback, traffic shedding, or additional healthy replicas when capacity permits.
  3. Classify the boundary: container cgroup, node pressure, or scheduler placement.
  4. Profile memory growth and decide whether the peak is legitimate, reducible, or leaking.
  5. Change one resource or application variable through a new revision.
  6. Replay representative traffic; compare reader errors, latency, usage, restarts, and an unaffected control.

Decision rule: raise a limit only for a measured legitimate peak with node headroom; fix the application for unbounded growth; add compatible nodes when justified requests cannot fit. A bigger machine can postpone a leak, but it cannot turn an unbounded process into a bounded one.

Key takeaways

An OOM incident is a chain from a customer request to a Linux enforcement event, with Kubernetes recording desired and observed state around it. A reliable operator names the boundary, preserves immutable evidence, applies one reversible change, and measures the same customer path again.

  • Requests affect placement; limits affect runtime enforcement; neither adds RAM.
  • OOMKilled, node-pressure eviction, and Pending placement require different actions.
  • Pod UID, revision, interval, and control probes make the root-cause claim falsifiable.

Checklist

A recovery is incomplete if Ziba appears healthy but the evidence cannot show why or whether the failure will recur. Confirm each item against a real incident record.

  • [ ] Captured failed request, pod UID, previous termination, events, and node conditions.
  • [ ] Separated container OOM, node pressure, and unschedulable placement.
  • [ ] Preserved original evidence before restarting or replacing the workload.
  • [ ] Mapped every configuration change to its scheduler, kernel, or hardware effect.
  • [ ] Replayed traffic and recorded positive, negative, and unaffected-control results.