|
| 1 | +# Feasibility: Native snapshot-fork in our libkrun fork |
| 2 | + |
| 3 | +Status: **survey complete — GO, native path chosen over a second VMM.** |
| 4 | +Supersedes the backend-selection question left open in |
| 5 | +`cow-snapshot-fork-design.md` (§2): instead of adding a Firecracker/Cloud |
| 6 | +Hypervisor second backend, we extend **our own libkrun fork** |
| 7 | +([A3S-Lab/libkrun](https://github.com/A3S-Lab/libkrun), vendored as a git |
| 8 | +checkout at `src/deps/libkrun-sys/vendor/libkrun`, built by our `build.rs`). |
| 9 | + |
| 10 | +## Why native (vs a second VMM) |
| 11 | + |
| 12 | +- Single VMM: rootfs (virtio-fs dir), vsock exec server, networking, lifecycle |
| 13 | + all stay as-is — no adapter layer, no dual ops surface. (Firecracker has no |
| 14 | + virtio-fs at all; CH would still mean a permanent second backend.) |
| 15 | +- We already patch vendored deps (the CRI vendored-h2 patch precedent). |
| 16 | +- The fork's VMM is Firecracker-derived and **already contains** much of the |
| 17 | + hard state code (below). |
| 18 | + |
| 19 | +## What the survey found (vendor/libkrun/src, x86_64 Linux) |
| 20 | + |
| 21 | +| Area | Status | |
| 22 | +|---|---| |
| 23 | +| vCPU state save+restore (regs/sregs/xsave/MSRs/LAPIC/CPUID, ordered) | **EXISTS** — `vmm/src/linux/vstate.rs:1302-1416` (`#[allow(unused)]` dead code) | |
| 24 | +| VM state save+restore (PIT, clock, PIC/IOAPIC irqchips) | **EXISTS** — `vstate.rs:834-892` | |
| 25 | +| vCPU Pause/Resume events | **HALF-WIRED** — `VcpuEvent::Pause/Resume` + `VcpuHandle::pause()` exist; `Vmm::resume_vcpus()` is public, `pause_vcpus()` missing | |
| 26 | +| Guest RAM | anonymous mmap (`builder.rs:3057`); **but the TEE path already uses `guest_memfd`** — in-tree precedent for non-anonymous backing | |
| 27 | +| Virtio device state serialization (fs/net/vsock/console/balloon/rng) | **MISSING entirely** — no Persist trait, nothing | |
| 28 | +| Snapshot orchestration / FFI | **MISSING** | |
| 29 | + |
| 30 | +## The key scoping insight: snapshot an IDLE deferred-main template |
| 31 | + |
| 32 | +The generic "snapshot any running VM" problem (Firecracker's) is dominated by |
| 33 | +device state. **Our use case doesn't need it.** The pool snapshots a template VM |
| 34 | +that booted with `deferred_main` (P2) and is **quiesced**: no container main, the |
| 35 | +exec server blocked on accept, virtio queues empty, no in-flight I/O. At that |
| 36 | +point: |
| 37 | + |
| 38 | +- queue rings live in guest RAM (captured by the RAM snapshot); host-side device |
| 39 | + state shrinks to per-queue ring addresses + ready/activation status + device |
| 40 | + config — small, explicit structs; |
| 41 | +- vsock: no live connections at idle → muxer state is empty; |
| 42 | +- console: stream positions only; |
| 43 | +- **virtio-fs is the one real device problem**: the in-process passthrough |
| 44 | + server holds a host-side inode/nodeid map the guest references. It must be |
| 45 | + serialized (or rebuilt deterministically) — this is the riskiest single item. |
| 46 | + |
| 47 | +## Phased plan (Linux x86_64 only first) |
| 48 | + |
| 49 | +1. **Phase A — RAM + CPU snapshot/restore of a quiesced idle VM (~1.5–2w)** |
| 50 | + - memfd/file-backed guest RAM (mirror the TEE `guest_memfd` plumbing for |
| 51 | + non-TEE). |
| 52 | + - `Vmm::pause_vcpus()` (wire the existing Pause event), then |
| 53 | + `krun_snapshot(path)`: vCPU+VM state (existing code) + RAM dump + per-queue |
| 54 | + MMIO/virtio registration state. |
| 55 | + - `krun_restore(path)`: rebuild the VM, **`MAP_PRIVATE` the RAM file** |
| 56 | + (kernel page-level CoW — this is the fork), restore vCPU/VM state, re-plug |
| 57 | + devices with the saved queue state, resume. |
| 58 | + - Exit criterion: an idle deferred-main alpine template restores and answers |
| 59 | + an exec heartbeat. Measure restore latency (target ~10–150ms). |
| 60 | +2. **Phase B — virtio-fs inode-map persistence (~1–2w, the hard part)** |
| 61 | + - Serialize the passthrough server's nodeid→inode map (paths + generation), |
| 62 | + re-open on restore. Exit criterion: restored VM can read/write its rootfs |
| 63 | + and run `spawn-main` to completion with correct logs/exit code. |
| 64 | +3. **Phase C — box/pool integration (~1w)** |
| 65 | + - Template manager: boot idle template → snapshot → N×`krun_restore` children; |
| 66 | + re-stamp identity via the existing exec server (P2 spawn-main already gives |
| 67 | + the command path). Pool flag `--fork` falling back to full boots. |
| 68 | +4. **Phase D — hardening**: concurrent restores, snapshot invalidation |
| 69 | + (image digest + config + libkrun version), KSM interaction, aarch64 later. |
| 70 | + |
| 71 | +Estimated total: **4–6 weeks** (matches the survey's verdict), with Phase A |
| 72 | +delivering a measurable go/no-go checkpoint in ~2 weeks. |
| 73 | + |
| 74 | +## Risks |
| 75 | + |
| 76 | +| risk | mitigation | |
| 77 | +|---|---| |
| 78 | +| virtio-fs inode map (guest holds nodeids) | Phase B dedicated; fallback: restart the fs device + remount in guest via a guest-init hook (slower, uglier) | |
| 79 | +| `vm_memory` crate hides mmap flags | TEE guest_memfd path shows where to hook; worst case small vm-memory patch in-fork | |
| 80 | +| device quiesce atomicity | snapshot only IDLE deferred-main templates (enforced by the box side) | |
| 81 | +| fork maintenance burden | changes confined to fork; upstream rebases already our responsibility | |
0 commit comments