Worked examples — Containers — namespaces, cgroups, difference from VMs
This page is the drill floor for Containers — namespaces, cgroups, difference from VMs. We take the two ideas from the parent — namespaces (what a process can SEE) and cgroups (what a process can USE) — and push them through every case that a real machine or an exam can throw at you.
Before we start, one reminder so nothing is used before it's built:
The scenario matrix
Every question about containers lands in one of these cells. The worked examples below are each tagged with the cell they cover, and together they hit all of them.
| # | Case class | The tricky part | Example |
|---|---|---|---|
| A | CPU quota < period (fraction of a core) | share below 1 | Ex 1 |
| B | CPU quota = period (exactly one core) | boundary value | Ex 2 |
| C | CPU quota > period (multiple cores) | share above 1 | Ex 3 |
| D | Degenerate quota = max (unlimited) |
division by "infinity" | Ex 4 |
| E | Memory limit + OOM decision | byte arithmetic, over/under | Ex 5 |
| F | PID namespace numbering | the "PID 1" surprise | Ex 6 |
| G | User namespace UID mapping | root-inside ≠ root-outside | Ex 7 |
| H | Container vs VM word problem | which one, and why | Ex 8 |
| I | Exam twist: cgroup without namespace | isolation vs limits confusion | Ex 9 |
CPU share: cases A, B, C, D
The next figure is a timeline of one CPU. Time runs left to right; each period is a box; the shaded part of each box is the quota the group is allowed to run. Look at how the fraction filled changes across the four cases — that fraction is the answer. (Case C needs more than one core, so it also needs more than one such timeline — we handle that in words in Ex 3.)

Memory: case E
Namespaces: cases F and G
The next figure shows two process tables side by side: the host's table (many PIDs) and the container's private PID-namespace table (renumbered from 1). Follow the red arrow — it's the same running process wearing two different ID badges.

On the host, ps shows your shell as PID 4021. You run unshare --pid --fork --mount-proc bash and inside run echo $$. What PID does it print, and does host-4021 still exist?
Forecast: a fresh PID namespace starts counting from where?
unshare --pidgives the new bash its own PID table. Why this step? Recall from the parent: eachCLONE_NEWPIDprivatises the one global PID list (built via Processes and the clone/fork syscall).- A new PID namespace numbers from 1, so the first process in it is PID 1.
echo $$prints1. Why this step? The figure's right table starts at 1; there is simply no other entry yet. - The host view is unchanged: the same process is still PID 4021 in the host namespace (red arrow — one process, two numbers). Why this step? Namespaces relabel views; they don't delete the process from the host's accounting.
Verify: two truths coexist — inside PID , outside PID . No contradiction because a process has one ID per namespace it belongs to. ✓
A rootless container maps container UIDs onto host UIDs starting at . Inside, whoami says root (UID 0). What host UID is that really, and can it delete /etc/passwd on the host?
Forecast: if inside-0 maps to outside-100000, what powers does outside-100000 actually have?
- Check the prerequisite: the host must have subordinate UIDs allocated to the launching user, e.g. a line
alice:100000:65536in/etc/subuid. Only then may the user namespace map onto host . Why this step? The kernel refuses a mapping into a range the user was never delegated; without the/etc/subuidgrant theunshare --user --map-rootsimply fails, so the whole example presupposes it. - Apply the mapping: container UID host UID . Why this step? A user namespace is a shift table: within the delegated range.
- Host UID is an ordinary, unprivileged account — it owns none of
/etc. Why this step? Only host UID is real root; , so no host-root powers. - Therefore container-root cannot delete the host's
/etc/passwd— the write is checked against host UID 100000 and denied.
Verify: ; and the delegated top, so the mapping is legal. The # prompt and whoami=root are inside-namespace truths; the file-permission check uses the host UID. All consistent. ✓
Container vs VM: cases H and I
A team must run 1000 copies of a small Linux web service on one host, each needing RAM, starting in milliseconds, all trusting the same company. Container or VM? Show the RAM difference.
Forecast: where does the "own kernel" cost bite hardest at 1000×?
- Each VM must boot its own guest kernel + base OS, roughly just for the OS before the app. Total OS overhead , which is (gibibytes — defined above). Why this step? A VM duplicates a full OS (see Virtualization and Hypervisors (Type 1 vs Type 2)); that fixed tax multiplies by the count.
- Each container shares the one host kernel — its OS overhead is ; only the app's counts. Total app RAM , no per-instance kernel tax. Why this step? One kernel = container; the kernel cost is paid once, not 1000 times.
- Decide: same OS, same trust domain, ms startup, huge count → containers.
Verify: VM OS tax ; container OS tax . The container plan saves the entire of duplicated kernels — decisive at scale. ✓
A process is placed in a cgroup with pids.max = 100 and memory.max = 256M, but is given no namespaces. From that process, can you (a) fork-bomb the host, (b) see and kill host PID 4021?
Forecast: which of the two features controls visibility, and did we turn it on?
- (a) Fork-bomb:
pids.max = 100caps the group at 100 tasks → the bomb is stopped at 100, the 101stforkis refused. Why this step? cgroups limit quantity; thepidscontroller counts the group's tasks and refuses the nextforkpast the cap (from Linux Kernel — syscalls). - (b) Visibility: with no PID namespace, this process shares the host's single global PID table. Why this step? Isolation of view comes only from namespaces — and we added none.
- (b) Result: it can see host PID 4021 in the shared table, and if it runs with sufficient privilege on the host (e.g. as host root, or as the owner of PID 4021) it can
killit — the cgroup never hid the process nor blocked the signal. Thepids.max/memory.maxlimits constrain how much it may consume, not whom it may see or signal. Why this step? This is the whole point of the twist: caps and isolation are orthogonal. A resource limit does nothing to remove a target from view or to deny akillsyscall the permission check would otherwise allow.
Verify: cgroup caps satisfied ( pids, MiB) yet host PID 4021 remains visible and killable — proving the parent's rule: cgroups = limits, namespaces = isolation; you need both. ✓
Recall Self-test across the whole matrix
cpu.max = "50000 200000" gives how many cores? ::: cores
memory.max = 134217728 is how many MiB? ::: MiB
New PID namespace: first process gets which PID? ::: 1
User-ns maps container-0 to base 100000 → host UID? ::: 100000 (unprivileged, not root)
What must /etc/subuid contain for that mapping to be allowed? ::: a delegated range for the user, e.g. alice:100000:65536
Cgroup alone, no namespace: can it hide host processes? ::: No — only namespaces isolate view
memory.current charges RSS plus what? ::: reclaimable page cache (so charged memory > RSS)
Ratio for CPU (quota/period), bytes for memory (÷ 1048576 for MiB), rename for PID (starts at 1), shift for UID (base + id). See = namespaces, Use = cgroups.
Back to the parent topic · prerequisite: Processes and the clone/fork syscall · related: Scheduling — CFS and CPU shares, Networking — virtual interfaces and bridges, Filesystems — mount and chroot.