Containers as Kernel Namespace and Resource-Control Mechanisms
LESSON
Containers as Kernel Namespace and Resource-Control Mechanisms
By the end of this lesson, you will be able to...
Map a container requirement to namespaces, cgroup controls, and privilege restrictions.
Trace how a host process becomes a restricted worker sharing the host kernel.
Explain why visibility isolation, resource control, and a trust boundary are different claims.
Idea in one sentence: A container is a normal host process whose view, resource budget, and authority are deliberately narrowed by kernel mechanisms, while the kernel itself remains shared.
render-worker processes image files supplied by customers. It needs a private /work directory, a temporary writable area, network access only to an object store, at most one CPU worth of time, 512 MiB of memory, and no ability to load kernel modules or inspect other tenants' processes.
Starting a process with a different root directory is not enough. It may still see host PIDs, use the host network, consume all memory, or hold privileges the workload does not need. Conversely, a CPU limit does not hide other processes, and a PID namespace does not protect the kernel from a kernel vulnerability.
The useful design is a composition: use namespaces to alter what the process can see, cgroups to account for and constrain what it can consume, and privilege/ syscall policy to reduce what it may ask the shared kernel to do. Each layer has a distinct promise and a distinct failure signal.
Core Insight
Treat a container boundary as a requirements table, not a product label:
Requirement for render-worker |
Kernel mechanism | What it changes | What it does not promise |
|---|---|---|---|
| “Do not list or signal host processes” | PID namespace | process-ID view and parent/child scope | CPU or memory limits |
“See /work instead of the host root” |
mount namespace plus controlled mounts/root | mount table and pathname view | a safe kernel or resource budget |
| “Use only the worker network” | network namespace plus configured interfaces/rules | network devices, routes, and ports visible to the process | application-level network authorization by itself |
| “Run as a non-host-root identity” | user namespace and ID mapping | identity and capability scope | immunity from kernel bugs |
| “Stay within CPU, memory, I/O, and process budgets” | cgroup controllers | accounting, weights, limits, and pressure/limit events | a private process or filesystem view |
| “Do not use unneeded privileged kernel features” | dropped capabilities and syscall filter | authority and allowed syscall surface | a complete isolation boundary alone |
Plain meaning:
A container gives a worker a smaller map, a smaller allowance, and a shorter list of keys. It does not build a second city underneath the worker.
In this scenario:
render-worker can call getpid() and see a small PID, but the host kernel schedules its thread with all other host threads. It can see /work/input.png in its mount view, but the bytes still live in host-managed page cache and filesystems. Its cgroup tracks and limits memory charged to the worker, but does not make the process invisible.
Technical name:
Linux namespaces wrap global resources so members see a scoped instance. A cgroup organizes processes hierarchically and applies resource controllers such as CPU, memory, I/O, and PID limits. Capabilities split root-like privileges into narrower checks. A syscall filter (for example, seccomp) can restrict which kernel-entry operations a process may request.
The Naive Boundary Breaks in Several Directions
Consider this incomplete launch:
fork worker
change its current directory to /srv/render
exec renderer
The worker starts in a convenient directory, but it still has the host PID view, host network, inherited file descriptors unless they were closed, and the same resource competition as any other process. If it is compromised, it may exploit every syscall and capability already available to its host identity. It can also exhaust the host by allocating or forking until global limits intervene.
Adding only a mount root is not a complete answer. Pathname resolution, mounts, file descriptors inherited across exec, device access, user identity, and system-call authority are separate boundaries. The same is true in the other direction: a cgroup can stop a worker from growing beyond a memory budget, but it does not stop it from seeing a host port if it remains in the host network namespace.
The design question is therefore precise: which observation, resource, or authority must be constrained, and which kernel mechanism actually controls that thing?
A Small Container Construction
Use a launcher process with enough host authority to create the boundary. The launcher creates the child in selected namespaces, configures the child's cgroup and filesystem view, closes unintended inherited descriptors, drops authority, then execs the worker. Exact system calls and ordering vary by runtime, but the dependencies are visible.
host launcher
-> create child with new user, PID, mount, and network namespaces
-> establish ID mapping and a controlled root/mount set
-> attach child to a resource cgroup
-> configure network interfaces and policy inside its net namespace
-> close inherited handles; drop capabilities; install syscall policy
-> exec render-worker
The child remains a process of the host kernel throughout. The kernel's process, memory, VFS, scheduler, networking, driver, and syscall mechanisms from earlier lessons now enforce a deliberately narrower context.
Namespaces change views, not consumption
| Namespace example | Host global resource | Worker sees |
|---|---|---|
| PID | process ID space | its process tree, where its init-like process is PID 1 in that namespace |
| mount | mount table | a selected root and mounted filesystems |
| network | interfaces, routing, ports | only configured interfaces, routes, and sockets in that network view |
| user | user/group IDs and capability checks | mapped IDs and namespace-scoped capabilities |
The important word is view. Namespaces make a global resource appear scoped. The host still owns the physical CPUs, RAM, devices, and kernel. A host administrator can observe and control the underlying process; processes in the namespace receive the limited view chosen for them.
Cgroups change budgets and accounting
Place render-worker in a cgroup such as workers/render-a with a memory maximum, a CPU quota or weight, a PID limit, and perhaps an I/O limit. The scheduler and memory systems account resource use against that cgroup while the worker runs. If it tries to exceed a configured memory limit, the kernel applies the controller's policy rather than allowing unrestricted host-wide growth. If it forks too many processes, the PID controller can reject the growth.
This is resource control, not a second address space. The process still has its ordinary per-process virtual address space from lesson 004; the cgroup is a group-level accounting and distribution boundary. Cgroup v2 also forms a hierarchy, so a child cannot escape a tighter budget by naming a new subdirectory; parent constraints remain relevant.
Worked Trace: launch the restricted renderer
Assume the host receives a job for tenant A. The launcher wants render-worker to read /work/input.png, write /work/out.png, use a private veth0 interface, and stay below 512 MiB with at most 64 worker processes.
Input: an executable path, a prepared read-only image, a writable work mount, network configuration, and the stated budgets.
1. The launcher creates scoped execution context
The launcher creates a child with new PID, mount, network, and user namespaces. It establishes the user-ID mapping appropriate to its security model before allowing the child to act with namespace capabilities. It prepares the mount namespace so the child has a controlled root: read-only runtime files plus /work as the intended writable place.
Intermediate state: the child is still a host task, but its calls that inspect PIDs, mounts, IDs, or network interfaces will be interpreted through the new namespace membership.
2. The launcher attaches resource controls before untrusted work
The launcher attaches the child to workers/render-a and configures a memory maximum of 512 MiB, a CPU policy, and a process-count maximum of 64. It may configure I/O constraints if the workload could monopolize a shared device.
State transition: resource use by the child and descendants is charged to the worker cgroup. A later attempt to allocate, fork, or consume time meets a visible policy boundary rather than silently competing without limit.
3. The launcher reduces authority and interface exposure
It closes host-only descriptors, configures the worker network namespace, drops capabilities unnecessary for image conversion, and installs a syscall policy that rejects operations the renderer has no reason to use. Mount policy can prevent additional mounts or writable host-sensitive paths. The exact allowlist needs careful compatibility testing; a too-small one can break a legitimate loader, runtime, or library call.
Decision: the worker may open only the files and make only the kernel requests granted by the assembled boundary. It cannot gain authority merely because it appears as UID 0 inside its user namespace; that identity is mapped and scoped by the host policy.
4. The child executes and the kernel enforces normal mechanisms in a narrower scope
exec render-worker replaces the launch program with the renderer. Its syscalls still enter the same kernel trap path from lesson 003 and use the same scheduler, VFS, networking, and memory mechanisms. Namespace membership alters lookup and visibility; cgroup membership alters accounting and allowed consumption; capability and filter checks alter authorization.
Output/decision: the worker can complete its job inside the selected scope, be terminated on policy failure, or receive an error when it requests a forbidden operation. The host can inspect cgroup events, resource counters, audit signals, and worker exit status to distinguish “job bug,” “resource budget reached,” and “boundary denied request.”
Naive contrast: a directory change alone provides none of this composition. A cgroup alone limits a noisy worker but leaves host visibility. A namespace alone alters a view but does not cap memory. A syscall filter alone does not give a private filesystem. Each mechanism closes one specific gap.
So far, we have constructed a container from kernel primitives rather than treating it as a tiny VM. The same kernel runs the launcher and worker; the design is valuable because it makes scope, budget, and authority explicit and inspectable.
Trade-offs and the Shared-Kernel Limit
Containers start quickly and share the host kernel, page cache, drivers, and scheduler. That often reduces per-workload overhead and makes ordinary process tooling useful. The trade-off is that the isolation boundary is assembled from many kernel features and still relies on the host kernel's correctness. A kernel vulnerability, unsafe device exposure, overly broad capability, leaked file descriptor, or mistaken mount can cross a boundary that looked convincing from inside the container.
Virtual machines generally introduce a stronger separation point by running a guest kernel behind virtualization, at the cost of another kernel, more resource overhead, and different operational complexity. This lesson does not choose universally between processes, containers, and VMs. It asks whether the trust model permits a shared kernel. For mutually trusted jobs with resource and visibility needs, containers may fit well. For a stronger hostile-tenant boundary, evaluate a VM or additional sandboxing rather than assuming namespaces alone are sufficient.
The resource trade-off is also dynamic. A tight memory maximum limits damage but can terminate or fail a legitimate render. A generous CPU quota reduces throttling but may starve neighboring work. A syscall allowlist cuts attack surface but creates compatibility cost. The signals to watch are cgroup limit/pressure events, throttling, denied-syscall logs, unexpected namespace-visible resources, and failed job behavior under the intended policy.
Common Confusions
Confusion: “A container is a virtual machine.”
Why it is tempting:
It can have its own process list, root filesystem view, and network interfaces.
Better model:
It is a host process under a composed kernel boundary. It normally shares the host kernel, unlike a VM with a guest kernel boundary.
Confusion: “Namespaces limit resource use.”
Why it is tempting:
The worker sees fewer things, so it feels contained.
Better model:
Namespaces alter visibility and scope. Cgroups account for and constrain CPU, memory, I/O, and process resources. Use both when the requirements include both kinds of boundary.
Confusion: “Root inside a user namespace is host root.”
Why it is tempting:
Tools may display UID 0 inside the worker.
Better model:
Identity and capabilities are scoped through the user namespace mapping. Host-root authority is not automatically granted; policy and mappings determine what the process can do outside its namespace.
Confusion: “A container security policy can ignore inherited file descriptors.”
Why it is tempting:
The mount namespace hides host paths.
Better model:
An already-open descriptor can remain a direct reference to a host resource across exec. Closing or deliberately passing descriptors is part of the boundary construction.
Check: A worker runs in a new PID and mount namespace but remains in the host network namespace and no cgroup. Which two requirements in the scenario still fail directly?
Think first, then reveal.
Answer: It can still use or observe the host network according to its host permissions, and it has no worker-specific CPU, memory, I/O, or process budget. PID/mount namespaces do not supply those boundaries.
Check: A worker is limited to 512 MiB by a cgroup and sees a private filesystem root. Is it correct to call this a complete hostile-tenant boundary?
Think first, then reveal.
Answer: No. Those mechanisms solve resource and mount-view requirements, but the process still shares the host kernel. The trust model must account for kernel attack surface, device exposure, capabilities, syscall policy, and possibly choose stronger isolation.
Practice: design a worker boundary
Design a boundary for pdf-worker, which needs to read /jobs/in.pdf, write /jobs/out.pdf, connect only to 10.0.0.5:443, use up to 1 CPU and 768 MiB, and must not inspect host processes or mount new filesystems.
Produce a table with one mechanism, one expected signal, and one remaining risk for each of these categories: filesystem view, process view, network view, resource budget, and authority. Use this rubric:
| Criterion | A good answer includes |
|---|---|
| View separation | mount, PID, and network namespaces are assigned to their respective visibility requirements |
| Resource policy | cgroup CPU/memory and optionally PID/I/O controls with a signal such as throttling or limit event |
| Authority reduction | closed inherited descriptors, dropped capabilities, and a justified syscall/mount policy |
| Trust boundary | explicit statement that the host kernel is shared and a remaining risk is named |
| Observability | a concrete policy-denial, limit, or resource-usage signal for debugging the design |
Resources
- [DOC] Linux namespaces overview — Focus: distinguish the global resources whose views namespaces scope.
- [DOC] Linux cgroup v2 — Focus: inspect hierarchical resource accounting, distribution, limits, protections, and controller events.
- [DOC] Linux capabilities overview — Focus: see how privileged authority is split into separate capability checks.
Key Takeaways
- Containers compose namespaces for visibility, cgroups for resource budgets, and privilege/syscall controls for authority reduction.
- A container remains a host process sharing the host kernel; each primitive solves a narrower requirement than “isolation” alone.
- Correct construction includes filesystem and network views, ID mapping, closed inherited handles, resource controls, and observable policy signals.
- The appropriate boundary depends on the trust model; a shared kernel is a deliberate trade-off, not an invisible implementation detail.