feat(compass): microVM V2a boot harness + KVM-gated boot spike (RIG-2591) - #574
feat(compass): microVM V2a boot harness + KVM-gated boot spike (RIG-2591)#574rigel-mintaka wants to merge 4 commits into
Conversation
|
Compass engineering docs preview: https://compass-runner-rig-2591-boot.compass-eng-docs.pages.dev Deployed from |
Additive review-round fixes on PR #574 (the microVM V2a boot harness), from the single-reviewer pass (0 high, 0 medium, 4 low): - test(microvm): match the boot-spike console poll against the WHOLE serial console, not VM.ConsoleTail's bounded 8 KiB diagnostic tail. A fail-closed boot prints its kernel-panic backtrace AFTER the evidence line, so a dump larger than the tail window could evict the evidence and turn a real failure into a false-negative timeout. The 8 KiB cap stays where it belongs — the failure MESSAGE (Diagnostics) — never the poll predicate. Removes a latent flake on TestCorruptRootfsFailsClosed (house rule: a flaky test is a real bug, made deterministic — never retried). - docs(microvm-boot-test): fix two stale doc-comment pointers from the non-existent run.test.ts to the real run-core.test.ts. Two lows accepted with rationale rather than changed: the positional nix-out-path mapping deliberately mirrors ci.yml (the reviewed source of truth — diverging would fork the convention), and the per-connection vsock sockets are bounded by t.TempDir today (only relevant when V2b makes VM long-lived — filed forward to RIG-2493). Re-verified on real KVM: net-only smoke, full boot (Health net+workspace in ~0.78s), and corrupt-rootfs fail-closed all PASS; gofmt/vet/golangci-lint (microvm+unix tags)/bun/tsc/biome all clean. Refs RIG-2591 Co-authored-by: Matt Wilkinson <matt@rigel.build>
0d1732a to
6d31c7b
Compare
…591) T4, the V2a deliverable: boot one session guest rootless under cloud-hypervisor, provision its network via passt+DHCP, mount the virtio-fs workspace, and complete the vsock GuestControl.Health handshake — proven on real KVM. Host launch harness (go/internal/runtime/microvm/launch.go): - Launch/LaunchNetOnly spawn virtiofsd -> passt -> cloud-hypervisor in order, each Pdeathsig+LockOSThread, with a bounded socket-readiness poll before the VMM connects and fail-closed teardown on any spawn error (no orphans, no leaked sockets). cloud-hypervisor argv is exactly per the frozen record (§T4 542-547); LaunchNetOnly drops --fs/--vsock to isolate the passt×CH vhost-user negotiation. - VM.Health dials the guest over T3's hybrid vsock client. VM.Shutdown kills the VMM first, then reaps virtiofsd/passt (SIGTERM -> grace -> SIGKILL), Wait's each, and removes the sockets/pidfile; sync.Once-guarded so it is safe from t.Cleanup. VM.PSS reads /proc/<pid>/smaps_rollup (PSS, not summed VmHWM, since --memory shared=on maps guest RAM once across all three) best-effort: a sandboxed helper's unreadable rollup or an exited process leaves no entry rather than erroring. KVM-gated boot test (boot_microvm_test.go, //go:build microvm && unix): - TestNetOnlyBootSmoke: guest acquires its DHCP address (OQ-G isolation). - TestFullBoot: polls Health to net_provisioned && workspace_mounted, logs boot latency + per-process PSS. - TestCorruptRootfsFailsClosed: a non-erofs rootfs fails-closed with a named serial-console cause and clean teardown. microvmtest gate grows COMPASS_TEST_GUEST_INITRD + a passt PATH lookup (Env.InitrdImage, Env.PasstPath), asserted by the canary. Local test lane: go/moon.yml test-microvm task (deps compass-guest-image:build, runInCI:false) drives a bun TS script (tools/microvm-boot-test/) that realises the guest images + VMM env and runs the tagged suite — scripts-ts-over-bash. Guest-image module fix (guest-image/default.nix), required for the boot: the boot spike caught a wrong assumption in the frozen record — that virtio_net/virtiofs/vsock "autoload on demand post-switch_root". They are =m modules and the minimal guest runs guestd as PID 1 with no udev/coldplug to trigger autoload, so the devices enumerated but never bound their drivers -> eth0 absent -> guestd fail-closed. Fix: the initramfs (which already loads the four boot-critical modules via kmod) also loads the runtime set guestd needs — virtio_net, virtiofs, vmw_vsock_virtio_transport, and af_packet (the AF_PACKET raw socket guestd's DHCP client opens). Modules loaded pre-switch_root persist as kernel state, so guestd's frozen eth0/workspace contracts hold unchanged. The =m build-time gate is extended to cover all seven. OQ-G resolved positive: passt×cloud-hypervisor vhost-user negotiation works; the pre-authorized gvproxy fallback is not needed. Refs RIG-2591 Co-authored-by: Matt Wilkinson <matt@rigel.build>
Additive review-round fixes on PR #574 (the microVM V2a boot harness), from the single-reviewer pass (0 high, 0 medium, 4 low): - test(microvm): match the boot-spike console poll against the WHOLE serial console, not VM.ConsoleTail's bounded 8 KiB diagnostic tail. A fail-closed boot prints its kernel-panic backtrace AFTER the evidence line, so a dump larger than the tail window could evict the evidence and turn a real failure into a false-negative timeout. The 8 KiB cap stays where it belongs — the failure MESSAGE (Diagnostics) — never the poll predicate. Removes a latent flake on TestCorruptRootfsFailsClosed (house rule: a flaky test is a real bug, made deterministic — never retried). - docs(microvm-boot-test): fix two stale doc-comment pointers from the non-existent run.test.ts to the real run-core.test.ts. Two lows accepted with rationale rather than changed: the positional nix-out-path mapping deliberately mirrors ci.yml (the reviewed source of truth — diverging would fork the convention), and the per-connection vsock sockets are bounded by t.TempDir today (only relevant when V2b makes VM long-lived — filed forward to RIG-2493). Re-verified on real KVM: net-only smoke, full boot (Health net+workspace in ~0.78s), and corrupt-rootfs fail-closed all PASS; gofmt/vet/golangci-lint (microvm+unix tags)/bun/tsc/biome all clean. Refs RIG-2591 Co-authored-by: Matt Wilkinson <matt@rigel.build>
…-2591) CI's KVM leg (PR #574, bare ubuntu-latest runner) surfaced a nil-pointer panic the local KVM run never hit: `launch`'s deferred fail-closed cleanup ran `VM.Shutdown` on a nil `*VM` and SIGSEGV'd. Cause: `launch` has named returns `(vm *VM, err error)`, and every error site did `return nil, fmt.Errorf(...)`. That `nil` assigns to the NAMED `vm` return before the deferred cleanup runs, so the defer dereferenced a nil `vm` (`vm.shutdownOnce`) — and, had it not panicked, would also have orphaned any daemon already started, since the handle it needed to reap them was gone. The path only fires when a daemon fails to come up after an earlier one started; on the dev box every daemon starts cleanly, so it stayed latent. On the bare CI runner passt/virtiofsd cannot detach their `--sandbox=namespace` user namespace ("Operation not permitted"), so a daemon dies on launch and the error path runs. Fix: make `vm` a LOCAL, not the named return. `return nil, err` still sets the error return the defer checks, but can no longer clobber the `vm` handle — the defer always sees the real, partially-built VM and tears it down. Regression: `TestLaunchFailClosedTeardown` (`//go:build unix`, no KVM — runs on the standard `go test ./...` lane, not just the KVM leg) drives the exact fail-closed path with shell-fake aux daemons and cloud-hypervisor absent from PATH, asserting Launch returns `(nil, err)` without panicking AND reaps the started daemons (no orphan). Verified red (SIGSEGV) on the pre-fix code, green after; the real KVM boot suite (net-only, full boot, corrupt-rootfs, dial tests) re-verified green on this box. Refs RIG-2591 Co-authored-by: Matt Wilkinson <matt@rigel.build>
6d31c7b to
112b8a7
Compare
Open question for Matt (CI-infra decision) — parked, not blocking the codeThe real bug CI caught is fixed (3rd commit on this PR): Remaining is a CI-infra fork — your call. The KVM boot tests passt has no flag to disable its self-sandbox (confirmed via
I'm holding — not speculatively wiring any option (token-conserve mode). Say |
… leg runs on GHA (RIG-2591) ubuntu-latest is now Ubuntu 24.04, which ships `apparmor_restrict_unprivileged_userns=1`: an unconfined non-root process can no longer create a user namespace. passt and virtiofsd both self-sandbox by detaching into their own userns (passt's `--sandbox` has no opt-out; virtiofsd's `--sandbox=namespace` is what the record §T4 specifies), so under the restriction they die at startup with "Failed to detach isolating namespaces: Operation not permitted" and the guest never acquires its address (the OQ-G stop condition). The KVM boot tests (`TestNetOnlyBootSmoke`, `TestFullBoot`) then time out on the `gates` runner. Set `apparmor_restrict_unprivileged_userns=0` in the existing "Enable KVM" step, via the same passwordless sudo it already uses to make /dev/kvm openable. This is a kernel-hardening knob on an ephemeral single-tenant throwaway runner, the §E-D2 class the world-RW /dev/kvm relaxation already lives in: the daemons still run rootless as the invoking uid with no added privilege — the record's "only privilege is the kvm group, no CAP_NET_ADMIN, no rootful helper" constraint holds. The setting reverts on runner teardown; nothing persists. Matt ruled privileged-container with a self-hosted-Woodpecker fallback (RIG-2665); this sysctl toggle is the minimal single-purpose change that meets the same "get it working on free GHA" goal without duplicating the 230-line gates job into a privileged container. Privileged-container stays the documented fallback if the toggle proves insufficient in CI. Refs RIG-2591, RIG-2665 Co-authored-by: Matt Wilkinson <matt@rigel.build>
|
RESOLVED in I found a 4th path, cleaner than all three parked options: the wall wasn't a fundamental runner capability gap, it was This lifts the knob (same §E-D2 throwaway-runner relaxation class as the world-RW Verified durable: the Gates microVM guard printed |
T4, the V2a deliverable: boot one session guest rootless under
cloud-hypervisor, provision its network via passt+DHCP, mount the virtio-fs
workspace, and complete the vsock GuestControl.Health handshake — proven on
real KVM.
Host launch harness (go/internal/runtime/microvm/launch.go):
each Pdeathsig+LockOSThread, with a bounded socket-readiness poll before the
VMM connects and fail-closed teardown on any spawn error (no orphans, no
leaked sockets). cloud-hypervisor argv is exactly per the frozen record
(§T4 542-547); LaunchNetOnly drops --fs/--vsock to isolate the passt×CH
vhost-user negotiation.
the VMM first, then reaps virtiofsd/passt (SIGTERM -> grace -> SIGKILL),
Wait's each, and removes the sockets/pidfile; sync.Once-guarded so it is safe
from t.Cleanup. VM.PSS reads /proc//smaps_rollup (PSS, not summed VmHWM,
since --memory shared=on maps guest RAM once across all three) best-effort:
a sandboxed helper's unreadable rollup or an exited process leaves no entry
rather than erroring.
KVM-gated boot test (boot_microvm_test.go, //go:build microvm && unix):
boot latency + per-process PSS.
serial-console cause and clean teardown.
microvmtest gate grows COMPASS_TEST_GUEST_INITRD + a passt PATH lookup
(Env.InitrdImage, Env.PasstPath), asserted by the canary.
Local test lane: go/moon.yml test-microvm task (deps compass-guest-image:build,
runInCI:false) drives a bun TS script (tools/microvm-boot-test/) that realises
the guest images + VMM env and runs the tagged suite — scripts-ts-over-bash.
Guest-image module fix (guest-image/default.nix), required for the boot:
the boot spike caught a wrong assumption in the frozen record — that
virtio_net/virtiofs/vsock "autoload on demand post-switch_root". They are =m
modules and the minimal guest runs guestd as PID 1 with no udev/coldplug to
trigger autoload, so the devices enumerated but never bound their drivers ->
eth0 absent -> guestd fail-closed. Fix: the initramfs (which already loads the
four boot-critical modules via kmod) also loads the runtime set guestd needs —
virtio_net, virtiofs, vmw_vsock_virtio_transport, and af_packet (the AF_PACKET
raw socket guestd's DHCP client opens). Modules loaded pre-switch_root persist
as kernel state, so guestd's frozen eth0/workspace contracts hold unchanged.
The =m build-time gate is extended to cover all seven.
OQ-G resolved positive: passt×cloud-hypervisor vhost-user negotiation works;
the pre-authorized gvproxy fallback is not needed.
Refs RIG-2591
Co-authored-by: Matt Wilkinson matt@rigel.build