This guide covers how to run Confidential Containers workloads across supported cloud and virtualization environments by using the Cloud API Adaptor (CAA) and the peer-pods runtime.
This deployment model provides strong workload isolation without tying the Kubernetes worker nodes to a particular virtualization or confidential-computing technology.
Because PodVMs are provisioned alongside the Kubernetes worker nodes rather than inside them, the worker nodes can themselves be virtual machines; bare-metal workers and nested virtualization are not required.
Note
The capabilities available to a workload depend on the infrastructure provider, region, machine type, and guest image.
A PodVM can use a supported Trusted Execution Environment (TEE), such as Intel® TDX, AMD SEV-SNP, or IBM Secure Execution, where available.
Some environments also support non-confidential VMs for development and testing.
Consult the provider-specific guide for current requirements and limitations.
At a high level, the Cloud API Adaptor integrates with a provider’s compute API or virtualization backend to provision a suitable VM for each pod.
The CAA then coordinates with the peer-pods runtime to run the pod workload in that dedicated PodVM.
When the selected environment provides a TEE, this architecture can protect the workload from the host or hypervisor and support attestation.
Application manifests remain largely portable between environments.
Provider-specific differences are handled during cluster setup and CAA configuration, including credentials, networking, regions or zones, instance types, VM images, and confidential-computing settings.
The provider-specific sections describe these differences and identify compatible machine types and isolation technologies where available.
Cloud API Adaptor
The Cloud API Adaptor is an implementation of the remote hypervisor interface of Kata Containers.
The CAA provisions Kata Containers VMs through the configured infrastructure backend instead of starting them on the worker nodes.
In Confidential Containers, a PodVM is the confidential virtual machine created to host a single Kubernetes Pod.
It is the VM boundary used by the peer-pods deployment model: when a pod is scheduled to a Confidential Containers runtime class, the runtime provisions a dedicated VM for that pod and then starts the pod’s containers inside the VM.
A PodVM typically includes:
a minimal guest OS,
a container runtime (for example, containerd), and
an agent that coordinates container lifecycle operations inside the guest.
From Kubernetes’ point of view, you still create a normal Pod (or a Deployment, Job, etc.).
The PodVM is an implementation detail of the runtime that provides a stronger isolation boundary than traditional containers:
No shared host kernel: the pod runs with its own guest kernel instead of the worker node’s kernel.
Dedicated VM boundary: the pod’s execution is isolated from other pods by virtualization.
Confidential computing support: when backed by Intel® TDX, memory inside the PodVM is protected from the host/hypervisor, and the platform can be attested before secrets are released.
In this guide, PodVMs are created on demand by the peer-pods runtime. The Cloud API Adaptor uses the configured infrastructure backend to provision and manage the VM instances that run those workloads.
Peer Pods
Peer Pods is a Confidential Containers deployment model where each Kubernetes Pod runs inside its own dedicated confidential VM (a PodVM).
Operationally, when you deploy a pod using a Confidential Containers RuntimeClass, the peer-pods components:
request the creation of a PodVM for that pod, using the configured isolation technology where available,
boot a minimal guest environment in the PodVM, and
start the pod’s containers inside the PodVM rather than on the Kubernetes worker node.
Across managed Kubernetes, self-managed clusters, and supported virtualization environments, the Cloud API Adaptor provides the infrastructure integration needed to provision the VM instances used for these PodVMs.
Key properties of Peer Pods:
Strong isolation per pod: Each pod gets a separate VM boundary. When a supported TEE is enabled, it can also protect the pod’s memory from the host or hypervisor.
Kubernetes-native workflow: You deploy standard Kubernetes resources (Pods/Deployments/Jobs). The runtime handles VM lifecycle without changing application manifests.
Attestation-ready: When supported by the selected TEE and environment, the PodVM can be attested to verify platform and workload identity before sensitive data is released.
Infrastructure-independent architecture: Provider-specific provisioning is handled by the Cloud API Adaptor, allowing peer pods to use compatible VM types across supported environments.
The following provider-specific sections cover cluster prerequisites, enabling Confidential Containers, selecting compatible VM types and images, configuring available isolation technologies, and deploying sample workloads using peer pods.
1 - Alibaba Cloud (ACK)
Cloud API Adaptor (CAA) on Alibaba Cloud
This documentation will walk you through setting up CAA (a.k.a. Peer Pods) on Alibaba Cloud Container Service for Kubernetes (ACK) and Alibaba Cloud Elastic Compute Service (ECS). It explains how to deploy:
One worker for ACK Managed Cluster
CAA on that Kubernetes cluster
An Nginx pod backed by CAA pod VM on ECS
Note: Run the following commands from the following directory - src/cloud-api-adaptor
Note: Now Confidential Computing instances are available in some regions.
Note: Official document from Alibaba Cloud can be found here.
Note:
There is a pre-built Community Image (id:m-j6c3kfz2vhu6ze04wacb) for version 0.22.0 in cn-hongkong that you can use for testing.
You can also use the region-specific image IDs listed under Export PodVM image version below.
If you want to build a pod VM image yourself, follow the steps below. The current PodVM build uses mkosi and targets Ubuntu 26.04 (resolute). See the PodVM README for prerequisites and customization options.
Build the Ubuntu 26.04 PodVM image.
cd podvm
make
This builds the PodVM binaries and the OS image. The resulting qcow2 file is:
podvm/build/podvm-ubuntu-amd64.qcow2
To rebuild only the OS image after binaries are already present:
cd podvm
make image
Upload to OSS storage and create an ECS Image.
From src/cloud-api-adaptor, upload the qcow2 file to OSS (Object Storage Service):
This assumes that you already have the code ready to use.
On your terminal change directory to the Cloud API Adaptor’s code base.
Export PodVM image version
Exports the PodVM image ID used by peer pods. This variable tells the deployment tooling which PodVM image version
to use when creating peer pod virtual machines in Alibaba Cloud.
exportIMAGEID="m-j6c3kfz2vhu6ze04wacb"
Note: Alibaba Cloud builds the images ahead of time. Different regions has different image id to use.
region
IMAGEID
cn-beijing
m-2ze2vxvrxsbue3sf8b02
cn-hongkong
m-j6c3kfz2vhu6ze04wacb
cn-hangzhou
m-bp146ws6x3iyuwjvbdw2
ap-southeast-1
m-t4ng1w8ipua3c0o57hor
Export CAA container image path
Define the Cloud API Adaptor (CAA) container image to deploy.
These variables tell the deployment tooling which CAA image and architecture-specific tag to pull and run.
The tag is derived from the CAA release version to ensure compatibility with the selected PodVM image and configuration.
Export the following environment variable to use the latest release image of CAA:
Find an appropriate tag of pre-built image suitable to your needs here.
exportCAA_TAG=""
Caution: You can also use the latest tag but it is not recommended, because of its lack of version control and potential for unpredictable updates, impacting stability and reproducibility in deployments.
If you have made changes to the CAA code and you want to deploy those changes then follow these instructions to build the container image. Once the image is built export the environment variables CAA_IMAGE and CAA_TAG.
Note: Below example assumes that you are using RRSA for auth hence
ALIBABACLOUD_ACCESS_KEY_ID and ALIBABACLOUD_ACCESS_KEY_SECRET are not provided,
while ALIBABA_CLOUD_ROLE_ARN and ALIBABA_CLOUD_OIDC_PROVIDER_ARN are provided.
Generic Peer pods Helm charts deployment instructions are also described
here.
Run sample application
Ensure runtimeclass is present
Verify that the runtimeclass is created after deploying CAA:
kubectl get runtimeclass
Once you can find a runtimeclass named kata-remote then you can be sure that the deployment was successful. A successful deployment will look like this:
$ kubectl get runtimeclass
NAME HANDLER AGE
kata-remote kata-remote 7m18s
Ensure that the tools curl, git and jq are installed.
AWS Preparation
Set AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY (or AWS_PROFILE) and AWS_REGION for AWS CLI access
Note: As an alternative to static credentials, you can use IRSA (IAM Roles for Service Accounts)
on EKS. With IRSA, the CAA pods authenticate via OIDC — no static AWS keys are stored in Kubernetes secrets.
You still need AWS_REGION and temporary credentials for the cluster setup steps below.
Set the region:
exportAWS_REGION="us-east-2"
Note: We have chose region us-east-2 as it has AMD SEV-SNP instances as well as prebuilt pod VM images readily available.
exportAWS_REGION="us-east-2"
Note: We have chose region us-east-2 because it has prebuilt pod VM images readily available.
Deploy Kubernetes using EKS
Make changes to the following environment variable as you see fit:
Port 15150 is the default port for CAA to connect to the agent-protocol-forwarder
running inside the pod VM.
Port 9000 is the VXLAN port used by CAA. Ensure it doesn’t conflict with the VXLAN port
used by the Kubernetes CNI.
Configure Authentication
Choose how CAA authenticates with AWS. Static credentials use a Kubernetes secret with access keys.
IRSA uses
OIDC federation so the pods assume an IAM role directly — no static keys needed.
Ensure AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (or AWS_PROFILE) are set in your environment.
These will be stored in a Kubernetes secret and used by the CAA pods.
No additional setup is needed here — the secret is created during the Helm chart deployment step.
IRSA eliminates the need for long-lived AWS access keys stored in Kubernetes secrets,
and is the recommended authentication method for EKS.
Enable OIDC Provider
Check if the IAM OIDC provider is already registered:
Create the IAM role and attach the AmazonEC2FullAccess managed policy:
aws iam create-role \
--role-name ${CAA_ROLE_NAME}\
--assume-role-policy-document file:///tmp/caa-trust-policy.json \
--description "IRSA role for Cloud API Adaptor on EKS"aws iam attach-role-policy \
--role-name ${CAA_ROLE_NAME}\
--policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess
Note:AmazonEC2FullAccess grants broad EC2 permissions. For production workloads, it is strongly
recommended to replace it with a custom least-privilege policy scoped to the specific EC2 actions
CAA requires.
See the AWS IRSA documentation for more configuration options.
Export the role ARN for later use:
exportCAA_ROLE_ARN=$(aws iam get-role \
--role-name ${CAA_ROLE_NAME}\
--query 'Role.Arn'\
--output text)echo"CAA Role ARN: ${CAA_ROLE_ARN}"
Create IAM Role for Peerpod-ctrl (optional)
This step is only required if you are deploying Peerpod-ctrl.
aws iam create-role \
--role-name ${CTRL_ROLE_NAME}\
--assume-role-policy-document file:///tmp/peerpod-ctrl-trust-policy.json \
--description "IRSA role for PeerPod Controller on EKS"aws iam attach-role-policy \
--role-name ${CTRL_ROLE_NAME}\
--policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess
Note:AmazonEC2FullAccess grants broad EC2 permissions. For production workloads, it is strongly
recommended to replace it with a custom least-privilege policy scoped to the specific EC2 actions
CAA requires.
See the AWS IRSA documentation for more configuration options.
Export the role ARN:
exportCTRL_ROLE_ARN=$(aws iam get-role \
--role-name ${CTRL_ROLE_NAME}\
--query 'Role.Arn'\
--output text)echo"Controller Role ARN: ${CTRL_ROLE_ARN}"
This assumes that you already have the code ready to use.
On your terminal change directory to the Cloud API Adaptor’s code base.
Export PodVM image version
Exports the PodVM image ID used by peer pods. This variable tells the deployment tooling which PodVM image version
to use when creating peer pod virtual machines in AWS.
The image is pulled from the Coco community gallery (or manually built) and must match the current CAA release version.
We have a pre-built debug pod VM image available in us-east-2 for PoCs. You can find the AMI id for the release specific image
by running the following CLI:
There are no pre-built pod VM AMI for latest builds. You’ll need to build your own pod VM image and then create the AMI by following
the instructions here.
Remember to set TEE_PLATFORM=amd before building the pod VM image for AWS.
Once image build is finished, export image id to the environment variable PODVM_AMI_ID.
You can build your custom pod VM image by following the instructions here.
Remember to set TEE_PLATFORM=amd before building the pod VM image for AWS.
Once image build is finished, export image id to the environment variable PODVM_AMI_ID.
Export CAA container image path
Define the Cloud API Adaptor (CAA) container image to deploy.
These variables tell the deployment tooling which CAA image and architecture-specific tag to pull and run.
The tag is derived from the CAA release version to ensure compatibility with the selected PodVM image and configuration.
Export the following environment variable to use the latest release image of CAA:
Find an appropriate tag of pre-built image suitable to your needs here.
exportCAA_TAG=""
Caution: You can also use the latest tag but it is not recommended, because of its lack of version control and potential for unpredictable updates, impacting stability and reproducibility in deployments.
If you have made changes to the CAA code and you want to deploy those changes then follow these instructions to build the container image. Once the image is built export the environment variables CAA_IMAGE and CAA_TAG.
Note: --from-file=id_rsa.pub=${SSH_KEY} is optional. It allows user to SSH into the pod VMs for troubleshooting purposes.
This option works only for custom debug enabled pod VM images. The prebuilt pod VM images do not have SSH connection enabled.
Note: The resourceCtrl.serviceAccount.annotations line is only required if you are deploying Peerpod-ctrl.
If not, omit that --set flag and skip the Peerpod-ctrl IAM role step above.
To verify IRSA is working, check the service account annotation and pod environment:
kubectl get serviceaccount cloud-api-adaptor \
-n confidential-containers-system \
-o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}'
The output should include AWS_WEB_IDENTITY_TOKEN_FILE and AWS_ROLE_ARN variables.
Generic Peer pods Helm charts deployment instructions are also described
here.
Run sample application
Ensure runtimeclass is present
Verify that the runtimeclass is created after deploying CAA:
kubectl get runtimeclass
Once you can find a runtimeclass named kata-remote then you can be sure that the deployment was successful. A successful deployment will look like this:
$ kubectl get runtimeclass
NAME HANDLER AGE
kata-remote kata-remote 7m18s
Ensure that the tools curl, git, jq and sipcalc are installed.
Azure Preparation
Azure login
Authenticate with Azure by running the following command and following the instructions in the command line:
az login
Note: If you have access to multiple Azure accounts, make sure to select the one you want to use for this deployment in the command line after running az login.
Export the Azure subscription ID into AZ_SUBSCR_ID using the subscription currently selected in the Azure CLI context:
exportAZ_SUBSCR_ID=$(az account show --query id --output tsv)
Verify that the correct subscription is selected
az account show --query '{name:name, id:id, tenantId:tenantId}' -o yaml
Set the AZURE_REGION environment variable to the region you want to use for this deployment.
This region will be used to deploy both the AKS cluster and the peer pod VMs.
exportAZURE_REGION="eastus"
Note: We selected the eastus region as it not only offers AMD SEV-SNP machines but also has prebuilt pod VM images readily available.
exportAZURE_REGION="eastus"
Note: We selected the eastus region as it not only offers Intel® TDX machines but also has prebuilt pod VM images readily available.
Note: Currently Intel® TDX machines are available in a limited number of regions:
(westeurope, westus, WestUS3, eastus, EastUS2EUAP, northcentralus), so make sure to select a region that supports them.
exportAZURE_REGION="eastus"
Note: We have chose region eastus because it has prebuilt pod VM images readily available.
Resource group
Note: Skip this step if you already have a resource group you want to use.
Please, export the resource group name in the AZURE_RESOURCE_GROUP environment variable.
Create an Azure resource group by running the following command:
Export the AZURE_RESOURCE_GROUP environment variable to a unique name for the resource group:
Create the resource group in the specified region:
az group create \
--name "${AZURE_RESOURCE_GROUP}"\
--location "${AZURE_REGION}"
Deploy Kubernetes using AKS
Export environment variables related to the AKS cluster deployment:
exportCLUSTER_NAME="caa-$(date '+%Y%m%b%d%H%M%S')"exportAKS_WORKER_USER_NAME="azuser"exportAKS_RG="${AZURE_RESOURCE_GROUP}-aks"# SSH key is optional, but it allows user to SSH into the pod VMs for troubleshooting purposes. # This option works only for custom debug enabled pod VM images. # The prebuilt pod VM images do not have SSH connection enabled.exportSSH_KEY=~/.ssh/id_rsa.pub
Deploy AKS with single worker node to the same resource group you have created:
Note: Optionally, deploy the worker nodes into an existing Azure Virtual Network (VNet) and subnet by adding the following flag: --vnet-subnet-id <MY_SUBNET_ID>.
Note: For sample usage Standard_F4s_v2 machine type is used for the worker node - this machine is General purpose - without enabled confidentiality.
Download kubeconfig locally to access the cluster using kubectl:
az aks get-credentials \
--resource-group "${AZURE_RESOURCE_GROUP}"\
--name "${CLUSTER_NAME}"
User assigned identity and federated credentials
CAA needs privileges to talk to Azure API.
This privilege is granted to CAA by associating a workload identity to the CAA service account.
This workload identity (a.k.a. user assigned identity) is given permissions to create VMs, fetch images and join networks in the next step.
Note: If you use an existing AKS cluster it might need to be configured to support workload identity and OpenID Connect (OIDC), please refer to the instructions in this guide.
Create an identity for CAA:
Set the AZURE_WORKLOAD_IDENTITY_NAME environment variable to a name for the user assigned identity:
The VMs that will host Pods will commonly require access to internet services, e.g. to pull images from a public OCI registry.
Create a discrete subnet next to the AKS cluster subnet in the same VNet for peer pod VMs.
Then create a NAT gateway, attach a public IP, and associate the NAT gateway with the peer-pods subnet to provide outbound connectivity.
Export the subnet ID to the environment variable AZURE_SUBNET_ID:
exportAZURE_SUBNET_ID="$( az network vnet subnet show \
-g "$AKS_RG"\
--vnet-name "$AZURE_VNET_NAME"\
-n peerpod \
--query id \
-o tsv)"
AKS resource group permissions
For CAA to be able to manage VMs, assign Virtual Machine and Network related roles to the user assigned identity.
The roles grant permissions to create VMs in AZURE_RESOURCE_GROUP and attach them to the VNet/subnet in AKS_RG.
Assign the “Virtual Machine Contributor” role to the user assigned identity:
az role assignment create \
--role "Virtual Machine Contributor"\
--assignee "$USER_ASSIGNED_CLIENT_ID"\
--scope "/subscriptions/${AZ_SUBSCR_ID}/resourcegroups/${AZURE_RESOURCE_GROUP}"
Assign the “Reader” role to the user assigned identity:
az role assignment create \
--role "Reader"\
--assignee "$USER_ASSIGNED_CLIENT_ID"\
--scope "/subscriptions/${AZ_SUBSCR_ID}/resourcegroups/${AZURE_RESOURCE_GROUP}"
Assign the “Network Contributor” role to the user assigned identity:
az role assignment create \
--role "Network Contributor"\
--assignee "$USER_ASSIGNED_CLIENT_ID"\
--scope "/subscriptions/${AZ_SUBSCR_ID}/resourcegroups/${AKS_RG}"
Create the federated credential for the CAA ServiceAccount using the OIDC endpoint from the AKS cluster:
Set the AKS_OIDC_ISSUER environment variable to the OIDC issuer URL of your AKS cluster:
exportAKS_OIDC_ISSUER="$(az aks show \
--name "${CLUSTER_NAME}"\
--resource-group "${AZURE_RESOURCE_GROUP}"\
--query "oidcIssuerProfile.issuerUrl"\
-o tsv)"
Note: If you are using Calico Container Network Interface (CNI) on the Kubernetes cluster, then, configure Virtual Extensible LAN (VXLAN) encapsulation for all inter workload traffic.
Download the CAA Helm chart
Currently, there are three options to choose from when downloading the CAA Helm chart:
This assumes that you already have the code ready to use.
On your terminal change directory to the Cloud API Adaptor’s code base.
Export PodVM image version
Exports the PodVM image ID used by peer pods. This variable tells the deployment tooling which PodVM image version
to use when creating peer pod virtual machines in Azure.
The image is pulled from the Coco community gallery (or manually built) and must match the current CAA release version.
Export this environment variable to use for the peer pod VM:
This repository contains the necessary scripts and configurations to build the PodVM image.
Build the PodVM image
Navigate to the cloud-api-adaptor/src/cloud-api-adaptor/podvm directory.
Build the binaries using below command:
ARCH=amd64 TEE_PLATFORM=az-cvm-vtpm \
make podvm-binaries
The ARCH parameter can be:
amd64 / x86_64: 64-bit x86 systems using Intel® or AMD processors
arm64 / aarch64: 64-bit Arm systems
s390x: 64-bit IBM systems
ppc64le: 64-bit IBM Power systems
The TEE_PLATFORM parameter can be:
none: for tests with non-confidential guests
all: for all following platforms
fs: for platforms with encrypted root filesystems (i.e. s390x)
tdx: for Intel® TDX
az-tdx-vtpm: for Intel® TDX with Azure vTPM
snp/amd: for AMD SEV-SNP
az-snp-vtpm: for AMD SEV-SNP with Azure vTPM
se: for IBM Secure Execution (SE)
Build the image:
Run below command to build the release image:
make image
Note: This will only build the pod VM image without SSH access.
Run command to build debug image
make image-debug
Note: This will only build the pod VM image with SSH access.
Convert QCOW2 image to Virtual Hard Disk (VHD) format
Caution: The below command can produce a VHD that is not properly aligned for Azure, which can cause issues when uploading and using the image.
To ensure that VHD image is properly aligned, follow the steps below to resize the QCOW2 image to a MiB boundary before converting it to VHD format.
Set the QCOW2 environment variable to the path of the generated QCOW2 image:
Azure requires VHD images to be aligned to 1 MiB, so the QCOW2 image must be resized to a multiple of 1 MiB before conversion.
Note: Currently the release image size is ~866MB in release builds, so resizing to 900MB ensures that the image is properly aligned while minimizing the additional space used.
Note: The flag --features SecurityType=ConfidentialVmSupported allows you to an upload custom image and boot it up as a Confidential Virtual Machine (CVM).
Create an image version in the Shared Image Gallery using the managed image:
az sig image-version create \
--resource-group "$AZURE_RESOURCE_GROUP"\
--gallery-name "$GALLERY_NAME"\
--gallery-image-definition "$GALLERY_IMAGE_DEF_NAME"\
--gallery-image-version "1.0.0"\
--target-regions "$AZURE_REGION"\
--managed-image "$MANAGED_IMAGE_ID"
Retrieve the image name and export it to use in CAA configuration:
exportAZURE_IMAGE_ID=$(az sig image-version list \
--resource-group "$AZURE_RESOURCE_GROUP"\
--gallery-name "$GALLERY_NAME"\
--gallery-image-definition "$GALLERY_IMAGE_DEF_NAME"\
--query "sort_by([], &name)[-1].id"\
--output tsv)echo"$AZURE_IMAGE_ID"
Set the CAA container image and tag
Define the Cloud API Adaptor (CAA) container image to deploy.
These variables tell the deployment tooling which CAA image and architecture-specific tag to pull and run.
The tag is derived from the CAA release version to ensure compatibility with the selected PodVM image and configuration.
Export the following environment variable to use the latest release image of CAA:
Find an appropriate tag of pre-built image suitable to your needs here.
exportCAA_TAG=""
Caution: You can also use the latest tag, but it is not recommended,
because of its lack of version control and potential for unpredictable
updates, impacting stability and reproducibility in deployments.
If you have made changes to the CAA code and you want to deploy those changes then follow these instructions to build the container image. Once the image is built export the environment variables CAA_IMAGE and CAA_TAG.
Note: --from-file=id_rsa.pub=${SSH_KEY} is optional. It allows user to SSH into the pod VMs for troubleshooting purposes.
This option works only for custom debug enabled pod VM images. The prebuilt pod VM images do not have SSH connection enabled.
Install helm chart:
Below command uses customization options -f and --set which are described here.
Note: Above example assumes that you are using workload identity for authentication.
This line: --set-json daemonset.podLabels='{"azure.workload.identity/use":"true"}' is required only when using workload identity.
Generic Peer pods Helm charts deployment instructions are also described
here.
Verify deployment
Verify that the runtimeclass is created after deploying Peer Pods Helm Charts:
kubectl get runtimeclass
Once you can find a runtimeclass named kata-remote then you can be sure that the deployment was successful.
A successful deployment will look like this:
$ kubectl get runtimeclass
NAME HANDLER AGE
kata-remote kata-remote 7m18s
Run sample application
To verify that everything is working as expected, you can deploy a sample application using the kata-remote runtime class provided by CAA.
Create a deployment file nginx-deployment.yaml for a sample nginx application:
Verify that the PodVM was created by running the following command:
az vm list \
--resource-group "${AZURE_RESOURCE_GROUP}"\
--output table
In command output there should be the VM associated with the pod nginx.
Note: If you run into problems then check the troubleshooting guide here.
Pod VM reference values
Reference values for the PCRs can be retrieved from the registry and used in attestation policies to verify the integrity of the Pod VM image before trusting it with sensitive workloads or secrets.
As part of a Pod VM image build expected PCR measurements are published into an OCI registry.
Pre-requisites
Install ORAS tool to pull the reference values from the OCI registry and GitHub CLI to verify its build provenance.
Verify build provenance
Assert that the measurements have been generated by a trusted build process on the official repository.
Specify --format=json to get more details about the build process.
To uninstall Confidential Containers from Azure AKS cluster, use the following commands:
Remove all pods with kata-runtime runtime class:
kubectl get pods -A -o custom-columns='NAME:.metadata.name,NAMESPACE:.metadata.namespace,RUNTIMECLASS:.spec.runtimeClassName'\
| grep kata-remote \
| awk '{print $1, $2}'\
| xargs -n 2 sh -c 'kubectl delete pod -n "$2" "$1"' _
List deployed Confidential Containers Helm chart:
Note: This command assumes that only one Helm release is deployed in the confidential-containers-system namespace.
If there are multiple releases, you may need to adjust the command to select the correct one.
exportHELM_COCO_CHART_NAME=$(helm list \
-n confidential-containers-system \
--short)
Delete Confidential Containers related Helm chart:
Note: The e2-standard-4 machine type is used for the GKE cluster nodes, which is a general-purpose machine.
The UBUNTU_CONTAINERD image type is specified to ensure compatibility with the container runtime used by CAA.
kubectl get nodes \
--selector='!node-role.kubernetes.io/master'\
-o name \
| xargs -I{} kubectl label {} node.kubernetes.io/worker=
This labeling step adds the node.kubernetes.io/worker label to non-control-plane nodes.
Note
Starting with GKE version 1.27, GCP configures containerd with the discard_unpacked_layers=true flag to optimize disk
usage by removing compressed image layers after they are unpacked. However, this can cause issues with PeerPods,
as the workload may fail to locate required layers.
To avoid this, disable the discard_unpacked_layers setting in the containerd configuration.
If you encounter problem with VM’s not running check Troubleshooting section on this page.
Configure VPC network
We need to make sure port 15150 is open under the default VPC network:
For production scenarios, it is advisable to restrict the source IP range to
minimize security risks. For example, you can restrict the source range to a
specific IP address or CIDR block:
This repository contains the necessary scripts and configurations to build the PodVM image.
Build Steps:
Navigate to the cloud-api-adaptor/src/cloud-api-adaptor/podvm directory.
Build the binaries using below command:
ARCH=amd64 TEE_PLATFORM=tdx \
make podvm-binaries
The ARCH parameter can be:
amd64 / x86_64: 64-bit x86 systems using Intel® or AMD processors
arm64 / aarch64: 64-bit Arm systems
s390x: 64-bit IBM systems
ppc64le: 64-bit IBM Power systems
The TEE_PLATFORM parameter can be:
none: for tests with non-confidential guests
all: for all following platforms
fs: for platforms with encrypted root filesystems (i.e. s390x)
tdx: for Intel® TDX
az-tdx-vtpm: for Intel® TDX with Azure vTPM
snp/amd: for AMD SEV-SNP
az-snp-vtpm: for AMD SEV-SNP with Azure vTPM
se: for IBM Secure Execution (SE)
Prepare SSH key to build debug image:
For using SSH, create a file resources/authorized_keys with your SSH public key.
Ensure the permissions are set to 0400 for the authorized_keys file.
SSH access is only possible for the root user.
Below are the commands to generate a new SSH key and create the authorized_keys file:
Create SSH key pair and copy public keys to proper location:
Note: TTL (time to live) is set to 0, which means that the key will not expire.
You can set it to any value you want, for example 1h for 1 hour or 30m for 30 minutes.
Build the debug image:
make image-debug
Note: This will build the pod VM image with SSH access.
Above command will produce ./build/system.raw (~1.6GB), a disk image that can be booted with an ESP/UEFI partition.
Prepare the raw disk image and package it as build/disk.tar.gz:
cp build/system.raw build/disk.raw &&\
tar -cvzf build/disk.tar.gz -C build disk.raw
Prerequisites:
Install the required tools:
Install the ORAS CLI tool for pulling OCI artifacts
Extract the qcow2 image from the downloaded tar.xz:
tar -xf podvm.tar.xz
Convert the qcow2 image to raw format:
mkdir -p build
qemu-img convert -O raw podvm-ubuntu-amd64.qcow2 build/disk.raw
Package the raw disk image as build/disk.tar.gz:
tar -cvzf build/disk.tar.gz -C build disk.raw
Publish image to Google Storage
Export the bucket name:
exportBUCKET_NAME="peerpods-bucket"
Note: GCP_PROJECT_ID and GCP_REGION should already be set from the GCP Preparation section.
The BUCKET_NAME should be globally unique across all of Google Cloud, so consider adding a random suffix if needed.
This command creates a new image in GCP with the specified name and the uploaded disk image.
The --guest-os-features flag ensures that the image is compatible with UEFI.
BothUEFI_COMPATIBLE and TDX_CAPABLE are required for tdx TEE.
This command creates a new image in GCP with the specified name and the uploaded disk image.
The --guest-os-features flag ensures that the image is compatible with UEFI and TDX.
Export PodVM image id
Export the PodVM image id to be used in the provider configuration. This is the name of the image created in the previous step.
exportPODVM_IMAGE_ID="$IMAGE_NAME"
Show command how to retrieve latest published image from GCP
Run below command to retrieve the latest published image from GCP:
This assumes that you already have the code ready to use.
On your terminal change directory to the Cloud API Adaptor’s code base.
Set the CAA container image and tag
Define the Cloud API Adaptor (CAA) container image to deploy.
These variables tell the deployment tooling which CAA image and architecture-specific tag to pull and run.
The tag is derived from the CAA release version to ensure compatibility with the selected PodVM image and configuration.
Export the following environment variable to use the latest release image of CAA:
Find an appropriate tag of pre-built image suitable to your needs here.
exportCAA_TAG=""
Caution: You can also use the latest tag, but it is not recommended,
because of its lack of version control and potential for unpredictable
updates, impacting stability and reproducibility in deployments.
If you have made changes to the CAA code and you want to deploy those changes
then follow these
instructions
to build the container image. Once the image is built export the environment
variables CAA_IMAGE and CAA_TAG.
Populate the provider file
List of all available configuration options can be found in two places:
Choose how CAA authenticates with GCP. You can use either static credentials (service account key file)
or Workload Identity Federation (WIF).
Note: Workload Identity Federation (WIF) is the recommended authentication method for GKE deployments.
With WIF, the CAA pods authenticate via OIDC — no static GCP credentials are stored in Kubernetes secrets.
Create GCP Service Account
Create a GCP service account with the necessary permissions for CAA to manage peer pod VMs:
exportGSA_NAME="cloud-api-adaptor"exportGSA_EMAIL="${GSA_NAME}@${GCP_PROJECT_ID}.iam.gserviceaccount.com"gcloud iam service-accounts create ${GSA_NAME}\
--description="Cloud API Adaptor Service Account"\
--display-name="Cloud API Adaptor Service Account"\
--project=${GCP_PROJECT_ID}gcloud projects add-iam-policy-binding ${GCP_PROJECT_ID}\
--member="serviceAccount:${GSA_EMAIL}"\
--role="roles/compute.instanceAdmin.v1"gcloud projects add-iam-policy-binding ${GCP_PROJECT_ID}\
--member="serviceAccount:${GSA_EMAIL}"\
--role="roles/iam.serviceAccountUser"
These roles allow the Cloud API Adaptor to:
create, start/stop, and delete the Compute Engine instances used as PodVMs (roles/compute.instanceAdmin.v1)
run actions as the service account when provisioning those resources (roles/iam.serviceAccountUser)
Note: IAM policy updates can take a few minutes to propagate. If later steps fail with permission errors, wait briefly and retry.
Choose Authentication Method
Generate and save the service account key file:
exportGOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/peerpods_application_key.json
gcloud iam service-accounts keys create \
${GOOGLE_APPLICATION_CREDENTIALS}\
--iam-account=${GSA_EMAIL}
Workload Identity Federation eliminates the need for long-lived service account key files stored in Kubernetes secrets,
and is the recommended authentication method for GKE.
# Note: Use 'locations' instead of 'zones' for the OIDC issuer URLexportOIDC_ISSUER="https://container.googleapis.com/v1/projects/${GCP_PROJECT_ID}/locations/${GCP_REGION}-a/clusters/${CLUSTER_NAME}"exportPROJECT_NUMBER=$(gcloud projects describe ${GCP_PROJECT_ID}\
--format="value(projectNumber)")echo"OIDC Issuer: ${OIDC_ISSUER}"echo"Project Number: ${PROJECT_NUMBER}"# Verify the OIDC endpoint is accessiblecurl -s "${OIDC_ISSUER}/.well-known/openid-configuration"| jq .issuer
Create Workload Identity Pool and OIDC Provider
Create a custom workload identity pool for direct token exchange:
exportWI_POOL_NAME="caa-direct-wif-pool"gcloud iam workload-identity-pools create ${WI_POOL_NAME}\
--location=global \
--description="Workload Identity pool for cloud-api-adaptor with hostNetwork true"\
--display-name="CAA Direct WIF Pool"\
--project=${GCP_PROJECT_ID}
Note: If you get an error that the pool already exists, it may be in a soft-deleted state (GCP delays actual deletion for 30 days). Either use a different pool name or undelete it.
Bind Kubernetes Service Account to GCP Service Account
Allow the Kubernetes service account to impersonate the GCP service account:
gcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL}\
--member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${WI_POOL_NAME}/subject/system:serviceaccount:${CAA_NAMESPACE}:${CAA_K8S_SERVICE_ACCOUNT}"\
--role="roles/iam.workloadIdentityUser"\
--project=${GCP_PROJECT_ID}gcloud iam service-accounts add-iam-policy-binding ${GSA_EMAIL}\
--member="principal://iam.googleapis.com/projects/${PROJECT_NUMBER}/locations/global/workloadIdentityPools/${WI_POOL_NAME}/subject/system:serviceaccount:${CAA_NAMESPACE}:${CAA_K8S_SERVICE_ACCOUNT}"\
--role="roles/iam.serviceAccountTokenCreator"\
--project=${GCP_PROJECT_ID}
Note: The binding uses principal:// with the full subject (system:serviceaccount:<namespace>:<name>) to scope impersonation to the exact Kubernetes service account. Using principalSet:// with only attribute.service_account_name would allow a service account with the same name in any namespace to impersonate the GSA.
This namespace will be used to deploy CAA and related components, and it is labeled and annotated to be managed by Helm.
Create ResourceQuota to allow system priority classes:
GKE enforces strict resource quota policies. The kata-deploy DaemonSet requires system
priority classes (system-node-critical or system-cluster-critical) to ensure proper
scheduling and execution. Create a ResourceQuota to allow these priority classes:
When using WIF, no GCP credentials secret is needed. The CAA pods authenticate via the
GCP service account configured in the Configure Authentication section.
The output should show $GCP_WIF_CREDENTIALS is SET.
Generic Peer pods Helm charts deployment instructions are also described
here.
Verify deployment
Verify that the runtimeclass is created after deploying Peer Pods Helm Charts:
kubectl get runtimeclass
Once you can find a runtimeclass named kata-remote then you can be sure that the deployment was successful.
A successful deployment will look like this:
$ kubectl get runtimeclass
NAME HANDLER AGE
kata-remote kata-remote 7m18s
Run sample application
This example showcases a more advanced deployment using TEE and confidential
VMs with the kata-remote runtime class. It demonstrates how to deploy a sample
pod and retrieve a secret securely within a confidential computing environment.
Prepare the init data configuration
Peerpods now supports init data, you can pass the required configuration files
(aa.toml, cdh.toml, and policy.rego) via the
io.katacontainers.config.hypervisor.cc_init_data annotation. Below is an example
of the configuration and usage.
Once the pod is successfully deployed with the initdata, you can retrieve secrets from the Trustee service running inside the pod.
Use the following command to fetch a specific secret:
This example demonstrates how to verify if Helm chart is successfully starting the PodVM within the cloud provider.
It is the simplest example available for deployment.
You can verify that the PodVM was created by running the following command:
gcloud compute instances list
Here you should see the VM associated with the pod used by the example above.
Uninstall
To uninstall Confidential Containers from GKE cluster, use the following commands:
Remove all pods with kata-runtime runtime class:
kubectl get pods -A -o custom-columns='NAME:.metadata.name,NAMESPACE:.metadata.namespace,RUNTIMECLASS:.spec.runtimeClassName'\
| grep kata-remote \
| awk '{print $1, $2}'\
| xargs -n 2 sh -c 'kubectl delete pod -n "$2" "$1"' _
Verify that all peer pod VMs are deleted:
Use the following command to list all the peer pod VMs (VMs having prefix podvm) and status.
gcloud compute instances list \
--filter="name~'podvm.*'"\
--format="table(name,zone,status)"
List deployed Confidential Containers Helm chart:
Note: This command assumes that only one Helm release is deployed in the confidential-containers-system namespace.
If there are multiple releases, you may need to adjust the command to select the correct one.
exportHELM_COCO_CHART_NAME=$(helm list \
-n confidential-containers-system \
--short)
Delete Confidential Containers related Helm chart:
(Workload Identity Federation only) Delete the OIDC provider and workload identity pool created during setup:
gcloud iam workload-identity-pools providers delete ${OIDC_PROVIDER_NAME}\
--location=global \
--workload-identity-pool=${WI_POOL_NAME}\
--project=${GCP_PROJECT_ID}gcloud iam workload-identity-pools delete ${WI_POOL_NAME}\
--location=global \
--project=${GCP_PROJECT_ID}
Note: Deleting a pool or provider is a soft delete; GCP retains the resource for 30 days before permanently removing it. During this window the name cannot be reused unless you undelete it.
Delete the GKE cluster by running the following command and confirming the deletion when prompted:
Note: SSH connection is available only for debug image, which is built with enabled SSH server and added public key to authorized_keys file.
If you want to have SSH access to the image, make sure to build debug image and upload it to Google using above instructions.
After creating debug image with enabled SSH, deploy CoCo with sample pod and use root account to access it:
Note: Remember to add your public key to Google using CLI
Export environment variable GCP_PODVM_IP using below code:
Note: If your case is not covered in section below check the troubleshooting guide here.
VM Doesn’t Start
Starting with GKE version 1.27, GCP configures containerd with the discard_unpacked_layers=true flag to optimize disk
usage by removing compressed image layers after they are unpacked. However, this can cause issues with PeerPods,
as the workload may fail to locate required layers.
To avoid this, disable the discard_unpacked_layers setting in the containerd configuration.
Most of the time you will see a generic message such as the following:
Error: failed to create containerd container: error unpacking image: failed to extract layer sha256:<SHA>: failed to get reader from content store: content digest sha256:<SHA>: not found
To disable the discard_unpacked_layers setting in the containerd configuration on Google Kubernetes Engine (GKE) version 1.27 or later, follow these steps: