<aside> ๐Ÿงญ

Module 12 ยท Namespaces, cgroups and containers

Every module so far described one machine: one set of processes, one filesystem tree, one network stack. This is where that stops being true. By the end you will be able to build a container by hand with three commands, and โ€” far more useful โ€” take one apart from the host when the tooling is not helping.

๐Ÿง  concept โ†’ ๐Ÿงฉ real-world analogy โ†’ ๐Ÿงช exercise โ†’ โœ… expected result (hidden) โ†’ ๐ŸŽฏ interview questions (hidden)

</aside>

<aside> โœ…

Before you start, you should already know:

From Module 02 โ€” fork, exec, PIDs, PID 1, and orphan reparenting.

From Module 03 โ€” mounts, the mount table, and file descriptors.

From Module 07 โ€” that a cgroup can cap CPU, and cpu.max.

From Module 09 โ€” memory.max versus memory.high, memory.events, and exit code 137.

From Module 10 โ€” io.stat and io.pressure.

From Module 11 โ€” that abstract sockets are bounded by a network namespace.

Everything here uses util-linux, which is already installed: unshare, nsenter, lsns, findmnt.

</aside>


๐Ÿชช Part A ยท Namespaces

A1 ยท What a namespace actually is

<aside> ๐Ÿ“–

Official docs: namespaces(7) ยท clone(2) ยท lsns(8)

</aside>

A namespace changes what a process can see, and nothing else. It does not restrict what a process may do, it does not limit resources, and it does not hide anything from the kernel โ€” it simply gives that process a different view of one particular global resource.

There are eight, each covering one kind of thing:

Namespace What it partitions Introduced
Mount (CLONE_NEWNS) The set of mounts โ€” what the filesystem tree looks like 2.4.19
UTS Hostname and domain name 2.6.19
IPC System V IPC objects and POSIX message queues 2.6.19
PID Process ID numbers 2.6.24
Network Interfaces, addresses, routes, ports, iptables 2.6.24
User UIDs, GIDs and capabilities (Module 13) 3.8 (usable)
Cgroup What the cgroup tree looks like from inside 4.6
Time The boot and monotonic clocks 5.6

<aside> ๐Ÿง 

The counter-intuitive part, and the whole point of this module. There is no such thing as a container in the Linux kernel. There is no container object, no container ID, no container_create() system call. A container is an ordinary process that a runtime has placed in some namespaces, put in a cgroup, stripped of capabilities and pointed at a different root filesystem. Every one of those is optional and independent. That is why containers can be assembled by hand โ€” and why they leak in ways virtual machines do not.

</aside>

A namespace is identified by an inode number. /proc/PID/ns/ holds one symlink per type, and two processes are in the same namespace exactly when those inodes match. That is the whole comparison, and it is how every tool here works.

<aside> ๐ŸŽฏ

Interview-grade detail. The version table in namespaces(7) lists when each /proc/PID/ns/ file appeared, not when the namespace was introduced โ€” those differ by years for several types, and clone(2) has the real dates. Quoting "mount namespaces arrived in 3.8" is the giveaway that someone read the wrong table: they arrived in 2.4.19, in 2002, long before anybody used the word container.

</aside>

<aside> ๐Ÿงฉ

Real-world analogy โ€” the office directory

Imagine a building where every employee consults a shared directory: room numbers, phone extensions, the list of departments, the staff list.

A namespace is giving one team its own copy of one page of that directory. Their staff-list page says they are employees 1, 2 and 3 โ€” while the building's real list has them at 4012, 4013 and 4014. Both are true. The team is not hidden, not locked in, not restricted; they simply read a different page.

Crucially, each page is separate. A team can have its own staff list while sharing the building's phone directory, or its own room numbering while sharing everything else. There is no single "private team" switch โ€” there are eight independent pages, and you choose which to replace.

The part that surprises people: from the building manager's desk, all of this is visible. Their master directory shows every employee under their real number, including the team that thinks it is numbered 1 to 3. Nothing is hidden from the host โ€” the view is one-way.

Where the analogy stops working. A directory page is a document you could photocopy. A namespace is a live kernel object, and processes can be moved between them at runtime with setns().

</aside>

๐Ÿงช Exercise A1.1 โ€” See your own namespaces, then leave one

# The eight namespaces this shell is in. The numbers are inodes.
ls -l /proc/self/ns/

# Every namespace on the machine, with how many processes are in each
lsns | head -12

# Create a new UTS namespace and change the hostname inside it
echo "host hostname: $(hostname)"
sudo unshare --uts bash -c '
  hostname container-demo
  echo "inside : $(hostname)"
  readlink /proc/self/ns/uts
'
echo "host after: $(hostname)"
readlink /proc/self/ns/uts

# Which namespaces did that child NOT change?
sudo unshare --uts bash -c '
  for n in uts pid net mnt ipc user cgroup time; do
    printf "%-7s %s\n" "$n" "$(readlink /proc/self/ns/$n)"
  done' > /tmp/child.ns
for n in uts pid net mnt ipc user cgroup time; do
  printf "%-7s %s\n" "$n" "$(readlink /proc/self/ns/$n)"
done > /tmp/host.ns
diff /tmp/host.ns /tmp/child.ns
rm -f /tmp/host.ns /tmp/child.ns

<aside> ๐Ÿญ

Now imagine this at 500 hosts. readlink /proc/PID/ns/* is the fastest way to answer "is this process containerised, and with what?" on a host where you have no container tooling and no idea what runtime is in use. Comparing a process's namespace inodes against PID 1's tells you instantly which kinds of isolation apply โ€” and it works identically for Docker, containerd, Podman, systemd sandboxing and anything else, because they all ultimately do the same thing.

</aside>

A2 ยท PID namespaces and the strange life of PID 1