Containers and orchestration
Namespaces and cgroups
A container is a Linux process with two kernel features applied. Namespaces control what it can see, and cgroups control what it can use.
A container is a Linux process that the kernel has been told to isolate. Two features do the work. Namespaces control what the process can see. Control groups (cgroups) control how much of the machine it can use.
Concept
A process is a running program with its own process ID (PID). The first process in a PID namespace gets PID 1. For the basics, see processes and threads.
Namespaces: what a process can see
Linux has several namespace types. Each one wraps one global resource, so the process sees its own private copy.
| Namespace | Isolates | What the container sees |
|---|---|---|
pid | Process IDs | Its entrypoint is PID 1; host processes are invisible |
net | Network stack | Its own interfaces, routing table and ports |
mnt | Mount points | Its own root filesystem (the image) |
uts | Hostname | Its own hostname |
ipc | Shared memory and message queues | Its own IPC objects |
user | User and group IDs | UID 0 inside can map to an unprivileged UID outside |
cgroup | The cgroup view | Its own cgroup appears as the root |
You can build a crude container with one command on any Linux box.
sudo unshare --pid --fork --mount-proc --uts --net bash
hostname demo
echo $$
ip linkExpected output, abridged:
1
1: lo: <LOOPBACK> mtu 65536 qdisc noop state DOWN mode DEFAULT ...The new shell is PID 1, host processes are gone, and the only network interface is a loopback that is down. Docker adds the rest: a different root filesystem, a virtual network link to a host bridge, and resource limits.
Cgroups: what a process can use
With cgroup v2, the runtime writes each container's limits into plain files.
cat /sys/fs/cgroup/system.slice/docker-<id>.scope/memory.max
cat /sys/fs/cgroup/system.slice/docker-<id>.scope/cpu.maxExpected output:
536870912
50000 100000The first value is a 512 MiB hard limit. The second means 50 ms of CPU time per 100 ms period, which is 0.5 CPU.
The two limits fail in different ways.
- Over the memory limit: the kernel's OOM killer sends
SIGKILL. The container exits with code 137 (128 + signal 9). - Over the CPU limit: nothing is killed. The kernel pauses the processes until the next 100 ms period. This is CPU throttling, and it looks like latency spikes, not errors.
Root's powers are also split into about 40 capabilities, and a seccomp profile blocks risky system calls. See container security.
Who sets this up
The Open Container Initiative (OCI) standardized these pieces. Two of them do the setup.
runc is the low-level runtime. It sets up namespaces, cgroups and capabilities, runs your process, and exits. containerd pulls images, unpacks layers and supervises containers. Kubernetes talks to containerd through the Container Runtime Interface. Docker-built images still run, because they are OCI images.
PID 1 and signals
Your entrypoint becomes PID 1 inside its PID namespace. The kernel gives PID 1 no default signal handlers, so a signal such as SIGTERM is ignored unless your process handles it. docker stop then waits 10 seconds and sends SIGKILL. Use the exec form, CMD ["python", "app.py"], so your app receives the signal directly.
Common mistakes
- Using the shell form of
CMD. The shell becomes PID 1 and does not forward signals to your app. - Reading exit 137 as an app bug. It usually means the OOM killer ran, so check memory first.
- Expecting CPU limits to kill. They throttle quietly, so the symptom is slow responses.
Check yourself
- A container exits with code 137. What happened? Answer: The kernel sent SIGKILL (128 + 9), usually because the process went over its memory limit.
- A container has
cpu.maxset to 0.5. Its latency spikes, but nothing crashes. Why? Answer: CPU throttling. Its processes are paused once their quota for each 100 ms period is used.
Key takeaways
- Namespaces control what a container sees. Cgroups control what it uses.
- A memory overrun ends in a kill (exit 137). A CPU overrun ends in throttling (latency).
- runc sets up the isolation, and containerd manages images and containers.
- PID 1 ignores signals it has no handler for, so handle
SIGTERMin your app.
Further learning
Liz Rice builds a working container from scratch in about 200 lines of Go, live on stage. Watching namespaces and chroot appear one by one makes the "a container is just a process" idea stick.
Jérôme Petazzoni's classic talk walks through each namespace and cgroup controller, and explains how copy-on-write storage drivers work.
Related
Containers vs virtual machines
A virtual machine boots its own kernel. A container shares the host kernel. That one difference explains the speed, the size and the security trade-off.
Container security
A container is a convenience boundary, not a strong security boundary. Defend it in layers, with minimal images, non-root users, read-only filesystems, dropped capabilities and no secrets in layers.
Processes, threads and the kernel
What the OS gives every program, and how to see it on a live Linux box. Start here for the process model.