Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

mikronova/tunnr-etcd

docs/rusternetes-on-tunnr.md

Rusternetes on tunnr: can tunnr be the installer for a Rusternetes node?

Findings from reading the tunnr source (tree at commit e364198, 2026-10-19) and the public Rusternetes README. This brief is written for an outside reviewer who has not seen either codebase.

1. Goal

We want an installer that boots from an ISO (or a cloud image), lays a Kubernetes node onto bare metal or a VM unattended, and comes up as a working Rusternetes cluster member. The question is whether our own appliance framework, tunnr, is the right base.

2. What Rusternetes is

3. What tunnr is

tunnr is our framework for minimal, distroless Linux appliances. Its rules:

3.1 The installer: crates/korp-installer (~49k lines, pure Rust)

This is the piece that matters for us.

It also handles multi-disk layouts (layout.rs, with rules for which disks become /data), mdraid RAID1, and UEFI plus legacy BIOS boot (korp-boot-bios). After writing, it re-reads the payload from the target disk and checks it against the hash pin and the ed25519 signature, and refuses the install on a mismatch. It narrates every step over serial (INSTALLER: …, INSTALL-OK).

Because exec replaces the process, the app becomes PID 1. The app's argv comes from korp.args= on the kernel cmdline or from the kernel -- tail.

3.2 Other parts of tunnr

3.3 Kernel build (in our sibling repo edda, crate nornir-build-thing)

Preset Base config Size
normal defconfig full
full defconfig full
slim tinyconfig small
nuked tinyconfig ~1.7 MB bzImage

Every preset gets a hardened (KSPP-style) profile on top. tunnr's appliance kernel uses nuked.

4. What "container support" in tunnr actually is

I looked for it specifically. tunnr has container images, but no container runtime.

Item What it really does Where
Appliance image A FROM scratch image with the static PID-1 as /init, content-addressed. It is a build descriptor, not something run under a runtime. crates/tunnr-build/src/desktop_project.rs
demo-container-appliance A PID-1 that installs "products" by writing systemd unit files and copying binaries from a bundle. It starts no OCI containers and uses no namespaces. payloads/demo-container-appliance/src/linux.rs
korp.container_backend= (youki, podman, …) Parsed and carried onto the installed disk, but no code reads it. It is an unfinished hook. korp-installer/src/cmdline.rs:118, live.rs:257
containers/ Containerfiles for build environments only (the kernel toolchain and RDP images). containers/*/Containerfile
nuked kernel CONFIG_NAMESPACES and CONFIG_MULTIUSER are deliberately off (MULTIUSER=n: nothing calls setuid or capset). Containers cannot run on it. edda/crates/nornir-build-thing/src/kernel_requires.rs:1114

5. How well they fit

Need What tunnr gives Gap
Unattended disk install, GPT/ESP/data, UEFI and BIOS, multi-disk, RAID1 Done and tested in KVM none
Signed, tamper-evident payload Done (blake3 + ed25519, checked at install and at every boot) none
Same image, different role per node NoCloud seed pattern (tunnr-demo) add a role field and k8s join data to the seed
WireGuard node mesh A WireGuard-first PID-1 exists optional, as an underlay
Persistent state (SQLite, images, volumes) P3 /data on XFS size it for container images
Container runtime none the main gap
Kernel with cgroups v2, namespaces, overlayfs, netfilter/iptables, veth, bridge, seccomp The presets exist, but nuked vetoes namespaces and multiuser a new k8s kernel variant, based on normal or slim, with those options declared via KconfigReq
PID-1 duties (reaping zombies, signals, mounts) appliance-pid1 crate (edda) Rusternetes would become PID 1 after fexecve, and it was not written for that
CoreDNS, CNI plugins, pause image none these are extra binaries and images, which conflicts with "one binary only"
iptables none (no userspace) kube-proxy needs iptables or nftables: a static binary, or rewrite the proxy to program netfilter over netlink directly

6. Options

A. Pure tunnr, with a Rust runtime built in (best fit with tunnr's rules)

  1. Ship the Rusternetes all-in-one binary (SQLite in /data) as the korp-start payload. Run it as the child of a small PID-1 wrapper that does the reaping and mounts, rather than as PID 1 itself.
  2. Replace the kubelet's bollard/Docker client with an in-process OCI runtime built on youki's libcontainer crate, and wire it to korp.container_backend=youki. Pulling images needs a registry client (for example the oci-client crate) and an overlayfs snapshotter.
  3. Add a k8s kernel variant with the needed options declared.
  4. Networking: a minimal built-in bridge + veth setup (no external CNI binaries). kube-proxy programs nftables over netlink (for example with the rustables crate) instead of shelling out to iptables.
  5. The NoCloud seed carries the node role, cluster CA and join token.

Cost: big. It means changing the Rusternetes kubelet, its networking and kube-proxy, effectively a fork.

B. tunnr installer + a Buildroot rootfs with Podman (fast, but breaks the rules) Keep korp-installer for the disk layout and trust chain. The P2 payload becomes a Buildroot rootfs with rootful Podman, iptables and CNI plugins, and Rusternetes runs unchanged against the Podman socket. tunnr already has a Buildroot road (variants/buildroot). Cost: small to medium. The price is breaking tunnr's "no general userspace" rule and losing the "one verified binary" property.

C. Hybrid Start with B to prove Rusternetes works on our installed disk and to check the conformance claims on our own hardware. Then move piece by piece to A (runtime first, then networking).

7. Risks

8. Questions for the reviewer

  1. Is A (Rust runtime inside, no daemons) worth the fork cost, or is C the sane path?
  2. Is youki's libcontainer mature enough to embed in a kubelet as a library rather than as a CLI?
  3. Pod networking without CNI binaries: is an in-process bridge + veth + nftables (over netlink) realistic for a conformance-passing node?
  4. Should the app run as PID 1 directly, or should there always be a supervising PID-1 in front of a complex app like this?
  5. For multi-node clusters: a tiny etcd-compatible store inside the appliance, or one external store?