QuantyKube

Two parts of Kubernetes, rewritten in Rust: a data store with an undo button, and a node runtime with no shim and no pause container per pod.

QuantyKube

Apache-2.0. Early: runs in CI against an unmodified kube-apiserver, nothing in production yet.

  • 15 MiB of memory idle, etcd 44
  • history kept an hour by default
  • a restore is an ordinary write
  • about 35,000 single-key reads a second, etcd about 19,000
  • no shim, no pause container
  • a pod running in 95 to 98 ms, containerd 223 to 236
  • 45 MB on disk after 500 ConfigMaps, etcd 124
  • the etcd v3 API the API server uses
  • Rust, no third-party crates
  • one kubectl apply, and back

An undo button for your cluster.

etcd numbers every change with a revision and throws old revisions away when the API server compacts, every five minutes. QuantyKube keeps them for a window you choose, an hour by default.

Inside that window you can list what happened to any object, read it as it was at a revision or a time, and put it back. Here is one ConfigMap, four revisions of its life. Scroll, or pick a revision.

ConfigMap

important

namespace
default
data.password
correct horse
stored
288 bytes

Revision 253: restored as of revision 243. The password is correct horse again.

The same, in a terminal

kubectl delete configmap importantconfigmap "important" deleted from default namespacekubectl quanty history configmap/default/important 218 2026-10-20 15:25:32Z create ... 220 bytes 243 2026-10-20 15:25:33Z update ... 288 bytes 251 2026-10-20 15:25:34Z delete ... 0 byteskubectl quanty restore --rev 243 configmap/default/importantrestored as of revision 243, now revision 253kubectl get configmap important -o jsonpath={.data.password}correct horse

A restore is an ordinary write.

It gets a new revision, so the API server's cache follows and every controller and watch sees the object return. Give --ago 5m instead of --rev for the state five minutes ago.

It works after the API server has compacted, for as long as --retain keeps history. The store keeps its data in the QuantyDB engine, where every commit is an immutable snapshot; the history itself lives in the change log.

kubectl quanty goes through the Kubernetes API, so RBAC decides who may read history and who may restore. It needs nothing but the kubeconfig kubectl already uses, and it is built for Windows too.

Next the node runtime

No shim, no pause container.

The other half is a node runtime: the program the kubelet starts containers through, where most clusters run containerd or CRI-O.

containerd starts a “pause” container in every pod, a process whose only job is to hold the pod's namespaces open, and runs each container through runc, a second binary, plus a shim process per pod.

QuantyKube holds the namespaces without a process. The network, IPC and UTS namespaces stay alive because their /proc/<pid>/ns/* files are bind mounted, and a container joins them with setns. Containers start inside the runtime itself. One process and one container start fewer per pod.

Not a new trick: CRI-O can drop its infra container the same way. A pod that asks for a shared PID namespace gets a small holder process, because a PID namespace dies with its first process.

A container start, step by step

  1. Image. overlayfs with the image's layers as lower directories and a fresh upper directory per container.
  2. clone3. New mount and PID namespaces, joining the pod's network, IPC and UTS namespaces.
  3. In the child. Mount /proc, /dev, /sys and the volumes, pivot_root, then capabilities, Docker's seccomp allowlist and no_new_privs, uid and gid, execve.
  4. In the runtime. The child goes into its cgroup v2 with the CPU, memory and pid limits the kubelet sent, its output into the log format the kubelet reads.

Every unsafe syscall sits behind one small crate. Pods get their network from the standard CNI plugins; the runtime runs them and does not reimplement them. Images are pulled over HTTPS with QuantyCore's own TLS, which nobody outside has reviewed yet: treat a pull as no safer than the network it crosses.

Next move a running cluster

One kubectl apply.

An operator moves a running kubeadm cluster from etcd onto the store, and labelled nodes onto the runtime. One patch moves it back. etcd and containerd keep running, untouched.

  1. Three servers and a laptop

    The demonstration cluster: cp runs the control plane on etcd, w1 and w2 are workers on containerd. Ubuntu 26.04 with cgroup v2, upstream Kubernetes v1.37.1 set up by kubeadm.

    The laptop runs Windows and holds only kubectl.exe, a kubeconfig and kubectl-quanty.exe. Nothing in the demonstration needs a shell on a server.

  2. Pick the nodes

    A label says which nodes get QuantyKube's runtime. Here, only w1.

    kubectl label node w1 install.quantyroot.io/runtime=quantykube
  3. Apply

    kubectl apply -f https://raw.githubusercontent.com/QuantyRoot/QuantyKube/<commit>/deploy/quantykube.yaml
    kubectl get installation quantykube -w

    One file: a namespace, the operator and an Installation that records every step it takes. The pods it starts on nodes are root there, as any runtime installer is, so read the file before you apply it.

  4. The store moves under the API server

    The operator moves the API server from etcd onto QuantyKube's store, with its revisions: every object keeps its resourceVersion. The API server is stopped while it happens. etcd keeps running underneath.

  5. w1: cordon, drain

    Then w1's kubelet, from containerd onto quantykube runtime. First the node is cordoned and drained, its kubelet stopped and its old pods cleared.

  6. w1: moved, Ready, uncordoned

    Ready on the new runtime, then uncordoned. kube-proxy and Flannel run on QuantyKube's runtime too, on the host's network. w2 has no label and stays on containerd, for comparison.

  7. And back

    kubectl patch installation quantykube --type merge -p '{"spec":{"undo":true}}'

    The undo copies what was written on the store back into etcd and puts w1 back on containerd. etcd and containerd kept running the whole time.

CI runs all of it on a one-node kubeadm cluster with the published image of each commit, and every piece on its own: the store as etcd from the start, every pod on the runtime, the store moved and back, the kubelet moved and back.

Move a cluster, the guide →

Next what it measures

Measured. Slower writes included.

Against etcd v3.6.15 on the same 2 vCPU machine, the same disk, the same kind of socket, with etcd's own benchmark tool. Every number can be reproduced with scripts/bench.sh; numbers that cannot are not published.

Reads of one key 16 clients, requests a second, more is better

QuantyKube 35,366

etcd 19,287

About 1.8 times. A read is a lookup in a copy on write snapshot with no lock held against writers. One tiny key: it measures the request path, not storage at scale.

Memory, idle with a k3s control plane on top, MiB, less is better

QuantyKube 15 MiB

etcd 44 MiB

A third to a half of etcd's, in every run. After the 16 client put run: 20.8 MiB at peak against 47.4.

On disk after 500 ConfigMaps created, listed and deleted through k3s, less is better

QuantyKube 45 MB

etcd 124 MB

Writes, one client requests a second, more is better

QuantyKube 982

etcd 1,392

Slower: about 70% of etcd's rate. QuantyDB commits with two fsyncs where etcd needs about one, and a lone writer has nobody to share them with.

Writes, 16 clients requests a second, more is better

QuantyKube 4,421

etcd 7,858

About 56%. Before group commit it was 20%. The store is now bound by CPU per write, mostly QuantyDB's commit work, and that is the next thing to profile.

On disk after 20,000 puts less is better

QuantyKube 169 MB

etcd 130 MB

Disk is a trade. Each commit writes new pages and keeps the old ones for the history window. Space is reused once history ages out; the file does not shrink, and etcdctl defrag is not supported.

In a Kubernetes control plane the stores are even.

The API server takes the same time to become ready on both, 3.3 to 3.4 seconds, and the same to apply and list 500 ConfigMaps.

The runtime, against containerd and runc

Pod sandbox, RunPodSandbox
37 to 39 ms containerd 107 to 109
Pod start, to container running
95 to 98 ms containerd 223 to 236
Memory for 10 pods
7 MiB, 1 process containerd and its shims 179 MiB, 11 processes
Sandbox start, median
3.6 to 4.2 ms runc 21.3 to 24.2

On the development machine, which makes these less than a claim: it has cgroup v1, so containerd put each pod into a cgroup and QuantyKube, which supports only v2, created none; runc applies AppArmor or SELinux labels where the host has them, and this one does not. The rented nodes run cgroup v2 and will measure both doing the same work.

Every table, every caveat →

Next see it live in Zurich

Watch it move a cluster, live.

When
17 November 2026
Where
Open Systems AG, Zurich
At
Cloud Native Computing Switzerland Meetup
After
Recorded and published on vshn.tv

The first public demonstration. A running kubeadm cluster moves onto QuantyKube with one kubectl apply, and back again.

Before that: a store that is killed with kill -9 in the middle of things and loses nothing, and a deleted ConfigMap that comes back while a watch looks on.

The commands are in the repository, in the order they will run. If something fails on stage, it gets said, and the recording plays.

The demo commands →

Next how far along it is

How far along each part is.

Early. Everything under “Runs in CI” is exercised there against the real etcdctl, an unmodified kube-apiserver, kubeadm, k3s and SIGKILL under load. Nothing has run in production.

Runs in CI

  • The etcd v3 subset kube-apiserver uses, watch with replay, leases
  • History beyond compaction: history, get, restore, and the kubectl plugin
  • Group commit: concurrent writes share fsyncs
  • Three store nodes on Raft under kube-apiserver, the leader killed
  • The store over a network with TLS and client certificates
  • The runtime under an unchanged kubelet: volumes, limits, stats, exec probes, CNI, image pulls
  • kubeadm on the store, every pod on the runtime, a cluster moved and back with one apply

Not yet

  • Streaming kubectl exec, attach and port-forward; pods that survive a runtime restart
  • A review of the TLS by anyone who did not write it
  • etcdctl defrag, and etcd clients beyond what the API server calls: this is not a general etcd replacement
  • Production. Any production.

The full status →

Next try it, read the docs

Try it.

Rust 1.89 or newer. Point the API server at the socket; with k3s, --datastore-endpoint does the same.

cargo build --release
target/release/quantykube serve \
  --data /var/lib/quantykube/db \
  --socket /run/quantykube.sock
kube-apiserver \
  --etcd-servers=unix:///run/quantykube.sock ...

The rest of the stack.

QuantyKube sits on top of the QuantyVerse: the store keeps its data in QuantyDB, its network and TLS are QuantyCore's. Every layer is Rust with no third-party crates, built by one person. Questions go to the person who builds it :3