Back to Blog

Container Sandboxing & Runtime Isolation for the CKS Exam: gVisor, Kata Containers & RuntimeClass

A hands-on guide to container sandboxing for the CKS exam — why a shared kernel isn't a security boundary, how gVisor's user-space kernel and Kata's lightweight VMs isolate untrusted workloads, and how to wire them into Kubernetes with RuntimeClass and containerd, with manifests, node config, and the verification steps the exam expects.

By Sailor Team , July 26, 2026

Most Kubernetes security controls assume the attacker is still inside the container. Sandboxing exists for the moment that assumption breaks. It’s the answer to a question the CKS keeps asking in one form or another: when a workload can’t be trusted, how do you stop a kernel-level exploit from turning one compromised pod into a compromised node?

Container runtime sandboxes live in the Minimize Microservice Vulnerabilities domain of the CKS — one of the heaviest sections of the exam at roughly 20% — and they show up as a specific objective: use container runtime sandboxes in multi-tenant environments (e.g. gVisor, Kata Containers). This guide covers what a sandbox actually is, the two mainstream implementations, and how to wire one into a cluster with RuntimeClass and containerd — with the manifests and verification commands you need to do it under a clock. For the broader domain, pair this with the Minimize Microservice Vulnerabilities guide; for the full blueprint, start with the CKS exam guide for 2026 and the CKS exam topics breakdown.

Why a Shared Kernel Isn’t a Security Boundary

The single most important idea behind sandboxing is this: ordinary containers all share the host’s Linux kernel. Namespaces isolate what a process can see (its own PID tree, network, mounts), and cgroups limit what it can consume (CPU, memory). But every container’s system calls are serviced by the same kernel running on the node.

That matters because the Linux kernel exposes a huge attack surface — 300-plus system calls, plus the driver and filesystem code behind them. If a malicious workload finds a kernel vulnerability (a container-escape CVE, a bad ioctl, a namespace bug), it doesn’t just crash its own container — it can break out to the host and reach every other pod on that node. seccomp and AppArmor (covered in the System Hardening guide) shrink that surface, but they still let the container talk directly to the real kernel.

A sandbox changes the model. Instead of confining a container that talks to the host kernel, it puts a second boundary between the workload and the host — either a user-space kernel that intercepts syscalls, or a lightweight virtual machine with its own guest kernel. Even a full kernel exploit inside the workload only compromises the sandbox, not the node.

When the Exam Expects a Sandbox

The trigger phrase to watch for is “untrusted” or “multi-tenant.” If a scenario describes running third-party code, customer-supplied images, CI jobs from untrusted sources, or tenants who shouldn’t be able to affect each other even through a kernel bug, the answer is a runtime sandbox. If the scenario is just “make this trusted app more secure,” the answer is usually the cheaper controls — securityContext, seccomp, AppArmor, Pod Security Standards — not a sandbox.

Two implementations dominate, and the CKS names both:

  • gVisor — a user-space application kernel from Google, invoked through the runsc runtime.
  • Kata Containers — lightweight virtual machines that wrap each pod, from the OpenInfra Foundation.

gVisor: A User-Space Kernel

gVisor inserts a sandbox kernel written in Go — called the Sentry — between the container and the host. When the application makes a system call, gVisor intercepts it (using a ptrace or KVM “platform”) and the Sentry services it in user space, implementing the Linux syscall surface itself. The Sentry only makes a small, tightly restricted set of real host syscalls, so the container never talks directly to the host kernel.

The runtime binary is runsc. Its trade-offs are the ones the exam likes to probe:

  • Strong isolation, no VM. You get a hard security boundary without hardware virtualization, so it runs on more infrastructure types than Kata.
  • Compatibility gaps. Because the Sentry re-implements the kernel, a minority of syscalls and /proc features aren’t fully supported. Some workloads (certain databases, anything doing exotic kernel calls) may misbehave.
  • Performance cost. Syscall-heavy and I/O-heavy workloads pay a measurable overhead because every syscall detours through the Sentry.

Kata Containers: Lightweight VMs

Kata takes the opposite approach: it wraps each pod in its own lightweight virtual machine with a real, separate guest kernel, launched by a hypervisor (QEMU, Cloud Hypervisor, or Firecracker). Isolation is enforced by the same hardware virtualization that separates VMs — the strongest boundary of the two.

Kata’s trade-offs:

  • Hardware-enforced isolation and near-full Linux compatibility (it’s a real kernel), so workloads that break under gVisor usually run fine.
  • Needs virtualization. It requires nested virtualization or bare-metal nodes with /dev/kvm. Many managed or nested environments can’t run it.
  • Higher overhead. Each pod boots a micro-VM, so there’s more memory per pod and slower startup than a normal container.

gVisor vs Kata: Choosing the Right One

DimensiongVisor (runsc)Kata Containers
Isolation mechanismUser-space kernel (Sentry) intercepts syscallsLightweight VM with its own guest kernel
Boundary strengthStrong (software)Strongest (hardware virtualization)
Infrastructure needsRuns without hardware virtNeeds /dev/kvm / nested virt or bare metal
CompatibilitySome syscalls unsupportedNear-full Linux compatibility
OverheadSyscall/I-O overhead, low memoryHigher memory + slower startup per pod
Typical fitUntrusted code on general nodesHard multi-tenant isolation on virt-capable nodes

For the exam you rarely have to install either from scratch — the cluster usually has the runtime available and your job is to wire it into Kubernetes. That wiring is the same for both, and it’s done with RuntimeClass.

RuntimeClass: Wiring a Sandbox into Kubernetes

Kubernetes doesn’t run sandboxes directly — the container runtime (containerd or CRI-O) does. RuntimeClass is the Kubernetes object that lets a pod select which runtime handler the node should use. Think of it as a named pointer from “I want gVisor” to “the runsc handler configured in containerd.”

A RuntimeClass is cluster-scoped and minimal:

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: gvisor          # the name pods reference
handler: runsc          # must match a runtime configured in containerd
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: kata
handler: kata-qemu      # matches the containerd kata runtime handler

The handler is the critical field: it must exactly match a runtime name configured on the node’s container runtime. If it doesn’t, pods that reference the class fail to start.

To run a pod in the sandbox, set runtimeClassName in the pod spec:

apiVersion: v1
kind: Pod
metadata:
  name: untrusted-app
spec:
  runtimeClassName: gvisor      # <-- routes this pod through runsc
  containers:
  - name: app
    image: nginx:1.27

For a Deployment, the field goes in the pod template (spec.template.spec.runtimeClassName). That one line is the whole “make this workload run sandboxed” task.

Scheduling and overhead (the fields candidates forget)

Sandbox runtimes are often only installed on some nodes. RuntimeClass can carry scheduling rules so pods that use it land only on capable nodes, and can declare per-pod resource overhead so the scheduler accounts for the VM/Sentry cost:

apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
  name: kata
handler: kata-qemu
scheduling:
  nodeSelector:
    sandbox: "kata"        # only schedule onto nodes labelled this way
overhead:
  podFixed:
    memory: "120Mi"        # extra memory the runtime itself consumes
    cpu: "250m"

With scheduling.nodeSelector set, Kubernetes automatically merges those constraints into any pod that uses the class — you don’t have to repeat the selector in every pod. This is the clean way to run a mixed cluster where only a labelled pool of nodes can host sandboxed workloads.

Configuring the Node: containerd (the part people miss)

RuntimeClass only points at a handler; the handler itself has to exist in containerd’s config. This is where sandbox tasks quietly fail. On the node, /etc/containerd/config.toml needs a runtime block whose name matches the handler:

# gVisor
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runsc]
  runtime_type = "io.containerd.runsc.v1"

# Kata
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.kata-qemu]
  runtime_type = "io.containerd.kata.v2"

After editing, restart the runtime so it picks up the change:

systemctl restart containerd

The map key (runsc, kata-qemu) is exactly the string your RuntimeClass.handler must use. If a task says “pods using RuntimeClass gvisor are stuck in ContainerCreating,” the first thing to check is whether the node’s containerd actually defines the runsc runtime and was restarted.

Verifying the Sandbox Actually Works

Creating the pod isn’t proof. On the exam, demonstrate isolation:

# 1. Confirm the RuntimeClass exists and its handler
kubectl get runtimeclass
kubectl get runtimeclass gvisor -o jsonpath='{.handler}{"\n"}'

# 2. Confirm the pod picked up the class
kubectl get pod untrusted-app -o jsonpath='{.spec.runtimeClassName}{"\n"}'

# 3. Prove you're in a sandbox kernel, not the host kernel
kubectl exec untrusted-app -- dmesg | head       # gVisor announces itself
kubectl exec untrusted-app -- uname -r            # gVisor reports a synthetic version
kubectl exec untrusted-app -- cat /proc/version

Inside a gVisor pod, dmesg typically prints a line identifying gVisor, and the kernel version won’t match the host’s real kernel — that mismatch is the tell that the workload is talking to the Sentry, not the node. On the node itself you can confirm the runtime process:

# On the worker node hosting the pod
ps aux | grep -E 'runsc|qemu' | grep -v grep

Seeing runsc (gVisor) or qemu/cloud-hypervisor (Kata) processes bound to the pod is direct evidence the sandbox is in force.

Where Sandboxing Fits Among CKS Controls

Sandboxing is a boundary, not a replacement for the rest of your hardening. The exam rewards defense in depth, so a sandboxed workload should still carry the usual controls:

LayerControlWhat it stops
AdmissionPod Security Standards / admission controlPrivileged, hostPath, root pods from being created
Syscallseccomp RuntimeDefaultDangerous syscalls from the app
File/capsAppArmor, dropped capabilitiesArbitrary file and capability access
KernelgVisor / Kata via RuntimeClassKernel exploits from reaching the host
RuntimeFalcoDetecting anomalous behaviour at runtime

Sandboxing plugs the one gap the others can’t: it assumes the syscall filter or capability drop was bypassed, and contains the blast radius anyway. That layered thinking — described end to end in the Kubernetes security best practices for CKS — is exactly what the exam is testing.

Common CKS Sandboxing Traps

TrapThe clarification
handler name doesn’t match containerd runtimeThe RuntimeClass.handler must equal the containerd runtime map key (runsc, kata-qemu) exactly, or pods won’t start
Forgetting to restart containerdConfig edits to /etc/containerd/config.toml need systemctl restart containerd to take effect
Running Kata where there’s no /dev/kvmKata needs hardware virtualization; on nested/managed nodes without it, gVisor is the workable choice
Expecting a sandbox to replace seccomp/PSASandboxing is an extra layer — keep the securityContext, seccomp, and admission controls too
Pod scheduled to a node without the runtimeUse RuntimeClass.scheduling.nodeSelector so sandboxed pods only land on capable nodes
Treating gVisor as 100% compatibleSome syscalls//proc features aren’t supported; a broken workload under gVisor may just need Kata

Frequently Asked Questions

What is container sandboxing in Kubernetes?

Sandboxing runs a container behind an extra isolation boundary — a user-space kernel (gVisor) or a lightweight VM (Kata Containers) — instead of letting it share the host’s Linux kernel directly. Even if an attacker exploits a kernel vulnerability inside the workload, they’re confined to the sandbox rather than the node. It’s the recommended control for untrusted or multi-tenant workloads.

What’s the difference between gVisor and Kata Containers?

gVisor intercepts system calls and services them in a user-space kernel (the Sentry) through the runsc runtime, giving strong isolation without hardware virtualization but with some syscall-compatibility gaps. Kata wraps each pod in a real lightweight VM with its own guest kernel, giving the strongest isolation and near-full compatibility, but it needs hardware virtualization and costs more memory and startup time.

How do I make a pod use a sandboxed runtime?

Create a RuntimeClass whose handler matches a runtime configured in containerd (for example runsc), then set runtimeClassName: <class> in the pod spec (or the Deployment’s pod template). The pod then runs through the sandbox runtime instead of the default runc.

Why is my pod with a RuntimeClass stuck in ContainerCreating?

Almost always the node’s container runtime doesn’t have a runtime handler matching the RuntimeClass.handler, or containerd wasn’t restarted after editing /etc/containerd/config.toml. Confirm the runtime block exists in the config, restart containerd, and make sure the pod is scheduled to a node that actually has the sandbox runtime installed.

Does sandboxing replace seccomp, AppArmor, and Pod Security Standards?

No. Sandboxing is an additional layer for the kernel-exploit case. Best practice — and what the CKS rewards — is defense in depth: keep Pod Security Standards, securityContext hardening, seccomp, AppArmor, and dropped capabilities and add a sandbox for untrusted workloads.

How much of the CKS covers sandboxing?

Sandboxing is one objective inside Minimize Microservice Vulnerabilities, which is about 20% of the exam. It’s a small but high-yield target because the task — create a RuntimeClass, set runtimeClassName, and verify isolation — is concrete and fast once you’ve practiced it. See the CKS exam topics breakdown for the full domain weighting.

Conclusion

Containers share a kernel, and a shared kernel is a shared fate. Sandboxing breaks that link: gVisor puts a user-space kernel between the workload and the host, Kata puts a whole VM there, and RuntimeClass is how Kubernetes lets a pod choose one. Master the three moving parts — a RuntimeClass with the right handler, a containerd runtime block that matches it, and runtimeClassName on the pod — and the sandboxing tasks become quick, verifiable points.

Like every hands-on CKS skill, this only becomes reflexive on a real cluster. Sailor.sh’s Certified Kubernetes Security Specialist (CKS) Mock Exam Bundle runs on a live Kubernetes cluster with exam-style performance tasks, including runtime-isolation scenarios like wiring up runsc and proving a pod is sandboxed. Warm up for free with the CKS practice environment guide and how to practice CKS for free, sequence your prep with the CKS study plan, confirm the CKS prerequisites, and connect this to the neighbouring domains through the System Hardening and supply chain security guides.

Limited Time Offer: Get 80% off all Mock Exam Bundles | Sale ends in 7 days. Start learning today.

Claim Now