kubernetes-sigs/agent-sandbox runs isolated, stateful agent workloads as Sandbox objects, with warm pools (SandboxWarmPool) and claims (SandboxClaim) on top. It picks its isolation through the pod’s runtimeClassName, and k7d plugs in there with no upstream change: every Sandbox pod on k7 or k7-fc is its own k7d microVM — the native in-process VMM, or stock Firecracker under the jailer (Backends).
Tested with agent-sandbox v1.0.5 (every image and manifest digest-pinned in utils/agent-sandbox/pins.env) on k7d main after 0.9.0 — the integration, and the kubectl port-forward support it needs, landed after the 0.9.0 tarball.
Node prerequisites#
- k3s, pinned:
sudo utils/install-k3s.sh(k3sv1.36.5+k3s1), or your own cluster. - k7d, the shim and RuntimeClasses
k7andk7-fc:sudo ./packaging/install.sh --with-k3sfrom the release tarball (Installation). On a K7 cluster,k7 install --backend k7d,k7d-fcdoes the same. - agent-sandbox, pinned and sha256-checked:
utils/agent-sandbox/install.shinstalls the controller, CRDs, extensions and the Gosandbox-router;utils/agent-sandbox/uninstall.shremoves all of it.
Then run the demo:
It creates a k7-fc warm pool and a claim, drives the upstream Python SDK through the router, and runs docker run hello-world inside a k7 sandbox.
Examples#
All in examples/agent-sandbox/. Every image is digest-pinned, and set CPU and memory limits — k7d sizes the VM from the container limits.
| File | What |
|---|---|
sandbox-k7.yaml | Bare Sandbox on k7: python-runtime, service: true, httpGet readiness probe |
template-warmpool-claim-k7-fc.yaml | SandboxTemplate (managed NetworkPolicy) + SandboxWarmPool (3) + SandboxClaim on k7-fc |
template-docker-k7.yaml | Docker-in-VM: in-guest dockerd via annotations, no privileged container |
sdk_smoke.py | Upstream SDK (k8s-agent-sandbox==1.0.5.post1, hash-pinned): claim, run, file write / read |
A bare Sandbox is an ordinary pod template with the RuntimeClass set:
A warm pool is a SandboxTemplate plus a SandboxWarmPool with replicas; a SandboxClaim adopts a booted VM from the pool without a restart (restartCount 0). networkPolicyManagement: Managed on the template gives the upstream default policy — ingress only from the sandbox-router, egress to public IPs only — and it is enforced on the pod IP of a k7d VM exactly as it would be for runc.
Docker-in-VM is two pod annotations on the template’s podTemplate; the workload keeps capabilities: {drop: [ALL]} and dockerd runs in the guest outside the container (Security — Docker as a guest service):
Support matrix#
From crates/k7d/tests/test_agent_sandbox.rs, single node, k3s v1.36.5+k3s1 (flannel, kube-router NetworkPolicy), 2026-10-07.
| # | agent-sandbox feature | k7 | k7-fc |
|---|---|---|---|
| V1 | Cold Sandbox → Ready, podIPs, serviceFQDN | ✅ | ✅ |
| V2 | Python SDK through sandbox-router (run, files) | ✅ | ✅ |
| V3 | kubectl port-forward (SDK tunnel mode) | ✅ | ✅ |
| V5 | Template + WarmPool(3) + Claim; adoption keeps the VM, restartCount 0 | ✅ | ✅ |
| V6 | Managed NetworkPolicy: public egress, private / link-local blocked, ingress only from the router | ✅ same as runc | ✅ same as runc |
| V7 | operatingMode: Suspended → Running: pod and VM gone, cold boot on resume | ✅ | ✅ |
| V7 | volumeClaimTemplates PVC data kept across suspend | ✅ (local-path via virtiofs) | ❌ refused loudly: “backend firecracker has no virtiofs” |
| V8 | shutdownTime + shutdownPolicy: Delete | ✅ | ✅ |
| V9 | httpGet readiness, exec liveness, startup probe under a burst of 4 | ✅ | ✅ |
| V10 | Projected SA token rotation reaches the guest | ❌ gap (below) | ❌ gap |
| V11 | Docker-in-VM: overlay2 on ext4, hello-world, not privileged, CapEff 0 | ✅ | ✅ |
| V13 | Out-of-band pause_vm: Ready flips False via the probe, back after resume | ✅ | ✅ |
| V14 | Upstream e2e TestRuntimeClassStartupComparison, TestRuntimeClassBurstRecovery | ✅ | ✅ |
| V14 | Upstream e2e TestRuntimeClassLifecycle | ⚠️ 1 of 3 runs: upstream test race | ✅ |
V4 (sandboxd + Go SDK) is not run: v1.0.5 publishes no sandboxd image. Warm-forking a Sandbox (V12) is the next increment; today a fork of a pod VM goes through the daemon API (fork_vm with pod_netns) or k7 fork on a K7 cluster.
Timings#
Intel Core Ultra 7 265 (20 threads), 62 GB, NVMe, Hetzner, single node, 2026-10-07, images already pulled:
| Path | k7 | k7-fc | runc |
|---|---|---|---|
Cold Sandbox → Ready, upstream TestRuntimeClassStartupComparison (no probe) | 0.84 s | 0.93 s | — |
Cold Sandbox → Ready, sandbox-k7.yaml (httpGet readiness, 5 s period) | 0.96–1.90 s | 5.91–7.17 s | — |
Warm SandboxClaim → Ready (pool of 3) | 0.05 s | 0.05 s | 0.05–0.06 s |
| Warm claim, upstream StartupComparison | 0.011 s | 0.011 s | — |
A warm claim adopts a running pool VM, so it costs the same as runc. With a readiness probe, cold start moves in steps of the probe period: k7-fc boots in about 1 s but misses the first probe.
Upstream TestRuntimeClassBurstRecovery with SANDBOX_POOL_SIZES=10,20,40 (two claims per pool slot, in batches):
| Pool | Backend | Pool fill | Warm / grey / cold claims | Worst start | All ready | Claims/s |
|---|---|---|---|---|---|---|
| 10 | k7 | 1.52 s | 19 / 1 / 0 of 20 | 1.08 s | 1.67 s | 12.0 |
| 20 | k7 | 2.97 s | 30 / 1 / 9 of 40 | 3.54 s | 4.07 s | 9.8 |
| 40 | k7 | 5.02 s | 70 / 1 / 9 of 80 | 6.21 s | 7.33 s | 10.9 |
| 10 | k7-fc | 2.37 s | 20 / 0 / 0 of 20 | 0.94 s | 1.53 s | 13.0 |
| 20 | k7-fc | 2.62 s | 30 / 9 / 1 of 40 | 2.85 s | 3.36 s | 11.9 |
| 40 | k7-fc | 5.12 s | 70 / 2 / 8 of 80 | 5.75 s | 6.89 s | 11.6 |
Known gaps#
- Kubelet volume updates do not reach the guest. ConfigMap, Secret, downwardAPI and projected volumes are packed into one read-only erofs image when the pod is created. kubelet rotates projected tokens and updates ConfigMaps on the host; the guest keeps the first copy. Use
automountServiceAccountToken: false(the examples do) or tokens whose lifetime covers the sandbox. The defaultkube-api-accesstoken stays valid for a year on clusters with--service-account-extend-token-expiration(the default), but it is never refreshed. k7-fchas no virtiofs.hostPathand local-path PVCs fail Create naming the backend. Usek7forvolumeClaimTemplateson local-path storage.
Related pages#
- RuntimeClass k7 — how the runtime is registered, annotation reference
- Feature matrix — everything else the host RuntimeClass supports
- Backends — choosing
k7vsk7-fc
