feat: initial Firecracker snapshot orchestrator (fc-orch)

A "poor man's" Firecracker VM orchestrator that boots a single golden VM,
snapshots it, then restores N clone VMs from that snapshot with minimal
per-clone overhead.

How it works:
- `init`   — downloads a Linux 6.1 kernel and builds a minimal Alpine 3.20
             rootfs (512 MiB ext4) with a basic init script
- `golden` — boots the golden VM, lets it settle, then pauses and snapshots
             it (vmstate + memory file); the golden VMM is then terminated
             since only the artifacts are needed
- `spawn N` — restores N clone VMs concurrently from the golden snapshot:
               * rootfs: filesystem-level COW copy via `cp --reflink` (falls
                 back to a plain copy if reflinks are not supported)
               * memory: shared golden `mem` file; Firecracker's MAP_PRIVATE
                 lets the kernel handle COW page-by-page at no up-front cost
               * vmstate: small file, cheap regular copy per clone
               * networking: per-clone TAP device (fctapN) bridged to fcbr0
                 with iptables MASQUERADE NAT on the default route interface
- `status`  — reads PID files and checks /proc to report alive/dead clones
- `kill`    — stops in-memory clones, kills any stragglers via PID files,
              and tears down all fctap* devices
- `cleanup` — kill + remove all state dirs and the bridge

All tunables (binary path, base dir, kernel/rootfs paths, vCPUs, memory,
bridge name/CIDR) are configurable via environment variables.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-09 09:53:16 +02:00
commit 640bf5065a
8 changed files with 2061 additions and 0 deletions

54
orchestrator/config.go Normal file
View File

@@ -0,0 +1,54 @@
package orchestrator
import (
"os"
"strconv"
)
// Config holds all tunables for the orchestrator.
type Config struct {
FCBin string // path to firecracker binary
BaseDir string // working directory for all state
Kernel string // path to vmlinux
Rootfs string // path to base rootfs.ext4
VCPUs int64
MemMiB int64
Bridge string // host bridge name, or "none" to skip networking
BridgeCIDR string // e.g. "172.30.0.1/24"
GuestPrefix string // e.g. "172.30.0" — clones get .10, .11, ...
GuestGW string
BootArgs string
}
func DefaultConfig() Config {
c := Config{
FCBin: envOr("FC_BIN", "firecracker"),
BaseDir: envOr("FC_BASE_DIR", "/tmp/fc-orch"),
VCPUs: envOrInt("FC_VCPUS", 1),
MemMiB: envOrInt("FC_MEM_MIB", 128),
Bridge: envOr("FC_BRIDGE", "fcbr0"),
BridgeCIDR: envOr("FC_BRIDGE_CIDR", "172.30.0.1/24"),
GuestPrefix: envOr("FC_GUEST_PREFIX", "172.30.0"),
GuestGW: envOr("FC_GUEST_GW", "172.30.0.1"),
BootArgs: "console=ttyS0 reboot=k panic=1 pci=off i8042.noaux quiet loglevel=0",
}
c.Kernel = envOr("FC_KERNEL", c.BaseDir+"/vmlinux")
c.Rootfs = envOr("FC_ROOTFS", c.BaseDir+"/rootfs.ext4")
return c
}
func envOr(key, fallback string) string {
if v := os.Getenv(key); v != "" {
return v
}
return fallback
}
func envOrInt(key string, fallback int64) int64 {
if v := os.Getenv(key); v != "" {
if n, err := strconv.ParseInt(v, 10, 64); err == nil {
return n
}
}
return fallback
}