mirror of
https://github.com/imjasonh/terraform-playground
synced 2026-07-18 15:08:24 +00:00
- Add scripts/ for downloading official Firecracker and building virtio-fs-enabled firecracker + vmlinux via devtool - Expand README with Quickstart, prerequisites, and concrete paths - Ignore .deps/ build artifacts Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
326 lines
13 KiB
Markdown
326 lines
13 KiB
Markdown
# firecracker-container-vm
|
|
|
|
Example **Firecracker VM runner** that uses a container image reference as the guest root filesystem, loading files **on demand** at runtime over HTTP range requests into gzip-compressed OCI layers.
|
|
|
|
This implements the lazy-pull approach described in [dagdotdev registry explorer](https://github.com/jonjohnsonjr/dagdotdev/blob/main/pkg/explore/README.md):
|
|
|
|
1. Build a **gzip zran-style index** (`indexed_deflate`) over each `tar+gzip` layer.
|
|
2. Record a **tar table of contents** mapping paths to uncompressed byte offsets.
|
|
3. On file read, fetch only the **compressed byte range** needed from the registry, seek in the gzip stream, and return file bytes.
|
|
4. Serve the merged overlay via **virtio-fs** using [`vhost-user-backend`](https://crates.io/crates/vhost-user-backend) + [`fuse-backend-rs`](https://crates.io/crates/fuse-backend-rs).
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────────┐ vhost-user UDS ┌──────────────────────┐
|
|
│ Firecracker (guest) │ ◄──────────────────────► │ fc-vhostfsd │
|
|
│ virtio-fs driver │ │ vhost-user-backend │
|
|
└─────────────────────┘ │ fuse-backend-rs │
|
|
│ OverlayFs (lazy) │
|
|
└──────────┬───────────┘
|
|
│ HTTP Range
|
|
▼
|
|
┌──────────────────────┐
|
|
│ OCI registry blobs │
|
|
│ (tar+gzip layers) │
|
|
└──────────────────────┘
|
|
```
|
|
|
|
### Crates
|
|
|
|
| Crate | Role |
|
|
|-------|------|
|
|
| `fc-oci-fs` | Registry client, gzip index, tar TOC, overlay `FileSystem` |
|
|
| `fc-vhostfsd` | vhost-user virtio-fs daemon |
|
|
| `fc-runner` | Spawns `fc-vhostfsd` and configures Firecracker |
|
|
|
|
## Prerequisites
|
|
|
|
| Requirement | Notes |
|
|
|-------------|-------|
|
|
| Rust 1.96+ | `source /usr/local/cargo/env` in the devcontainer |
|
|
| `/dev/kvm` read/write | `[ -w /dev/kvm ] && echo OK` — add yourself to the `kvm` group if needed |
|
|
| Docker config (optional) | `~/.docker/config.json` for private registries |
|
|
| Host tools for VM path | `git`, `curl`, `docker`, `sudo` (kernel + Firecracker builds use Firecracker's `devtool`) |
|
|
|
|
**Important:** virtio-fs needs two things that official Firecracker release artifacts do not provide out of the box:
|
|
|
|
1. A **guest `vmlinux` with `CONFIG_VIRTIO_FS=y`** — the CI kernels from S3 do not enable virtio-fs.
|
|
2. A **Firecracker binary with generic vhost-user** ([PR #5773](https://github.com/firecracker-microvm/firecracker/pull/5773)) — release builds only expose vhost-user **block**, not virtio-fs.
|
|
|
|
The scripts under `scripts/` automate both builds into `.deps/`.
|
|
|
|
## Build this project
|
|
|
|
```bash
|
|
source /usr/local/cargo/env
|
|
cd firecracker-container-vm
|
|
cargo build --release
|
|
```
|
|
|
|
## Quickstart (full path)
|
|
|
|
### 1. Host dependencies (Firecracker + vmlinux)
|
|
|
|
One-shot (builds from source; needs Docker and sudo):
|
|
|
|
```bash
|
|
cd firecracker-container-vm
|
|
./scripts/setup-host.sh
|
|
```
|
|
|
|
Or step by step:
|
|
|
|
```bash
|
|
export DEPS_DIR="$PWD/.deps"
|
|
|
|
# Firecracker with PUT /vhost-user-devices/{id} (PR #5773 branch)
|
|
./scripts/build-firecracker-virtiofs.sh
|
|
|
|
# Guest kernel with virtio-fs enabled (patches Firecracker's 6.1 CI config)
|
|
./scripts/build-vmlinux-virtiofs.sh
|
|
```
|
|
|
|
To download a **stock** Firecracker release only (no virtio-fs frontend):
|
|
|
|
```bash
|
|
./scripts/download-firecracker.sh # installs .deps/firecracker
|
|
```
|
|
|
|
### 2. Verify KVM
|
|
|
|
```bash
|
|
[ -r /dev/kvm ] && [ -w /dev/kvm ] && echo "KVM OK" || echo "Fix KVM access first"
|
|
```
|
|
|
|
### 3. Run the lazy rootfs daemon
|
|
|
|
```bash
|
|
./target/release/fc-vhostfsd \
|
|
--image docker.io/library/alpine:3.20 \
|
|
--socket /tmp/fc-vhostfs.sock \
|
|
--cache-dir /tmp/fc-oci-cache \
|
|
--tag rootfs \
|
|
--metrics-addr 127.0.0.1:9100
|
|
```
|
|
|
|
In another terminal, confirm metrics and that the socket exists:
|
|
|
|
```bash
|
|
curl -s localhost:9100/metrics | rg '^fc_startup_ready_milliseconds'
|
|
ls -l /tmp/fc-vhostfs.sock
|
|
```
|
|
|
|
### 4. Boot Firecracker with the container image as rootfs
|
|
|
|
```bash
|
|
./target/release/fc-runner \
|
|
--image docker.io/library/alpine:3.20 \
|
|
--firecracker "$PWD/.deps/firecracker-virtiofs" \
|
|
--kernel "$PWD/.deps/vmlinux-virtiofs" \
|
|
--cache-dir /tmp/fc-oci-cache \
|
|
--vhost-socket /tmp/fc-vhostfs.sock \
|
|
--tag rootfs \
|
|
--memory-mib 512
|
|
```
|
|
|
|
Use `--dry-run` to print the Firecracker API payload without starting the VM (still starts `fc-vhostfsd`).
|
|
|
|
Guest cmdline (set by `fc-runner`):
|
|
|
|
```
|
|
console=ttyS0 reboot=k panic=1 pci=off init=/bin/sh rootfstype=virtiofs root=/ root=rootfs
|
|
```
|
|
|
|
Alpine provides `/bin/sh` on the merged root. You should get a shell on the serial console when the VM starts.
|
|
|
|
### 5. Smoke test without a VM (daemon only)
|
|
|
|
If you only want to validate lazy pulls + vhost-user handshake:
|
|
|
|
```bash
|
|
cargo test
|
|
# vhost-user protocol test against a local fixture layer (no KVM/registry)
|
|
cargo test -p fc-runner --test vhost_e2e
|
|
```
|
|
|
|
## Run the vhost-fs daemon (reference)
|
|
|
|
```bash
|
|
./target/release/fc-vhostfsd \
|
|
--image docker.io/library/alpine:3.20 \
|
|
--socket /tmp/fc-vhostfs.sock \
|
|
--cache-dir /tmp/fc-oci-cache \
|
|
--tag rootfs
|
|
```
|
|
|
|
On first access to a layer, the daemon downloads the blob (if not cached), builds a gzip index + tar TOC, then serves files via range reads.
|
|
|
|
### Registry authentication
|
|
|
|
`fc-vhostfsd` / `fc-oci-fs` read credentials the same way the Docker CLI does:
|
|
|
|
- `~/.docker/config.json` (or `$DOCKER_CONFIG/config.json`)
|
|
- `auths` entries (inline base64 user/pass)
|
|
- per-registry `credHelpers`
|
|
- global `credsStore` (e.g. `docker-credential-gcr`, `osxkeychain`, `pass`)
|
|
|
|
Run `docker login` (or your cloud provider's helper) once; private images should work without extra flags.
|
|
|
|
## Metrics
|
|
|
|
`fc-vhostfsd` exposes Prometheus text on `http://127.0.0.1:9100/metrics` by default (`--metrics-addr`).
|
|
|
|
| Metric | Meaning |
|
|
|--------|---------|
|
|
| `fc_bytes_fetched_from_registry` | Compressed bytes actually pulled from the registry |
|
|
| `fc_bytes_saved_vs_full_pull` | `sum(layer sizes) - fetched` (data you did **not** download vs a full `docker pull`) |
|
|
| `fc_layer_compressed_bytes_total` | Total compressed layer bytes in the resolved image |
|
|
| `fc_registry_range_requests_total` | HTTP range requests to blob URLs |
|
|
| `fc_full_blob_downloads_total` | Layers fully copied to local cache (first-time index build) |
|
|
| `fc_gzip_index_builds_total` | Gzip index builds |
|
|
| `fc_fuse_requests_total` / `fc_fuse_reads_total` | virtio-fs / FUSE traffic |
|
|
| `fc_startup_ready_milliseconds` | Image open → vhost socket listening |
|
|
| `fc_process_rss_bytes` | Daemon resident memory |
|
|
| `fc_cache_dir_bytes_on_disk` | Local cache size (blobs + indexes) |
|
|
|
|
```bash
|
|
curl -s localhost:9100/metrics | rg '^fc_'
|
|
```
|
|
|
|
## Which VMM can use this? (virtio-fs frontends)
|
|
|
|
`fc-vhostfsd` is a **vhost-user backend**. Something in the VMM must act as the **vhost-user frontend** and connect to `--socket`. Your options:
|
|
|
|
| Frontend | Status | Notes |
|
|
|----------|--------|-------|
|
|
| **Firecracker** (generic vhost-user) | Needs recent build | [PR #5773](https://github.com/firecracker-microvm/firecracker/pull/5773) adds `PUT /vhost-user-devices/{id}` so virtio-fs works without native Firecracker virtio-fs code. `fc-runner` targets this API. |
|
|
| **Cloud Hypervisor** | Works today | First-class virtio-fs + vhost-user; point `--socket` at the same path. No Firecracker-specific API. |
|
|
| **QEMU** | Works today | `virtiofsd` / custom daemon via `-chardev socket` + `vhost-user-fs-pci` device. |
|
|
| **crosvm** | Works today | Can run vhost-user fs backends against a virtio-fs device. |
|
|
| **Stock Firecracker (released)** | No virtio-fs | Only block/net/vsock unless you build from the generic vhost-user branch. |
|
|
|
|
**You do not need to change `fc-vhostfsd` between these** — only the VMM configuration differs. `fc-runner` is Firecracker-specific; for Cloud Hypervisor or QEMU, run `fc-vhostfsd` manually and wire the socket in that VMM's config.
|
|
|
|
## Guest kernel (`vmlinux`)
|
|
|
|
You need a **Linux guest kernel with virtio-fs over virtio-mmio** (Firecracker's bus).
|
|
|
|
### Do not use the stock CI vmlinux for this example
|
|
|
|
Firecracker's [getting-started guide](https://github.com/firecracker-microvm/firecracker/blob/main/docs/getting-started.md) downloads kernels from S3:
|
|
|
|
```bash
|
|
ARCH="$(uname -m)"
|
|
release_url="https://github.com/firecracker-microvm/firecracker/releases"
|
|
latest_version=$(basename "$(curl -fsSLI -o /dev/null -w '%{url_effective}' "${release_url}/latest")")
|
|
CI_VERSION=${latest_version%.*}
|
|
latest_kernel_key=$(curl "http://spec.ccfc.min.s3.amazonaws.com/?prefix=firecracker-ci/$CI_VERSION/$ARCH/vmlinux-&list-type=2" \
|
|
| grep -oP "(?<=<Key>)(firecracker-ci/$CI_VERSION/$ARCH/vmlinux-[0-9]+\.[0-9]+\.[0-9]{1,3})(?=</Key>)" \
|
|
| sort -V | tail -1)
|
|
wget -O vmlinux-ci "https://s3.amazonaws.com/spec.ccfc.min/${latest_kernel_key}"
|
|
```
|
|
|
|
That kernel is fine for block-device rootfs smoke tests, but its config has `# CONFIG_VIRTIO_FS is not set`. Use `./scripts/build-vmlinux-virtiofs.sh` instead.
|
|
|
|
### Recommended: build virtio-fs-enabled vmlinux
|
|
|
|
```bash
|
|
./scripts/build-vmlinux-virtiofs.sh
|
|
# output: .deps/vmlinux-virtiofs
|
|
```
|
|
|
|
This clones Firecracker, enables `CONFIG_VIRTIO_FS=y` in the `6.1` CI guest config, and runs `./tools/devtool build_ci_artifacts kernels 6.1`.
|
|
|
|
Minimum kernel options (already present in Firecracker CI configs except virtio-fs):
|
|
|
|
- `CONFIG_VIRTIO_MMIO=y`
|
|
- `CONFIG_VIRTIO_FS=y`
|
|
- `CONFIG_FUSE_FS=y`
|
|
- `CONFIG_SERIAL_8250_CONSOLE=y` for `console=ttyS0`
|
|
|
|
Manual build details: [Firecracker rootfs and kernel setup](https://github.com/firecracker-microvm/firecracker/blob/main/docs/rootfs-and-kernel-setup.md).
|
|
|
|
## Firecracker binary
|
|
|
|
### For virtio-fs (`fc-runner`)
|
|
|
|
Released binaries from [GitHub releases](https://github.com/firecracker-microvm/firecracker/releases) do **not** include `PUT /vhost-user-devices/{id}` yet. Build from PR #5773:
|
|
|
|
```bash
|
|
./scripts/build-firecracker-virtiofs.sh
|
|
# output: .deps/firecracker-virtiofs
|
|
```
|
|
|
|
### Download latest official release (block/net only)
|
|
|
|
```bash
|
|
./scripts/download-firecracker.sh
|
|
# output: .deps/firecracker
|
|
curl -fsSL https://github.com/firecracker-microvm/firecracker/releases/latest/download/firecracker-$(curl -fsSLI -o /dev/null -w '%{url_effective}' https://github.com/firecracker-microvm/firecracker/releases/latest | xargs basename)-$(uname -m).tgz | tar -xz
|
|
```
|
|
|
|
Or manually (from upstream docs):
|
|
|
|
```bash
|
|
ARCH="$(uname -m)"
|
|
release_url="https://github.com/firecracker-microvm/firecracker/releases"
|
|
latest=$(basename "$(curl -fsSLI -o /dev/null -w '%{url_effective}' "${release_url}/latest")")
|
|
curl -fsSL "${release_url}/download/${latest}/firecracker-${latest}-${ARCH}.tgz" | tar -xz
|
|
mv "release-${latest}-${ARCH}/firecracker-${latest}-${ARCH}" firecracker
|
|
```
|
|
|
|
## Run the Firecracker example (reference)
|
|
|
|
```bash
|
|
./target/release/fc-runner \
|
|
--image docker.io/library/alpine:3.20 \
|
|
--firecracker "$PWD/.deps/firecracker-virtiofs" \
|
|
--kernel "$PWD/.deps/vmlinux-virtiofs" \
|
|
--cache-dir /tmp/fc-oci-cache \
|
|
--dry-run
|
|
```
|
|
|
|
Without `--dry-run`, `fc-runner` configures Firecracker via its HTTP API:
|
|
|
|
- `PUT /boot-source`
|
|
- `PUT /machine-config`
|
|
- `PUT /vhost-user-devices/rootfs` (virtio-fs via generic vhost-user frontend)
|
|
- `PUT /actions` (`InstanceStart`)
|
|
|
|
Guest kernel cmdline includes `rootfstype=virtiofs root=/ root=rootfs`.
|
|
|
|
## Tests
|
|
|
|
```bash
|
|
cargo test
|
|
```
|
|
|
|
Integration test `vhost_user_virtiofs_daemon_handshake` verifies the vhost-user protocol end-to-end against a local fixture layer (no registry/KVM required).
|
|
|
|
## Benchmarks
|
|
|
|
```bash
|
|
cargo bench -p fc-oci-fs
|
|
```
|
|
|
|
Benchmarks build a synthetic `tar.gz` layer with hundreds of files and measure:
|
|
|
|
- **read_last_file_1kb** — random read of the last tar entry (exercises gzip seek + range-style decompression)
|
|
- **lookup_opt_data_file** — FUSE lookup through the overlay
|
|
|
|
## Design notes & limitations (example code)
|
|
|
|
This is an **example** implementation, not production hardened:
|
|
|
|
- **Read-only** rootfs; no whiteout/opaque-dir completeness guarantees beyond basic `.wh.*` handling
|
|
- **Single-platform** manifests only (no OCI index platform selection)
|
|
- Layer blobs are cached to disk on first full fetch; subsequent reads use the on-disk gzip index
|
|
- `fc-runner` is Firecracker-specific; other VMMs run `fc-vhostfsd` directly (see table above)
|
|
|
|
## References
|
|
|
|
- [dagdotdev explore README](https://github.com/jonjohnsonjr/dagdotdev/blob/main/pkg/explore/README.md) — gzip zran / range request strategy
|
|
- [`indexed_deflate`](https://crates.io/crates/indexed_deflate) — gzip random access indexes
|
|
- [`vhost-user-backend`](https://crates.io/crates/vhost-user-backend) — Rust vhost-user daemon framework
|
|
- [Firecracker generic vhost-user](https://github.com/firecracker-microvm/firecracker/pull/5773)
|