How containerd-shim Decouples Container Lifecycles
In Linux container environments, containerd-shim serves
as a lightweight intermediary process between high-level runtimes like
containerd and low-level OCI runtimes like
runc. This article explains how the Linux operating system
implements process reparenting, file descriptor preservation, and system
calls to allow containerd-shim to decouple containers from
daemon lifecycles. By isolating runtime dependencies, Linux ensures that
containers remain running even if management daemons crash, restart, or
upgrade.
The Architecture Challenge of Direct Execution
Under the Open Container Initiative (OCI) model, low-level runtimes
like runc are responsible for setting up namespaces,
cgroups, and root filesystems before executing the container's initial
process. However, runc is ephemeral: it executes the setup,
starts the container workload, and exits immediately.
If the central container manager (such as containerd)
monitored the container process directly, it would create a tight
coupling between the management daemon and the container. Under standard
Linux process semantics, terminating or restarting
containerd would drop child process handlers, sever
standard I/O pipes, and terminate all active containers.
Process
Reparenting and PR_SET_CHILD_SUBREAPER
Linux handles process lifecycles hierarchically. When a parent
process terminates, its child processes are traditionally reparented to
PID 1 (init or systemd). If runc
exits, the container process becomes orphaned.
To solve this, Linux introduced the prctl system call
with the PR_SET_CHILD_SUBREAPER option. When
containerd creates a container, it does not launch
runc directly; it first spawns
containerd-shim. The shim executes:
prctl(PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0);By marking itself as a subreaper, the shim informs the Linux kernel
that any orphaned processes within its descendant tree must be
reparented to the shim process instead of PID 1. When
containerd-shim executes runc, and
runc subsequently exits, the container process's parent PID
automatically becomes the shim.
Decoupling from the Daemon Hierarchy
To completely detach the container from the parent daemon,
containerd-shim is executed as an orphaned or detached
process relative to containerd. When
containerd starts a shim, it uses a standard double-fork
mechanism or launches the process in a new session
(setsid()).
Because the shim is detached from the daemon’s process group:
containerdcan be stopped, upgraded, or restarted without propagating signals (such asSIGTERMorSIGHUP) to the shim.- The container workload continues running uninterrupted in its respective namespaces.
- Once
containerdrestarts, it queries the local filesystem (typically finding Unix domain sockets left by each shim) to reattach to the running shims without disturbing the underlying containers.
Preserving I/O Streams and File Descriptors
A container relies on standard input, output, and error streams
(stdin, stdout, stderr). If the
process managing these pipes terminates, the Linux kernel generates a
SIGPIPE or reaches an EOF state, causing the
container process to fail.
The containerd-shim addresses this by maintaining the
container's standard streams directly:
- The shim opens the Linux FIFO pipes or pseudo-terminals (PTYs) allocated for the container.
- The shim retains these open file descriptors regardless of whether
containerdis actively listening. - If
containerdshuts down, the shim buffers output or holds the pipe open untilcontainerdreconnects to the shim's control socket.
Because the file descriptors reside in the shim’s file descriptor table, the container’s streams remain valid and open at the kernel level.
Exit Status Retention and Zombie Prevention
In Linux, when a child process terminates, it enters a "zombie" state
until its parent process invokes waitpid() or
waitid() to read its exit status.
Because containerd-shim acts as the subreaper and
immediate parent of the container process:
- It continuously monitors the container process for termination.
- When the container exits, the shim collects the exit status code immediately, preventing zombie process accumulation.
- The shim persists this exit code in memory and on disk, holding it
until
containerdrequests it, even if the primary daemon was offline at the exact moment the container exited. Once the status is retrieved, the shim exits cleanly.