Skip to content

gVisor (runsc) Self-Host Install

Phase 3 Story S5 / Jira PB-54. Implements D4 (gVisor self-host; no GKE Sandbox dependency).

The compute Helm chart ships a DaemonSet (runsc-installer) that:

  1. Downloads and verifies runsc from the official gVisor release bucket.
  2. Installs it at /usr/local/bin/runsc on each K8s node.
  3. Appends the runsc runtime block to /etc/containerd/config.toml and restarts containerd.
  4. Registers a RuntimeClass named gvisor cluster-wide (handler: runsc).

Sandbox pods (S6/PB-55) reference this RuntimeClass via runtimeClassName: gvisor in their pod spec.

  • Kubernetes 1.26+ (RuntimeClass node.k8s.io/v1 GA since 1.20).
  • containerd 1.5+ as the CRI (the default on GKE standard, EKS, AKS, and kind).
  • Nodes must have wget, sha512sum, and nsenter available in the installer image. The default busybox:stable-glibc satisfies all three.
  • The DaemonSet pod runs as privileged: true to write to /usr/local/bin and restart containerd. This is intentional and necessary — gVisor node setup has no unprivileged install path.
gvisor:
enabled: true # set false to skip DaemonSet + RuntimeClass
version: "20240212" # gVisor release date tag; update to pin a new version
installer:
image:
repository: busybox
tag: stable-glibc
pullPolicy: IfNotPresent
nodeSelector: {} # empty = all nodes
tolerations: []
scheduling: {} # passed into RuntimeClass.scheduling; empty = unconstrained
Section titled “Scoping to a node pool (recommended for production)”
gvisor:
nodeSelector:
cloud.google.com/gke-nodepool: sandbox-pool
tolerations:
- key: sandbox
operator: Equal
value: "true"
effect: NoSchedule
scheduling:
nodeSelector:
cloud.google.com/gke-nodepool: sandbox-pool
tolerations:
- key: sandbox
operator: Equal
value: "true"
effect: NoSchedule

When scheduling is set on the RuntimeClass, the K8s scheduler automatically places pods with runtimeClassName: gvisor onto nodes that match those constraints — no per-pod nodeSelector needed on sandbox pods.

kind uses containerd. The DaemonSet installs runsc successfully on kind nodes.

Caveats:

  • kind nodes are Docker containers; nsenter --mount=/proc/1/ns/mnt resolves to the containerd process inside the kind node container, which is correct.
  • After helm install, wait ~60 seconds for the DaemonSet init containers to complete before scheduling the smoke pod.
  • Verified against kind v0.22+ with K8s 1.29.

GCP standard-rwo (CI target — locked 2026-05-22)

Section titled “GCP standard-rwo (CI target — locked 2026-05-22)”

GKE standard nodes run containerd. The DaemonSet installs runsc in the same way as kind.

No GKE Sandbox (--sandbox-type=gvisor) is used — the chart self-installs runsc, which is the D4 cloud-agnostic constraint.

StorageClass used for workspace PVCs: standard-rwo (GCP Regional PD, ReadWriteOnce). Set pvc.storageClassName: standard-rwo in values for GCP deployments (S6/PB-55).

Secrets required for GCP CI:

  • GCP_SA_KEY — service account JSON with GKE cluster admin + storage permissions.
  • GCP_PROJECT_ID — GCP project ID hosting the cluster.

If these secrets are absent from the repository, the gvisor-smoke / gcp-standard-rwo CI matrix leg is skipped with an explicit warning (see .github/workflows/gvisor-smoke.yml).

Deferred to Phase 6 hardening per ADR-0015 + design doc §10 row S5. AWS EKS uses containerd; the DaemonSet approach is identical. StorageClass would be gp3.

The init containers run sequentially. Init:0/2 means neither has finished.

Terminal window
kubectl logs -n paper-board <runsc-installer-pod> -c install-runsc
kubectl logs -n paper-board <runsc-installer-pod> -c configure-containerd

Common causes:

  • Network unreachable — storage.googleapis.com blocked. On air-gap clusters, host the gVisor binary in an internal mirror and override gvisor.version + the download URL (requires chart modification).
  • SHA512 mismatch — corrupt download. Re-run by deleting the pod; DaemonSet recreates it.
  • /etc/containerd/config.toml missing — add a type: FileOrCreate hostPath for the containerd config dir to auto-create the file.
Terminal window
kubectl get runtimeclass gvisor

If missing, check helm/compute/templates/runtime-class.yaml is rendered:

Terminal window
helm template compute ./helm/compute --set gvisor.enabled=true | grep -A5 RuntimeClass

The DaemonSet pods may not have finished their init containers yet. Wait and re-check:

Terminal window
kubectl get ds -n paper-board -w

Smoke pod fails: dmesg: read kernel buffer failed: Operation not permitted

Section titled “Smoke pod fails: dmesg: read kernel buffer failed: Operation not permitted”

gVisor restricts some syscalls. dmesg works inside gVisor because gVisor provides its own kernel ring buffer. If you see this error, the pod is running on a real Linux kernel (not gVisor) — verify runtimeClassName: gvisor is set on the pod spec and the node is in the RuntimeClass scheduling set.

containerd not restarted after config change

Section titled “containerd not restarted after config change”

The configure-containerd init container sends SIGHUP to PID 1 on the host as a fallback when systemctl is unavailable (e.g., in kind containers). If containerd still does not load the runsc plugin:

Terminal window
# On the node:
nsenter --mount=/proc/1/ns/mnt -- systemctl restart containerd

Verifying the runtime class on a running pod

Section titled “Verifying the runtime class on a running pod”
Terminal window
kubectl get pod <sandbox-pod> -o jsonpath='{.spec.runtimeClassName}'
# expected output: gvisor
kubectl describe pod <sandbox-pod> | grep "Runtime Class"
# expected: Runtime Class: gvisor
  1. Update gvisor.version in values.yaml to the new release date tag.
  2. Bump helm/compute/Chart.yaml version.
  3. helm upgrade compute ./helm/compute — DaemonSet rolls out, init container detects the version mismatch and re-installs runsc on each node. Containerd is restarted.
  4. Drain sandbox pods from the node before the DaemonSet pod reaches that node if zero- downtime is required (Phase 6 hardening concern).