mirror of
https://github.com/Katakate/k7.git
synced 2026-09-21 17:43:20 +00:00
13fafe0997
Per-key node pins, k7d-fc pause/resume/exec, and HA-soak fixes. Playbook pins k7d 0.7.0. GitHub .deb, Launchpad PPA, and PyPI k7-sdk are 0.4.0.
431 lines
23 KiB
Markdown
431 lines
23 KiB
Markdown
# Performance
|
||
|
||
## Show HN cut — 2026-08-18 (apt `k7` 0.2.1, two-node, all backends)
|
||
|
||
Two Hetzner AX41-1-LTD boxes (k7-node-01 + k7-node-02). The third box
|
||
in the fleet was left untouched. Each node `TWO_DISK=1` (Ubuntu on one
|
||
NVMe, spare raw for the kfd thin-pool — NVMe names were **swapped**
|
||
across the two boxes, so auto-detect is mandatory).
|
||
|
||
Install path: `apt install k7` (PPA 0.2.1) on the first master, public
|
||
`Katakate/k7` v0.2.1 checkout as `k7_repo_root`, **one** command:
|
||
|
||
```bash
|
||
k7 install -i /root/k7-2n.ini --k7d-version 0.2.1
|
||
```
|
||
|
||
`failed=0` in 9m09s. Confirmed: PPA `k7` 0.2.1, PyPI `k7-sdk` 0.2.1,
|
||
`k7d` 0.2.1 on both nodes, RuntimeClasses `kata` / `kata-qemu` / `k7`,
|
||
thin-pool + `/var/lib/k7d/disks` on both. n=1 wall-clock via `k7 --core`
|
||
(create/resume/fork timed until an exec answers).
|
||
|
||
| Operation | kfd | kql | k7d |
|
||
|---|---:|---:|---:|
|
||
| create → exec answers | 3.74 s | 20.76 s | 3.83 s |
|
||
| pause (API) | 0.81 s | 0.82 s | 1.00 s |
|
||
| resume → exec answers | 4.48 s | 12.95 s | 1.95 s |
|
||
| fork → exec answers | n/a (rejected) | 83.30 s | **3.93 s** |
|
||
| fork inherits `/tmp` marker | n/a | no (disk-only clone) | **yes** (warm memory CoW) |
|
||
| sidecar create → `docker info` | 5.02 s | 21.02 s | 5.33 s |
|
||
| sidecar `docker run --rm hello-world` | 3.39 s | 4.01 s | 12.57 s |
|
||
|
||
kql fork is slower than the 2026-08-10 single-node r=1 median (~47 s)
|
||
because this cluster is two nodes / Longhorn r=2. k7d is the only backend
|
||
whose fork carries live memory. kfd has no fork by design. Docker-in-VM
|
||
sidecar (`--sidecar docker`) works on all three; k7d's `hello-world` pull
|
||
is the slow path (tmpfs + virtio + NAT), matching the 2026-08-10 note.
|
||
|
||
## Backend lifecycle: kql vs k7d — 2026-08-10
|
||
|
||
Hetzner AX41 dedicated node (Ryzen 5 3600, 64 GiB, NVMe), Ubuntu 24.04,
|
||
kernel 6.8.0-137, k3s v1.36.3, flannel CNI, Longhorn 1.10 (r=1), k7d 0.1.0.
|
||
Single node, interleaved runs, `alpine:3.20` sandboxes; median of 3
|
||
(sidecar legs n=1). Raw samples in the JSON the bench writes. Reproduce
|
||
on a node with both backends installed:
|
||
|
||
```bash
|
||
K7_BENCH_BACKENDS=kata-qemu-longhorn,k7d K7_BENCH_REPS=3 \
|
||
uv run pytest -m bench tests/integration/bench_backend_lifecycle.py -v -s
|
||
```
|
||
|
||
| Operation | kql (kata-qemu-longhorn) | k7d | notes |
|
||
|-----------|--------------------------|-----|-------|
|
||
| create → pod Ready | 17.11 s (15.05–17.13) | **2.13 s** (2.13–2.16) | cold boot; kql pays Longhorn PVC provision + QEMU boot |
|
||
| exec round-trip | **0.04 s** (0.03–0.04) | 0.12 s (0.12–0.13) | k7d exec bridges through vsock |
|
||
| named snapshot ready | 6.51 s (2.72–6.52) | n/a | Longhorn-only by design; k7d rejects `k7 snapshot` loudly |
|
||
| fork (API call) | 7.78 s (7.77–7.80) | 2.15 s (2.14–2.16) | kql: Longhorn snapshot + PVC clone; k7d: CoW disk+memory `fork_vm` |
|
||
| **fork → forked pod Ready + exec** | 46.66 s (46.45–46.67) | **2.37 s** (2.37–2.38) | **~20×**: kql cold-boots a VM on the cloned disk; the k7d fork *inherits the source's live memory* (tmpfs, processes, page cache) |
|
||
| pause effective | 1.31 s (1.29–1.90) | **0.20 s** (0.20) | kql: scale-to-0, pods terminated; k7d: vCPUs frozen in place |
|
||
| resume → exec answers | 4.11 s (2.10–9.19) | **0.34 s** (0.33–0.38) | kql: reschedules pod + VM boot; k7d: restart vCPU loop, memory intact |
|
||
| delete | 0.05 s | 0.04 s | |
|
||
| docker sidecar: create → `docker info` | 21.11 s | **8.60 s** | dind in the same VM (k7d) / pod (kql) |
|
||
| docker sidecar: `docker pull alpine:3.21` | **2.35 s** | 8.21 s | **kql wins**: its dind unpacks onto the Longhorn ext4 volume, k7d's dind data dir sits on guest tmpfs behind virtio + NAT — pull streaming is the k7d sidecar's slow path today |
|
||
| docker sidecar: `docker run --rm alpine echo` | 0.91 s | **0.33 s** | |
|
||
|
||
Honest summary: k7d dominates every lifecycle operation (create ~8×,
|
||
fork-to-usable ~20×, pause ~6×, resume ~12×) and is the only backend whose
|
||
fork carries **memory state** — the forked sandbox resumes mid-thought
|
||
instead of cold-booting. kql keeps two real advantages: named snapshots
|
||
that persist after the sandbox dies (restore later, GC, cross-pod
|
||
persistence) and faster registry pulls inside the docker sidecar. Plain
|
||
exec is also ~3× faster on kql (80 ms absolute difference; both are
|
||
interactive-fast).
|
||
|
||
## Kata-qemu-longhorn backend baseline (2026-04-10)
|
||
|
||
Observed latencies on a single Hetzner dedicated node (3x NVMe, Ubuntu 24.04, Longhorn replicas=1).
|
||
|
||
| Operation | Latency | Limit |
|
||
|-----------|---------|-------|
|
||
| Cold create to pod ready | 15.20s | — |
|
||
| Snapshot ready | 6.98s | < 60s |
|
||
| Pause (scale to 0) | 0.13s | — |
|
||
| Resume (scale to 1 + pod ready) | 4.54s | < 60s |
|
||
| **Fork (total)** | **44.85s** | < 120s |
|
||
| — snapshot | 2.97s | |
|
||
| — clone PVC bound | 2.71s | |
|
||
| — deployment ready (pod + VM boot) | ~39.17s | |
|
||
|
||
Fork is ~3x slower than a cold create. The snapshot and clone PVC steps add ~6s, but the main cost is the forked deployment's VM boot (~39s vs ~15s for a fresh PVC) — likely due to Longhorn replaying cloned data on first attach.
|
||
|
||
Measured via integration tests (`tests/integration/test_qemu.py`) on 2026-04-10.
|
||
|
||
## Docker workloads inside sandboxes — 2026-05-29
|
||
|
||
Hetzner AX52 dedicated node (Ryzen 7 7700, 64 GiB, NVMe), Ubuntu 24.04,
|
||
kernel 6.8.0-100, k3s v1.35.5, docker host 29.5.2 / sandbox 27.5.1,
|
||
Longhorn 1.10. 3 runs per cell after one warm-up; median (range in parens).
|
||
⚠ marks cells whose (max−min)/median > 0.30 (range noise gate).
|
||
|
||
Workload is `bench/docker-perf/bench.Dockerfile`: pull `debian:12-slim`, a
|
||
no-cache build that does `apt install build-essential python3 git ca-certificates`,
|
||
`pip install numpy pandas requests pytest httpx pydantic`, a 256 MB
|
||
`dd ... conv=fsync`, and `pip check`. Run phase exercises a 10-s CPU loop,
|
||
a 2 000-small-files + 512 MB-fsync IO workload, and a cat-the-venv-tree
|
||
read workload. Reproduce with:
|
||
|
||
```bash
|
||
K7_BENCH_ENVS=host,k7-fd,k7-ql-r1,k7-ql-r2 K7_BENCH_REPS=3 \
|
||
K7_BENCH_OUT=/tmp/bench-out \
|
||
uv run pytest -m bench tests/integration/bench_docker_perf.py
|
||
uv run python bench/docker-perf/render.py -i /tmp/bench-out/bench-results-*.csv \
|
||
--title "Docker workloads ..." --hardware "<one-line note>"
|
||
```
|
||
|
||
| Operation | host | k7-fd | k7-ql-r1 | k7-ql-r2 |
|
||
|---------------------------|------|-------|----------|----------|
|
||
| pull debian:12-slim | 3.06 s (3.00 s–3.06 s) | 2.78 s (2.77 s–2.83 s) | 3.66 s (3.62 s–3.67 s) | 3.64 s (3.62 s–3.81 s) |
|
||
| build (no-cache) | 53.2 s (53.2 s–53.8 s) | 26.0 s (25.0 s–30.2 s) | 72.3 s (72.0 s–72.8 s) | 68.7 s (68.6 s–71.6 s) |
|
||
| build (cached) | ⚠ 714 ms (710 ms–936 ms) | 250 ms (250 ms–291 ms) | ⚠ 690 ms (469 ms–4.49 s) | ⚠ 448 ms (436 ms–663 ms) |
|
||
| run cpu (10s budget) | 10.6 s (10.5 s–10.6 s) | 10.2 s (10.2 s–10.2 s) | 27.6 s (26.0 s–29.7 s) | 27.3 s (25.9 s–29.1 s) |
|
||
| run io (2k small + 512 MB)| ⚠ 1.14 s (1.08 s–1.68 s) | 485 ms (475 ms–593 ms) | 37.3 s (37.2 s–37.7 s) | 37.4 s (36.9 s–37.6 s) |
|
||
| run read (venv tree cat) | 992 ms (989 ms–1.02 s) | 225 ms (213 ms–235 ms) | 16.4 s (16.3 s–16.9 s) | 16.4 s (16.4 s–16.9 s) |
|
||
|
||
Ratios to host (the number that matters for the sandbox tax story):
|
||
|
||
| Cell | k7-fd / host | k7-ql-r1 / host | k7-ql-r2 / host |
|
||
|---------------------------|--------------|-----------------|-----------------|
|
||
| build (no-cache) ratio | 0.49× | 1.36× | 1.29× |
|
||
| run io ratio | 0.42× | 32.6× | 32.7× |
|
||
| run cpu ratio | 0.96× | 2.62× | 2.59× |
|
||
|
||
### What this says
|
||
|
||
**k7-fd (Firecracker, docker daemon on emptyDir) is *faster* than the
|
||
host** on every operation that touches disk — `build (no-cache)` 0.49×,
|
||
`run io` 0.42×, `run read` 0.23×. CPU is host-parity (0.96×).
|
||
|
||
Why faster, when the FD path strictly has more layers (Firecracker VM +
|
||
kata virtio-fs sharing the emptyDir from `/var/lib/kubelet/.../sidecar-data/`
|
||
into the guest, vs the host docker daemon going straight to ext4 on
|
||
`/dev/md2`)? Four contributing factors, in roughly decreasing order of
|
||
size:
|
||
|
||
1. **Storage driver delta.** The host docker daemon picks `overlayfs`
|
||
(the legacy single-layer overlay implementation that ships with the
|
||
host kernel). The FD sandbox's docker daemon picks `overlay2`. On
|
||
metadata-heavy workloads — apt extracts ~5 600 files, pip+venv
|
||
extracts another few thousand — overlay2 is significantly faster
|
||
than overlayfs (different layer-handling code path, better
|
||
d_type/userxattr behaviour). The whole "build (no-cache)" column is
|
||
apt+pip metadata churn, which is exactly where this difference bites.
|
||
2. **Clean cache state.** The FD sandbox starts with an empty
|
||
`/var/lib/docker`. The host docker daemon has accumulated state from
|
||
prior `k7 install` builds, k3s-imported images, and the bench's own
|
||
warm-up — manifest lookups, dangling layer GC, layer dedup all run
|
||
on a populated tree.
|
||
3. **virtio-fs writeback caching.** Kata-fc shares the emptyDir into
|
||
the guest via virtio-fs (the default for shared filesystem mounts;
|
||
the alternative virtio-blk would require an explicit pod annotation
|
||
for "direct block device" which we don't set). virtio-fs runs a
|
||
writeback cache in virtiofsd on the host. Inside the guest VM,
|
||
`dd if=/dev/zero of=/tmp/x bs=1M count=256 conv=fsync` measured
|
||
1.3 GB/s — the fsync completes when virtiofsd acks, not necessarily
|
||
when the data is durable on NVMe. This is a weaker fsync than the
|
||
host gets directly on ext4. It's the same trade-off `cache=writeback`
|
||
gets you in qemu/9pfs setups: faster, less crash-safe.
|
||
4. **Devmapper snapshotter on the VM rootfs.** The Firecracker VM's
|
||
rootfs is a thin-provisioned LVM volume (containerd devmapper
|
||
snapshotter). The `dd` and IO ops the bench runs go through the
|
||
sandbox container's rootfs (devmapper) when writing inside the
|
||
docker daemon's overlay2 upperdir, which itself is on the emptyDir
|
||
(virtio-fs). LVM thin pools are read-cached aggressively at the
|
||
page-cache layer — read-heavy ops like `run read` (cat the venv
|
||
tree) benefit from page cache hits on the *host* even when the
|
||
guest thinks it's doing fresh reads.
|
||
|
||
**Net of all of this:** k7-fd's 0.4–0.5× ratios *do* reflect a real
|
||
performance win for build/dev workloads on this hardware, but it would
|
||
be wrong to attribute the win to "VM is faster than bare metal".
|
||
Honest framing: *host docker is using a slow storage driver against a
|
||
populated daemon, and the FD sandbox is using a fast storage driver
|
||
against a clean daemon, with virtio-fs writeback caching softening the
|
||
guest's fsync semantics*. A cleaner future bench would (a) run host
|
||
docker with `--storage-driver=overlay2` and a fresh `/var/lib/docker`,
|
||
(b) note the virtio-fs cache mode explicitly, and (c) report the
|
||
device-level bandwidth so the absolute numbers are anchorable.
|
||
|
||
**k7-ql (qemu, docker daemon on a Longhorn PVC sub_path) pays a steep
|
||
storage tax** — `build (no-cache)` 1.3×, `run cpu` 2.6×, `run io` ~33×,
|
||
`run read` ~16×. Two things stacked here: (1) docker auto-picks the **vfs**
|
||
storage driver inside the qemu VM because Longhorn's iSCSI-attached block
|
||
device doesn't expose the filesystem features overlay2 wants; vfs copies
|
||
entire layer trees on every operation, so cached-build and image-layer
|
||
work get hit hard, and (2) reads/writes go disk → iSCSI → qemu virtio →
|
||
guest, where the host went directly to the page cache. The `run io`
|
||
column (~37 s for 2 000 small files plus a 512 MB fsync) is the worst-case
|
||
shape — pure disk-bound work with no compute.
|
||
|
||
**r=2 vs r=1 is statistically indistinguishable** (build 68.7 s vs 72.3 s,
|
||
io 37.4 s vs 37.3 s). The second Longhorn replica adds one cross-node
|
||
sync but the dominant cost on this hardware is vfs inside the guest, not
|
||
the replica copy on the wire. Useful negative result — picking r=2 for
|
||
durability does not double the cost on these workloads.
|
||
|
||
### Known confounds
|
||
|
||
- **Storage driver mismatch (the big one).** Host uses overlayfs; k7-fd
|
||
uses overlay2 on a virtio-fs-shared emptyDir; k7-ql uses vfs. Every
|
||
cross-env comparison is also a "different docker storage driver"
|
||
comparison. We cannot cleanly isolate "VM tax" from "driver tax"
|
||
without rebuilding the docker daemon image inside the guest with a
|
||
matching driver, which the spec deliberately doesn't attempt.
|
||
- **Asymmetric daemon state.** Host docker has accumulated images,
|
||
layers, and dangling refs from prior `k7 install` runs. Each sandbox
|
||
starts with a fresh daemon. We're partly measuring "warm vs cold
|
||
daemon".
|
||
- **virtio-fs writeback caching changes fsync semantics.** Guest fsync
|
||
acks when virtiofsd has the data, not when NVMe has it. Compare the
|
||
k7-fd dd fsync (1.3 GB/s) with the host's bare dd fsync (not
|
||
benchmarked here — would need a follow-up rep).
|
||
- **Build noise.** `build (cached)` is sub-second everywhere and lands
|
||
in the noise floor; the ⚠ flags on host and k7-ql-r1 are real
|
||
variance (4.5 s outlier on k7-ql-r1 rep 3) but not a signal about the
|
||
backend — they're "this op is too fast to time meaningfully with 3 reps."
|
||
- **Same physical disk for everything.** k7-fd's `/var/lib/docker`
|
||
emptyDir (which lives as a regular directory under
|
||
`/var/lib/kubelet/pods/.../volumes/kubernetes.io~empty-dir/` on the
|
||
host ext4 root), k7-ql's Longhorn PV, and the host's docker root all
|
||
live on the same `/dev/md2` (RAID1 NVMe pair). We're not measuring
|
||
cross-disk effects. Note: emptyDir without `medium: Memory` is *not*
|
||
tmpfs — it's a plain directory on the kubelet root FS.
|
||
- **Single-node sample.** Bench was driven on `k7-node-01` only. r=2's
|
||
cross-node sync went to `k7-node-02` but the workload pod stayed on
|
||
the primary. A future spec could pin pods to different nodes to also
|
||
exercise the read-from-remote-replica path.
|
||
|
||
Raw per-leg logs and the aggregated CSV are under `bench/docker-perf/results/`
|
||
(gitignored — keep them in agent or local scratch space, paste into the
|
||
table above when adding a new run).
|
||
|
||
## Docker-in-VM under real Longhorn replica counts (2026-08)
|
||
|
||
The "r=2 vs r=1 indistinguishable" result above is **invalid**: the old
|
||
bench patched a Longhorn *setting* that only affects newly-created volumes,
|
||
so both legs actually ran r=1 (CHALLENGES.md #2). `bench_docker_perf.py` now
|
||
sets real per-volume replica counts (`k7-ql-r3` leg). Host / kfd / r1 / r2
|
||
medians from the HA run (5 reps); **k7-ql-r3 column re-filled later** after
|
||
the virtiofsd wedge fix (5 reps, zero VM restarts):
|
||
|
||
| op | host | k7-fd | k7-ql-r1 | k7-ql-r2 | k7-ql-r3 |
|
||
|---|---|---|---|---|---|
|
||
| build no-cache | 76.4s | 52.5s | 108.2s | 270.9s | 292.5s |
|
||
| run io (2k files + 512MB fsync) | 1.43s | 1.02s | 41.4s | 68.4s | 88.3s |
|
||
| run read (venv tree cat) | — | — | — | — | 47.7s |
|
||
| run cpu (10s budget) | 10.5s | 10.4s | 41.5s | 47.8s | 56.4s |
|
||
|
||
Two takeaways:
|
||
|
||
- **The r1→r2 jump dominates the redundancy cost** (build 108→271s; r2→r3
|
||
adds only ~8% on build): the second replica forces synchronous
|
||
cross-node writes, the third mostly parallelizes with them. Post-wedge
|
||
r3 `run io` (88.3s) is higher than the old single-rep 64.5s sample —
|
||
that sample was the lucky survivor of a ~50% kill rate, not a median.
|
||
- **kql-r3 IO wedge — root-caused and FIXED:** the "VM exec path dies
|
||
after a run_io rep" wedge was not guest memory, not dockerd, and not
|
||
Longhorn faulting — the guest was healthy (load 0.4, 1.5 GB free, zero
|
||
dirty pages, clean dmesg) at the moment of death. The killer was the
|
||
**kata shim**: kata's default virtiofsd runs with
|
||
`--thread-pool-size=1`, so all virtio-fs IO (container rootfs + the
|
||
Longhorn-PVC-backed `/var/lib/docker`) serializes through one thread. A
|
||
`docker run` of the ~790 MB bench image makes vfs copy the whole rootfs
|
||
and then fsync 512 MB through that single thread against an r=3 volume
|
||
(~60–80 s saturated); any agent RPC touching virtio-fs blocks behind
|
||
it, the shim's agent health ping (`CheckRequest`) times out, and the
|
||
shim declares "Dead agent" and kills the healthy VM (`sandbox stopped
|
||
unexpectedly`, pod sandbox recreated). Repro rate was ~50% per run_io
|
||
rep. Fix: `k7 install` now sets `virtio_fs_extra_args =
|
||
["--thread-pool-size=16", ...]` in the kata-qemu config — we re-ran 5/5
|
||
`run_io` + 5/5 `run_read` with zero VM restarts on the same r=3 volume
|
||
(`run_read` median 47.7s). Exec-probe pressure was reduced too
|
||
(kubelet's default 1 s exec-probe timeout sprayed cancelled ttrpc execs
|
||
— `docker info` legitimately takes >1 s while dockerd copies vfs
|
||
layers), which cuts the `ttrpc: received message on inactive stream`
|
||
noise but was NOT sufficient on its own.
|
||
|
||
## k7d vs k7d-fc `--docker` overlay2 — 2026-09-10
|
||
|
||
Same node as the 08-10 lifecycle cut (k7-node-01 / Hetzner AX41, Ryzen 5
|
||
3600, 64 GiB, NVMe), Ubuntu 24.04, kernel 6.8.0-139, k3s v1.36.3, k7d
|
||
0.5.0 (locally built artifact, not the public GitHub tarball of the same
|
||
version string). Guests 3Gi / 4 CPU, `DOCKER_BUILDKIT=0`, graph on
|
||
virtio-blk scratch, `Storage Driver: overlay2` on the source **and** on
|
||
the forked child. 3 runs per cell after one warm-up; median (range).
|
||
⚠ marks `(max−min)/median > 0.30`.
|
||
|
||
```bash
|
||
K7_BENCH_ENVS=k7d,k7d-fc K7_BENCH_REPS=3 K7_BENCH_WARMUP=1 \
|
||
K7_BENCH_OUT=/tmp/bench-out \
|
||
uv run pytest -m bench tests/integration/bench_docker_perf.py::test_bench_k7d \
|
||
tests/integration/bench_docker_perf.py::test_bench_k7d_fc -v -s
|
||
uv run python bench/docker-perf/render.py -i /tmp/bench-out/bench-results-*.csv \
|
||
--title "k7d vs k7d-fc --docker overlay2" --hardware "<one-line note>"
|
||
```
|
||
|
||
| Operation | k7d | k7d-fc |
|
||
|---------------------------|-----|--------|
|
||
| pull debian:12-slim | 11.3 s (9.06 s–11.5 s) | ⚠ 8.16 s (7.24 s–11.2 s) |
|
||
| build (no-cache) | 52.0 s (49.8 s–53.2 s) | 51.6 s (50.4 s–52.5 s) |
|
||
| build (cached) | ⚠ 469 ms (269 ms–477 ms) | 470 ms (369 ms–471 ms) |
|
||
| run cpu (10s budget) | 10.8 s (10.7 s–10.8 s) | 10.7 s (10.6 s–10.7 s) |
|
||
| run io (2k small + 512 MB)| ⚠ 1.18 s (1.11 s–1.48 s) | 1.28 s (1.28 s–1.38 s) |
|
||
| run read (venv tree cat) | 774 ms (674 ms–789 ms) | 674 ms (670 ms–777 ms) |
|
||
| fork warm engine (API) | 6.23 s (n=1) | 8.35 s (n=1) |
|
||
| fork → Ready + overlay2 | 6.79 s (n=1) | 9.13 s (n=1) |
|
||
|
||
Forked-child log line: `# storage_driver_child=Storage Driver: overlay2` on
|
||
both backends (`TestDockerK7d` / `TestDockerK7dFc` also assert this).
|
||
|
||
k7d-fc is host-parity with k7d on the storage-bound ops (no-cache build
|
||
~52 s, `run_io` ~1.3 s, `run_read` ~0.7 s). That is the overlay2 /
|
||
virtio-blk number, not the vfs-shaped ~37 s `run_io` in the earlier
|
||
sidecar rows or the 4.24 s kql Longhorn-block overlay2 row. Warm-engine
|
||
fork is the k8s adopt path (not the ~5 ms VMM CoW floor); alpine
|
||
create→Ready / fork→exec on the same node this run were **2.15 s /
|
||
2.54 s** (k7d) and **2.14 s / 2.45 s** (k7d-fc), n=1.
|
||
|
||
### k7d vs k7d-fc lifecycle — 2026-09-14
|
||
|
||
Same 3-node HA cluster (k7-node-01 as the bench host: Hetzner AX41,
|
||
Ryzen 5 3600, 64 GiB, NVMe, Ubuntu 24.04, kernel 6.8.0-138, k3s
|
||
v1.36.4), k7d 0.6.0 built from the Firecracker jail-integrity branch
|
||
(not the public GitHub tarball of the same version string), Firecracker v1.16.2,
|
||
`alpine:3.20`, 3 reps interleaved, median. `resume → exec` is
|
||
`resume_sandbox()` until an `echo` exec answers; `pause` is the API
|
||
call until the VM is frozen.
|
||
|
||
```bash
|
||
K7_BENCH_BACKENDS=k7d,k7d-fc K7_BENCH_REPS=3 \
|
||
uv run pytest -m bench tests/integration/bench_backend_lifecycle.py -v -s
|
||
```
|
||
|
||
| Operation | k7d | k7d-fc |
|
||
|-----------|-----|--------|
|
||
| create → Ready | 2.39 s | 2.38 s |
|
||
| exec round-trip (median of 10) | 0.37 s | 0.37 s |
|
||
| fork call | 2.73 s | 2.66 s |
|
||
| fork → Ready + exec answers | 3.23 s | 3.15 s |
|
||
| pause effective | 0.35 s | 0.35 s |
|
||
| **resume → exec answers** | **0.67 s** (0.62–0.76) | **0.70 s** (0.69–0.72) |
|
||
| delete | 0.70 s | 0.66 s |
|
||
|
||
The earlier caveat on this section — k7-fc CRI exec after pause/resume
|
||
hung, do not quote a resume→exec number — is gone: the hang was
|
||
Firecracker v1.16.0/v1.16.1 gating vsock RX after a bare resume
|
||
(upstream #6100, fixed in v1.16.2), plus a shim exec bridge that left
|
||
kubelet's probe parked (k7d CHALLENGES #240 / #241; CHALLENGES #17
|
||
here).
|
||
|
||
## k7d `--docker` (first-class guest service)
|
||
|
||
`k7 create --docker --backend k7d ubuntu:24.04` stamps
|
||
`k7d.katakate.org/docker=true` (no sidecar, no privileged). Graph is a
|
||
per-sandbox virtio-blk scratch disk (overlay2), so `run io` is no longer
|
||
vfs/tmpfs-shaped. `k7 fork` of a warm engine is in-scope: the child keeps
|
||
dockerd and running inner containers.
|
||
|
||
Reproduce:
|
||
|
||
```bash
|
||
K7_BENCH_ENVS=k7d K7_BENCH_REPS=1 K7_BENCH_WARMUP=0 \
|
||
uv run pytest -m bench tests/integration/bench_docker_perf.py::test_bench_k7d -v -s
|
||
```
|
||
|
||
1-rep log from k7-node-01 (2026-09-07, k7d + guest docker payload). **Superseded**
|
||
by the 2026-09-10 k7d vs k7d-fc table above (full pull/build/cpu/io/read
|
||
column, child overlay2). Kept as the first overlay2 `run_io` datapoint:
|
||
|
||
| op | k7d `--docker` | notes |
|
||
|---|---|---|
|
||
| `run_io` | **1.24 s** | overlay2 on virtio-blk; 2k files + 512 MB fsync |
|
||
| `fork_warm_engine` | **6.26 s** | CoW of live dockerd (not the 100–300 ms memory-fork floor; includes k7 Deployment adopt) |
|
||
|
||
Historical sidecar/tmpfs numbers from 2026-08-11 are in the section below.
|
||
|
||
## kql `--docker` (overlay2 on Longhorn block)
|
||
|
||
`k7 create --docker --backend kql ubuntu:24.04` injects a privileged
|
||
`docker-vehicle` and a second Longhorn **Block** PVC (`<name>-docker-lh`).
|
||
The graph is ext4 on that device; dockerd is forced to overlay2. This is
|
||
the number that used to be vfs-shaped (~37 s `run_io` in the earlier
|
||
sidecar/subPath rows above). Do not treat a vfs `run_io` as the kql
|
||
`--docker` baseline.
|
||
|
||
Reproduce:
|
||
|
||
```bash
|
||
K7_BENCH_ENVS=k7-ql-r1 K7_BENCH_REPS=1 K7_BENCH_WARMUP=0 \
|
||
uv run pytest -m bench tests/integration/bench_docker_perf.py::test_bench_k7_ql_r1 -v -s
|
||
```
|
||
|
||
Numbers from this increment (<node-ip> / k7-node-01, 2026-09-10,
|
||
`K7_BENCH_ENVS=k7-ql-r1 K7_BENCH_REPS=1 K7_BENCH_WARMUP=0`,
|
||
`# docker_data_path=longhorn-block-overlay2`,
|
||
`Storage Driver: overlay2`, Docker 27.5.1, Longhorn r=1 on one node):
|
||
|
||
| op | kql `--docker` overlay2 / Longhorn block | notes |
|
||
|---|---|---|
|
||
| `run_io` | **4.24 s** | 2k files + 512 MB `dd conv=fsync`; overlay2 on Block PVC (not the ~37 s vfs row) |
|
||
|
||
## k7d docker-perf leg (2026-08-11, partial, **sidecar / tmpfs**, superseded)
|
||
|
||
`test_bench_k7d` is wired (`K7_BENCH_ENVS=k7d`). Guest sized at **3Gi /
|
||
4 vCPU** — largest size that reliably reaches Ready on this k7d build;
|
||
≥~4Gi fails agent connect (`Connection timed out`, MMIO base moves past
|
||
4 GiB). At 3Gi the guest tmpfs upper is ~1.5 GiB, which is enough for
|
||
`docker pull debian:12-slim` but not for a sustained no-cache build of
|
||
`bench.Dockerfile` (apt+pip layers already ~1.4 GiB before the cpython
|
||
clone / 256 MB `dd`).
|
||
|
||
Measured on the 3-node HA cluster (pod on k7-node-02), 3 reps, no warmup:
|
||
|
||
| op | k7d (3Gi) | notes |
|
||
|---|---|---|
|
||
| pull debian:12-slim | **9.99 s** (9.88–10.19) | overlay2; slow vs kql's ~2.4 s lifecycle-bench pull — same NAT/tmpfs path |
|
||
| build / run_* | n/a | hits ENOSPC / guest wedge mid-build under the 1.5 GiB tmpfs ceiling |
|
||
|
||
One-shot smoke (same limits, single `docker build --no-cache`) completed in
|
||
~302 s earlier the same day — reproducible multi-rep builds did not.
|
||
Unblocking the full column needs a k7d fix for ≥4 GiB guests (or a larger
|
||
non-tmpfs docker data disk).
|