Page 1 of 2
At the scale of a fleet rather than a single box, capacity planning stops being "does this server have enough RAM" and becomes a statistical question: given the actual distribution of CPU, memory, and I/O usage across hundreds of instances over weeks, what's the right headroom to carry so that normal variance and the occasional spike don't turn into an incident, without paying for capacity that just sits idle. The OS-level metrics from earlier tiers become the raw material for that: sustained RSS growth per instance feeds memory request sizing, p95/p99 CPU throttling percentages (from cgroup CPU limits) feed CPU request and limit sizing, and disk %util and queue depth from iostat feed decisions about storage class and provisioned IOPS.
# a rough per-fleet signal worth tracking over time, not just per-incident:
kubectl top pods --all-namespaces | awk '{print $3}' | sort -n | tail -20
# the highest memory consumers right now, fleet-wide - is this the same handful
# of pods every time, or does it rotate, which changes the fix entirely
The distinction that matters here: a resource problem affecting the same specific workload every time is a sizing problem for that workload, fixable by adjusting its own request and limit. A resource problem that rotates across different workloads on the same nodes is a fleet-level capacity or scheduling problem, and no amount of resizing one workload's limits fixes it, because the underlying issue is that the nodes themselves don't have enough aggregate headroom for what's being scheduled onto them.
Setting cgroup limits fleet-wide is a genuine tradeoff, not a solved problem with one right answer. Set CPU quotas tight and you get predictable, contained resource usage per workload, but you also get CPU throttling (hard tier) under real load, sometimes invisibly, since average utilization graphs smooth right over short throttling windows. Set them loose, or skip CPU limits entirely and rely only on requests, and a single workload with a bug, or just a legitimate burst of traffic, can starve every other workload sharing that node of CPU: the classic noisy neighbor problem, where one tenant's misbehavior degrades performance for tenants who did nothing wrong.
resources:
requests:
cpu: "500m" # guaranteed floor - the scheduler won't overcommit past this
memory: "512Mi"
limits:
cpu: "2" # ceiling - can burst up to this if the node has spare capacity, then gets throttled
memory: "512Mi" # memory limits should usually equal the request; there's no safe "burst" for memory
Notice the memory limit equals the memory request, while the CPU limit is set well above the CPU request. That asymmetry is deliberate and reflects the asymmetry between the two resources covered in the hard tier: CPU overuse gets throttled, a recoverable, if painful, degradation, while memory overuse gets a process killed outright, an unrecoverable event for whatever was running. Setting a memory limit meaningfully above the request just delays an eventual OOM kill while making it less predictable when it happens; setting a CPU limit meaningfully above the request lets a workload absorb legitimate bursts using genuinely idle capacity elsewhere on the node, which is usually a good trade as long as you're watching throttling metrics, not just CPU utilization, to know when that idle capacity has run out for everyone at once.
The hard tier's coverage of cgroups and namespaces described the primitives; a container runtime is the software that actually assembles them into a running container on your behalf. In the standard stack underneath Docker and Kubernetes, that work is split across two layers. containerd is the higher-level daemon that manages container lifecycle: pulling images, managing storage layers, tracking which containers exist and their state. When it's actually time to start one, containerd hands off to runc, a low-level tool that does the one specific job of constructing a single container's isolated environment and then getting out of the way.
# roughly what runc does when starting a container, spelled out as discrete steps:
# 1. create the namespaces: pid, net, mount, uts, ipc
# 2. create and configure a cgroup with the container's resource limits
# 3. set up the root filesystem, usually a union of read-only image layers
# plus one writable layer on top (this is why containers are "ephemeral" -
# delete the writable layer and you're back to the pristine image)
# 4. pivot_root into that filesystem, so the container's "/" really is that root, not the host's
# 5. exec the container's actual entrypoint process inside all of the above
runc --version
Once that entrypoint process is running, runc's job is done; it doesn't stick around supervising the container, containerd does that part. This is why "the container" you inspect with ps aux on the host is genuinely just an ordinary Linux process with a PID like any other, sitting inside the namespaces and cgroup that runc constructed for it in steps 1 and 2. Understanding this layering matters operationally because it tells you where to look when something goes wrong at each stage: an image pull failure or a container that won't even schedule is a containerd, or higher-level, problem, while a container that starts but immediately behaves as though its filesystem or network is wrong points at the lower-level namespace and mount construction that runc is responsible for.
Real scenario-based DevOps questions, hands-on practice, and clear explanations for every answer.