Run in the secure (Kata) runtime
The secure runtime class runs each pod in its own lightweight virtual machine, with its own kernel. If a process escapes the container it stays inside that VM, unable to reach the host or a neighbouring pod, even through a kernel-level bug. That is a harder boundary than a user namespace , which remaps UIDs but leaves every pod sharing the one host kernel. Use it when you run untrusted code, mix trust levels in one cluster, or cannot afford a kernel escape.
Where it runs#
The secure runtime is Kata Containers, and Kata boots a real kernel, so it needs bare-metal nodes with hardware virtualization (KVM). Hetzner Cloud VMs do not expose nested virtualization, so secure pods will not schedule on them. No bare-metal workers yet? Add a bare-metal pool first.
You wire up no node selector; the runtime class carries its own, so the scheduler steers secure pods onto capable nodes. When none is free the pod sits Pending; it never quietly drops back to the shared-kernel runtime.
Set the runtime class#
One line. Add runtimeClassName: secure to the pod spec; nothing else about the workload changes.
apiVersion: apps/v1
kind: Deployment
metadata:
name: secure-app
spec:
replicas: 2
selector:
matchLabels:
app: secure-app
template:
metadata:
labels:
app: secure-app
spec:
runtimeClassName: secure
containers:
- name: app
image: nginx:stable
resources:
requests: # size for the container; the VM overhead is added on top
cpu: "250m"
memory: "128Mi"
Verify the VM boundary#
Compare the kernel the pod sees with the node's:
$ kubectl exec <pod> -- uname -r
$ kubectl get node <node> -o jsonpath='{.status.nodeInfo.kernelVersion}'
The two versions differ: each secure pod runs its own guest kernel inside the VM. A standard-runtime pod reports the node's kernel, because it shares it.
What a drain does to a secure pod#
A secure pod is still a pod: cordon or drain its node and it evicts and reschedules like any workload. The cost lands only on the way back up: the guest kernel boots again before the container starts, eating into the pool's drain budget. The default nodeDrainTimeoutSeconds is 180s; raise it per pool if secure pods routinely need longer to hand off.
Limits to budget for#
- Per-pod VM overhead. The VM layer costs roughly 250m CPU and 160Mi memory on top of the container's request.
- hostNetwork and privileged are out. A pod that sets either cannot use the
secureruntime.
standard(the default) uses crun, the lightweight OCI runtime. A pod with noruntimeClassNamelands here.runcis the fallback for the rare image that misbehaves on crun.secureis Kata Containers, the per-pod VM described on this page.
Confidential-computing variants (Intel TDX, AMD SEV-SNP) are not built, because Hetzner's dedicated servers lack those CPU features.
Run a workload in a user namespace
Map container root onto an unprivileged host UID with one field, so a container escape does not land as root on the node.
Set security contexts on a workload
Where security contexts fit in the workload-hardening ladder, with a pointer to the security page that owns the fields, the restricted example, and Pod Security Standards enforcement.