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
- 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
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.
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
- Image. overlayfs with the image's layers as lower directories and a fresh upper directory per container.
clone3. New mount and PID namespaces, joining the pod's network, IPC and UTS namespaces.- In the child. Mount
/proc,/dev,/sysand the volumes,pivot_root, then capabilities, Docker's seccomp allowlist and no_new_privs, uid and gid,execve. - 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.
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.
-
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 andkubectl-quanty.exe. Nothing in the demonstration needs a shell on a server. -
Pick the nodes
A label says which nodes get QuantyKube's runtime. Here, only w1.
kubectl label node w1 install.quantyroot.io/runtime=quantykube -
Apply
kubectl apply -f https://raw.githubusercontent.com/QuantyRoot/QuantyKube/<commit>/deploy/quantykube.yaml kubectl get installation quantykube -wOne 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.
-
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.
-
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. -
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.
-
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.
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.
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.
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,attachandport-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.
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