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
runscruntime. - 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
/procfeatures 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
| Dimension | gVisor (runsc) | Kata Containers |
|---|---|---|
| Isolation mechanism | User-space kernel (Sentry) intercepts syscalls | Lightweight VM with its own guest kernel |
| Boundary strength | Strong (software) | Strongest (hardware virtualization) |
| Infrastructure needs | Runs without hardware virt | Needs /dev/kvm / nested virt or bare metal |
| Compatibility | Some syscalls unsupported | Near-full Linux compatibility |
| Overhead | Syscall/I-O overhead, low memory | Higher memory + slower startup per pod |
| Typical fit | Untrusted code on general nodes | Hard 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:
| Layer | Control | What it stops |
|---|---|---|
| Admission | Pod Security Standards / admission control | Privileged, hostPath, root pods from being created |
| Syscall | seccomp RuntimeDefault | Dangerous syscalls from the app |
| File/caps | AppArmor, dropped capabilities | Arbitrary file and capability access |
| Kernel | gVisor / Kata via RuntimeClass | Kernel exploits from reaching the host |
| Runtime | Falco | Detecting 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
| Trap | The clarification |
|---|---|
handler name doesn’t match containerd runtime | The RuntimeClass.handler must equal the containerd runtime map key (runsc, kata-qemu) exactly, or pods won’t start |
| Forgetting to restart containerd | Config edits to /etc/containerd/config.toml need systemctl restart containerd to take effect |
Running Kata where there’s no /dev/kvm | Kata needs hardware virtualization; on nested/managed nodes without it, gVisor is the workable choice |
| Expecting a sandbox to replace seccomp/PSA | Sandboxing is an extra layer — keep the securityContext, seccomp, and admission controls too |
| Pod scheduled to a node without the runtime | Use RuntimeClass.scheduling.nodeSelector so sandboxed pods only land on capable nodes |
| Treating gVisor as 100% compatible | Some 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.