πŸ“šBook Signing at KubeCon EU 2026Meet us at Booking.com HQ (Mon 18:30-21:00) & vCluster booth #521 (Tue 24 Mar, 12:30-1:30pm) β€” free book giveaway!RSVP Booking.com Event
Security intermediate ⏱ 15 minutes K8s 1.28+

Kubernetes gVisor and Kata Containers RuntimeClass

Deploy sandboxed container runtimes on Kubernetes using RuntimeClass with gVisor (runsc) and Kata Containers. Isolate untrusted workloads with kernel-level

By Luca Berton β€’ β€’ πŸ“– 5 min read

πŸ’‘ Quick Answer: RuntimeClass lets you run specific pods with sandboxed runtimes instead of the default runc. gVisor (runsc) interposes a user-space kernel between the container and host β€” no direct syscalls. Kata Containers runs each pod in a lightweight VM. Create a RuntimeClass, configure containerd with the handler, then set runtimeClassName in your pod spec.

The Problem

  • Default runc containers share the host kernel β€” a kernel exploit escapes to host
  • Multi-tenant clusters need stronger isolation than Linux namespaces/cgroups
  • Untrusted code (CI builds, user uploads, AI inference) needs sandboxing
  • Compliance requires defense-in-depth beyond standard container boundaries
  • Need different isolation levels for different workloads in same cluster

The Solution

RuntimeClass Definition

# gVisor RuntimeClass
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor
handler: runsc    # Must match containerd config handler name
scheduling:
  nodeSelector:
    sandbox.gvisor.dev/runtime: "true"
---
# Kata Containers RuntimeClass
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: kata
handler: kata    # Must match containerd config handler name
scheduling:
  nodeSelector:
    katacontainers.io/kata-runtime: "true"
overhead:
  podFixed:
    memory: "160Mi"    # VM overhead
    cpu: "250m"

Configure containerd for gVisor

# /etc/containerd/config.toml β€” add gVisor handler
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
  runtime_type = "io.containerd.runsc.v1"

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc.options]
  TypeUrl = "io.containerd.runsc.v1.options"
  ConfigPath = "/etc/containerd/runsc.toml"
# Install gVisor
curl -fsSL https://gvisor.dev/archive.key | gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" > /etc/apt/sources.list.d/gvisor.list
apt-get update && apt-get install -y runsc

# Verify
runsc --version
systemctl restart containerd

Configure containerd for Kata

# /etc/containerd/config.toml β€” add Kata handler
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata]
  runtime_type = "io.containerd.kata.v2"
  privileged_without_host_devices = true

[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata.options]
  ConfigPath = "/opt/kata/share/defaults/kata-containers/configuration.toml"

Use RuntimeClass in Pods

# Run untrusted workload in gVisor sandbox
apiVersion: v1
kind: Pod
metadata:
  name: untrusted-build
spec:
  runtimeClassName: gvisor    # ← This is all you need
  containers:
    - name: build
      image: registry.example.com/ci-runner:v1
      command: ["./run-untrusted-build.sh"]
      resources:
        limits:
          cpu: "2"
          memory: "4Gi"
---
# Run in Kata VM-level isolation
apiVersion: v1
kind: Pod
metadata:
  name: isolated-inference
spec:
  runtimeClassName: kata
  containers:
    - name: inference
      image: registry.example.com/model-server:v1
      resources:
        limits:
          cpu: "4"
          memory: "8Gi"

Comparison: runc vs gVisor vs Kata

Feature          β”‚ runc (default) β”‚ gVisor (runsc)  β”‚ Kata Containers
─────────────────┼────────────────┼─────────────────┼─────────────────
Isolation        β”‚ Namespaces     β”‚ User-space       β”‚ VM (hypervisor)
                 β”‚ + cgroups      β”‚ kernel           β”‚
─────────────────┼────────────────┼─────────────────┼─────────────────
Kernel access    β”‚ Shared host    β”‚ Intercepted      β”‚ Separate guest
                 β”‚ kernel         β”‚ (Sentry)         β”‚ kernel
─────────────────┼────────────────┼─────────────────┼─────────────────
Startup time     β”‚ ~100ms         β”‚ ~200ms           β”‚ ~1-2s
─────────────────┼────────────────┼─────────────────┼─────────────────
Memory overhead  β”‚ ~5MB           β”‚ ~50-100MB        β”‚ ~128-256MB
─────────────────┼────────────────┼─────────────────┼─────────────────
Syscall compat   β”‚ 100%           β”‚ ~80% (growing)   β”‚ ~100%
─────────────────┼────────────────┼─────────────────┼─────────────────
Performance      β”‚ Near-native    β”‚ Varies (I/O hit) β”‚ Near-native
                 β”‚                β”‚                  β”‚ (after start)
─────────────────┼────────────────┼─────────────────┼─────────────────
GPU support      β”‚ Full           β”‚ Limited          β”‚ VFIO passthrough
─────────────────┼────────────────┼─────────────────┼─────────────────
Use case         β”‚ Trusted apps   β”‚ Untrusted code,  β”‚ Hard multi-tenancy,
                 β”‚                β”‚ CI/CD, serverlessβ”‚ compliance, secrets
─────────────────┴────────────────┴─────────────────┴─────────────────

Common Issues

Pod stuck in ContainerCreating with RuntimeClass

  • Cause: Handler not installed on node; or node doesn’t have required label
  • Fix: Verify gVisor/Kata installed on node; check scheduling.nodeSelector matches

”operation not supported” errors inside gVisor container

  • Cause: gVisor doesn’t support all syscalls (e.g., io_uring, some ioctl)
  • Fix: Check gVisor compatibility matrix; fall back to runc for incompatible workloads

Kata pod fails with β€œno hardware virtualization support”

  • Cause: Node doesn’t have KVM/VT-x enabled
  • Fix: Enable virtualization in BIOS; or use cloud instances with nested virtualization

Performance degradation with gVisor

  • Cause: File I/O intensive workloads hit gVisor’s user-space filesystem overhead
  • Fix: Use directfs gVisor option for better I/O; or use Kata for I/O-heavy workloads

Best Practices

  1. Use RuntimeClass (not runtime annotations) β€” the standard K8s API since 1.20
  2. gVisor for untrusted code β€” CI builds, user-submitted code, serverless functions
  3. Kata for hard multi-tenancy β€” when namespace isolation isn’t sufficient
  4. Set overhead in RuntimeClass β€” accounts for sandbox memory in scheduling
  5. Node selectors on RuntimeClass β€” only schedule sandboxed pods on prepared nodes
  6. Test syscall compatibility β€” validate your application works under gVisor before production
  7. Default runc for trusted workloads β€” no need to sandbox everything (overhead vs security)

Key Takeaways

  • RuntimeClass (node.k8s.io/v1) selects container runtime per-pod via runtimeClassName
  • gVisor: user-space kernel intercepting syscalls β€” fast startup, some compatibility gaps
  • Kata Containers: lightweight VM per pod β€” full syscall compatibility, higher overhead
  • Both provide stronger isolation than default runc (namespaces + cgroups only)
  • Configure handler in containerd config, create RuntimeClass, set runtimeClassName in pod
  • Use gVisor for untrusted code; Kata for compliance/hard multi-tenancy; runc for everything else
#gvisor #kata-containers #runtimeclass #security #sandboxing #container-runtime
Luca Berton
Written by Luca Berton

Principal Solutions Architect specializing in Kubernetes, AI/GPU infrastructure, and cloud-native platforms. Author of Kubernetes Recipes and creator of CopyPasteLearn courses.

Kubernetes Recipes book cover

Want More Kubernetes Recipes?

This recipe is from Kubernetes Recipes, our 750-page practical guide with hundreds of production-ready patterns.

Luca Berton Ansible Pilot Ansible by Example Open Empower K8s Recipes Terraform Pilot CopyPasteLearn ProteinLens