KubernetesXXX · Container Runtime and CRIContainer runtime
RuntimeClass and runtime alternatives — runc, kata, gVisor
What you'll learn
- Identify the three runtime families and their security posture
- Configure RuntimeClass to select the runtime per Pod
- Apply the trade-offs of runtime selection to the workload
- Diagnose a Pod that is failing because of a runtime mismatch
Prerequisites
Verified against Kubernetes 1.34.x · kubeadm 1.34.x · kubectl 1.34.x · etcd 3.6.x · CoreDNS 1.11.x · containerd 1.7.x / 2.x · 2026-08-16
RuntimeClass is the Kubernetes mechanism for selecting the
container runtime per Pod. The default is runc; the
alternatives are kata-containers (hardware isolation)
and gVisor (user-space kernel). Each runtime has a
different security posture and a different performance
profile. This lesson walks the three runtime families,
the RuntimeClass configuration, and the operational
patterns.
The three runtime families
The runtime is the component that the kubelet calls to start the container. The runtime spawns the container’s process according to the OCI runtime spec.
flowchart LR
A[kubelet] -->|CRI| B[containerd]
B -->|runtime<br/>selection| C[runc]
B -->|runtime<br/>selection| D[kata]
B -->|runtime<br/>selection| E[gVisor]
C -->|process on host| F[Container]
D -->|VM| F
E -->|user-space kernel| F
- runc (default): the Open Container Initiative (OCI) reference implementation. Runs the container as a regular process on the host, using the kernel’s namespaces and cgroups for isolation.
- kata-containers: runs the container in a lightweight VM. Each Pod carries its own guest kernel, and the hypervisor becomes the isolation boundary.
- gVisor: a user-space kernel. It intercepts the container’s system calls and services most of them in user space, so only a narrow subset reaches the host kernel.
The choice of runtime is the security/performance
trade-off. runc is the fastest but least isolated;
kata is the most isolated but slowest; gVisor is a
middle ground.
runc — the default
runc is the OCI reference implementation. It is the default runtime for containerd and CRI-O. The runtime spawns the container’s process on the host, using the kernel’s namespaces and cgroups for isolation.
The isolation properties:
- Process isolation: the container’s process is in its own PID namespace, network namespace, and mount namespace.
- Resource isolation: the container’s cgroup limits CPU, memory, and I/O.
- Security: the container’s capabilities, seccomp rules, and AppArmor profiles are applied.
The security posture is bounded by the host kernel. A kernel vulnerability can be exploited by a privileged container. The production rule is to apply the Pod Security Standards (restricted, baseline, privileged) to the workload.
kata-containers — hardware isolation
kata-containers runs the container in a lightweight VM. The container’s process is in a separate kernel instance; the host kernel is not directly exposed.
The isolation properties:
- VM isolation: the container’s process is in a separate kernel instance.
- Hypervisor: the VM is managed by a hypervisor (QEMU, Firecracker).
- Network: the VM has a virtual network interface; the CNI plugin configures the interface.
The security posture is the strongest. The host kernel is not exposed to the container; a kernel vulnerability in the container is bounded by the VM. The performance is the slowest; the VM has a startup cost of 1-2 seconds and a memory overhead of 50-100Mi.
The deployment pattern:
- Multi-tenant clusters: a cluster that runs untrusted workloads uses kata to isolate the workloads from the host.
- Compliance: a workload that requires a higher isolation level (PCI, FedRAMP) uses kata to satisfy the requirement.
- Cryptocurrency mining: a workload that processes untrusted code uses kata to isolate the workload.
gVisor — user-space kernel
gVisor is a user-space kernel. The container’s process interacts with the gVisor kernel, which proxies system calls to the host kernel. The host kernel is not directly exposed.
The isolation properties:
- User-space kernel: the container’s process interacts with the gVisor kernel, not the host kernel.
- System call interception: gVisor intercepts the container’s system calls and proxies them to the host.
- Network: gVisor implements a user-space network stack.
The security posture is strong. The host kernel is not exposed to the container; a kernel vulnerability in the container is bounded by the gVisor sandbox. The performance is slower than runc; faster than kata.
The deployment pattern:
- Serverless platforms: a serverless platform (Cloud Run, Fly.io) uses gVisor to isolate the workloads.
- Web hosting: a web hosting platform uses gVisor to isolate the customers’ code.
- Sandbox workloads: a workload that processes untrusted code uses gVisor to isolate the workload.
The RuntimeClass
The RuntimeClass is a cluster-scoped object that associates a runtime handler with a Pod:
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata
overhead:
podFixed:
memory: "120Mi"
cpu: "250m"
scheduling:
nodeSelector:
node-role.kubernetes.io/secure: "true"
The handler field is the runtime handler (the name
containerd uses to invoke the runtime). The overhead
field declares the runtime’s resource overhead; the
scheduler accounts for the overhead when scheduling the
Pod. The scheduling field selects the nodes that
support the runtime.
The Pod references the RuntimeClass:
spec:
runtimeClassName: kata
containers:
- name: app
image: registry.example.com/app:1.0.0
The kubelet reads the RuntimeClass and selects the appropriate runtime when calling the CRI.
The RuntimeClass’s overhead
The RuntimeClass’s overhead field declares the
runtime’s resource overhead. The scheduler accounts for
the overhead when scheduling the Pod.
The overhead is the runtime’s memory and CPU usage when running the Pod. kata uses 50-100Mi of memory and 100m of CPU per Pod. gVisor uses 30-50Mi of memory and 50m of CPU per Pod.
The overhead is added to the Pod’s resource requests. A Pod with 1Gi memory request and a kata RuntimeClass has a total memory request of 1Gi + 120Mi.
The RuntimeClass’s scheduling
The RuntimeClass’s scheduling field selects the nodes
that support the runtime. The field is a node selector;
the Pod’s node selector is the intersection of the
RuntimeClass’s selector and the Pod’s selector.
scheduling:
nodeSelector:
node-role.kubernetes.io/secure: "true"
A Pod with a RuntimeClass that selects
node-role.kubernetes.io/secure: "true" is scheduled
only on nodes with the secure label. The cluster
operator should label the nodes that support the
runtime.
The runtime’s installation
The runtime is installed on the node. The kubelet
discovers the runtime via the runtime’s configuration
file (e.g., containerd’s config.toml).
[plugins."io.containerd.runtime.v2.task"]
platforms = ["linux/amd64"]
[plugins."io.containerd.runtime.v2.task.options"]
BinaryName = "runc"
[plugins."io.containerd.grpc.v1.cri"]
# ... other settings ...
The runtime is configured per-RuntimeClass. The
RuntimeClass’s handler is the name containerd uses to
invoke the runtime. The runtime’s binary is configured
in containerd’s config.toml.
A node that is missing the runtime is a node that cannot
run Pods with the RuntimeClass. The Pod is stuck in
ContainerCreating.
The runtime’s failure modes
The runtime’s failure modes:
| Failure | Symptom | Root cause |
|---|---|---|
| Runtime missing | Pod stuck in ContainerCreating | runtime binary not installed, configuration wrong |
| Runtime configuration error | Pod stuck in ContainerCreating | config.toml wrong, runtime handler not registered |
| Kernel feature missing | Pod stuck in ContainerCreating | kernel module not loaded, kernel too old |
| Resource exhaustion | Pod OOMKilled | runtime overhead exceeds node capacity |
The diagnostic:
# Substitute the Pod stuck in ContainerCreating:
POD=gvisor-workload-6c9d7f4b58-qk4xr
kubectl describe pod "$POD" | grep -A 10 "Events"
The events show the runtime’s error. The fix is to investigate the runtime’s logs and the node’s configuration.
Quiz
Knowledge check · 4 questions
Q1. What does a RuntimeClass select?
Q2. A Pod referencing a RuntimeClass whose handler is not configured on its node will fail to start.
Q3. Fix a workload that requests a hardware-isolated runtime and lands on nodes that cannot provide it.
The team running untrusted customer builds added `runtimeClassName: kata` to the Deployment `ci-runner` (8 replicas) in namespace `builds`. Five Pods are Running; three are stuck in `ContainerCreating` with `Failed to create pod sandbox: no runtime for "kata" is configured`. The cluster has 30 nodes, of which 12 were built from the hardened image that installs `containerd-shim-kata-v2`. The RuntimeClass `kata` declares only `handler: kata`.
Q4. Besides `handler`, which two RuntimeClass fields affect placement and capacity, and what does each one do?
Passing score: 75%. Answers are checked in this browser.
Production discipline
- Choose the runtime based on the workload’s isolation requirements. A multi-tenant cluster uses kata or gVisor for untrusted workloads. A single-tenant cluster uses runc.
- The RuntimeClass’s overhead is the runtime’s resource cost. The scheduler accounts for the overhead. The node must have capacity for the overhead.
- The RuntimeClass’s scheduling is the runtime’s node selection. Label the nodes that support the runtime with the same label as the RuntimeClass’s selector.
- Audit the runtime’s installation at every node repave. A new node that joins the cluster without the runtime is a node that cannot run Pods with the RuntimeClass.
- Monitor the runtime’s metrics. The runtime’s metrics expose the runtime’s health. The operator should alert on the error rate.
- Test the runtime in non-production. The runtime’s performance is workload-dependent. A staging cluster that mirrors production is the right place to test.