Who owns what
- The daemon owns the VMs.
k7druns as a long-lived systemd service and owns every VM’s KVM file descriptors, guest memory, vsock, virtiofsd children, bridges, nftables jail tables, cgroups, disk images, and snapshot trees. Clients — the containerd shim, the K7 Python backend, your RL trainer, an MCP server — talk to it over a Unix socket, one JSON object per line. See Daemon API. - The daemon owns the guest clock. Every guest reads
kvm-clock; there is no RTC and no NTP client. Restore re-bases the clock withKVM_SET_CLOCKso a resumed guest knows the real time, and the same mechanism lets the daemon jump or dilate a whole cluster’s clock in lockstep. See Time warp. - The shim is a thin client. containerd spawns one
containerd-shim-k7-v1per pod; its sandboxCreatecalls the daemon over/run/k7d/k7d.sockand fails loudly if the daemon is not running. Because VMs outlive shims, a respawned shim reattaches to its still-running VM (reattach_sandbox). - The agent is not a Kubernetes component.
k7-agentis a tiny static binary — PID 1 in the minimal guest, a systemd unit on an Ubuntu node. It exposes a JSON-line RPC over vsock (commands likeping,exec,read_file,write_file,container_start,container_exec,container_stop,container_output). Clients reach the agent directly over vsock using theguest_cidthe daemon returns — the daemon does not proxy the agent protocol. runcruns inside the guest, never on the host. In the minimal guest the agent invokes it against erofs images on virtio-blk; on an Ubuntu node k3s’ own containerd does.
Two guests, one VMM
The daemon is the only process that picks a guest:
create_vm without a kernel uses the daemon’s configured pair, a disk-booted config gets no initrd, and neither is ever silently substituted for the other. See Ubuntu nodes.
Design choices worth knowing
- virtio-MMIO only, no PCI. Each virtio device is a 4 KiB MMIO window above guest RAM, discovered from the kernel cmdline. PCI bus emulation would add ~2k LoC for nothing k7d needs. Device set: virtio-net (userspace TAP pump), vhost-vsock (kernel) for the agent, virtiofs (vhost-user) for shared directories, virtio-blk for disk images, plus two legacy serial ports for console and agent fallback. The IOAPIC’s 24 pins cap a VM at 19 virtio devices — which is why per-container scratch, data disks, and image slots are opt-in.
- Guest memory is one mmap. Either memfd-backed (fresh VM) or
MAP_PRIVATEover a keyframe file (restored/forked VM) — the foundation of CoW fork. Multi-vCPU guests (SMP via an MP table) snapshot, fork, and dilate like single-vCPU ones. - Writable disks live on one reflink filesystem. The daemon loop-mounts a sparse XFS image (
reflink=1) at/var/lib/k7d/disks; every scratch disk, data disk, and Ubuntu root disk is an ext4 image inside it, so a fork’s copy is aFICLONEon the same filesystem. No reflink support ⇒ error, never a silent full copy. - No async in the VMM. Plain threads +
Arc<Mutex<...>>; only the shim is tokio-based (containerd’s shim protocol is async). Every cross-thread signal is an eventfd. - Errors are loud. Typed errors per module, no silent fallbacks, no
unwrap()in library code. Resources (KVM fds, TAP devices, nft tables, cgroups, virtiofsd children) are cleaned up viaDrop— drop order is deliberate so device worker threads never touch freed guest memory — and stale jail tables from a crashed daemon are reaped at startup. - Rootfs via erofs on virtio-blk. Read-only host paths are packed into cached erofs images and attached as block devices. This is what keeps cluster forks CoW: no per-fork memory copies for filesystem shares (the virtiofs path forced a full memory copy per fork and survives only as the fallback for read-write hostPath).
- CNI is the host network contract, not a Flannel special case. When
network.pod_netnsis set, the daemon moves the TAP into the CNI-provided pod netns so the VM answers on the pod IP. That path follows the CNI netns + eth0 + pod IP contract; it works the same if Flannel is replaced or disabled. - One jail per tree, on by default. nftables per bridge, cgroup per tree, seccomp on the daemon, chroot on virtiofsd. See Security model.
The “start a pod” path
kubectl apply (with runtimeClassName: k7) → kubelet → containerd → exec containerd-shim-k7-v1 → shim create_vm over the daemon socket (daemon builds the VM: kernel, initramfs, vsock, network, block devices incl. the scratch disk; ~163–205 ms to agent-ready, or served warm from the pool) → shim tells the agent to container_start the OCI bundle → agent runs runc → an output bridge polls container_output and writes into containerd’s FIFOs — which is how kubectl logs works.
Setup steps for the RuntimeClass: RuntimeClass k7.