Files
k7/PERFORMANCE.md
G 13fafe0997 Release 0.4.0
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.
2026-09-19 23:22:14 +02:00

431 lines
23 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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.0517.13) | **2.13 s** (2.132.16) | cold boot; kql pays Longhorn PVC provision + QEMU boot |
| exec round-trip | **0.04 s** (0.030.04) | 0.12 s (0.120.13) | k7d exec bridges through vsock |
| named snapshot ready | 6.51 s (2.726.52) | n/a | Longhorn-only by design; k7d rejects `k7 snapshot` loudly |
| fork (API call) | 7.78 s (7.777.80) | 2.15 s (2.142.16) | kql: Longhorn snapshot + PVC clone; k7d: CoW disk+memory `fork_vm` |
| **fork → forked pod Ready + exec** | 46.66 s (46.4546.67) | **2.37 s** (2.372.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.291.90) | **0.20 s** (0.20) | kql: scale-to-0, pods terminated; k7d: vCPUs frozen in place |
| resume → exec answers | 4.11 s (2.109.19) | **0.34 s** (0.330.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 (maxmin)/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 s3.06 s) | 2.78 s (2.77 s2.83 s) | 3.66 s (3.62 s3.67 s) | 3.64 s (3.62 s3.81 s) |
| build (no-cache) | 53.2 s (53.2 s53.8 s) | 26.0 s (25.0 s30.2 s) | 72.3 s (72.0 s72.8 s) | 68.7 s (68.6 s71.6 s) |
| build (cached) | ⚠ 714 ms (710 ms936 ms) | 250 ms (250 ms291 ms) | ⚠ 690 ms (469 ms4.49 s) | ⚠ 448 ms (436 ms663 ms) |
| run cpu (10s budget) | 10.6 s (10.5 s10.6 s) | 10.2 s (10.2 s10.2 s) | 27.6 s (26.0 s29.7 s) | 27.3 s (25.9 s29.1 s) |
| run io (2k small + 512 MB)| ⚠ 1.14 s (1.08 s1.68 s) | 485 ms (475 ms593 ms) | 37.3 s (37.2 s37.7 s) | 37.4 s (36.9 s37.6 s) |
| run read (venv tree cat) | 992 ms (989 ms1.02 s) | 225 ms (213 ms235 ms) | 16.4 s (16.3 s16.9 s) | 16.4 s (16.4 s16.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.40.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
(~6080 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 `(maxmin)/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 s11.5 s) | ⚠ 8.16 s (7.24 s11.2 s) |
| build (no-cache) | 52.0 s (49.8 s53.2 s) | 51.6 s (50.4 s52.5 s) |
| build (cached) | ⚠ 469 ms (269 ms477 ms) | 470 ms (369 ms471 ms) |
| run cpu (10s budget) | 10.8 s (10.7 s10.8 s) | 10.7 s (10.6 s10.7 s) |
| run io (2k small + 512 MB)| ⚠ 1.18 s (1.11 s1.48 s) | 1.28 s (1.28 s1.38 s) |
| run read (venv tree cat) | 774 ms (674 ms789 ms) | 674 ms (670 ms777 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.620.76) | **0.70 s** (0.690.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 100300 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.8810.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).