QuantyKube

ADR-017 A pod is three bind mounted namespaces, and runtime state is not kept

All pages

ADR-017, from docs/DECISIONS.md in the QuantyKube repository.

Status In force

src/runtime.rs is the core of the node runtime: images, pods and containers in the shape the CRI asks for, without the gRPC around them.

A pod sandbox holds no process. containerd keeps a pause container in every pod so that the pod's network, IPC and UTS namespaces outlive its containers. Here a short lived child creates them and sets the pod's hostname, the runtime bind mounts the three namespace files into the pod's directory, and the child exits; containers join them with setns. Releasing the pod unmounts them. CRI-O can do the same and notes the same limit: a PID namespace ends with its first process, so a pod whose containers share one (shareProcessNamespace) gets a holder process that only reaps orphans, a fork of the runtime that closes every descriptor it inherited. Its containers are cloned into that namespace by the thread that starts them, which joins it for the one clone and returns.

Each container gets an overlay over the image's unpacked root, so the image is never written to, and its output goes through a pipe into the CRI log format the kubelet reads, split into partial lines past 16 KiB as containerd splits them.

Images are imported from a manifest and its blobs on disk, and a pull is an import of what was fetched (ADR-025). Manifests and configs are checked against their SHA-256 before they are read; a layer is hashed while it is unpacked into a staging directory, read from disk in pieces so that no layer is ever whole in memory, and a layer that does not match its digest throws the staging directory away. The image is visible under its final name only once complete. Pulling nginx this way peaks at 6 MiB of memory.

State is kept in memory. A restart of the runtime is treated as a reboot of the node: at start it kills the containers a previous run left, found by pid and start time so that a reused pid is never hit, takes their pods off the network with the CNI result saved for each, releases their namespaces and cgroups, and the kubelet starts the pods again.

The cost: every pod on a node restarts when the runtime does, where containerd keeps them running through its shims. Keeping them would need a process per container that holds its output pipe and its exit status, which is what a shim is.

A test builds an image from this machine's own binaries with real digests and takes a pod through its whole life as root, in CI too: two containers share the pod's network but not the host's, logs arrive in the CRI format, a shell that ignores SIGTERM is killed after the grace period with 137, and nothing stays mounted after removal. A tampered layer is refused.