Repository navigation
feat(vmimage): build the guest kernel and the disks session VMs boot - #890
Draft
ilovecrayons wants to merge 3 commits into
Conversation
Sessions are moving into Cloud Hypervisor VMs (docs/plans/ 2026-11-13-microvm-sessions.md, work item 4). Nothing could yet turn today's images into something a VM boots. images/kernel holds a miniconfig for a 6.18 LTS guest kernel (virtio block, net, vsock, fs and balloon with free page reporting, cgroup v2, namespaces, seccomp, ext4, XFS, overlayfs, bridge, veth, nftables and iptables for Docker inside the VM) and the script that builds it. `make kernel` runs it in a Debian container pinned by digest with packages from a dated snapshot, so two builds give the same bytes; the release builds it and ships aether-vmlinux-x86_64. internal/vmimage is the image store the VM engine builds on. It pulls an image with go-containerregistry (pure Go, no daemon; it applies whiteouts and opaque directories and verifies layer digests), extracts the flattened filesystem through os.Root so no entry, symlink or hard link leaves the staging directory, and makes a read-only 64 GiB sparse ext4 base disk with mkfs.ext4 -d, cached by config digest. Images that exist only in Docker, the saved aether/ ones, come from docker save during the transition. Per session it makes a qcow2 root disk backed by a hard link to the base, which pins the base until the session's files go, and an ext4 data disk; it saves a root disk as a new base, builds a read-only helper disk holding the in-VM helper as /init, and sweeps what nothing uses. qcow2 won over reflink copies on the KVM host: it works on ext4 hosts, where reflink fails, and every session shares one host page-cache copy of a base. A helper disk won over an initramfs, which pinned 54 MB of every guest's memory.
TestKVMStandardImageRunsDocker, behind the kvm build tag, builds the standard image's disks with the store, boots them under Cloud Hypervisor with the test binary as the helper disk's /init, and checks that dockerd starts, a container runs, a -p port mapping answers and SaveBase refuses the root disk of a running VM.
This was referenced Nov 13, 2026
[Medium impact] The reviewed update appears safe to merge; all four earlier findings are fixed. SummaryAdds the guest kernel build and the image store that creates VM boot disks.
|
A rebuild onto another base relinked <root>.base before the new root disk existed, so a failed rebuild left the old root reading unwritten blocks from the wrong image. Each base now gets its own link name, <root>.<base id>.base, and the links the root disk no longer uses go only after it is replaced; SaveBase reads the backing file from the qcow2 header. Relinking the same base skips rename(2), which leaves the temporary link behind for two links of one file. Concurrent first uses of an image shared a build that ran on the first caller's context, so one cancelled launch failed the others. The whole pull, including a docker save, is now shared per reference, runs on its own context bounded by an hour, and each caller stops waiting on its own. The test helper nlink also compiles on arm64.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #889.
Sessions are moving into Cloud Hypervisor VMs (plan, work item 4). Before this, nothing turned today's images into disks a VM boots, and there was no guest kernel. Now:
images/kernel/): a miniconfig for Linux 6.18.63 LTS applied withallnoconfig, so every option is deliberate, andbuild.sh, which fails if any listed option does not reach.config.make kernelbuildsdist/aether-vmlinux-x86_64indebian:trixie-slimpinned by digest with packages from snapshot.debian.org 2026-11-10. The release builds it in a newkerneljob and ships it as a fifteenth asset in the signed manifest.internal/vmimage), the interface agreed with item 2:BaseDisk(ctx, ref)pulls an image anonymously, as Docker does today, flattens it, and makes a read-only 64 GiB sparse ext4 base disk cached by config digest. It returns the image's User, Env, WorkingDir and Labels. Concurrent first uses share one pull on its own context, so a cancelled launch does not fail the others.aether/references are never pulled. They are bases saved withSaveBase, or, during the transition, come fromdocker savethroughruntime.Docker.SaveImage(new).NewRootDiskmakes a qcow2 root disk backed by a hard link<path>.<base id>.baseto the base. The link pins the base until the run dir is deleted, so sweeping needs no callback from the engine. A failed rebuild leaves the old root disk and its link as they were.NewDataDiskmakes an empty ext4 data disk labelledaether-dataand refuses to overwrite one.HelperDiskmakes a read-only ext4 disk holding the in-VM helper as/init, with/dev /proc /sys /mnt, once per binary digest, and links it into the run dir.SaveBasesaves a stopped VM's root disk as a new base.Tag,Untag,Tags,LookupBase,SweepandKernelcover the runtime image methods.Decisions and why
go-containerregistry for pulling and flattening. It is pure Go and needs no daemon.
mutate.Extractapplies whiteouts and opaque directories, refuses unsafe paths and verifies layer digests. It is what crane, ko and cosign use. Its daemon package is not used: Docker-only images arrive as adocker savetar read with itstarballpackage.ext4 made by
mkfs.ext4 -dfrom a staging directory, extracted throughos.Root, so no entry, symlink or hard link reaches outside it. hcsshim'star2ext4writes without staging but marks the filesystem read-only (RO_COMPAT_READONLY), and a root disk must be writable. Device files are dropped under/dev(the guest mounts devtmpfs there) and refused elsewhere. Limits: 48 GiB and 2M entries per image. Owners, set-user-ID bits, file capabilities and mtimes are kept. mkfs marks the inode tables of a regular file zeroed, so the guest never writes them out (ITABLE_ZEROEDon 256 of 257 groups).qcow2 over a raw base, not reflink copies. Measured on the KVM host (m8id.4xlarge, CH v54.1, XFS /srv, 4-vCPU guest, standard image, host cache evicted per file,
direct=off):tarof/usrin the guestEOPNOTSUPPqcow2 is the only format that works everywhere. Heavy writes go to the raw data disk (
/workspace,/tmp,/var/lib/docker), so the root disk's write gap matters little. Withdirect=on, CH reads qcow2 backing files synchronously (9k IOPS), so the engine keeps the defaultdirect=off.Item 1 measured independently with fio in a 2-vCPU guest and CH
direct=on, against a 4 GiB random-filled base:Those qcow2 figures are the synchronous
direct=onbacking path above; the engine does not run qcow2 that way. A reflink copy also fragmented to about 16k extents under random writes.The coordinator settled it (2026-11-13): qcow2 over a raw base with
direct=off, one path, no reflink path. It works on ext4 hosts, and the shared host page cache of the base is the density win the move to VMs optimises for. Root-disk writes are rare (package installs). CH docs:--disk ... image_type=qcow2,backing_files=on(vmm/src/config.rs,block/src/formats/qcow/backing.rsat v54.1, raw backing supported). Two VMs on one base run together without a lock conflict. Saving a root disk as a base withqemu-img converttook 4.0 s for a 4.1 GB result.Helper disk, not an initramfs, for the in-VM helper (item 3) as PID 1. With a 55 MB init in a 1 GiB guest, the initramfs left 53.7 MB of Shmem that
drop_cachescannot free (MemAvailable 812 MB, CH RSS 801 MB at 12 s). The helper disk left 0 Shmem (MemAvailable 909 MB, CH RSS 589 MB). Base disks stay independent of the server version.Host tools the server now needs on VM hosts:
mkfs.ext4(e2fsprogs) andqemu-img(qemu-utils). Item 10 owns installing them.Verification
go test ./internal/vmimage: extraction refuses..paths, writes through absolute and relative symlinks, directories through symlinks, hard links outside or through symlinks, device files outside/dev, duplicates, out-of-range owners, oversize and truncated entries. As root it keeps other owners, set-user-ID bits andsecurity.capability. The store test pushes a two-layer image (whiteout, opaque dir, hard link across layers) to an in-memory registry and runs four concurrentBaseDiskcalls, which fetch each layer once. It then checks the disk withdebugfs, the qcow2 backing withqemu-img info, a root-disk write reaching the saved base (qemu-io), tags, and the sweep with and without a pin. It also covers the Docker source, a wrong architecture, the data disk and the helper disk. Passed on moss (no qemu-img, so those tests skip) and on the KVM host asubuntuand as root. CI now installsqemu-utilsif missing so they run there too.make kerneltwice on the KVM host: identical SHA-25684ccf6c354fc66d032fc9329ba691de0a08a007ca6a34be41c8ab7c6ddfc23e4, about 1.5 min each.check-config.shagainst the kernel config: every "Generally Necessary" option is enabled. The only optional options missing are swarm overlay networks, macvlan/ipvlan, btrfs, SELinux and AppArmor.TestKVMStandardImageRunsDocker(-tags kvm, as root, kernel frommake kernel, imageghcr.io/3xdevops/aether-standard:v0.8.7-beta.14). The VM boots from disks the store built: helper disk (the static test binary as/init), qcow2 root, raw data.qemu-imgprocess.qemu-img createalone takes 5-7 ms.docker runof an imported image worked, a-p 8080:80mapping answered, and the iptables natDOCKERchain was set up.docker info: cgroup v2, overlayfs storage./usr/bin/sudokept its set-user-ID bit.SaveBasewhile the VM ran was refused:qemu-img: Could not open '.../root.qcow2': Failed to lock byte 100. CH's disk lock protects it.SaveBasetook 0.6 s (2082 MiB). The root disk used 2 MiB after the run.ip=for eth0.--fs,--vsockand--balloon free_page_reporting=on. Bound drivers:virtio_balloon(reporting feature bit 5 set),virtio_blk,virtio_console,virtio_mem,virtio_net,virtio_rng,virtio_rtc,virtiofsandvmw_vsock_virtio_transport./dev/vsockwas present, a virtio-fs mount read a host file, and/proc/filesystemslists ext4, xfs, overlay and virtiofs.aether/image made by twodocker commits became a base disk. The file deleted in the second commit stayed deleted, andsustayed root-owned 04755.greptile: command not found), so only the Greptile bot reviews this PR.Open
page_reporting.page_reporting_order=0belongs on the guest command line (item 1's measurement); item 2 owns the command line.This PR was made with the help of Claude Code.
Opened from Aether run run-w212tvpy6z