Linux Namespaces & cgroups
Summary
- Namespaces decide what a process can see; cgroups decide what a process can use. Together they are what "container isolation" actually means.
- Insight: These are plain kernel features you can drive with
unshare,nsenter, and the cgroup filesystem - runtimes just automate them. - Key Concepts: the seven namespace types, cgroup v2 controllers, OOM kill, CPU throttling, and how Kubernetes requests/limits map onto both.
- When to Use: Any time you reason about isolation, resource limits, noisy-neighbor problems, or debug why a container was killed or throttled.
- Limitations: They isolate and constrain but share one kernel, so they are not a hardware boundary against hostile code.
Recipe
Understand isolation as two independent knobs and configure both deliberately.
Use namespaces (and pod shared namespaces) to control visibility, and use cgroups - surfaced in Kubernetes as requests and limits - to control consumption.
Set memory limits to prevent host-wide OOM, set CPU requests for fair scheduling, and reason about problems as either a visibility issue (namespace) or a resource issue (cgroup).
Working Example
You can create isolation by hand to see there is no magic.
sudo unshare --pid --fork --mount-proc --net --uts bash
# inside the new namespaces:
hostname isolated
ps aux # only this shell and children are visible
ip addr # only loopback, a fresh network namespaceThat shell now has its own PID, network, mount, and UTS namespaces - the core of what a runtime sets up.
In Kubernetes you express the resource half declaratively.
apiVersion: v1
kind: Pod
metadata:
name: sized
spec:
containers:
- name: app
image: registry.example.com/team/app:1.0.0
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"The kubelet translates these into cgroup v2 values, so memory.max becomes 512Mi and CPU is throttled at half a core under contention.
Deep Dive
The namespace types
Linux has seven namespace types, and each isolates one dimension.
PID gives the container its own process tree so its entrypoint is PID 1 and it cannot see host processes.
Network provides private interfaces, routes, and ports, which is why each pod gets its own IP.
Mount isolates the filesystem tree, letting the container have its own root without touching the host's mounts.
UTS isolates hostname and domain name; IPC isolates System V IPC and POSIX message queues.
User maps UIDs and GIDs, so root (UID 0) inside can map to an unprivileged UID outside - the basis of rootless containers.
Cgroup namespaces virtualize the cgroup hierarchy view so a container does not see the host's full cgroup paths.
How pods use shared namespaces
A Kubernetes pod is a group of containers that share some namespaces on purpose.
All containers in a pod share the network namespace, so they reach each other over localhost and share one pod IP.
They can optionally share the PID namespace (shareProcessNamespace: true), which is how sidecars can see and signal the main process.
The pause container holds these shared namespaces open for the pod's lifetime, which is why you see pause processes on nodes.
cgroups v2 and controllers
Cgroups group processes and apply resource controllers to the whole group.
The memory controller enforces memory.max; exceeding it triggers the kernel OOM killer against the cgroup, not the host.
The cpu controller uses weights (cpu.weight, from requests) and quotas (cpu.max, from limits) to allocate and cap CPU.
Modern distros use the unified cgroup v2 hierarchy, which containerd, runc, and the kubelet all target through the systemd cgroup driver.
You can read the live values under /sys/fs/cgroup for a running container to confirm what the runtime applied.
Requests versus limits
Requests and limits are different tools that people constantly conflate.
Requests reserve capacity for scheduling - the scheduler only places a pod where the summed requests fit.
Limits are hard ceilings enforced by cgroups at runtime - memory over limit is OOM-killed, CPU over limit is throttled.
A CPU limit does not kill; it throttles, which shows up as latency, while a memory limit does kill.
Gotchas
Setting a memory limit far below real usage causes silent OOM kills that look like crashes; size limits from observed usage, not guesses.
CPU limits throttle rather than kill, so an aggressive limit can add tail latency that is hard to attribute without cgroup throttling metrics.
Many runtimes still report host CPU count and total memory through /proc, so JVMs, Node, and Go may misjudge sizing unless told the cgroup limits.
Namespaces are not a security boundary by themselves; without dropped capabilities, seccomp, and non-root, a container is still soft against escape.
A limit without a matching request sets the request equal to the limit for that resource, which can waste reserved capacity - set both intentionally.
Alternatives
For stronger isolation than namespaces, use sandbox runtimes (gVisor, Kata) that add a user-space or VM kernel boundary.
For finer resource control, cgroup v2 also supports io and pids controllers, which limit disk I/O and process counts per pod.
For NUMA- and CPU-pinning-sensitive workloads, the kubelet's CPU Manager and Topology Manager pin cores rather than relying on shares alone.
For visibility control across pods, NetworkPolicies restrict the network namespace's reachability even though the isolation itself is namespace-based.
Pick based on whether your problem is isolation strength, resource fairness, or reachability.
FAQs
What is the one-line difference? Namespaces control what a process can see; cgroups control how much it can use.
Why is my container OOM-killed with free host memory? Because its memory cgroup limit, not host memory, is the ceiling the kernel enforces.
Do CPU limits kill my pod? No - CPU limits throttle scheduling, adding latency; only memory limits cause OOM kills.
Why do pod containers share localhost? They share one network namespace, so they see the same loopback and pod IP.
Are namespaces enough for security? No - combine them with non-root users, dropped capabilities, seccomp, and read-only rootfs, or a sandbox runtime.
Can I inspect the applied limits? Yes - read /sys/fs/cgroup/... inside the container or check node cgroup paths to see memory.max and cpu.max.
Related
- What a Container Really Is
- Container Basics
- Containers vs Virtual Machines
- OCI Image & Runtime Specs
- Container Fundamentals Best Practices
Stack versions: This page was written for Kubernetes 1.36.2, Docker Engine 29.6.1 (BuildKit default), containerd (CRI runtime on nodes), Helm 3, Compose v2, Argo CD (latest - verify at build), and Gateway API (GA - verify controller support at build).