Kata Containers
How c8s uses Kata Containers to run each pod as its own confidential VM (AMD SEV-SNP or Intel TDX) — measured direct-kernel boot, the sealed guest image, and the guest-pull model.
When you install c8s with --kata, every workload pod becomes its own confidential VM —
AMD SEV-SNP by default, Intel TDX with --hardware-platform=tdx. This is the
pod-as-kata-cvm model: "each pod is its own confidential VM." Kata Containers provides the
per-pod micro-VM; c8s provides the attestation, identity, and policy that make it confidential
and verifiable.
What gets installed
c8s install --kata deploys the Kata runtime stack and registers four RuntimeClasses — the two
non-confidential classes plus the declared platform's confidential (CPU, GPU) pair:
-
kata-deploy(DaemonSet, digest-pinned) installs the Kata runtime andcontainerd-shim-kata-v2on every node — including the declared platform's confidential QEMU build, the guest kernel, guest image, and OVMF firmware. It restarts containerd/RKE2 after install; running pods survive via shim persistence. -
RuntimeClasses:
RuntimeClass Hypervisor Confidential? kata-qemuQEMU micro-VM No — VM isolation from the host only kata-clhCloud Hypervisor No — VM isolation only kata-qemu-snp/kata-qemu-tdxQEMU + SEV-SNP / Intel TDX Yes — pod memory is encrypted against the host; schedules only on platform-labeled nodes kata-qemu-snp-nvidia/kata-qemu-tdx-nvidiaas above + VFIO GPU passthrough Yes — a confidential VM with a dedicated NVIDIA GPU A cluster runs one platform's confidential classes: only the pair matching the install's
--hardware-platformis rendered, and the webhook promotesconfidential.ai/cwpods to its CPU class (and pods requestingnvidia.com/*to its GPU class) — mixed-TEE clusters are not supported. The rest of this page describes the SEV-SNP path.
Installing --kata also disables the host-side ratls-mesh, attestation-api, and
nri-image-policy: under Kata their function moves inside the guest image, where the host
cannot tamper with it.
Measured direct-kernel boot
Kata's confidential path does not use IGVM or a UKI. It uses measured direct-kernel boot with dm-verity:
| Component | What's supplied |
|---|---|
| kernel | a bare vmlinuz (a hardened kernel) |
| image | kata-rootfs.img — an ext4 rootfs plus a dm-verity hash tree |
| verity params | root_hash=…,salt=…,data_blocks=… on the kernel cmdline |
The dm-verity root hash rides on the kernel cmdline, and the cmdline is folded into the
SNP launch measurement at boot. So the launch digest commits to OVMF + vmlinuz + the
exact rootfs. To keep the digest stable it is measured at 1 vCPU and predicted with
sev-snp-measure, then published so an operator can pre-attest it and pin it in
kata.guestImage.tag.
The sealed guest image
The kata-guest-base image is the dm-verity-sealed guest rootfs. Because it is part of
the launch measurement, everything baked into it is transitively attested:
| Baked-in | Role |
|---|---|
attestation-service | localhost-only attester at 127.0.0.1:8400 |
ratls-mesh | in-guest mesh proxy (ratls-mesh in-guest) |
policy-monitor | watches container creation and SIGKILLs non-allowlisted images |
/etc/c8s/bootstrap-allowlist.json | the baked image-digest allowlist (part of the measurement) |
/etc/kata-opa/default-policy.rego | OPA policy denying host exec/log RPCs and SetPolicyRequest |
A peer that verifies an RA-TLS handshake therefore transitively verifies that the attestation service, mesh proxy, and policy monitor are the genuine, sealed versions — they cannot be swapped out by the host.
In debug builds (--kata --debug), the guest policy allows host log/exec streams so
kubectl logs and kubectl exec work — but container I/O becomes readable by the
untrusted host, and the launch measurement differs from the locked image. Development only.
Three layers of attestation
LAYER 0 LAYER 1 LAYER 2
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Guest image │ │ Container │ │ Workload │
│ │──────► │ image │──────► │ identity │
│ SNP launch │ │ │ │ │
│ digest │ │ OCI digest · │ │ RA-TLS │
│ │ │ allowlist │ │ certificate │
└───────────────┘ └───────────────┘ └───────────────┘- Guest image — the SNP launch digest over OVMF +
vmlinuz+ cmdline (embedding the dm-verity root hash). Operator pre-attests and pins it. - Container image — the OCI image digest, checked against the
allowlist. The pod webhook injects
get-cert, which fetches a leaf cert from the CDS — the CDS verifies the container measurement and signs in one step. - Workload identity — an RA-TLS cert carrying attestation evidence; peers verify it on the mTLS handshake.
The guest-pull model
With shared_fs="none" and experimental_force_guest_pull=true, the kata-agent's
confidential-data-hub pulls the OCI image inside the guest over virtio-net — not from a
host bind-mount. The host never sees the unpacked workload rootfs, only encrypted VM memory.
A consequence worth planning for: a private workload image needs two credentials — one
host-side (for the kubelet's pull) and one in-guest at /run/image-security/auth.json (for
the confidential-data-hub's pull).
Constraints
- x86-64, SEV-SNP or Intel TDX. The confidential pod path needs an AMD SEV-SNP host
(Genoa / Milan / Turin class) or an Intel TDX host, declared at install time via
--hardware-platform. On a host with neither TEE you can still runkata-qemu(isolation, not confidentiality) but no confidential class. - Confidential GPU is passthrough-based and comes with constraints. GPU pods run as
confidential VMs with the NVIDIA GPU passed through over VFIO (request an
nvidia.com/*resource; the webhook injects the GPU RuntimeClass). The GPU host setup (vfio-pci binding, GPU CC mode, BAR resize) is provisioning's job, not the NVIDIA GPU Operator's — the operator assumes host-visible GPUs with a host driver and must not be installed on these clusters. GPU attestation (SPDM) is not yet surfaced to the relying party — see Limitations. - Per-pod, not per-node. Pod-as-CVM attests each pod; it does not by itself attest the node. If you need a single confidential node instead, see pod-as-CVM vs node-as-CVM.
- Not available on Azure AKS. Per-pod CVMs need nesting that AKS does not provide; use the node-as-CVM shape there — see Provisioning on Azure.