38

Kubernetes Memory: From Physical RAM to Pods

What “increase memory” physically means—from DRAM and cloud machines to nodes, cgroups, containers, and pods.

The enterprise problem and today’s slice

Enterprise problem: A customer-facing API is restarting with OOMKilled, while a batch job is Pending with Insufficient memory; treating both as “the cluster needs more RAM” risks wasting money without restoring either workload. Whole-course context: The SWE course has established workloads, infrastructure, and operations; today turns their resource settings and runtime evidence into one end-to-end capacity model. Today’s slice: We separate physical machine memory, node allocatable memory, scheduler reservations, and Linux enforcement, then choose the smallest layer that resolves each failure. End-of-day evidence: A reviewer can trace a 32 GiB node budget, explain an OOM or Pending event, and name the exact container, pod, node, or cluster change required. Still unsolved: Workload-specific heap profiling, cloud-node pricing, and production rollout policy remain environment-dependent decisions.

Customer use cases

An ambiguous memory request can send the wrong team toward the wrong change, prolonging an outage or buying idle machines. These two use cases distinguish a workload ceiling from missing placement capacity before anyone edits a resource.

Use case IDActorCustomer jobSuccess outcomeDenial or recovery evidence
D43-UC-01Payments platform operatorStop a memory-bound API container from restarting during a measured peakThe replacement container completes the peak below its justified limit with no new OOM eventAn unsafe resize is rejected when node headroom is absent; the previous OOM event and unaffected node health remain visible
D43-UC-02Data engineering leadRun a 20 GiB ETL pod when no current node can satisfy its requestA compatible node joins, the scheduler binds the pod, and the job startsThe pod remains Pending with Insufficient memory until compatible capacity exists; unrelated running pods remain healthy

Actor-centred user stories

Memory changes are safe only when an accountable actor can state both the intended outcome and the evidence that would disprove it. These stories make the resize and capacity-addition decisions observable instead of relying on a generic “more memory” ticket.

Story IDUse case IDsUser storyObservable acceptance conditions
D43-US-01D43-UC-01As a payments platform operator, I want to distinguish a container-limit breach from node pressure, so that I resize only the failing workload without endangering its neighboursThe termination reason is OOMKilled, measured peak usage justifies the new request and limit, the target node has headroom, and a replay completes without another OOM event
D43-US-02D43-UC-02As a data engineering lead, I want a Pending ETL pod to trigger compatible node capacity, so that the job runs without weakening its justified reservationScheduler events show Insufficient memory, a new node exposes enough allocatable memory, the pod binds there, and existing workloads remain running

End-to-end product flows

A resource edit crosses several owners, so losing the sequence can confuse configuration with physical capacity. The happy path changes a container ceiling only after checking node headroom; the recovery path adds a machine because no existing per-node budget can fit the pod.

Flow IDUse case IDsPathTriggerNumbered stepsTerminal evidence
D43-FLOW-01D43-UC-01HappyOperator opens an OOM incident for the payments API1. Read the container termination reason and usage curve.<br>2. Rule out node MemoryPressure.<br>3. Profile leak versus legitimate peak.<br>4. Update request and limit.<br>5. Recreate or resize the container under policy.<br>6. Replay peak traffic.Deployment revision, old and new resource values, node headroom, load-test run ID, zero new OOM events, and stable customer requests
D43-FLOW-02D43-UC-02RecoveryData lead submits a 20 GiB ETL pod and it remains Pending1. Read scheduler events.<br>2. Compare the request with every node’s allocatable remainder.<br>3. Keep the justified request unchanged.<br>4. Add a compatible node through the node group.<br>5. Wait for kubelet registration.<br>6. Observe scheduling and job start.Insufficient memory event followed by node identity, allocatable memory, pod binding, job start time, and unaffected positive-control pod

System design derived from the flows

The same word—memory—appears in hardware, Kubernetes, and Linux with different meanings, so a flat component diagram hides the real failure boundary. The architecture below follows physical bytes upward and shows where scheduling stops and runtime enforcement begins.

Use case IDEntry pointResponsible servicesAuthoritative storeFailure evidence
D43-UC-01Deployment resource edit and incident viewKubernetes API server, scheduler, kubelet, container runtime, Linux cgroup controller, metrics pipelineKubernetes API desired state plus kernel cgroup counters and pod statusOOMKilled, exit 137, cgroup OOM counter, restart count, or rejected resize because node headroom is insufficient
D43-UC-02ETL workload submission and node-group capacity policyKubernetes API server, scheduler, cluster autoscaler, cloud or bare-metal provisioner, kubeletKubernetes API pod/node status plus infrastructure provider node-group statePending pod with Insufficient memory, failed node provisioning, incompatible taint/affinity, or absent allocatable capacity

Data model and ownership

Capacity decisions fail when live usage, declared reservations, and physical supply are stored as if they were one number. Each record below has a distinct owner and lifecycle, so an operator can reconstruct why a workload was placed, killed, evicted, or left Pending.

Generated-application database: Not created in this slice — Kubernetes desired state, node status, kernel counters, infrastructure state, metrics, and events already provide the durable control-plane and evidence records required by both flows.

Record or entityStore and ownerPrimary keyForeign key or opaque referenceTenant keyMaterial constraintLifecycle and deletionUse case IDs
WorkloadResourceRevisionKubernetes API store, owned by the workload controller and platform policyworkload UID plus revisionPod-template reference to container name; no cross-plane authority transferNamespace, the Kubernetes isolation key for the owning teamEvery container request must be schedulable under policy; limit must not disguise an unprofiled leakCreated on spec change, retained in rollout history and audit, removed with workload retention policyD43-UC-01
PodMemoryStatusAndEventKubernetes API status/events plus metrics store, owned by kubelet and observability platformPod UID plus observation timestamp or event UIDOpaque node UID and container runtime IDNamespaceTermination reason, restart, usage, and node condition must refer to the same observed intervalStatus follows the pod; metrics expire by retention; events age out but incident evidence is retainedD43-UC-01, D43-UC-02
NodeCapacitySnapshotKubernetes Node status, owned by kubelet and platform engineeringNode UID plus observed generationOpaque infrastructure instance IDNone — a node is cluster-scoped rather than namespace-scopedAllocatable equals capacity less configured system, Kubernetes, and eviction reservationsCreated at node registration, refreshed by kubelet, deleted when the node is retired; audit snapshot retainedD43-UC-01, D43-UC-02
NodeGroupCapacityActionCloud or bare-metal infrastructure state, owned by platform engineeringScaling action IDOpaque Kubernetes cluster and eventual node UIDsCluster or cloud-account identifierRequested machine shape must be compatible with pod selectors, taints, zones, and memory requestCreated on scale action, completed or failed with evidence, retained for cost/audit, removed by policyD43-UC-02

The physical hierarchy: where a byte lives

An operator cannot decide whether to edit YAML or buy capacity until the location of the shortage is clear. A pod ultimately uses electrical charge stored in physical dynamic random-access memory (DRAM), but several ownership and accounting layers stand between the process and those chips.

  1. DRAM: Memory modules attached to a server’s CPU sockets physically hold active pages.
  2. Cloud host or bare-metal server: On bare metal, Linux sees installed DRAM directly. In a cloud, a hypervisor assigns a virtual machine a memory size backed by provider hardware.
  3. Node operating system: Linux uses some memory for its kernel, page cache, kubelet, container runtime, and system daemons.
  4. Kubernetes node: Kubelet reports detected capacity.memory, then exposes a smaller allocatable.memory budget for pods after reservations and eviction headroom.
  5. Linux cgroup: A control group accounts for a container’s processes and enforces their memory ceiling.
  6. Container and pod: A container is a process boundary; a pod is the scheduling unit containing one or more containers, all on the same node.

Virtual address space does not create physical RAM. It gives a process addresses that the kernel maps to resident pages, reclaimable cache, or—only when configured—swap storage.

Requests, limits, and actual usage

Confusing a request with a limit produces opposite failures: an oversized request leaves a pod Pending, while an undersized limit kills a process that might have fit on the node. Kubernetes scheduling and Linux enforcement therefore use separate values.

QuantityQuestion it answersEnforced or consumed by
Node capacityHow much memory did kubelet detect on this machine?Kubelet reports it
Node allocatableHow much of capacity may pods request?Scheduler uses it as the pod budget
Container requestHow much allocatable memory must placement reserve?Scheduler and autoscalers
Container limitWhat is the maximum memory charged to this container cgroup?Linux kernel
Actual usageHow many bytes are charged now?Kernel counters and metrics pipeline
resources:
  requests:
    memory: "2Gi"
  limits:
    memory: "3Gi"

The request does not pre-allocate a contiguous 2 GiB block of DRAM, and the process need not consume it. The limit does not add memory to the node; it permits this cgroup to compete for up to 3 GiB of the node’s real memory. A pod’s placement cost includes the requests of its containers, relevant init-container rules, and any declared pod overhead.

A 32 GiB node, counted three ways

A machine label such as “32 GiB” overstates what workloads can safely reserve, so capacity planning must show every subtraction. This example separates physical supply, Kubernetes placement budget, and live demand.

Node accounting stepAmount
VM or bare-metal memory32 GiB
System and Kubernetes reservations−3 GiB
Eviction headroom−1 GiB
Node allocatable28 GiB
Requests of already scheduled pods−22 GiB
Remaining schedulable requests6 GiB

An 8 GiB-request pod stays Pending even if a dashboard temporarily shows 10 GiB available: the scheduler protects declared reservations and sees only 6 GiB of request budget. A 4 GiB request with a 10 GiB limit can schedule, but several such pods may collectively use more than the node can supply if they peak together. That is memory overcommit, not free capacity.

OOM kill, eviction, and a Pending pod are different failures

All three symptoms mention memory, but responding to the wrong one can move rather than solve the outage. The decisive question is whether enforcement failed inside one cgroup, node safety failed across workloads, or placement failed before the pod ever ran.

FailureDecision layerEvidenceCorrect first move
Container exceeds its cgroup limitLinux runtime enforcementOOMKilled, exit 137, restart count, cgroup OOM eventProfile leak versus legitimate peak; then justify a new request and limit
Node approaches unsafe available memoryKubelet node protectionMemoryPressure, eviction event, node availability metricsReduce pressure, rebalance, or add node capacity; preserve system headroom
No node fits the pod requestKubernetes schedulingPending pod and Insufficient memory eventAdd a compatible node, use a larger node shape, or correct an unjustified request

Swap moves selected memory pages to slower storage and is configuration-dependent in Kubernetes environments. It can change failure timing and latency, but it does not make disk equivalent to DRAM or remove the need for a truthful node budget.

What “increase memory” changes at each level

A safe change request must name both the object being changed and the physical consequence; otherwise several teams may each interpret “increase memory” differently. The smallest effective layer depends on whether one process, one pod, one node, or total fleet capacity is the bottleneck.

ChangeWhat changes under the hoodWhat does not happen
Raise a container limitRuntime configures a higher cgroup ceiling on a resized or replacement containerNo DRAM is added to the node
Raise a container requestScheduler must reserve more of one node’s allocatable budgetThe process does not immediately consume that amount
Raise pod memoryRequests/limits change for the relevant app and sidecar containers; aggregate placement cost risesA pod still cannot span two nodes
Resize a nodeA larger VM/server supplies more machine memory and usually more allocatable memoryExisting pod specs do not automatically become sensible
Add nodesThe cluster gains more separate per-node allocatable poolsMemory does not become one shared cluster-wide heap
Add replicasMore pods can serve throughput across nodesOne process does not receive a larger heap

On bare metal, more physical memory may literally mean installing compatible DIMMs and rebooting the server. In a cloud node group, it usually means replacing or adding virtual machines with a larger memory-backed instance shape. In both cases kubelet must register the resulting capacity before the scheduler can use it.

Vertical, horizontal, and node scaling

Autoscalers operate on different axes, so choosing one by name rather than by bottleneck can amplify cost or preserve the original failure. Vertical scaling changes a pod’s resource envelope, horizontal scaling changes pod count, and cluster scaling changes machine count.

Vertical Pod Autoscaler (VPA) can recommend or apply new resource requests according to policy. A normal spec change often creates replacement pods; in-place resize can reduce disruption when the cluster, runtime, and workload support it, but it still cannot exceed real node capacity.

Horizontal Pod Autoscaler (HPA) adds or removes replicas from a scalable workload. It improves throughput or redundancy when work can be divided, but every new replica adds requests and usage somewhere in the cluster.

Cluster Autoscaler reacts to pods that cannot schedule by adding compatible nodes from a configured group. It fixes placement capacity, not a container that repeatedly crosses an unchanged 512 MiB limit.

Commands that identify the failed layer

Current-use charts alone cannot explain why Kubernetes scheduled or killed something, so diagnosis must combine declared state, node state, and events. These commands collect each view without mutating the cluster.

# Requests, limits, last termination state, placement, and pod events
kubectl describe pod api-7d9c -n payments

# Live container usage when Metrics Server is available
kubectl top pod api-7d9c -n payments --containers

# Capacity, allocatable, aggregate requests/limits, and MemoryPressure
kubectl describe node worker-a
kubectl top node worker-a

# Scheduling, eviction, and OOM-related event chronology
kubectl get events -n payments --sort-by=.lastTimestamp

Read the last container state for OOMKilled, the pod events for Insufficient memory, and the node conditions for MemoryPressure. Compare live usage with requests and limits, but size from a representative time window and a workload profile rather than a single sample.

A practical decision sequence

Changing capacity during an incident is safer when each branch has a falsifiable observation. This sequence starts from the visible symptom and ends at the narrowest change that can remove it.

  1. Pending with Insufficient memory: keep a justified request; add a compatible node or larger node shape, or correct an inflated request.
  2. OOMKilled while the node is healthy: profile the process, distinguish leak from legitimate peak, then raise the container request and limit only with headroom.
  3. Node MemoryPressure or evictions: restore node headroom, reduce unsafe overcommit, rebalance pods, or add node capacity.
  4. Throughput pressure without a per-process memory ceiling: test horizontal scaling before making every replica larger.
  5. Declared requests diverge from representative usage: use VPA recommendations as evidence, load-test the proposed envelope, and roll out gradually.

The most useful ticket replaces “increase Kubernetes memory” with a statement such as: “Raise the API container request from 1 GiB to 2 GiB and limit from 2 GiB to 3 GiB because load test run-184 peaked at 2.4 GiB without a leak; place the replacement only on nodes with at least 3 GiB headroom, and roll back on any OOM or node-pressure event.”

Key takeaways

Memory incidents repeat when teams remember values but forget ownership boundaries, so retain the hierarchy rather than a single rule. Physical DRAM belongs to a machine, Kubernetes exposes a safe per-node budget, the scheduler reserves requests, and the Linux kernel enforces container cgroups.

  • A cluster is a collection of separate node memory pools, not one giant shared RAM bank.
  • Capacity is detected machine memory; allocatable is the smaller pod budget after reservations and eviction headroom.
  • Requests govern placement; limits govern runtime enforcement; actual usage is a third value.
  • A cgroup OOM kill, a kubelet eviction, and a scheduler Pending event require different fixes.
  • Raising a limit permits more use of existing node RAM; resizing or adding nodes changes underlying capacity.
  • VPA changes the vertical envelope, HPA changes replica count, and Cluster Autoscaler changes node count.

Checklist

A memory change is not ready until another engineer can reproduce the diagnosis and see which boundary owns the fix. Use this checklist before approving the rollout.

  • [ ] I can trace memory from physical DRAM through the host, node, cgroup, container, and pod.
  • [ ] I can calculate node allocatable memory and remaining schedulable requests.
  • [ ] I can explain why a request neither pre-allocates DRAM nor acts as a hard ceiling.
  • [ ] I can distinguish OOMKilled, eviction under MemoryPressure, and Pending with Insufficient memory.
  • [ ] I have included app containers, sidecars, init-container rules, and pod overhead in the capacity review.
  • [ ] I have profiled a suspected leak before feeding it a larger limit.
  • [ ] I can name whether this change resizes a container, pod, node, or cluster.
  • [ ] I can justify VPA, HPA, or Cluster Autoscaler from the measured bottleneck.
  • [ ] I have captured pre-change evidence, a positive control, rollout proof, and rollback conditions.