Confidential Containers 是一个开源项目,将机密计算引入云原生环境,利用硬件技术保护复杂工作负载。 Confidential Containers 是 CNCF Incubating 项目。
文档
- 1: 概览
- 2: 快速开始
- 3: 术语对照表
- 4: 云提供商
- 4.1: AWS (EKS)
- 4.2: Azure Kubernetes Service (AKS)
- 4.3: Google Cloud Platform (GCP)
- 4.4: 阿里云
- 5: NVIDIA GPU 示例
- 6: NVIDIA 机密 NIM 部署
- 7: 使用 SNP 内存加密启动容器
1 - 概览
什么是 Confidential Containers(CoCo)项目?
Confidential Containers(CoCo)通过将 Pod 运行在机密虚拟机(Confidential Virtual Machine)中, 让云原生工作负载在几乎无需修改的情况下利用机密计算硬件提供的安全能力。
Confidential Containers 将机密计算保护能力扩展到复杂工作负载场景。 借助 Confidential Containers,敏感工作负载即便运行在不受信任的主机环境(untrusted hosts)上, 也能够抵御来自已遭入侵或恶意的用户、软件和管理员的攻击。
Confidential Containers 提供一个 工作负载部署、远程证明(Remote Attestation) 以及 密钥/机密信息(Keys/Secrets)分发 的端到端框架。
Confidential Containers 支持哪些硬件?
在裸金属环境下,Confidential Containers 支持以下平台:
| 平台 | 支持远程证明(Remote Attestation) |
|---|---|
| Intel TDX | 是 |
| AMD SEV-SNP | 是 |
| IBM Secure Execution | 是 |
硬件加速器(Accelerators)
| 硬件加速器 | 单设备透传 | 多设备透传 |
|---|---|---|
| Hygon DCU | 是 | 否 |
| NVIDIA Hopper | 是 | Protected PCIe |
| NVIDIA RTX Pro 6000 BSE | 是 | 否 |
| NVIDIA Blackwell | 是 | 是 |
NVIDIA 多设备透传要求将主机上的全部 GPU 分配给同一个 Pod。 NVIDIA Protected PCIe 还要求在 Pod 中列出 NVIDIA NVLink 交换机。
云平台
还可以借助cloud-api-adaptor在云环境中部署Confidential Containers。
目前支持以下平台。
| 平台 | 云 | 备注 |
|---|---|---|
| SNP | Azure | |
| TDX | Azure | |
| TDX | 阿里云 | |
| Secure Execution | IBM | |
| None | AWS | 开发中 |
| SNP | GCP | |
| TDX | GCP | 开发中 |
| None | LibVirt | 用于本地测试 |
远程证明(Attestation)
Confidential Containers 提供了一个名为 Trustee 的远程证明与密钥管理引擎,可为以下平台提供远程证明:
| 平台 |
|---|
| AMD SEV-SNP |
| Intel TDX |
| Intel SGX |
| AMD SEV-SNP with Azure vTPM |
| Intel TDX with Azure vTPM |
| IBM Secure Execution |
| ARM CCA |
| Hygon CSV |
| Hygon DCU |
| NVIDIA GPU |
Trustee 既可以与 Confidential Containers 配合使用,也可以用于对独立运行的机密虚拟机(即非 CoCo 场景)进行远程证明。 更多信息请参见“远程证明(Attestation)”章节。
2 - 快速开始
本节将介绍部署 Confidential Containers 所需的软硬件前提条件、 如何使用 Helm Chart 完成安装、如何验证安装结果, 以及如何运行使用 Confidential Containers 的 Pod。
2.1 - 前提条件
本节将介绍使用 Helm Chart 安装 Confidential Containers 所需的软硬件前提条件。
2.2 - 安装
安装 Confidential Containers 之前,请先完成前提条件章节中的准备工作。
使用 Helm 安装 CoCo
通过 Confidential Containers Charts 仓库提供的 Helm Chart 安装 CoCo 运行时。
安装最新版本:
helm install coco oci://ghcr.io/confidential-containers/charts/confidential-containers \
--namespace coco-system \
--create-namespace
将 <VERSION> 替换为所需的发布版本:
helm install coco oci://ghcr.io/confidential-containers/charts/confidential-containers \
--version <VERSION> \
--namespace coco-system \
--create-namespace
例如,安装 v0.18.0 版本:
helm install coco oci://ghcr.io/confidential-containers/charts/confidential-containers \
--version 0.18.0 \
--namespace coco-system \
--create-namespace
等待所有 Pod 的 STATUS 变为 Running。
kubectl get pods -n coco-system --watch
如果需要了解特定场景的安装选项(例如 s390x、peer-pods)或高级配置,请参见
Charts 仓库文档。
验证安装
检查预期的 RuntimeClass 是否已创建。
kubectl get runtimeclass
可用的 RuntimeClass 取决于具体架构:
| runtimeclass | 说明 |
|---|---|
kata-qemu-coco-dev |
开发/测试运行时 |
kata-qemu-coco-dev-runtime-rs |
开发/测试运行时(基于 Rust) |
kata-qemu-snp |
AMD SEV-SNP |
kata-qemu-tdx |
Intel TDX |
kata-qemu-nvidia-gpu-snp |
NVIDIA GPU 加 AMD SNP 组合 |
kata-qemu-nvidia-gpu-tdx |
NVIDIA GPU 加 Intel TDX 组合 |
| runtimeclass | 说明 |
|---|---|
kata-qemu-coco-dev |
开发/测试运行时 |
kata-qemu-coco-dev-runtime-rs |
开发/测试运行时(基于 Rust) |
kata-qemu-se |
IBM Secure Execution |
kata-qemu-se-runtime-rs |
IBM Secure Execution(基于 Rust) |
| runtimeclass | 说明 |
|---|---|
kata-remote |
Peer-pods |
卸载
如需卸载 Confidential Containers 并删除 coco-system 命名空间,请执行:
helm uninstall coco --namespace coco-system
kubectl delete namespace coco-system
2.3 - 示例工作负载
创建示例 Confidential Containers 工作负载
使用 Helm Chart 安装 Confidential Containers 后,只需为 Pod 添加一个 RuntimeClass,
即可运行 CoCo 工作负载。
我们使用 kata-qemu-coco-dev RuntimeClass,它会在不依赖机密计算硬件支持的情况下运行 CoCo。
首先,使用一个未加密的容器镜像进行演示。
本示例使用如下 YAML 所示的 nginx 镜像:
apiVersion: v1
kind: Pod
metadata:
labels:
run: nginx
name: nginx
annotations:
io.containerd.cri.runtime-handler: kata-qemu-coco-dev
spec:
containers:
- name: nginx
image: nginx:1.29.4
dnsPolicy: ClusterFirst
runtimeClassName: kata-qemu-coco-dev
对于大多数基础的工作负载来说,通常只需在 Pod YAML 中设置 runtimeClassName
和 runtime-handler 注解即可。
按照上面的内容创建一个 Pod YAML 文件(本示例命名为 nginx.yaml)。
创建工作负载:
kubectl apply -f nginx.yaml
输出示例:
pod/nginx created
确认 Pod 已成功创建并处于运行状态:
kubectl get pods
输出示例:
NAME READY STATUS RESTARTS AGE
nginx 1/1 Running 0 3m50s
2.4 - 加固工作负载
现在你已经部署了一个简单的 Confidential Containers 工作负载, 接下来可以进一步了解如何将其加固到适用于生产环境的水平。 本页重点介绍你需要做出的几个关键决策:
- 为你的硬件选择合适的 RuntimeClass
- 理解并配置用于保护工作负载的各类策略
- 利用额外的安全特性进一步提升防护能力
选择合适的 RuntimeClass
在前面的示例中,我们使用的是 kata-qemu-coco-dev,
它主要用于测试场景,不依赖机密计算硬件支持。
如果是在生产环境中部署,则必须选择与实际 TEE 硬件相匹配的 RuntimeClass。
RuntimeClass 选择指南
用于开发与测试:
kata-qemu-coco-dev- 不依赖 TEE 硬件的测试运行时(⚠️ 不提供安全保证)
用于 x86_64 裸金属生产环境:
kata-qemu-tdx- Intel TDX(Trust Domain Extensions)kata-qemu-snp- AMD SEV-SNP(Secure Encrypted Virtualization)kata-qemu-sev- AMD SEV(较早一代)kata-qemu-nvidia-gpu-tdx- NVIDIA GPU 加 Intel TDX 组合kata-qemu-nvidia-gpu-snp- NVIDIA GPU 加 AMD SNP 组合
用于 s390x 生产环境:
kata-qemu-se- IBM Secure Execution
用于云上部署(Peer Pods):
kata-remote- 面向 AWS、Azure、GCP 等平台的 Cloud API Adaptor
示例:迁移到生产环境配置
下面展示如何将 Pod 更新为使用真实的 TEE 硬件。
以 Intel TDX 为例:
apiVersion: v1
kind: Pod
metadata:
name: nginx-production
spec:
runtimeClassName: kata-qemu-tdx
containers:
- image: bitnami/nginx:latest
name: nginx
设置 runtimeClassName 字段通常已经足够。
有些示例为了兼容较旧配置,还会保留 io.containerd.cri.runtime-handler 注解(annotation),
但在使用 RuntimeClass 时,这个注解通常是冗余的。
使用Intel TDX 加 NVIDIA Hopper GPU:
apiVersion: v1
kind: Pod
metadata:
name: cuda-vectoradd-kata
namespace: default
spec:
runtimeClassName: kata-qemu-nvidia-gpu-tdx
restartPolicy: Never
containers:
- name: cuda-vectoradd
image: "nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0-ubuntu22.04"
resources:
limits:
nvidia.com/pgpu: "1"
memory: 16Gi
关于 Hopper、Blackwell 多 GPU 示例,以及 Hopper PPCIE 标签配置,请参见 NVIDIA GPU 示例。
所选 RuntimeClass 必须与你的硬件能力匹配。
如果 RuntimeClass 与实际硬件不一致(例如在 AMD 硬件上使用 kata-qemu-tdx),
Pod 创建将会失败。
理解 CoCo 策略体系
Confidential Containers 使用三类策略(policy),分别在不同层面保护工作负载。 若要加固生产环境部署,理解这三类策略至关重要。
三类策略
| 策略类型 | 执行位置 | 控制内容 | 配置方式 |
|---|---|---|---|
| Kata Agent 策略 (Kata Agent Policy) | TEE 内部,由 Kata Agent 执行 | 控制 Agent 可执行的操作(如创建容器、对 Pod 执行 exec 等) |
通过包含 init-data 的 Pod 注解配置 |
| KBS 资源策略 (KBS Resource Policy) | 由 Trustee KBS 执行 | 控制哪些机密信息可以释放给哪些工作负载 | 通过 KBS Client 或 Trustee Operator 配置 |
| 远程证明策略 | 由 Trustee AS 执行 | 控制如何评估硬件证据(例如接受哪些 TCB 状态) | 通过 KBS Client 或 Trustee Operator 配置 |
图中展示的 Rego 文件名仅为示例。
这些名称本身没有特殊含义。
实际配置时,策略始终是通过命令行参数(例如 kbs-client ... set-resource-policy --policy-file <path>)
或配置项按路径传入,服务实际使用的是文件内容,而不是文件名。
1. Kata Agent 策略(TEE 内)
Kata Agent 策略决定了 Kata Agent 在 TEE 内允许执行哪些操作。 这是防御恶意或已被入侵的 Kubernetes 控制平面的第一道防线。
典型用途包括:
- 禁止对生产 Pod 执行
kubectl exec - 限制允许启动的容器镜像
- 控制允许执行的命令
下面是一个较为严格的 Kata Agent 策略示例:
package agent_policy
import rego.v1
default CreateContainerRequest := false
default ExecProcessRequest := false
# Only allow specific image digests
CreateContainerRequest if {
input.storages[0].source == "docker.io/library/nginx@sha256:abc123..."
}
Kata Agent 策略会嵌入到 Init-Data 配置文件中。 该文件还可提供其他配置,例如 Trustee 的地址信息。
2. KBS 资源策略(KBS 侧)
KBS 资源策略用于控制在何种条件下释放哪些机密信息。 它会检查工作负载提交的证明令牌,并据此做出决策。
典型用途包括:
- 验证工作负载是否使用了特定的 Kata Agent 策略(通过 Init-Data 哈希)
- 仅向通过 TDX 远程证明的 TDVM 实例释放数据库凭据
- 要求特定的信任级别(例如
affirming或contraindicated) - 为不同平台(TDX 或 SNP)下发不同的机密信息
示例:校验 Init-Data 哈希
当你在 Pod 中提供 Init-Data(其中包含 Kata Agent 策略)时, 证明服务(Attestation Service)会对其进行校验,并将相应哈希写入令牌。 你的 KBS 资源策略可以进一步验证这个特定的 Init-Data 哈希, 从而确保实际使用的是期望的 Kata Agent 策略与配置。
package policy
import rego.v1
default allow = false
# Only release secrets to workloads with the expected Init-Data hash
allow if {
input["submods"]["cpu0"]["ear.status"] == "affirming"
# Verify the specific Init-Data hash (includes kata agent policy + config)
input["submods"]["cpu0"]["ear.veraison.annotated-evidence"]["init_data"] == "expected-hash-here"
}
请使用你在 initdata.toml 中指定的哈希算法计算期望值。
例如,对 TDX 场景,通常会使用 sha384,此时可在命令行中执行:
sha384sum initdata.toml
延伸阅读:KBS 资源策略
3. 远程证明策略(Attestation Service 侧)
远程证明策略定义了如何评估硬件证据,包括: 接受哪些测量值、与哪些参考值进行比对,以及如何计算信任向量。
典型用途包括:
- 定义可接受的固件版本
- 为不同工作负载指定所需的安全级别
- 将硬件测量值映射为可信声明(trust claims)
延伸阅读:Attestation Service Policies
CoCo 已为 TDX、SNP 实例以及 NVIDIA 机密计算 GPU 提供了合理的默认证明策略。 对大多数用户而言,只需补充参考值即可,策略本身通常已具备合适配置。
额外的安全特性
完成基础配置后,可以进一步了解以下功能以增强整体安全性: 功能概览
生产环境快速检查清单
在部署到生产环境之前,请确认已完成以下事项:
- 已根据实际硬件选择正确的 RuntimeClass
- 已生成并嵌入适合当前工作负载的 Kata Agent 策略
- 已在 KBS 中配置 KBS 资源策略
- 已向证明服务预置参考值
后续步骤
- 部署 Trustee: 参见Trustee 安装,以启用远程证明
- 深入策略体系: 参见各类策略说明
- 云上部署: 参见AWS、Azure、GCP 云平台示例
3 - 术语对照表
本文根据当前 CoCo 英文文档整理出常见的专业名称、关键术语与专有名词,便于中文文档翻译时保持用词一致。
说明:
- 组件名、项目名、协议名、工具名以及
RuntimeClass名称通常保留英文。 - 中文列给出建议译法;对于不宜直译的项目名,采用“保留英文 + 中文解释”的方式。
- 该表会随着英文文档演进持续补充和调整。
核心项目与组件
| English | 中文建议 | 说明 |
|---|---|---|
| Confidential Containers | 机密容器 | 项目正式名称 |
| CoCo | CoCo / 机密容器 | Confidential Containers 的简称 |
| Kata Containers | Kata Containers | CoCo 依赖的容器运行时项目 |
| Kata Agent | Kata Agent / Kata 代理 | 运行在客户机内的代理组件 |
| Kata Shim | Kata Shim | 宿主机侧连接容器运行时与客户机的组件 |
| PodVM | PodVM / Pod 虚拟机 | 承载机密工作负载的虚拟机 |
| Peer Pods | Peer Pods | 通过云 API 启动 PodVM 的部署模式 |
| guest-components | 客户机内组件 | 客户机内组件的集合 |
| image-rs | 镜像处理组件 | 客户机内镜像拉取与解包组件 |
| ocicrypt-rs | OCI 镜像解密组件 | 客户机内镜像解密组件 |
| confidential-data-hub | 机密数据中心 | 客户机内数据与机密访问组件 |
| CDH | 机密数据中心枢纽 | confidential-data-hub 的简称 |
| attestation-agent | 远程证明代理 | 客户机内远程证明客户端 |
| AA | 证明代理 | attestation-agent 的简称 |
| api-server-rest | API 服务代理 | Pod / 客户机内 API 访问代理 |
| cloud-api-adaptor | 云 API 适配器 | 通过云平台接口创建 PodVM 的组件 |
| CAA | 云 API 适配器 | cloud-api-adaptor 的简称 |
| Trustee | Trustee | CoCo 的证明与机密管理服务栈 |
| Key Broker Service | 密钥代理服务 | Trustee 中用于资源发放的服务 |
| KBS | 密钥代理服务 | Key Broker Service 的简称 |
| Attestation Service | 远程证明服务 | Trustee 中负责验证证据的服务 |
| AS | 证明服务 | Attestation Service 的简称 |
| Reference Value Provider Service | 参考值提供服务 | 提供参考测量值的服务 |
| RVPS | 参考值提供服务 | Reference Value Provider Service 的简称 |
| CoCo operator | CoCo Operator | 部署和管理 CoCo 的 Kubernetes Operator |
| nydus-snapshotter | Nydus 快照器 | 配合镜像拉取与快照管理的组件 |
安全、TEE 与证明术语
| English | 中文建议 | 说明 |
|---|---|---|
| confidential computing | 机密计算 | 总体技术范畴 |
| Trusted Execution Environment | 可信执行环境 | 机密计算的核心执行环境 |
| TEE | 可信执行环境 | Trusted Execution Environment 的简称 |
| enclave | 安全飞地 / 隔离执行区 | 受保护的隔离执行环境 |
| Trusted Computing Base | 可信计算基 | 信任边界内的软硬件集合 |
| TCB | 可信计算基 | Trusted Computing Base 的简称 |
| Remote Attestation | 远程证明 | 验证平台或客户机可信性的过程 |
| attestation token | 远程证明令牌 | 远程证明结果的令牌化表示 |
| hardware evidence | 硬件证据 | 由 TEE 平台提供的证据 |
| reference values | 参考值 | 与实际度量结果比对的基准值 |
| verifier | 验证器 / 验证者 | 用于校验证据的组件 / 角色 |
| root of trust | 信任根 | 构建信任链的基础 |
| attestation policy | 远程证明策略 | 判定环境是否可信的策略 |
| resource policy | 资源策略 | 控制 KBS 是否发放资源的策略 |
| TCB claims | TCB 声明 | 从证据中提取的可信属性 |
| trust vector | 信任向量 | 结构化的证明结果表达 |
| EAR | 远程证明结果令牌 | Trustee 文档中使用的证明结果格式术语 |
| AR4SI | 远程证明结果互操作模型 | Trustee 使用的信任结果表达模型 |
| Init-Data | 初始化数据 | 注入客户机内的配置、策略和输入数据 |
| sealed secrets | 密封机密 | 保护机密数据的功能 |
| encrypted images | 加密镜像 | 经过加密保护的容器镜像 |
| signed images | 签名镜像 | 带有签名与完整性校验的镜像 |
| memory encryption | 内存加密 | 保护运行中数据的硬件能力 |
| GetResource | 获取资源请求 | 通过证明后向 KBS 获取机密资源的动作 |
Kubernetes 与容器术语
| English | 中文建议 | 说明 |
|---|---|---|
| CRI | 容器运行时接口 | Container Runtime Interface |
| CRD | 自定义资源定义 | Custom Resource Definition |
| OLM | Operator 生命周期管理器 | Operator Lifecycle Manager |
| Helm | Helm | Kubernetes 包管理工具 |
| Helm Chart | Helm Chart | Helm 安装包 |
| kubectl | kubectl | Kubernetes 命令行工具 |
| KServe | KServe | Kubernetes 上的模型推理平台 |
| ConfigMap | 配置映射 | Kubernetes 配置对象 |
平台、硬件与云环境
| English | 中文建议 | 说明 |
|---|---|---|
| Intel TDX | Intel TDX | Intel Trusted Domain Extension TEE 技术 |
| Intel SGX | Intel SGX | Intel 安全飞地技术 |
| AMD SEV | AMD SEV | AMD 安全加密虚拟化技术 |
| AMD SEV-ES | AMD SEV-ES | 带寄存器状态保护的 SEV 扩展 |
| AMD SEV-SNP | AMD SEV-SNP | 带更强完整性保护的 AMD TEE 技术 |
| IBM Secure Execution | IBM-SE | IBM Z 平台的机密计算能力 |
| IBM Z | IBM Z | IBM 大型机平台 |
| LinuxONE | LinuxONE | IBM 企业级 Linux 平台 |
| s390x | s390x | IBM Z 使用的体系结构 |
| ARM CCA | ARM CCA | ARM 机密计算架构 |
| Hygon CSV | 海光 CSV | 海光平台机密计算技术 |
| Hygon DCU | 海光 DCU | 海光硬件加速器平台 |
| NVIDIA GPU | NVIDIA GPU | 英伟达GPU 机密计算相关平台 |
| Hopper | Hopper | NVIDIA GPU 架构代号 |
| Blackwell | Blackwell | NVIDIA GPU 架构代号 |
| Protected PCIe | 受保护的 PCIe | 英伟达 Hopper 一代多GPU互联总线保护技术 |
| vTPM | 虚拟 TPM | 虚拟化可信平台模块 |
| AWS | 亚马逊云 | 云平台 |
| Azure | 微软云 | 云平台 |
| GCP | Google Cloud | 云平台 |
| Alibaba Cloud | 阿里云 | 云平台 |
| OpenShift | OpenShift | 企业级 Kubernetes 发行版 |
| LibVirt | LibVirt | 本地虚拟化测试场景中使用的平台 |
标准、协议与工具
| English | 中文建议 | 说明 |
|---|---|---|
| OCI | OCI 开放容器规范 | 容器镜像与运行时相关规范 |
| OCI Registry | OCI 镜像仓库 | 存储 OCI 镜像与工件的仓库 |
| SBOM | 软件物料清单 | Software Bill of Materials |
| SLSA | 供应链安全等级框架 | Supply-chain Levels for Software Artifacts |
| in-toto | in-toto 供应链证明框架 | 供应链证明格式与框架 |
| OPA | 开放策略代理 | Open Policy Agent |
| Rego | Rego 策略语言 | OPA 使用的策略语言 |
| ORAS | OCI 工件工具 | 用于操作 OCI Artifact 的 CLI |
| JWK | JSON Web Key | JSON Web 密钥格式 |
| JWT | JSON Web Token | 常见令牌格式 |
| PluginResponse | 插件响应 | KBS 外部插件文档中的接口名 |
| KBC | 密钥代理客户端 | Key Broker Client 的简称 |
扩展项目名与场景术语
| English | 中文建议 | 说明 |
|---|---|---|
| BYOM | 自带机器模型 | Bring Your Own Machine |
| External Secrets Operator | 外部机密 Operator | 常简称 ESO |
| ESO | 外部机密 Operator | External Secrets Operator 的简称 |
| NIM | NVIDIA 推理微服务 | NVIDIA Inference Microservices |
| Switchboard | Switchboard | 相关博客中出现的项目名 |
| IRSA | 服务账户 IAM 角色机制 | AWS 场景常见术语 |
| EKS | Amazon Elastic Kubernetes Service | AWS Kubernetes 托管服务 |
| AKS | Azure Kubernetes Service | Azure Kubernetes 托管服务 |
| GKE | Google Kubernetes Engine | GCP Kubernetes 托管服务 |
| NGC API | NGC API | NVIDIA NGC 访问接口 |
RuntimeClass 与字面标识符
以下名称通常不翻译,中文仅作解释。
| English | 中文建议 | 说明 |
|---|---|---|
kata-qemu-coco-dev |
CoCo 开发运行时 | 非 TEE 开发与测试运行时 |
kata-qemu-coco-dev-runtime-rs |
CoCo Rust 开发运行时 | 基于 Rust 的开发运行时 |
kata-qemu-tdx |
TDX 运行时 | Intel TDX RuntimeClass |
kata-qemu-snp |
SNP 运行时 | AMD SEV-SNP RuntimeClass |
kata-qemu-sev |
SEV 运行时 | AMD SEV RuntimeClass |
kata-qemu-se |
Secure Execution 运行时 | IBM Secure Execution RuntimeClass |
kata-qemu-nvidia-gpu-tdx |
TDX + NVIDIA GPU 运行时 | GPU 机密计算场景 |
kata-qemu-nvidia-gpu-snp |
SNP + NVIDIA GPU 运行时 | GPU 机密计算场景 |
kata-remote |
远程虚拟机运行时 | Peer Pods 或 CAA 场景 |
kata-clh |
Cloud Hypervisor 运行时 | 发行说明中出现的运行时名称 |
kata-clh-tdx |
Cloud Hypervisor TDX 运行时 | 发行说明中出现的运行时名称 |
kata-cc |
CoCo 运行时 | 部分策略示例中出现 |
io.containerd.cri.runtime-handler |
containerd 运行时处理器注解 | 用于选择运行时的注解 |
4 - 云提供商
4.1 - AWS (EKS)
说明: 本文为英文文档的中文译版,英文原版请参见 AWS 示例(英文版)。
本文将介绍如何在 AWS Elastic Kubernetes Service (EKS) 上配置 CAA(即 Peer Pods),具体包括:
- 一个基于 Elastic Kubernetes Service (EKS) 的单工作节点 Kubernetes 集群
- 运行在该 Kubernetes 集群上的 CAA
- 一个由 CAA PodVM 支撑的 Nginx Pod
前提条件
安装所需工具:
AWS 准备工作
- 为 AWS CLI 访问设置
AWS_ACCESS_KEY_ID、AWS_SECRET_ACCESS_KEY(或AWS_PROFILE)以及AWS_REGION
说明: 除了静态凭证外,也可以在 EKS 上使用 IRSA(IAM Roles for Service Accounts)。使用 IRSA 时,CAA Pod 通过 OIDC 完成认证,无需在 Kubernetes Secret 中保存静态 AWS 密钥。集群初始化步骤中仍然需要
AWS_REGION和临时凭证。
- 设置区域:
export AWS_REGION="us-east-2"
说明: 选择
us-east-2区域,是因为这里既提供 AMD SEV-SNP 实例,也提供可直接使用的预构建 PodVM 镜像。
export AWS_REGION="us-east-2"
说明: 选择
us-east-2区域,是因为这里提供可直接使用的预构建 PodVM 镜像。
使用 EKS 部署 Kubernetes
按需修改以下环境变量:
export CLUSTER_NAME="caa-$(date '+%Y%m%b%d%H%M%S')"
export CLUSTER_NODE_TYPE="m5.xlarge"
export CLUSTER_NODE_FAMILY_TYPE="Ubuntu2204"
export SSH_KEY=~/.ssh/id_rsa.pub
以下示例使用默认的 AWS VPC-CNI 创建 EKS 集群:
eksctl create cluster --name "$CLUSTER_NAME" \
--node-type "$CLUSTER_NODE_TYPE" \
--node-ami-family "$CLUSTER_NODE_FAMILY_TYPE" \
--nodes 1 \
--nodes-min 0 \
--nodes-max 2 \
--node-private-networking \
--kubeconfig "$CLUSTER_NAME"-kubeconfig
等待集群创建完成。
为集群节点添加 node.kubernetes.io/worker= 标签:
for NODE_NAME in $(kubectl get nodes -o jsonpath='{.items[*].metadata.name}'); do
kubectl label node $NODE_NAME node.kubernetes.io/worker=
done
放通所需网络端口
EKS_VPC_ID=$(aws eks describe-cluster --name "$CLUSTER_NAME" \
--query "cluster.resourcesVpcConfig.vpcId" \
--output text)
echo $EKS_VPC_ID
EKS_CLUSTER_SG=$(aws eks describe-cluster --name "$CLUSTER_NAME" \
--query "cluster.resourcesVpcConfig.clusterSecurityGroupId" \
--output text)
echo $EKS_CLUSTER_SG
EKS_VPC_CIDR=$(aws ec2 describe-vpcs --vpc-ids "$EKS_VPC_ID" \
--query 'Vpcs[0].CidrBlock' --output text)
echo $EKS_VPC_CIDR
# agent-protocol-forwarder 端口
aws ec2 authorize-security-group-ingress --group-id "$EKS_CLUSTER_SG" --protocol tcp --port 15150 --cidr "$EKS_VPC_CIDR"
# vxlan 端口
aws ec2 authorize-security-group-ingress --group-id "$EKS_CLUSTER_SG" --protocol tcp --port 9000 --cidr "$EKS_VPC_CIDR"
aws ec2 authorize-security-group-ingress --group-id "$EKS_CLUSTER_SG" --protocol udp --port 9000 --cidr "$EKS_VPC_CIDR"
说明:
- 端口
15150是 CAA 连接 PodVM 内agent-protocol-forwarder时使用的默认端口。- 端口
9000是 CAA 使用的 VXLAN 端口。请确保它与 Kubernetes CNI 使用的 VXLAN 端口不冲突。
配置认证
选择 CAA 与 AWS 交互时的认证方式。静态凭证方式通过 Kubernetes Secret 保存访问密钥;IRSA 则通过 OIDC 联邦让 Pod 直接承担 IAM 角色,无需静态密钥。
确保环境中已设置 AWS_ACCESS_KEY_ID 和 AWS_SECRET_ACCESS_KEY(或 AWS_PROFILE)。这些值会存入 Kubernetes Secret,供 CAA Pod 使用。
此处无需额外操作,Secret 将在后续的 在 Kubernetes 集群中部署 Helm Chart 步骤中创建。
IRSA 不再需要把长期 AWS 访问密钥保存在 Kubernetes Secret 中,是 EKS 上推荐使用的认证方式。
启用 OIDC Provider
检查 IAM OIDC provider 是否已经注册:
OIDC_ID=$(aws eks describe-cluster \
--name ${CLUSTER_NAME} \
--region ${AWS_REGION} \
--query "cluster.identity.oidc.issuer" \
--output text | awk -F'/' '{print $NF}')
aws iam list-open-id-connect-providers | grep ${OIDC_ID}
如果命令没有返回结果,则创建 OIDC provider:
eksctl utils associate-iam-oidc-provider \
--cluster ${CLUSTER_NAME} \
--region ${AWS_REGION} \
--approve
导出账户 ID 和 OIDC provider,供后续步骤使用:
export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
export OIDC_PROVIDER=$(aws eks describe-cluster \
--name ${CLUSTER_NAME} \
--region ${AWS_REGION} \
--query "cluster.identity.oidc.issuer" \
--output text | sed 's|https://||')
为 cloud-api-adaptor 创建 IAM Role
export NAMESPACE="confidential-containers-system"
export CAA_SERVICE_ACCOUNT="cloud-api-adaptor"
export CAA_ROLE_NAME="CAA-IRSA-Role"
创建信任策略:
cat > /tmp/caa-trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"${OIDC_PROVIDER}:sub": "system:serviceaccount:${NAMESPACE}:${CAA_SERVICE_ACCOUNT}",
"${OIDC_PROVIDER}:aud": "sts.amazonaws.com"
}
}
}
]
}
EOF
创建 IAM 角色,并附加 AmazonEC2FullAccess 托管策略:
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
说明:
AmazonEC2FullAccess会授予较宽泛的 EC2 权限。用于生产环境时,强烈建议改用更小权限范围的自定义策略,仅授予 CAA 所需的 EC2 操作权限。更多配置方式请参考 AWS IRSA 文档。
导出 role ARN,供后续使用:
export CAA_ROLE_ARN=$(aws iam get-role \
--role-name ${CAA_ROLE_NAME} \
--query 'Role.Arn' \
--output text)
echo "CAA Role ARN: ${CAA_ROLE_ARN}"
为 Peerpod-ctrl 创建 IAM Role(可选)
只有在部署 Peerpod-ctrl 时才需要这一步。
export CTRL_SERVICE_ACCOUNT="peerpodctrl-controller-manager"
export CTRL_ROLE_NAME="PeerpodCtrl-IRSA-Role"
创建信任策略:
cat > /tmp/peerpod-ctrl-trust-policy.json <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/${OIDC_PROVIDER}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"${OIDC_PROVIDER}:sub": "system:serviceaccount:${NAMESPACE}:${CTRL_SERVICE_ACCOUNT}",
"${OIDC_PROVIDER}:aud": "sts.amazonaws.com"
}
}
}
]
}
EOF
创建角色并附加权限:
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
说明:
AmazonEC2FullAccess会授予较宽泛的 EC2 权限。用于生产环境时,强烈建议改用更小权限范围的自定义策略,仅授予 CAA 所需的 EC2 操作权限。更多配置方式请参考 AWS IRSA 文档。
导出 role ARN:
export CTRL_ROLE_ARN=$(aws iam get-role \
--role-name ${CTRL_ROLE_NAME} \
--query 'Role.Arn' \
--output text)
echo "Controller Role ARN: ${CTRL_ROLE_ARN}"
部署 CAA Helm Chart
下载 CAA Helm 部署资源
CAA_VERSION="$(
curl -fsSL \
"https://api.github.com/repos/confidential-containers/cloud-api-adaptor/releases/latest" |
jq -er '.tag_name | sub("^v"; "")'
)"
curl -LO "https://github.com/confidential-containers/cloud-api-adaptor/archive/refs/tags/v${CAA_VERSION}.tar.gz"
tar -xvzf "v${CAA_VERSION}.tar.gz"
cd "cloud-api-adaptor-${CAA_VERSION}/src/cloud-api-adaptor/install/charts/peerpods"
export CAA_BRANCH="main"
curl -LO "https://github.com/confidential-containers/cloud-api-adaptor/archive/refs/heads/${CAA_BRANCH}.tar.gz"
tar -xvzf "${CAA_BRANCH}.tar.gz"
cd "cloud-api-adaptor-${CAA_BRANCH}/src/cloud-api-adaptor/install/charts/peerpods"
假如你已在本地准备好代码,请在终端中切换到 Cloud API Adaptor 的代码目录。
导出 PodVM 镜像版本
导出 peer pods 所用的 PodVM 镜像 ID。该变量告诉部署工具在 AWS 中创建 peer pod 虚拟机时应使用哪个 PodVM 镜像版本。
镜像来自 CoCo 社区镜像库(或由你手动构建),并且必须与当前 CAA 发布版本匹配。
在 us-east-2 区域中,我们提供了一个可用于 PoC 的预构建调试版 PodVM 镜像。可通过以下命令查询对应发布版本的 AMI ID:
export PODVM_AMI_ID=$(aws ec2 describe-images \
--filters Name=name,Values="podvm-ubuntu-amd64-${CAA_VERSION//./-}" \
--query 'Images[*].[ImageId]' --output text)
echo $PODVM_AMI_ID
最新构建没有预构建的 PodVM AMI。你需要自行构建 PodVM 镜像,然后按照这里的说明创建 AMI。
为 AWS 构建 PodVM 镜像前,请记得设置 TEE_PLATFORM=amd。
镜像构建完成后,将镜像 ID 导出到环境变量 PODVM_AMI_ID。
你可以按照这里的说明构建自定义 PodVM 镜像。
为 AWS 构建 PodVM 镜像前,请记得设置 TEE_PLATFORM=amd。
镜像构建完成后,将镜像 ID 导出到环境变量 PODVM_AMI_ID。
导出 CAA 容器镜像路径
定义要部署的 Cloud API Adaptor(CAA)容器镜像。 这些变量指定了部署工具所要拉取和运行的 CAA 镜像及其架构专属 tag。 tag 与 CAA 发布版本对应,以确保与所选 PodVM 镜像和配置兼容。
导出以下环境变量以使用 CAA 最新发布镜像:
export CAA_IMAGE="quay.io/confidential-containers/cloud-api-adaptor"
export CAA_TAG="v${CAA_VERSION}-amd64"
导出以下环境变量,以使用每次合并到 main 后由 CAA CI 构建的镜像:
export CAA_IMAGE="quay.io/confidential-containers/cloud-api-adaptor"
你可以在这里找到适合需求的预构建镜像 tag。
export CAA_TAG=""
注意: 你也可以使用
latesttag,但不推荐这样做,因为它缺少版本控制,可能引入不可预期的更新,影响部署稳定性和可复现性。
如果你修改了 CAA 代码并希望部署这些改动,请按这些说明构建容器镜像。镜像构建完成后,导出环境变量 CAA_IMAGE 和 CAA_TAG。
选择 peer-pods 机型
export PODVM_INSTANCE_TYPE="m6a.large"
export DISABLECVM="false"
更多 AMD SEV-SNP 机型可参考这份 AWS 文档。
export PODVM_INSTANCE_TYPE="t3.large"
export DISABLECVM="true"
填充 providers/aws.yaml 文件
全部可用配置项可在以下两个位置找到:
运行以下命令更新 providers/aws.yaml 文件:
cat <<EOF > providers/aws.yaml
provider: aws
image:
name: "${CAA_IMAGE}"
tag: "${CAA_TAG}"
providerConfigs:
aws:
DISABLECVM: ${DISABLECVM}
PODVM_AMI_ID: "${PODVM_AMI_ID}"
PODVM_INSTANCE_TYPE: "${PODVM_INSTANCE_TYPE}"
VXLAN_PORT: 9000
EOF
安装 cert-manager
CAA 依赖 cert-manager。除非环境中已经安装,否则请使用以下方式部署:
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true \
--wait \
--timeout 5m
在 Kubernetes 集群中部署 Helm Chart
-
创建由 Helm 管理的命名空间:
kubectl apply -f - << EOF apiVersion: v1 kind: Namespace metadata: name: confidential-containers-system labels: app.kubernetes.io/managed-by: Helm annotations: meta.helm.sh/release-name: peerpods meta.helm.sh/release-namespace: confidential-containers-system EOF -
创建凭证并安装 Helm Chart:
下面命令使用了
-f和--set这两个自定义选项,其含义可参考这里。
使用 kubectl 创建 Secret。所需 key 请参见 providers/aws-secrets.yaml.template。
kubectl create secret generic my-provider-creds \
-n confidential-containers-system \
--from-literal=AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID} \
--from-literal=AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} \
--from-file=id_rsa.pub=${SSH_KEY}
说明:
--from-file=id_rsa.pub=${SSH_KEY}是可选项。它允许用户为排障目的通过 SSH 登录 PodVM。 该选项只对启用了调试功能的自定义 PodVM 镜像有效。预构建的 PodVM 镜像默认不启用 SSH 连接。
安装 Helm Chart:
helm install peerpods . \
-f providers/aws.yaml \
--set secrets.mode=reference \
--set secrets.existingSecretName=my-provider-creds \
--dependency-update \
-n confidential-containers-system
使用 IRSA 时,无需创建 AWS 凭证 Secret。CAA Pod 会通过配置认证章节中配置的 IAM 角色完成认证。
使用 IRSA 注解安装 Helm Chart:
helm install peerpods . \
-f providers/aws.yaml \
--set "daemonset.serviceAccount.annotations.eks\.amazonaws\.com/role-arn=${CAA_ROLE_ARN}" \
--set "resourceCtrl.serviceAccount.annotations.eks\.amazonaws\.com/role-arn=${CTRL_ROLE_ARN}" \
--dependency-update \
-n confidential-containers-system
说明:
resourceCtrl.serviceAccount.annotations这一行只有在部署 Peerpod-ctrl 时才需要。 如果不部署它,可以省略这个--set参数,并跳过前文中的 为 Peerpod-ctrl 创建 IAM Role(可选) 步骤。
验证 IRSA 是否生效,可检查服务账号注解和 Pod 环境变量:
kubectl get serviceaccount cloud-api-adaptor \
-n confidential-containers-system \
-o jsonpath='{.metadata.annotations.eks\.amazonaws\.com/role-arn}'
CAA_POD=$(kubectl get pods -n confidential-containers-system \
-l app=cloud-api-adaptor \
-o jsonpath='{.items[0].metadata.name}')
kubectl exec -n confidential-containers-system ${CAA_POD} -- env | grep AWS
输出中应包含 AWS_WEB_IDENTITY_TOKEN_FILE 和 AWS_ROLE_ARN 变量。
通用的 Peer Pods Helm Chart 部署说明也可参考这里。
运行示例应用
确认 RuntimeClass 已创建
部署 CAA 后,请确认已创建 RuntimeClass:
kubectl get runtimeclass
当你看到名为 kata-remote 的 RuntimeClass 时,就说明部署成功。成功输出类似如下:
$ kubectl get runtimeclass
NAME HANDLER AGE
kata-remote kata-remote 7m18s
部署工作负载
创建一个 nginx deployment:
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
runtimeClassName: kata-remote
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
imagePullPolicy: Always
EOF
确认 pod 已成功启动:
kubectl get pods -n default
你可以通过运行以下命令确认 peer pod VM 是否已经创建:
aws ec2 describe-instances --filters "Name=tag:Name,Values=podvm*" \
--query 'Reservations[*].Instances[*].[InstanceId, Tags[?Key==`Name`].Value | [0]]' --output table
此时你应该能看到与 pod nginx 对应的虚拟机。
说明: 如果遇到问题,请查看故障排查指南。
清理
删除所有使用 kata-remote runtimeclass 运行的 Pod。可以使用以下命令:
kubectl get pods -A -o custom-columns='NAME:.metadata.name,NAMESPACE:.metadata.namespace,RUNTIMECLASS:.spec.runtimeClassName' | grep kata-remote | awk '{print $1, $2}'
确认所有 peer-pod VM 都已删除。你可以使用以下命令列出所有 peer-pod VM(名称前缀为 podvm)及其状态:
aws ec2 describe-instances --filters "Name=tag:Name,Values=podvm*" \
--query 'Reservations[*].Instances[*].[InstanceId, Tags[?Key==`Name`].Value | [0], State.Name]' --output table
运行以下命令删除 EKS 集群:
eksctl delete cluster --name=$CLUSTER_NAME
4.2 - Azure Kubernetes Service (AKS)
说明: 本文为英文文档的中文译版,英文原版请参见 Azure 示例(英文版)。
本文将介绍如何在 Azure Kubernetes Service (AKS) 上配置 CAA(即 Peer Pods),具体包括:
- 一个基于 Azure Kubernetes Service (AKS) 的单工作节点 Kubernetes 集群
- 运行在该 Kubernetes 集群上的 CAA
- 一个由 CAA PodVM 支撑的 Nginx Pod
Confidential Containers 也支持将 Azure Key Vault 用作 Trustee 的资源后端。 更多信息
前提条件
安装所需工具:
Azure 准备工作
登录 Azure
以下许多步骤均需先登录 Azure 账号:
az login
获取你的订阅 ID:
export AZURE_SUBSCRIPTION_ID=$(az account show --query id --output tsv)
设置区域:
export AZURE_REGION="eastus"
说明: 选择
eastus区域,是因为这里既提供 AMD SEV-SNP 实例,也提供可直接使用的预构建 PodVM 镜像。
export AZURE_REGION="eastus2"
说明: 选择
eastus2区域,是因为这里既提供 Intel TDX 实例,也提供可直接使用的预构建 PodVM 镜像。
export AZURE_REGION="eastus"
说明: 选择
eastus区域,是因为这里提供可直接使用的预构建 PodVM 镜像。
资源组
说明: 如果你已经有可用的资源组,可以跳过这一步。请将资源组名称导出到环境变量
AZURE_RESOURCE_GROUP。
运行以下命令创建 Azure 资源组:
export AZURE_RESOURCE_GROUP="caa-rg-$(date '+%Y%m%b%d%H%M%S')"
az group create \
--name "${AZURE_RESOURCE_GROUP}" \
--location "${AZURE_REGION}"
使用 AKS 部署 Kubernetes
按需修改以下环境变量:
export CLUSTER_NAME="caa-$(date '+%Y%m%b%d%H%M%S')"
export AKS_WORKER_USER_NAME="azuser"
export AKS_RG="${AZURE_RESOURCE_GROUP}-aks"
export SSH_KEY=~/.ssh/id_rsa.pub
说明: 你也可以通过增加参数
--vnet-subnet-id $MY_SUBNET_ID,将工作节点部署到现有的 Azure Virtual Network (VNet) 和子网中。
将一个单工作节点的 AKS 集群部署到你刚刚创建的资源组中:
az aks create \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--node-resource-group "${AKS_RG}" \
--name "${CLUSTER_NAME}" \
--enable-oidc-issuer \
--enable-workload-identity \
--location "${AZURE_REGION}" \
--node-count 1 \
--node-vm-size Standard_F4s_v2 \
--nodepool-labels node.kubernetes.io/worker= \
--ssh-access disabled \
--admin-username "${AKS_WORKER_USER_NAME}" \
--os-sku Ubuntu
将 kubeconfig 下载到本地,以便用 kubectl 访问集群:
az aks get-credentials \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--name "${CLUSTER_NAME}"
用户分配身份与联合凭证
CAA 需要具备访问 Azure API 的权限。做法是将工作负载身份(workload identity)关联到 CAA 的服务账号上。该工作负载身份(即用户分配身份)将在下一步被授予创建虚拟机、获取镜像和访问网络的权限。
说明: 如果你使用的是现有 AKS 集群,可能需要先配置工作负载身份(workload identity)和 OpenID Connect (OIDC)。可参考指南。
先为 CAA 创建一个身份:
export AZURE_WORKLOAD_IDENTITY_NAME="${CLUSTER_NAME}-identity"
az identity create \
--name "${AZURE_WORKLOAD_IDENTITY_NAME}" \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--location "${AZURE_REGION}"
export USER_ASSIGNED_CLIENT_ID="$(az identity show \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--name "${AZURE_WORKLOAD_IDENTITY_NAME}" \
--query 'clientId' \
-otsv)"
网络
承载 Pod 的虚拟机通常需要访问互联网服务,例如从公共 OCI 镜像仓库拉取镜像。你可以在 AKS 集群所在 VNet 中,为 AKS 子网旁边新建一个独立子网,再为该子网绑定带公网 IP 的 NAT 网关:
export AZURE_VNET_NAME="$(az network vnet list -g ${AKS_RG} --query '[].name' -o tsv)"
export AKS_CIDR="$(az network vnet show -n $AZURE_VNET_NAME -g $AKS_RG --query "subnets[?name == 'aks-subnet'].addressPrefix" -o tsv)"
# 10.224.0.0/16
export MASK="${AKS_CIDR#*/}"
# 16
PEERPOD_CIDR="$(sipcalc $AKS_CIDR -n 2 | grep ^Network | grep -v current | cut -d' ' -f2)/${MASK}"
# 10.225.0.0/16
az network public-ip create -g "$AKS_RG" -n peerpod
az network nat gateway create -g "$AKS_RG" -l "$AZURE_REGION" --public-ip-addresses peerpod -n peerpod
az network vnet subnet create -g "$AKS_RG" --vnet-name "$AZURE_VNET_NAME" --nat-gateway peerpod --address-prefixes "$PEERPOD_CIDR" -n peerpod
export AZURE_SUBNET_ID="$(az network vnet subnet show -g "$AKS_RG" --vnet-name "$AZURE_VNET_NAME" -n peerpod --query id -o tsv)"
AKS 资源组权限
为了让 CAA 能管理虚拟机,需为该身份授予虚拟机和网络相关权限,使其能够在 $AZURE_RESOURCE_GROUP 中创建虚拟机,并连接 $AKS_RG 中的 VNet。
az role assignment create \
--role "Virtual Machine Contributor" \
--assignee "$USER_ASSIGNED_CLIENT_ID" \
--scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourcegroups/${AZURE_RESOURCE_GROUP}"
az role assignment create \
--role "Reader" \
--assignee "$USER_ASSIGNED_CLIENT_ID" \
--scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourcegroups/${AZURE_RESOURCE_GROUP}"
az role assignment create \
--role "Network Contributor" \
--assignee "$USER_ASSIGNED_CLIENT_ID" \
--scope "/subscriptions/${AZURE_SUBSCRIPTION_ID}/resourcegroups/${AKS_RG}"
使用 AKS 集群的 OIDC endpoint 为 CAA ServiceAccount 创建联合凭证:
export AKS_OIDC_ISSUER="$(az aks show \
--name "${CLUSTER_NAME}" \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--query "oidcIssuerProfile.issuerUrl" \
-otsv)"
az identity federated-credential create \
--name "${CLUSTER_NAME}-federated" \
--identity-name "${AZURE_WORKLOAD_IDENTITY_NAME}" \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--issuer "${AKS_OIDC_ISSUER}" \
--subject system:serviceaccount:confidential-containers-system:cloud-api-adaptor \
--audience api://AzureADTokenExchange
部署 CAA Helm Chart
说明: 如果你的 Kubernetes 集群使用 Calico Container Network Interface (CNI),请先按说明为所有工作负载间流量配置 Virtual Extensible LAN (VXLAN) 封装。
下载 CAA Helm 部署资源
CAA_VERSION="$(
curl -fsSL \
"https://api.github.com/repos/confidential-containers/cloud-api-adaptor/releases/latest" |
jq -er '.tag_name | sub("^v"; "")'
)"
curl -LO "https://github.com/confidential-containers/cloud-api-adaptor/archive/refs/tags/v${CAA_VERSION}.tar.gz"
tar -xvzf "v${CAA_VERSION}.tar.gz"
cd "cloud-api-adaptor-${CAA_VERSION}/src/cloud-api-adaptor/install/charts/peerpods"
export CAA_BRANCH="main"
curl -LO "https://github.com/confidential-containers/cloud-api-adaptor/archive/refs/heads/${CAA_BRANCH}.tar.gz"
tar -xvzf "${CAA_BRANCH}.tar.gz"
cd "cloud-api-adaptor-${CAA_BRANCH}/src/cloud-api-adaptor/install/charts/peerpods"
假如你已在本地准备好代码,请在终端中切换到 Cloud API Adaptor 的代码目录。
导出 PodVM 镜像版本
导出 peer pods 所用的 PodVM 镜像 ID。该变量告诉部署工具在 Azure 中创建 peer pod 虚拟机时应使用哪个 PodVM 镜像版本。
镜像来自 CoCo 社区镜像库(或由你手动构建),并且必须与当前 CAA 发布版本匹配。
导出以下环境变量,作为 peer pod VM 使用的镜像:
export AZURE_IMAGE_ID="/CommunityGalleries/cococommunity-42d8482d-92cd-415b-b332-7648bd978eff/Images/peerpod-podvm-fedora/Versions/${CAA_VERSION}"
自动化任务会在每天 00:00 UTC 构建一次 PodVM 镜像。你可以导出以下环境变量来使用该镜像:
SUCCESS_TIME=$(curl -s \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/confidential-containers/cloud-api-adaptor/actions/workflows/azure-nightly-build.yml/runs?status=success" \
| jq -r '.workflow_runs[0].updated_at')
export AZURE_IMAGE_ID="/CommunityGalleries/cocopodvm-d0e4f35f-5530-4b9c-8596-112487cdea85/Images/podvm_image0/Versions/$(date -u -jf "%Y-%m-%dT%H:%M:%SZ" "$SUCCESS_TIME" "+%Y.%m.%d" 2>/dev/null || date -d "$SUCCESS_TIME" +%Y.%m.%d)"
版本号格式为 YYYY.MM.DD,最新镜像对应今天或前一天的日期。
如果你修改了会影响 PodVM 镜像的 CAA 代码,并希望部署这些改动,请按这些说明构建 PodVM 镜像。
镜像构建完成后,将镜像 ID 导出到环境变量 AZURE_IMAGE_ID。
导出 CAA 容器镜像路径
定义要部署的 Cloud API Adaptor(CAA)容器镜像。 这些变量指定了部署工具所要拉取和运行的 CAA 镜像及其架构专属 tag。 tag 与 CAA 发布版本对应,以确保与所选 PodVM 镜像和配置兼容。
导出以下环境变量以使用 CAA 最新发布镜像:
export CAA_IMAGE="quay.io/confidential-containers/cloud-api-adaptor"
export CAA_TAG="v${CAA_VERSION}-amd64"
导出以下环境变量,以使用每次合并到 main 后由 CAA CI 构建的镜像:
export CAA_IMAGE="quay.io/confidential-containers/cloud-api-adaptor"
你可以在这里找到适合需求的预构建镜像 tag。
export CAA_TAG=""
注意: 你也可以使用
latesttag,但不推荐这样做,因为它缺少版本控制,可能引入不可预期的更新,影响部署稳定性和可复现性。
如果你修改了 CAA 代码并希望部署这些改动,请按说明构建容器镜像。镜像构建完成后,导出环境变量 CAA_IMAGE 和 CAA_TAG。
选择 peer-pods 机型
export AZURE_INSTANCE_SIZE="Standard_DC2as_v5"
export DISABLECVM="false"
更多 AMD SEV-SNP 机型可参考Azure 文档 。
export AZURE_INSTANCE_SIZE="Standard_DC2es_v6"
export DISABLECVM="false"
更多 Intel TDX 机型可参考Azure 文档。
export AZURE_INSTANCE_SIZE="Standard_D2as_v5"
export DISABLECVM="true"
填充 providers/azure.yaml 文件
全部可用配置项可在以下两个位置找到:
运行以下命令更新 providers/azure.yaml 文件:
cat <<EOF > providers/azure.yaml
provider: azure
image:
name: "${CAA_IMAGE}"
tag: "${CAA_TAG}"
providerConfigs:
azure:
AZURE_IMAGE_ID: "${AZURE_IMAGE_ID}"
AZURE_REGION: "${AZURE_REGION}"
AZURE_RESOURCE_GROUP: "${AZURE_RESOURCE_GROUP}"
AZURE_SUBNET_ID: "${AZURE_SUBNET_ID}"
AZURE_SUBSCRIPTION_ID: "${AZURE_SUBSCRIPTION_ID}"
AZURE_INSTANCE_SIZE: "${AZURE_INSTANCE_SIZE}"
DISABLECVM: ${DISABLECVM}
EOF
在 Kubernetes 集群中部署 Helm Chart
-
创建由 Helm 管理的命名空间:
kubectl apply -f - << EOF apiVersion: v1 kind: Namespace metadata: name: confidential-containers-system labels: app.kubernetes.io/managed-by: Helm annotations: meta.helm.sh/release-name: peerpods meta.helm.sh/release-namespace: confidential-containers-system EOF -
使用
kubectl创建 Secret:所需 key 请参见 providers/azure-secrets.yaml.template。
说明: 以下示例假定你使用工作负载身份(workload identity)进行认证,因此不需要提供
AZURE_CLIENT_SECRET和AZURE_TENANT_ID。
kubectl create secret generic my-provider-creds \
-n confidential-containers-system \
--from-literal=AZURE_CLIENT_ID="${USER_ASSIGNED_CLIENT_ID}" \
--from-file=id_rsa.pub=${SSH_KEY}
说明:
--from-file=id_rsa.pub=${SSH_KEY}是可选项。它允许用户为排障目的通过 SSH 登录 PodVM。 该选项只对启用了调试功能的自定义 PodVM 镜像有效。预构建的 PodVM 镜像默认不启用 SSH 连接。
-
安装 Helm Chart:
下面命令使用了
-f和--set这两个自定义选项,其含义可参考这里。helm install peerpods . \ -f providers/azure.yaml \ --set secrets.mode=reference \ --set secrets.existingSecretName=my-provider-creds \ --set-json daemonset.podLabels='{"azure.workload.identity/use":"true"}' \ --dependency-update \ -n confidential-containers-system
说明: 上述示例假定你使用工作负载身份(workload identity)进行认证。
--set-json daemonset.podLabels='{{"azure.workload.identity/use":"true"}}'参数仅在使用工作负载身份时才需要。
通用的 Peer Pods Helm Chart 部署说明也可参考这里。
运行示例应用
确认 RuntimeClass 已创建
部署 Peer Pods Helm Chart 后,请确认已创建 runtimeclass:
kubectl get runtimeclass
当你看到名为 kata-remote 的 runtimeclass 时,就说明部署成功。
成功输出类似如下:
$ kubectl get runtimeclass
NAME HANDLER AGE
kata-remote kata-remote 7m18s
部署工作负载
创建一个 nginx deployment:
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
runtimeClassName: kata-remote
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
imagePullPolicy: Always
EOF
确认 pod 已成功启动:
kubectl get pods -n default
你可以通过运行以下命令确认 peer pod VM 是否已经创建:
az vm list \
--resource-group "${AZURE_RESOURCE_GROUP}" \
--output table
此时你应该能看到与 pod nginx 对应的虚拟机。
说明: 如果遇到问题,请查看故障排查指南。
PodVM 参考值
PodVM 镜像在构建过程中,会把预期的 PCR 测量值发布到 OCI 镜像仓库中。
前提条件
安装 ORAS 工具,以便从 OCI 镜像仓库拉取参考值;安装 GitHub CLI,以便验证构建来源。
验证构建来源
确认这些测量值是由官方仓库中可信的构建流程生成的。指定 --format=json 可以看到更详细的构建信息。
CAA_REPO="confidential-containers/cloud-api-adaptor"
OCI_REGISTRY="ghcr.io/${CAA_REPO}/measurements/azure/podvm:${CAA_VERSION}"
gh attestation verify -R "$CAA_REPO" "oci://${OCI_REGISTRY}"
获取参考值
这些 PCR 值可用于远程证明策略中,以校验 PodVM 镜像的完整性。
oras pull "$OCI_REGISTRY"
jq -r .measurements.sha256.pcr11 < measurements.json
0x58e8afdf5b105fc6b202eb8e537a9f1512a4b33cd5921171b518645a86ca5a75
清理
如果你希望清理整个环境,可以运行以下命令删除资源组:
az group delete \
--name "${AZURE_RESOURCE_GROUP}" \
--yes --no-wait
4.3 - Google Cloud Platform (GCP)
说明: 本文为英文文档的中文译版,英文原版请参见 GCP 示例(英文版)。
本文将介绍如何在 Google Kubernetes Engine (GKE) 上配置 CAA(即 Peer Pods),具体包括:
- 一个基于 GKE 的单工作节点 Kubernetes 集群
- 运行在该 Kubernetes 集群上的 CAA
- 一个由 CAA PodVM 支持的示例应用
前提条件
安装所需工具:
Google Cloud 项目:
- 确保你已经创建了一个 Google Cloud 项目
- 记录项目 ID(导出为
GCP_PROJECT_ID)
GCP 准备工作
先完成 Google 认证,并选择要使用的项目:
export GCP_PROJECT_ID="YOUR_PROJECT_ID"
gcloud auth login
gcloud config set project ${GCP_PROJECT_ID}
启用所需 API:
gcloud services enable container.googleapis.com --project=${GCP_PROJECT_ID}
创建一个具备所需权限的服务账号:
gcloud iam service-accounts create peerpods \
--description="Peerpods Service Account" \
--display-name="Peerpods Service Account"
gcloud projects add-iam-policy-binding ${GCP_PROJECT_ID} \
--member="serviceAccount:peerpods@${GCP_PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/compute.instanceAdmin.v1"
gcloud projects add-iam-policy-binding ${GCP_PROJECT_ID} \
--member="serviceAccount:peerpods@${GCP_PROJECT_ID}.iam.gserviceaccount.com" \
--role="roles/iam.serviceAccountUser"
生成并保存凭证文件:
gcloud iam service-accounts keys create \
~/.config/gcloud/peerpods_application_key.json \
--iam-account=peerpods@${GCP_PROJECT_ID}.iam.gserviceaccount.com
export GOOGLE_APPLICATION_CREDENTIALS=~/.config/gcloud/peerpods_application_key.json
配置后续要使用的其他环境变量。
设置区域:
export GCP_REGION="us-central1"
之所以选择 us-central1,是因为它支持机密虚拟机。支持区域的完整列表请访问:
https://cloud.google.com/confidential-computing/confidential-vm/docs/supported-configurations#supported-zones
设置 PodVM 实例类型:
export PODVM_INSTANCE_TYPE="n2d-standard-4"
export DISABLECVM=false
export GCP_CONFIDENTIAL_TYPE="SEV" # SEV or SEV_SNP
export GCP_DISK_TYPE="pd-standard"
export PODVM_INSTANCE_TYPE="c3-standard-4"
export DISABLECVM=false
export GCP_CONFIDENTIAL_TYPE="TDX"
export GCP_DISK_TYPE="pd-balanced"
export PODVM_INSTANCE_TYPE="e2-medium"
export DISABLECVM=true
export GCP_CONFIDENTIAL_TYPE=""
export GCP_DISK_TYPE="pd-standard"
使用 GKE 部署 Kubernetes
使用 GKE 部署一个单节点 Kubernetes 集群:
gcloud container clusters create my-cluster \
--zone ${GCP_REGION}-a \
--machine-type "e2-standard-4" \
--image-type UBUNTU_CONTAINERD \
--num-nodes 1
为工作节点添加标签:
kubectl get nodes --selector='!node-role.kubernetes.io/master' -o name | \
xargs -I{} kubectl label {} node.kubernetes.io/worker=
从 GKE 1.27 开始,GCP 会为 containerd 配置 discard_unpacked_layers=true,以节省磁盘空间(移除解包后的压缩镜像层)。但这可能会导致 PeerPods 出现问题,因为工作负载可能找不到所需镜像层。
为避免这个问题,请在 containerd 配置中禁用 discard_unpacked_layers。
如果遇到虚拟机未正常运行的问题,请查看本页的故障排查部分。
配置 VPC 网络
我们需要确保默认 VPC 网络中已经放通 15150 端口:
gcloud compute firewall-rules create allow-port-15150 \
--project=${GCP_PROJECT_ID} \
--network=default \
--allow=tcp:15150
在生产场景中,建议限制来源 IP 范围,以降低安全风险。例如,可以把来源范围限制为特定 IP 地址或 CIDR 段:
gcloud compute firewall-rules create allow-port-15150-restricted \
--project=${GCP_PROJECT_ID} \
--network=default \
--allow=tcp:15150 \
--source-ranges=[YOUR_EXTERNAL_IP]
部署 CAA Helm Chart
下载 CAA Helm 部署资源
CAA_VERSION="$(
curl -fsSL \
"https://api.github.com/repos/confidential-containers/cloud-api-adaptor/releases/latest" |
jq -er '.tag_name | sub("^v"; "")'
)"
curl -LO "https://github.com/confidential-containers/cloud-api-adaptor/archive/refs/tags/v${CAA_VERSION}.tar.gz"
tar -xvzf "v${CAA_VERSION}.tar.gz"
cd "cloud-api-adaptor-${CAA_VERSION}/src/cloud-api-adaptor/install/charts/peerpods"
export CAA_BRANCH="main"
curl -LO "https://github.com/confidential-containers/cloud-api-adaptor/archive/refs/heads/${CAA_BRANCH}.tar.gz"
tar -xvzf "${CAA_BRANCH}.tar.gz"
cd "cloud-api-adaptor-${CAA_BRANCH}/src/cloud-api-adaptor/install/charts/peerpods"
此方式假定你已在本地准备好代码,请在终端中切换到 Cloud API Adaptor 的代码目录。
导出 PodVM 镜像版本
导出 peer pods 所用的 PodVM 镜像 ID。该变量告诉部署工具在 Google Cloud 中创建 peer pod 虚拟机时应使用哪个 PodVM 镜像版本。
镜像来自 CoCo 社区镜像库(或由你手动构建),并且必须与当前 CAA 发布版本匹配。
导出以下环境变量,作为 PodVM 使用的镜像:
export PODVM_IMAGE_ID="/projects/it-cloud-gcp-prod-osc-devel/global/images/fedora-mkosi-tee-amd-1-11-0"
最新构建没有预构建的 PodVM 镜像。你需要按说明构建 PodVM 镜像。镜像构建完成后,将镜像 ID 导出到环境变量 PODVM_IMAGE_ID。
如果你修改了会影响 PodVM 镜像的 CAA 代码,并希望部署这些改动,请按说明构建 PodVM 镜像。镜像构建完成后,将镜像 ID 导出到环境变量 PODVM_IMAGE_ID。
导出 CAA 容器镜像路径
定义要部署的 Cloud API Adaptor(CAA)容器镜像。 这些变量指定了部署工具所要拉取和运行的 CAA 镜像及其架构专属 tag。 tag 与 CAA 发布版本对应,以确保与所选 PodVM 镜像和配置兼容。
导出以下环境变量以使用 CAA 最新发布镜像:
export CAA_IMAGE="quay.io/confidential-containers/cloud-api-adaptor"
export CAA_TAG="v${CAA_VERSION}-amd64"
导出以下环境变量,以使用每次合并到 main 后由 CAA CI 构建的镜像:
export CAA_IMAGE="quay.io/confidential-containers/cloud-api-adaptor"
你可以在这里找到适合需求的预构建镜像 tag。
export CAA_TAG=""
注意: 你也可以使用
latesttag,但不推荐这样做,因为它缺少版本控制,可能引入不可预期的更新,影响部署稳定性和可复现性。
如果你修改了 CAA 代码并希望部署这些改动,请按这些说明构建容器镜像。镜像构建完成后,导出环境变量 CAA_IMAGE 和 CAA_TAG。
填充 providers/gcp.yaml 文件
全部可用配置项可在以下两个位置找到:
运行以下命令更新 providers/gcp.yaml 文件:
cat <<EOF > providers/gcp.yaml
provider: gcp
image:
name: "${CAA_IMAGE}"
tag: "${CAA_TAG}"
providerConfigs:
gcp:
GCP_NETWORK: "global/networks/default"
GCP_PROJECT_ID: "${GCP_PROJECT_ID}"
GCP_ZONE: "${GCP_REGION}-a"
GCP_MACHINE_TYPE: "${PODVM_INSTANCE_TYPE}"
GCP_DISK_TYPE: "${GCP_DISK_TYPE}"
PODVM_IMAGE_NAME: "${PODVM_IMAGE_ID}"
GCP_CONFIDENTIAL_TYPE: "${GCP_CONFIDENTIAL_TYPE}"
DISABLECVM: ${DISABLECVM}
EOF
在 Kubernetes 集群中部署 Helm Chart
-
创建由 Helm 管理的命名空间:
kubectl apply -f - << EOF apiVersion: v1 kind: Namespace metadata: name: confidential-containers-system labels: app.kubernetes.io/managed-by: Helm annotations: meta.helm.sh/release-name: peerpods meta.helm.sh/release-namespace: confidential-containers-system EOF -
使用
kubectl创建 Secret:所需 key 请参见 providers/gcp-secrets.yaml.template。
kubectl create secret generic my-provider-creds \ -n confidential-containers-system \ --from-file=GCP_CREDENTIALS="${GOOGLE_APPLICATION_CREDENTIALS}" -
安装 Helm Chart:
下面命令使用了
-f和--set这两个自定义选项,其含义可参考这里。helm install peerpods . \ -f providers/gcp.yaml \ --set secrets.mode=reference \ --set secrets.existingSecretName=my-provider-creds \ --dependency-update \ -n confidential-containers-system
通用的 Peer Pods Helm Chart 部署说明也可参考这里。
运行示例应用
确认 RuntimeClass 已创建
部署 Peer Pods Helm Chart 后,请确认已创建 runtimeclass:
kubectl get runtimeclass
当你看到名为 kata-remote 的 runtimeclass 时,就说明部署成功。
成功输出类似如下:
$ kubectl get runtimeclass
NAME HANDLER AGE
kata-remote kata-remote 7m18s
部署工作负载
本示例展示了一个更完整的部署方式:结合 TEE、机密虚拟机以及 kata-remote RuntimeClass,演示如何部署示例 Pod 并在机密计算环境中安全获取机密。
准备 init data 配置
Peer Pods 现已支持 init data。你可以通过注解 io.katacontainers.config.hypervisor.cc_init_data 传入所需配置文件(aa.toml、cdh.toml 和 policy.rego)。下面给出配置和使用示例。
# initdata.toml
algorithm = "sha384"
version = "0.1.0"
[data]
"aa.toml" = '''
[token_configs]
[token_configs.coco_as]
url = 'http://127.0.0.1:8080'
[token_configs.kbs]
url = 'http://127.0.0.1:8080'
cert = """
-----BEGIN CERTIFICATE-----
MIIDljCCAn6gAwIBAgIUR/UNh13GFam4emgludtype/S9BIwDQYJKoZIhvcNAQEL
BQAwdTELMAkGA1UEBhMCQ04xETAPBgNVBAgMCFpoZWppYW5nMREwDwYDVQQHDAhI
YW5nemhvdTERMA8GA1UECgwIQUFTLVRFU1QxFDASBgNVBAsMC0RldmVsb3BtZW50
MRcwFQYDVQQDDA5BQVMtVEVTVC1IVFRQUzAeFw0yNDAzMTgwNzAzNTNaFw0yNTAz
MTgwNzAzNTNaMHUxCzAJBgNVBAYTAkNOMREwDwYDVQQIDAhaaGVqaWFuZzERMA8G
A1UEBwwISGFuZ3pob3UxETAPBgNVBAoMCEFBUy1URVNUMRQwEgYDVQQLDAtEZXZl
bG9wbWVudDEXMBUGA1UEAwwOQUFTLVRFU1QtSFRUUFMwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQDfp1aBr6LiNRBlJUcDGcAbcUCPG6UzywtVIc8+comS
ay//gwz2AkDmFVvqwI4bdp/NUCwSC6ShHzxsrCEiagRKtA3af/ckM7hOkb4S6u/5
ewHHFcL6YOUp+NOH5/dSLrFHLjet0dt4LkyNBPe7mKAyCJXfiX3wb25wIBB0Tfa0
p5VoKzwWeDQBx7aX8TKbG6/FZIiOXGZdl24DGARiqE3XifX7DH9iVZ2V2RL9+3WY
05GETNFPKtcrNwTy8St8/HsWVxjAzGFzf75Lbys9Ff3JMDsg9zQzgcJJzYWisxlY
g3CmnbENP0eoHS4WjQlTUyY0mtnOwodo4Vdf8ZOkU4wJAgMBAAGjHjAcMBoGA1Ud
EQQTMBGCCWxvY2FsaG9zdIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAKW32spii
t2JB7C1IvYpJw5mQ5bhIlldE0iB5rwWvNbuDgPrgfTI4xiX5sumdHw+P2+GU9KXF
nWkFRZ9W/26xFrVgGIS/a07aI7xrlp0Oj+1uO91UhCL3HhME/0tPC6z1iaFeZp8Y
T1tLnafqiGiThFUgvg6PKt86enX60vGaTY7sslRlgbDr9sAi/NDSS7U1PviuC6yo
yJi7BDiRSx7KrMGLscQ+AKKo2RF1MLzlJMa1kIZfvKDBXFzRd61K5IjDRQ4HQhwX
DYEbQvoZIkUTc1gBUWDcAUS5ztbJg9LCb9WVtvUTqTP2lGuNymOvdsuXq+sAZh9b
M9QaC1mzQ/OStg==
-----END CERTIFICATE-----
"""
'''
"cdh.toml" = '''
socket = 'unix:///run/confidential-containers/cdh.sock'
credentials = []
[kbc]
name = 'cc_kbc'
url = 'http://1.2.3.4:8080'
kbs_cert = """
-----BEGIN CERTIFICATE-----
MIIFTDCCAvugAwIBAgIBADBGBgkqhkiG9w0BAQowOaAPMA0GCWCGSAFlAwQCAgUA
oRwwGgYJKoZIhvcNAQEIMA0GCWCGSAFlAwQCAgUAogMCATCjAwIBATB7MRQwEgYD
VQQLDAtFbmdpbmVlcmluZzELMAkGA1UEBhMCVVMxFDASBgNVBAcMC1NhbnRhIENs
YXJhMQswCQYDVQQIDAJDQTEfMB0GA1UECgwWQWR2YW5jZWQgTWljcm8gRGV2aWNl
czESMBAGA1UEAwwJU0VWLU1pbGFuMB4XDTIzMDEyNDE3NTgyNloXDTMwMDEyNDE3
NTgyNlowejEUMBIGA1UECwwLRW5naW5lZXJpbmcxCzAJBgNVBAYTAlVTMRQwEgYD
VQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExHzAdBgNVBAoMFkFkdmFuY2Vk
IE1pY3JvIERldmljZXMxETAPBgNVBAMMCFNFVi1WQ0VLMHYwEAYHKoZIzj0CAQYF
K4EEACIDYgAExmG1ZbuoAQK93USRyZQcsyobfbaAEoKEELf/jK39cOVJt1t4s83W
XM3rqIbS7qHUHQw/FGyOvdaEUs5+wwxpCWfDnmJMAQ+ctgZqgDEKh1NqlOuuKcKq
2YAWE5cTH7sHo4IBFjCCARIwEAYJKwYBBAGceAEBBAMCAQAwFwYJKwYBBAGceAEC
BAoWCE1pbGFuLUIwMBEGCisGAQQBnHgBAwEEAwIBAzARBgorBgEEAZx4AQMCBAMC
AQAwEQYKKwYBBAGceAEDBAQDAgEAMBEGCisGAQQBnHgBAwUEAwIBADARBgorBgEE
AZx4AQMGBAMCAQAwEQYKKwYBBAGceAEDBwQDAgEAMBEGCisGAQQBnHgBAwMEAwIB
CDARBgorBgEEAZx4AQMIBAMCAXMwTQYJKwYBBAGceAEEBEDDhCejDUx6+dlvehW5
cmmCWmTLdqI1L/1dGBFdia1HP46MC82aXZKGYSutSq37RCYgWjueT+qCMBE1oXDk
d1JOMEYGCSqGSIb3DQEBCjA5oA8wDQYJYIZIAWUDBAICBQChHDAaBgkqhkiG9w0B
AQgwDQYJYIZIAWUDBAICBQCiAwIBMKMDAgEBA4ICAQACgCai9x8DAWzX/2IelNWm
ituEBSiq9C9eDnBEckQYikAhPasfagnoWFAtKu/ZWTKHi+BMbhKwswBS8W0G1ywi
cUWGlzigI4tdxxf1YBJyCoTSNssSbKmIh5jemBfrvIBo1yEd+e56ZJMdhN8e+xWU
bvovUC2/7Dl76fzAaACLSorZUv5XPJwKXwEOHo7FIcREjoZn+fKjJTnmdXce0LD6
9RHr+r+ceyE79gmK31bI9DYiJoL4LeGdXZ3gMOVDR1OnDos5lOBcV+quJ6JujpgH
d9g3Sa7Du7pusD9Fdap98ocZslRfFjFi//2YdVM4MKbq6IwpYNB+2PCEKNC7SfbO
NgZYJuPZnM/wViES/cP7MZNJ1KUKBI9yh6TmlSsZZOclGJvrOsBZimTXpATjdNMt
cluKwqAUUzYQmU7bf2TMdOXyA9iH5wIpj1kWGE1VuFADTKILkTc6LzLzOWCofLxf
onhTtSDtzIv/uel547GZqq+rVRvmIieEuEvDETwuookfV6qu3D/9KuSr9xiznmEg
xynud/f525jppJMcD/ofbQxUZuGKvb3f3zy+aLxqidoX7gca2Xd9jyUy5Y/83+ZN
bz4PZx81UJzXVI9ABEh8/xilATh1ZxOePTBJjN7lgr0lXtKYjV/43yyxgUYrXNZS
oLSG2dLCK9mjjraPjau34Q==
-----END CERTIFICATE-----
"""
'''
"policy.rego" = '''
package agent_policy
import future.keywords.in
import future.keywords.every
import input
# Default values, returned by OPA when rules cannot be evaluated to true.
default CopyFileRequest := true
default CreateContainerRequest := true
default CreateSandboxRequest := true
default DestroySandboxRequest := true
default ExecProcessRequest := false
default GetOOMEventRequest := true
default GuestDetailsRequest := true
default OnlineCPUMemRequest := true
default PullImageRequest := true
default ReadStreamRequest := false
default RemoveContainerRequest := true
default RemoveStaleVirtiofsShareMountsRequest := true
default SignalProcessRequest := true
default StartContainerRequest := true
default StatsContainerRequest := true
default TtyWinResizeRequest := true
default UpdateEphemeralMountsRequest := true
default UpdateInterfaceRequest := true
default UpdateRoutesRequest := true
default WaitProcessRequest := true
default WriteStreamRequest := false
'''
请确认策略正确,并且 KBC URL 已指向你的 Key Broker Service。
然后对 initdata.toml 进行编码并保存为变量:
INITDATA=$(cat initdata.toml | gzip | base64 -w0)
使用以下命令部署 Pod:
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: example-pod
annotations:
io.katacontainers.config.hypervisor.cc_init_data: "$INITDATA"
spec:
runtimeClassName: kata-remote
containers:
- name: example-container
image: alpine:latest
command:
- sleep
- "3600"
securityContext:
privileged: false
seccompProfile:
type: RuntimeDefault
EOF
从 Trustee 获取机密
Pod 成功携带 initdata 部署后,你可以在 Pod 内部从 Trustee 服务获取机密。使用以下命令获取指定机密:
kubectl exec -it example-pod -- curl http://127.0.0.1:8006/cdh/resource/default/kbsres1/key1
本示例是最基础的部署验证,用于确认 Helm Chart 是否已在云厂商侧成功启动 PodVM。
创建一个 nginx deployment:
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
namespace: default
spec:
selector:
matchLabels:
app: nginx
replicas: 1
template:
metadata:
labels:
app: nginx
spec:
runtimeClassName: kata-remote
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
imagePullPolicy: Always
EOF
确认 pod 已成功启动:
kubectl get pods -n default
你可以通过运行以下命令确认 PodVM 是否已经创建:
gcloud compute instances list
此时你应该能看到与上述示例 Pod 对应的虚拟机。
清理
删除所有使用 kata-remote RuntimeClass 运行的 Pod。可以使用以下命令:
kubectl get pods -A -o custom-columns='NAME:.metadata.name,NAMESPACE:.metadata.namespace,RUNTIMECLASS:.spec.runtimeClassName' | grep kata-remote | awk '{print $1, $2}'
确认所有 peer-pod VM 都已删除。你可以使用以下命令列出所有 peer-pod VM(名称前缀为 podvm)及其状态:
gcloud compute instances list \
--filter="name~'podvm.*'" \
--format="table(name,zone,status)"
运行以下命令删除 GKE 集群:
gcloud container clusters delete my-cluster --zone ${GCP_REGION}-a
故障排查
说明: 如果你的问题不在下面的范围内,请查看这里的故障排查指南。
虚拟机未启动
从 GKE 1.27 开始,GCP 会为 containerd 配置 discard_unpacked_layers=true,以节省磁盘空间(移除解包后的压缩镜像层)。但这可能会导致 PeerPods 出现问题,因为工作负载可能找不到所需镜像层。
为避免这个问题,请在 containerd 配置中禁用 discard_unpacked_layers。
大多数情况下,你会看到类似下面的通用报错:
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
要在 Google Kubernetes Engine (GKE) 1.27 及以上版本中禁用 containerd 配置里的 discard_unpacked_layers,请按以下步骤操作:
- 在 Google Console 中 SSH 登录工作节点
- 运行命令
sudo sed -i 's/discard_unpacked_layers = true/discard_unpacked_layers = false/' /etc/containerd/config.toml - 运行
sudo cat /etc/containerd/config.toml | grep discard_unpacked_layers验证修改结果 - 重启 containerd:
sudo systemctl restart containerd
4.4 - 阿里云
说明: 本文为英文文档的中文译版,英文原版请参见阿里云示例(英文版)。
本文将介绍如何在阿里云容器服务 Kubernetes 版(ACK)和阿里云弹性计算服务(ECS)上部署 CAA(即 Peer Pods),具体包括:
- ACK 托管集群中的一个工作节点
- 运行在该 Kubernetes 集群上的 CAA
- 一个由运行在 ECS 上 CAA PodVM 支撑的 Nginx Pod
说明: 请在目录
src/cloud-api-adaptor下运行以下命令。
说明: 当前机密计算实例已在部分地域提供。
说明: 阿里云官方文档可参考这里。
前提条件
安装所需工具:
创建 PodVM 镜像
说明: 在
cn-hongkong地域中,版本0.22.0已提供一个预构建的社区镜像(id:m-j6c3kfz2vhu6ze04wacb),可直接用于测试。 也可以使用下方导出 PodVM 镜像版本中列出的各地域镜像 ID。
如果你希望自行构建 PodVM 镜像,请按以下步骤操作。当前 PodVM 构建使用 mkosi,目标系统为 Ubuntu 26.04(resolute)。前提条件和自定义选项请参见 PodVM README。
-
构建 Ubuntu 26.04 PodVM 镜像。
cd podvm make此命令将构建 PodVM 二进制文件和操作系统镜像,生成的 qcow2 文件路径为:
podvm/build/podvm-ubuntu-amd64.qcow2若二进制文件已存在,仅重新构建操作系统镜像:
cd podvm make image -
上传到 OSS,并创建 ECS 镜像。
在
src/cloud-api-adaptor目录下,将 qcow2 文件上传到 OSS(对象存储服务):cd .. export REGION_ID=<region-id> export IMAGE_FILE=podvm/build/podvm-ubuntu-amd64.qcow2 export BUCKET=<OSS-bucket-name> export OBJECT=<object-name> aliyun oss cp ${IMAGE_FILE} oss://${BUCKET}/${OBJECT}然后将该镜像文件导入为 ECS 镜像:
export IMAGE_NAME=$(basename ${IMAGE_FILE%.*}) aliyun ecs ImportImage --ImageName ${IMAGE_NAME} \ --region ${REGION_ID} --RegionId ${REGION_ID} \ --BootMode UEFI \ --DiskDeviceMapping.1.OSSBucket ${BUCKET} --DiskDeviceMapping.1.OSSObject ${OBJECT} \ --Features.NvmeSupport supported \ --method POST --force export POD_IMAGE_ID=<ImageId>
构建 CAA 开发镜像
如果你希望自行构建 CAA DaemonSet 镜像:
export registry=<registry-address>
export RELEASE_BUILD=true
export CLOUD_PROVIDER=alibabacloud
make image
请记录该镜像使用的 tag,后续会用到。
使用 ACK 托管集群部署 Kubernetes
-
创建 ACK 托管集群。
export CONTAINER_CIDR=172.18.0.0/16 export REGION_ID=cn-beijing export ZONES='["cn-beijing-i"]' aliyun cs CreateCluster --header "Content-Type=application/json" --body " { \"cluster_type\":\"ManagedKubernetes\", \"name\":\"caa\", \"region_id\":\"${REGION_ID}\", \"zone_ids\":${ZONES}, \"enable_rrsa\":true, \"container_cidr\":\"${CONTAINER_CIDR}\", \"addons\":[ { \"name\":\"flannel\" } ] }" export CLUSTER_ID=<cluster-id> export SECURITY_GROUP_ID=$(aliyun cs DescribeClusterDetail --ClusterId ${CLUSTER_ID} | jq -r ".security_group_id")等待集群创建完成。获取该集群的 vSwitch ID,然后为集群添加一个工作节点。
-
为集群所在 VPC 添加公网访问能力。
export VPC_ID=$(aliyun cs DescribeClusterDetail --ClusterId ${CLUSTER_ID} | jq -r ".vpc_id") export VSWITCH_ID=$(echo ${VSWITCH_IDS} | sed 's/[][]//g' | sed 's/"//g') aliyun vpc CreateNatGateway \ --region ${REGION_ID} \ --RegionId ${REGION_ID} \ --VpcId ${VPC_ID} \ --NatType Enhanced \ --VSwitchId ${VSWITCH_ID} \ --NetworkType internet export GATEWAY_ID="<NatGatewayId>" export SNAT_TABLE_ID="<SnatTableId>" # 公网 IP 带宽(Mbps) export BAND_WIDTH=5 aliyun vpc AllocateEipAddress \ --region ${REGION_ID} \ --RegionId ${REGION_ID} \ --Bandwidth ${BAND_WIDTH} export EIP_ID="<AllocationId>" export EIP_ADDRESS="<EipAddress>" aliyun vpc AssociateEipAddress \ --region ${REGION_ID} \ --RegionId ${REGION_ID} \ --AllocationId ${EIP_ID} \ --InstanceId ${GATEWAY_ID} \ --InstanceType Nat aliyun vpc CreateSnatEntry \ --region ${REGION_ID} \ --RegionId ${REGION_ID} \ --SnatTableId ${SNAT_TABLE_ID} \ --SourceVSwitchId ${VSWITCH_ID} \ --SnatIp ${EIP_ADDRESS} -
授予角色权限。
为集群工作节点授予相应角色权限,使其能够创建 ECS 实例。
部署 CAA Helm Chart
下载 CAA Helm 部署资源
CAA_VERSION="$(
curl -fsSL \
"https://api.github.com/repos/confidential-containers/cloud-api-adaptor/releases/latest" |
jq -er '.tag_name | sub("^v"; "")'
)"
curl -LO "https://github.com/confidential-containers/cloud-api-adaptor/archive/refs/tags/v${CAA_VERSION}.tar.gz"
tar -xvzf "v${CAA_VERSION}.tar.gz"
cd "cloud-api-adaptor-${CAA_VERSION}/src/cloud-api-adaptor/install/charts/peerpods"
export CAA_BRANCH="main"
curl -LO "https://github.com/confidential-containers/cloud-api-adaptor/archive/refs/heads/${CAA_BRANCH}.tar.gz"
tar -xvzf "${CAA_BRANCH}.tar.gz"
cd "cloud-api-adaptor-${CAA_BRANCH}/src/cloud-api-adaptor/install/charts/peerpods"
假如你已在本地准备好代码,请在终端中切换到 Cloud API Adaptor 的代码目录。
导出 PodVM 镜像版本
导出 peer pods 所用的 PodVM 镜像 ID。该变量告诉部署工具在阿里云中创建 peer pod 虚拟机时应使用哪个 PodVM 镜像版本。
export IMAGEID="m-j6c3kfz2vhu6ze04wacb"
说明: 阿里云会预先构建这些镜像,不同地域使用不同的镜像 ID。
region IMAGEID cn-beijing m-2ze2vxvrxsbue3sf8b02 cn-hongkong m-j6c3kfz2vhu6ze04wacb cn-hangzhou m-bp146ws6x3iyuwjvbdw2 ap-southeast-1 m-t4ng1w8ipua3c0o57hor
导出 CAA 容器镜像路径
定义要部署的 Cloud API Adaptor(CAA)容器镜像。 这些变量指定了部署工具所要拉取和运行的 CAA 镜像及其架构专属 tag。 tag 与 CAA 发布版本对应,以确保与所选 PodVM 镜像和配置兼容。
导出以下环境变量以使用 CAA 最新发布镜像:
export CAA_IMAGE="quay.io/confidential-containers/cloud-api-adaptor"
export CAA_TAG="v${CAA_VERSION}-amd64"
导出以下环境变量,以使用每次合并到 main 后由 CAA CI 构建的镜像:
export CAA_IMAGE="quay.io/confidential-containers/cloud-api-adaptor"
你可以在这里找到适合需求的预构建镜像 tag。
export CAA_TAG=""
注意: 你也可以使用
latesttag,但不推荐这样做,因为它缺少版本控制,可能引入不可预期的更新,影响部署稳定性和可复现性。
如果你修改了 CAA 代码并希望部署这些改动,请按说明构建容器镜像。镜像构建完成后,导出环境变量 CAA_IMAGE 和 CAA_TAG。
选择 peer-pods 机型
export PODVM_INSTANCE_TYPE="ecs.g8i.xlarge"
export DISABLECVM="false"
说明: 更多支持机密计算的实例规格,请参考官方文档。
填充 providers/alibabacloud.yaml 文件
全部可用配置项可在以下两个位置找到:
运行以下命令更新 providers/alibabacloud.yaml 文件:
cat <<EOF > providers/alibabacloud.yaml
provider: alibabacloud
image:
name: "${CAA_IMAGE}"
tag: "${CAA_TAG}"
providerConfigs:
alibabacloud:
IMAGEID: "${IMAGEID}"
REGION: "${REGION_ID}"
SECURITY_GROUP_IDS: "${SECURITY_GROUP_ID}"
VSWITCH_ID: "${VSWITCH_ID}"
DISABLECVM: ${DISABLECVM}
alibabacloud:
rrsa:
enable: true
EOF
说明: 如果你不使用 RRSA 进行认证,请将 yaml 中的
alibabacloud.rrsa.enable改为false。
在 Kubernetes 集群中部署 Helm Chart
-
创建由 Helm 管理的命名空间:
kubectl apply -f - << EOF apiVersion: v1 kind: Namespace metadata: name: confidential-containers-system labels: app.kubernetes.io/managed-by: Helm annotations: meta.helm.sh/release-name: peerpods meta.helm.sh/release-namespace: confidential-containers-system EOF -
使用
kubectl创建 Secret:所需 key 请参见 providers/alibabacloud-secrets.yaml.template。
说明: 以下示例假定你使用 RRSA 进行认证,因此不需要提供
ALIBABACLOUD_ACCESS_KEY_ID和ALIBABACLOUD_ACCESS_KEY_SECRET,而是提供ALIBABA_CLOUD_ROLE_ARN和ALIBABA_CLOUD_OIDC_PROVIDER_ARN。kubectl create secret generic my-provider-creds \ -n confidential-containers-system \ --from-literal=ALIBABA_CLOUD_ROLE_ARN=${ALIBABA_CLOUD_ROLE_ARN} \ --from-literal=ALIBABA_CLOUD_OIDC_PROVIDER_ARN=${ALIBABA_CLOUD_OIDC_PROVIDER_ARN} \ --from-literal=ALIBABA_CLOUD_OIDC_TOKEN_FILE=/var/run/secrets/ack.alibabacloud.com/rrsa-tokens/token -
安装 Helm Chart:
下面命令使用了
-f和--set这两个自定义选项,其含义可参考这里。helm install peerpods . \ -f providers/alibabacloud.yaml \ --set secrets.mode=reference \ --set secrets.existingSecretName=my-provider-creds \ --dependency-update \ -n confidential-containers-system
通用的 Peer Pods Helm Chart 部署说明也可参考这里。
运行示例应用
确认 runtimeclass 已创建
部署 CAA 后,请确认 runtimeclass 已创建:
kubectl get runtimeclass
当你看到名为 kata-remote 的 RuntimeClass 时,就说明部署成功。成功输出类似如下:
$ kubectl get runtimeclass
NAME HANDLER AGE
kata-remote kata-remote 7m18s
部署工作负载
创建一个 nginx deployment:
echo '
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
runtimeClassName: kata-remote
containers:
- name: nginx
image: registry.openanolis.cn/openanolis/nginx:1.14.1-8.6
' | kubectl apply -f -
确认 pod 已成功启动:
kubectl get pods -n default
你可以通过运行以下命令确认 peer-pod VM 是否已经创建:
aliyun ecs DescribeInstances --RegionId ${REGION_ID} --InstanceName 'podvm-*'
此时你应该能看到与 pod nginx 对应的虚拟机。
如果遇到问题,请查看故障排查指南。
远程证明
TODO
清理
删除所有使用 runtimeClass kata-remote 运行的 Pod。
确认所有 peer-pod VM 都已删除。你可以使用以下命令列出所有 peer-pod VM(名称前缀为 podvm)及其状态:
aliyun ecs DescribeInstances --RegionId ${REGION_ID} --InstanceName 'podvm-*'
运行以下命令删除 ACK 集群:
aliyun cs DELETE /clusters/${CLUSTER_ID} --region ${REGION_ID} --keep_slb false --retain_all_resources false --header "Content-Type=application/json;" --body "{}"
5 - NVIDIA GPU 示例
说明: 本文为英文文档的中文译版,英文原版请参见 NVIDIA GPU examples。
本文展示如何在 Pod 中配置 NVIDIA 设备直通。
配置前提是你已经按照 NVIDIA Confidential Containers Reference Architecture 部署了 CoCo,并且所用组件版本与下文的直通模式相匹配。若你想看一个把 GPU runtime class、Trustee/KBS、sealed secret、guest pull 和与之对应生成的 agent policy 串成完整 NIM 场景,可参考 带 GPU 远程证明的 NVIDIA NIM 部署场景。
简要说明如下:NVIDIA Hopper、NVIDIA Blackwell 和 NVIDIA RTX Pro 6000 都支持单 GPU 直通(SPT)。Hopper 和 Blackwell 另外支持多 GPU 场景( PPCIE 和 MPT )。Protected PCIe(PPCIE)只用于 Hopper 多 GPU,并且要求 NVLink switch 进入 PPCIE 模式,注意这里 CoCo 会帮你实现。多 GPU 直通(Multi-GPU Passthrough, MPT)用于 Blackwell GPU 之间的端到端加密。NVIDIA RTX Pro 不支持多 GPU 配置。下面分别给出各场景下 Pod 的配置。
请根据你的 CPU TEE 类型选择相应的 runtimeClassName,例如 kata-qemu-nvidia-gpu-tdx、kata-qemu-nvidia-gpu-snp。
用 kubectl describe node <node-name> 查看节点 Allocatable 字段,确认 nvidia.com/pgpu 和 nvidia.com/nvswitch 的资源上限与节点实际能力一致。下面的数值以常见的 8 GPU 节点为例。
默认情况下,机密 GPU 使用 nvidia.com/cc.mode=on;只有下面的 Hopper PPCIE 示例需要改这个标签。
1. Hopper、Blackwell 或 RTX Pro 6000 BSE:单 GPU 直通(SPT)
在默认的 Confidential Containers / GPU Operator 配置下(on),不需要修改 nvidia.com/cc.mode。
下面的 Pod 示例会在 Hopper、Blackwell 或 RTX Pro 6000 BSE 上申请 1 张 GPU:
apiVersion: v1
kind: Pod
metadata:
name: cuda-vectoradd-kata
namespace: default
spec:
runtimeClassName: kata-qemu-nvidia-gpu-tdx
restartPolicy: Never
containers:
- name: cuda-vectoradd
image: "nvcr.io/nvidia/k8s/cuda-sample:vectoradd-cuda12.5.0-ubuntu22.04"
resources:
limits:
nvidia.com/pgpu: "1"
2. Blackwell(B200、B300):多 GPU 直通(MPT)
Pod 其他部分不变,只需要把资源配置改成:
resources:
limits:
nvidia.com/pgpu: "8"
3. Hopper:带 Protected PCIe(PPCIE)和 NVSwitch 的多 GPU 直通
在 Hopper 上,多 GPU 机密直通使用 Protected PCIe。也就是说,Pod 必须同时申请 GPU 和 NVSwitch 设备,节点也必须切到 ppcie 机密 GPU 模式。
kubectl label node <node-name> nvidia.com/cc.mode=ppcie --overwrite
Pod 其他部分仍然保持不变,只需要把资源段改成如下配置,把节点上的 GPU 与 NVSwitch 分配给 Pod:
resources:
limits:
nvidia.com/pgpu: "8"
nvidia.com/nvswitch: "4"
修改 nvidia.com/cc.mode 之后,等待 GPU Operator 相关组件重新稳定,并确认 Pod 状态正常(kubectl get pods -A)。可参考 Kata QEMU GPU guide 的做法。
如果要恢复为单 GPU 直通,把标签改回 on:
kubectl label node <node-name> nvidia.com/cc.mode=on --overwrite
6 - NVIDIA 机密 NIM 部署
说明: 本文为英文文档的中文译版,英文原版请参见 NVIDIA confidential NIM deployment。
本文演示如何把 NVIDIA NIM 推理工作负载部署到 Kubernetes,并运行在 Confidential Containers 上。这里以一台启用了 NVIDIA GPU 机密计算能力的 AMD SEV-SNP Kubernetes 工作节点为例。相同的方法也可以迁移到 Intel TDX 节点,但参考值与证明策略需要按 TDX 重新生成,本文不展开。
NVIDIA NIM 将基础模型封装成容器,并提供针对 GPU 优化的运行时与 HTTP API。本文从一个普通的 NIM Pod 清单开始,镜像为 nvcr.io/nim/meta/llama-3.1-8b-instruct:1.13.1,通过 chat completions API 提供 Meta Llama 3.1 8B Instruct 模型服务。可选的基线步骤会先用非机密的 kata-qemu-nvidia-gpu runtime class 启动它,并检查 8000 端口上的健康状态、模型列表与推理接口。机密场景则切换为 kata-qemu-nvidia-gpu-snp runtime class,把 Pod 放进机密虚拟机中;但仅改 runtime class 还不够,还需要补齐 Trustee 的 KBS、guest pull、AA 与 CDH 配置、sealed secret、镜像签名策略、生成的 Kata agent policy、可信存储,以及批准 CPU、GPU 与 initdata 证据的 KBS 策略。下面按 checkpoint 逐步完成这些配置。
这是一份面向平台与基础设施工程师的单节点动手教程,用于通过远程证明端到端验证机密 NIM 工作负载。它不是面向全集群部署的生产运维指南。文中多个步骤刻意保留为手工操作,以便逐一检查各信任边界;教程还会在 worker 节点上执行本地操作,例如创建本地 backing storage、在与工作负载同一节点上运行 Trustee,或从在线 QEMU 进程收集 SNP 启动输入。
此外,本教程在整个流程中刻意采用单一操作者角色:同一人完成平台搭建、KBS/资源配置、策略生成、观测启动数据收集以及工作负载验证。这样在单节点教程里能完整看到端到端流程,但并不反映生产环境中的职责划分。相关步骤中会标注生产注意事项,文末 展望:生产自动化 总结了如何将同一流程结构化用于生产环境。
前提条件
开始前请准备:
- 一个已经部署好 Confidential Containers、Kata、NVIDIA GPU Operator,并提供机密 GPU runtime class(例如
kata-qemu-nvidia-gpu-snp)的 Kubernetes 集群。可参考 NVIDIA Confidential Containers deployment guide - 可访问 Kubernetes 工作节点,因为本文需要从在线 QEMU 进程中收集 SNP 启动输入
- 一个可拉取 NIM 镜像的 NGC API key
- 工作节点上安装好
kubectl、curl、jq、openssl、oras、skopeo、envsubst、base64、tar、zstd和cargo - 工作节点上安装 Docker 及 Docker Compose 插件
- 工作节点上安装 Python 包
cryptography和jwcrypto,后面生成 sealed secret 辅助脚本会用到
本文尽量使用已经发布或预构建的软件制品,避免读者为了验证流程再去克隆仓库或本地构建组件。只有在确实没有可直接使用的软件制品时,才会局部引入额外步骤。
流程概览
本文把机密部署拆成一组可逐步验证的 checkpoints。每完成一步,都应能看到当前状态并在继续前修正问题。
- 可选:非机密基线验证:把 GPU 切到非机密模式,部署基线 Pod,确认 NIM API 可用,删除 Pod,再把 GPU 切回机密模式。
- 部署 Trustee 并安装 kbs-client:在节点上直接部署 Trustee,选择匹配的 Trustee 版本,配置 KBS HTTPS 端点,并准备 KBS。
- 准备 TEE NIM 清单与 sealed secret:生成运行时密钥对应的 sealed secret,定义引用该密钥、镜像拉取 Secret 与可信存储的 SNP GPU Pod 清单。
- 创建 Pod initdata 注解:为 AA 和 CDH 配置 KBS 地址、KBS 证书、镜像仓库凭据 URI 以及镜像签名策略 URI。
- 生成 Kata agent policy:对 TEE Pod 清单运行
genpolicy,把策略与 initdata 合并,写回生成的cc_init_data注解,并保留生成后的清单但暂不部署。 - 收集 SNP 启动参考值:如果还没有现成的参考值,就用生成好的
cc_init_data启动一个轻量级度量 Pod,记录 SNP launch measurement、SNP TCB 值以及已证明的 initdata 摘要,然后删除测量 Pod。 - 写入 KBS 资源、参考值与策略:配置 NVIDIA 镜像签名校验、公有镜像仓库凭据、NIM 运行时 API key、SNP 参考值、sealed secret 验证公钥,以及要求 CPU、GPU 证据和 initdata 摘要都匹配的 KBS 策略。
- 准备可信存储资源:创建 TEE NIM Pod 所依赖的可信存储。
- 运行端到端场景:创建 Kubernetes 侧的 Secret,部署带策略的 TEE NIM 清单,并验证在 KBS 策略约束下 NIM API 仍可正常访问。
初始 NIM Manifest
先导出一个可拉取 NIM 镜像的 NGC API key,然后生成初始清单模板。如果你不做 checkpoint 1 的非机密基线验证,那么这个步骤不需要实际部署。
这个 Manifest 会在两个地方使用 NGC API key:ngc-secret-instruct 供 Kubernetes 从 nvcr.io 拉取镜像,ngc-api-key-instruct 则把 key 传给 NIM 容器在运行时使用。为了简化示例,本文复用了同一个 key。实际环境中,建议把镜像拉取与容器运行时使用的 key 分开。
该文件是可选基线部署的基础 Manifest。它保留 ${NGC_API_KEY} 作为占位符,后面的校验和部署命令会通过 envsubst 把它替换后再交给 kubectl。
export NGC_API_KEY="<NGC_API_KEY>"
cat <<'EOF' | tee nvidia-nim-llama-3-1-8b-instruct.yaml >/dev/null
apiVersion: v1
kind: Secret
metadata:
name: ngc-secret-instruct
type: kubernetes.io/dockerconfigjson
stringData:
.dockerconfigjson: |
{
"auths": {
"nvcr.io": {
"username": "$oauthtoken",
"password": "${NGC_API_KEY}"
}
}
}
---
apiVersion: v1
kind: Secret
metadata:
name: ngc-api-key-instruct
type: Opaque
stringData:
api-key: "${NGC_API_KEY}"
---
apiVersion: v1
kind: Pod
metadata:
name: nvidia-nim-llama-3-1-8b-instruct
labels:
app: nvidia-nim-llama-3-1-8b-instruct
spec:
restartPolicy: Never
runtimeClassName: kata-qemu-nvidia-gpu
imagePullSecrets:
- name: ngc-secret-instruct
containers:
- name: nvidia-nim-llama-3-1-8b-instruct
image: nvcr.io/nim/meta/llama-3.1-8b-instruct:1.13.1
ports:
- containerPort: 8000
name: http-openai
livenessProbe:
httpGet:
path: /v1/health/live
port: http-openai
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
readinessProbe:
httpGet:
path: /v1/health/ready
port: http-openai
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
startupProbe:
httpGet:
path: /v1/health/ready
port: http-openai
initialDelaySeconds: 360
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 120
env:
- name: NGC_API_KEY
valueFrom:
secretKeyRef:
name: ngc-api-key-instruct
key: api-key
resources:
limits:
nvidia.com/pgpu: "1"
cpu: "16"
memory: "48Gi"
EOF
Checkpoint 1:可选的非机密基线验证
如果你只关心机密部署,可以跳过这一节。这里的作用只是先验证普通 NIM Pod 在 Kata Containers 上能否正常运行。流程是:临时把 GPU 切到非机密模式,运行非 TEE Pod,验证后删除,再切回机密模式。
这里通过节点标签触发模式切换,NVIDIA GPU Operator 会根据标签完成相应编排。最后一定要把节点切回机密 GPU 模式,后续 checkpoint 才能继续。关于 GPU runtime class 和机密 GPU 模式标签,可参考 NVIDIA GPU 示例。
先确保节点处于 VM passthrough 和非机密 GPU 模式:
kubectl label nodes --all \
nvidia.com/gpu.workload.config=vm-passthrough \
--overwrite
kubectl label nodes --all nvidia.com/cc.mode=off --overwrite
切换 CC 模式时,GPU Operator 的一些组件可能会重启。先等待节点标签确认模式切换完成,再等待 GPU Operator 相关控制器稳定。这里不要直接等所有 Pod 全部就绪,因为模式切换期间 operator 可能会删除并重建一部分 Pod。
kubectl wait \
--for=jsonpath='{.metadata.labels.nvidia\.com/cc\.mode\.state}'=off \
node --all \
--timeout=15m
kubectl -n gpu-operator wait \
--for=condition=Available \
deployment --all \
--timeout=10m
kubectl -n gpu-operator rollout status \
daemonset/nvidia-vfio-manager \
--timeout=10m
kubectl -n gpu-operator rollout status \
daemonset/nvidia-sandbox-validator \
--timeout=10m
kubectl -n gpu-operator rollout status \
daemonset/nvidia-kata-sandbox-device-plugin-daemonset \
--timeout=10m
kubectl -n gpu-operator rollout status \
daemonset/nvidia-cc-manager \
--timeout=10m
kubectl get nodes \
-L nvidia.com/gpu.workload.config,nvidia.com/cc.mode,nvidia.com/cc.mode.state
部署非机密模式:
: "${NGC_API_KEY:?set NGC_API_KEY to your NGC API key}"
envsubst '${NGC_API_KEY}' \
< nvidia-nim-llama-3-1-8b-instruct.yaml \
| kubectl apply -f -
kubectl wait --for=condition=Ready \
--timeout=600s \
pod/nvidia-nim-llama-3-1-8b-instruct
演示环境里,直接从能访问集群 Pod 网络的机器上查询 Pod IP,例如单节点集群宿主机。这一步不会创建 Kubernetes Service。生产环境通常会通过 Service、Ingress、Gateway 或其他受控入口暴露 NIM。
检查 NIM 是否就绪并列出模型名:
POD_IP="$(kubectl get pod nvidia-nim-llama-3-1-8b-instruct -o jsonpath='{.status.podIP}')"
curl -fsS "http://${POD_IP}:8000/v1/health/ready" | jq .
curl -fsS "http://${POD_IP}:8000/v1/models" | jq -r '.data[].id'
发送一个最简单的 chat completion 请求:
MODEL_NAME="$(curl -fsS "http://${POD_IP}:8000/v1/models" | jq -r '.data[0].id')"
curl -fsS "http://${POD_IP}:8000/v1/chat/completions" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg model "${MODEL_NAME}" '{
model: $model,
messages: [{role: "user", content: "Reply with exactly: hello from nim"}],
max_tokens: 16,
temperature: 0
}')" | jq -r '.choices[0].message.content'
验证通过后,删除这个非 TEE 示例 Pod 及其 Kubernetes Secret:
kubectl delete pod nvidia-nim-llama-3-1-8b-instruct --ignore-not-found
kubectl delete secret ngc-secret-instruct ngc-api-key-instruct \
--ignore-not-found
把节点切回机密 GPU 模式:
kubectl label nodes --all nvidia.com/cc.mode=on --overwrite
然后重复上面的 CC 模式稳定性检查,只是这次等待 nvidia.com/cc.mode.state 变成 on。确认节点报告 cc.mode=on 和 cc.mode.state=on 后再继续。
Checkpoint 2:部署 Trustee 并安装 kbs-client
这一节会把 Trustee 直接部署在节点上,后续用于写入 KBS 资源、参考值和资源策略的管理工具 kbs-client。KBS 是 Trustee 暴露给 Guest 侧的证明与资源服务,同时也提供 kbs-client 使用的管理接口。
Trustee 可以通过 Docker Compose、Helm Chart、Kubernetes 清单或其他方式部署。本文选择 Docker Compose,是因为它能直接从 Trustee 源码树同时启动 KBS、Attestation Service(AS)和 Reference Value Provider Service(RVPS)。在这个 Compose 部署中,KBS、AS 和 RVPS 是三个独立容器:KBS 调用 AS 验证证据,AS 再调用 RVPS 获取参考值。Compose 文件里还会有一个一次性运行的 setup 服务,用来生成演示所需的部分密钥材料。
把 Trustee 跑在与工作负载同一台单节点机器上只是为了演示方便,并不代表推荐的生产部署方式。生产环境应把 Trustee 部署在独立的可信环境中,给 KBS 使用持久化存储,使用受信任 CA 签发的 HTTPS 证书,并严格限制管理接口访问。其他部署方式可参考 Trustee installation 文档。
设置 KBS 部署参数
先定义本地工作目录、Guest 组件访问 KBS 时使用的 URL,以及 Compose 部署派生的各个路径。Docker Compose 会把 KBS 端口发布到工作节点主机上。由于这里是单节点集群,Guest 可以直接通过节点 IP 和宿主机端口访问 KBS:
# Local paths.
KBS_WORKDIR="${HOME}/nim-kbs"
KBS_TRUSTEE_DIR="${KBS_WORKDIR}/trustee"
KBS_CONFIG_DIR="${KBS_TRUSTEE_DIR}/kbs/config"
KBS_COMPOSE_CONFIG_DIR="${KBS_CONFIG_DIR}/docker-compose"
KBS_DATA_DIR="${KBS_TRUSTEE_DIR}/kbs/data"
KBS_STORAGE_DIR="${KBS_DATA_DIR}/kbs-storage"
# Artifact selection.
KBS_CLIENT_ARCH="${KBS_CLIENT_ARCH:-x86_64}"
# Network endpoint.
KBS_PORT="${KBS_PORT:-8080}"
KBS_NODE_IP="$(kubectl get nodes \
-o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')"
KBS_URL="https://${KBS_NODE_IP}:${KBS_PORT}"
mkdir -p "${KBS_WORKDIR}"
这些路径基本都由 KBS_WORKDIR 派生。通常只需要决定 KBS_WORKDIR、KBS_PORT,以及可选的 KBS_CLIENT_ARCH:
KBS_TRUSTEE_DIR:下载后的 Trustee 源码KBS_CONFIG_DIR:Compose 服务挂载使用的演示配置和身份信息目录KBS_COMPOSE_CONFIG_DIR:Compose 部署使用的 KBS TOML 配置文件目录KBS_DATA_DIR:Compose 管理的演示状态目录,RVPS 参考值和 AS 状态都保存在它的子目录中KBS_STORAGE_DIR:KBS 的资源与资源策略仓库存储位置
后面会把 KBS_URL 写进 Pod 的 initdata,供来 Guest 内的 AA 与 CDH 访问 KBS。当前 Compose 方案中,KBS 监听 KBS_PORT 暴露出的宿主机端口,Kata Guest 通过节点 IP 到达该端点。生产环境里,KBS 一般位于独立可信环境,并通过稳定的服务地址暴露;Guest 网络也需要确保能够访问该地址。
生产环境中,建议根据 Trustee 所在信任边界使用合适的持久化存储,例如专用持久卷或可信外部后端。
选择 Trustee 部署制品
这一步要选定 Trustee 源码、Compose 服务镜像,以及与之匹配的 kbs-client 软件制品。原则上,应使用与你当前 CoCo 版本和 Guest 组件匹配、已经验证过的 Trustee 版本。NVIDIA 的 supported-platforms 文档通常是查这个映射关系的权威来源,可参考 NVIDIA supported platforms。
如果你的 Kata 是通过 kata-deploy Helm chart 安装的,通常会在 /opt/kata/versions.yaml 中携带发布元数据。下面的命令会自动读取其中的 coco-trustee 项并解析出匹配的 Trustee 版本。如果这个文件不可用,则需要到对应 Kata 版本的上游 versions.yaml 查找 externals.coco-trustee.version。按当前版本约定,Trustee 的版本号通常比 CoCo 小一个 minor,例如 CoCo v0.18.0 对应 Trustee v0.17.0。如果仍然无法解析,请手工设置 KBS_TRUSTEE_REF。
这种方式很适合本教程,因为相关命令会直接在安装 Kata 的节点上运行。但在生产环境中,应将版本选择纳入部署流程。如果你已经确定要使用的 Trustee 引用,请在运行以下代码片段前设置 KBS_TRUSTEE_REF,这样即可跳过自动查找过程:
KATA_VERSIONS_FILE="${KATA_VERSIONS_FILE:-/opt/kata/versions.yaml}"
KBS_TRUSTEE_REF="${KBS_TRUSTEE_REF:-}"
KBS_IMAGE_REPO="ghcr.io/confidential-containers/staged-images/kbs-grpc-as"
KBS_AS_IMAGE_REPO="ghcr.io/confidential-containers/staged-images/coco-as-grpc"
KBS_RVPS_IMAGE_REPO="ghcr.io/confidential-containers/staged-images/rvps"
KBS_CLIENT_IMAGE_REPO="ghcr.io/confidential-containers/staged-images/kbs-client"
if test -z "${KBS_TRUSTEE_REF}"; then
if ! test -r "${KATA_VERSIONS_FILE}"; then
echo "ERROR: cannot resolve KBS_TRUSTEE_REF because ${KATA_VERSIONS_FILE} is not readable." >&2
echo "Look up the validated Trustee reference for your platform, set KBS_TRUSTEE_REF, and rerun this snippet." >&2
exit 1
fi
KBS_TRUSTEE_REF="$(
awk '
$1 == "coco-trustee:" { in_trustee = 1; next }
in_trustee && $1 == "version:" {
gsub(/"/, "", $2)
print $2
exit
}
' "${KATA_VERSIONS_FILE}"
)"
fi
if test -z "${KBS_TRUSTEE_REF}"; then
echo "ERROR: could not find the coco-trustee version in ${KATA_VERSIONS_FILE}." >&2
echo "Look up the validated Trustee reference for your platform, set KBS_TRUSTEE_REF, and rerun this snippet." >&2
exit 1
fi
KBS_COMPOSE_IMAGE_TAG="${KBS_COMPOSE_IMAGE_TAG:-${KBS_TRUSTEE_REF}-${KBS_CLIENT_ARCH}}"
KBS_CLIENT_ARTIFACT="${KBS_CLIENT_IMAGE_REPO}:sample_only-${KBS_COMPOSE_IMAGE_TAG}"
KBS_TRUSTEE_TARBALL_URL="${KBS_TRUSTEE_TARBALL_URL:-https://github.com/confidential-containers/trustee/archive/${KBS_TRUSTEE_REF}.tar.gz}"
skopeo inspect "docker://${KBS_IMAGE_REPO}:${KBS_COMPOSE_IMAGE_TAG}" >/dev/null
skopeo inspect "docker://${KBS_AS_IMAGE_REPO}:${KBS_COMPOSE_IMAGE_TAG}" >/dev/null
skopeo inspect "docker://${KBS_RVPS_IMAGE_REPO}:${KBS_COMPOSE_IMAGE_TAG}" >/dev/null
oras manifest fetch "${KBS_CLIENT_ARTIFACT}" >/dev/null
printf 'KBS_TRUSTEE_REF=%s\nKBS_IMAGE=%s:%s\nAS_IMAGE=%s:%s\nRVPS_IMAGE=%s:%s\nKBS_CLIENT_ARTIFACT=%s\nKBS_URL=%s\n' \
"${KBS_TRUSTEE_REF}" \
"${KBS_IMAGE_REPO}" \
"${KBS_COMPOSE_IMAGE_TAG}" \
"${KBS_AS_IMAGE_REPO}" \
"${KBS_COMPOSE_IMAGE_TAG}" \
"${KBS_RVPS_IMAGE_REPO}" \
"${KBS_COMPOSE_IMAGE_TAG}" \
"${KBS_CLIENT_ARTIFACT}" \
"${KBS_URL}"
如果你手动将 KBS_TRUSTEE_REF 指定为某个发布版本标签或其他源码引用,并且 Compose 镜像使用了不同的标签命名方式,那么还应同时设置 KBS_COMPOSE_IMAGE_TAG。默认情况下,上述配置假定 Compose 镜像标签与 Kata 发布元数据所引用的预构建镜像保持一致。
此外,如果对应的源码树无法通过默认的 GitHub Archive URL 获取,还需要额外设置 KBS_TRUSTEE_TARBALL_URL,以指定正确的源码压缩包地址。
准备 Trustee Compose 部署
下载与所选版本匹配的 Trustee 源码 tarball,后面会直接复用其中的 Docker Compose 逻辑和 kbs/config 下的配置文件:
rm -rf "${KBS_TRUSTEE_DIR}"
mkdir -p "${KBS_TRUSTEE_DIR}"
curl -fsSL "${KBS_TRUSTEE_TARBALL_URL}" |
tar -xz --strip-components=1 -C "${KBS_TRUSTEE_DIR}"
这一节会用到以下输入文件:
docker-compose.yml:启动 KBS、AS、RVPS,以及一次性的setup服务,用于生成演示环境需要的管理密钥和 AS token 签名密钥kbs/config/docker-compose/kbs-config.toml:配置 KBS 端点、管理认证和资源存储kbs/config/as-config.json:配置 AS,包括它如何访问 RVPS 以及 NVIDIA verifierkbs/config/rvps.json:配置 RVPS
本文不逐行展开这些配置;如果你想看默认值,可直接检查下载的源码或 Trustee 仓库中的相同路径。
上游 Compose 文件默认使用 latest 镜像标签。下面把 KBS、AS、RVPS 镜像固定到上面解析出的 Trustee 版本,并把 KBS 暴露到 KBS_PORT:
sed -i \
-e "s#image: ghcr.io/confidential-containers/staged-images/kbs-grpc-as:latest#image: ${KBS_IMAGE_REPO}:${KBS_COMPOSE_IMAGE_TAG}#" \
-e "s#image: ghcr.io/confidential-containers/staged-images/coco-as-grpc:latest#image: ${KBS_AS_IMAGE_REPO}:${KBS_COMPOSE_IMAGE_TAG}#" \
-e "s#image: ghcr.io/confidential-containers/staged-images/rvps:latest#image: ${KBS_RVPS_IMAGE_REPO}:${KBS_COMPOSE_IMAGE_TAG}#" \
-e "s#\"8080:8080\"#\"${KBS_PORT}:8080\"#" \
"${KBS_TRUSTEE_DIR}/docker-compose.yml"
Compose 中的 setup 服务会在启动时生成演示用的管理员密钥对和 AS token 签名材料,但它不会生成 KBS HTTPS 证书。因此这里还要补上 kbs-https.* 证书,并把它接到 KBS 配置里。证书的 subject alternative name 必须覆盖 KBS_URL 中使用的节点 IP,这样Guest组件和 kbs-client 才能正确校验证书:
openssl req -x509 \
-out "${KBS_CONFIG_DIR}/kbs-https.crt" \
-keyout "${KBS_CONFIG_DIR}/kbs-https.key" \
-newkey rsa:2048 \
-nodes \
-sha256 \
-days 365 \
-subj "/CN=${KBS_NODE_IP}" \
-addext "subjectAltName=IP:${KBS_NODE_IP},IP:127.0.0.1,DNS:localhost" \
-addext "basicConstraints=CA:FALSE"
sed -i \
-e 's#insecure_http = true#insecure_http = false\
private_key = "/opt/confidential-containers/kbs/user-keys/kbs-https.key"\
certificate = "/opt/confidential-containers/kbs/user-keys/kbs-https.crt"#' \
"${KBS_COMPOSE_CONFIG_DIR}/kbs-config.toml"
grep -q 'insecure_http = false' "${KBS_COMPOSE_CONFIG_DIR}/kbs-config.toml"
grep -q 'type = "Simple"' "${KBS_COMPOSE_CONFIG_DIR}/kbs-config.toml"
jq -e '.verifier_config.nvidia_verifier.type == "Remote"' \
"${KBS_CONFIG_DIR}/as-config.json" >/dev/null
这个演示环境里会涉及三组身份材料:
kbs-https.key与kbs-https.crt:标识 KBS HTTPS 端点private.key与public.pub:用于kbs-client调用管理 API 的认证。KBS 配置中使用公钥,kbs-client用私钥签名请求。这对密钥由 Compose 的setup服务生成ca.key、ca-cert.pem、token.key、token-cert.pem、token-cert-chain.pem:AS 用它们签发 KBS 可验证的 attestation token。这部分同样由setup服务生成
在当前单节点演示里,KBS 和 AS 一起启动,所以最后一组材料并不体现独立运营边界;但在 KBS 与 AS 分开部署时,它们就非常重要了。
生产环境里要明确这些身份信息的归属和保护方式。例如:KBS 证书通常应由组织 CA 签发;管理签名密钥应由正式身份流程发放和保护;AS token 签名私钥可能需要 HSM 或托管密钥服务来托管。
部署 Trustee
启动 Compose 部署。--project-directory 用于确保 docker-compose.yml 里的相对路径都在下载后的 Trustee 源码内解析。由于 Compose 文件本身也包含本地开发用的 build: 段,下面会先拉取固定版本镜像,再用 --no-build 避免在教程节点上本地构建:
docker compose \
--project-directory "${KBS_TRUSTEE_DIR}" \
--project-name nim-trustee \
pull kbs as rvps setup
docker compose \
--project-directory "${KBS_TRUSTEE_DIR}" \
--project-name nim-trustee \
up -d --no-build
检查 Compose 服务状态。这个版本的 Trustee 没有独立 health endpoint,因此功能性验证会放到安装完 kbs-client 之后再做:
docker compose \
--project-directory "${KBS_TRUSTEE_DIR}" \
--project-name nim-trustee \
ps
保留好 KBS_URL、管理员 token 和 KBS HTTPS 证书,后续 checkpoint 还会继续用到:
KBS_ADMIN_TOKEN_FILE="${KBS_CONFIG_DIR}/admin-token"
KBS_CERT_FILE="${KBS_CONFIG_DIR}/kbs-https.crt"
test -s "${KBS_ADMIN_TOKEN_FILE}"
test -s "${KBS_CERT_FILE}"
安装 kbs-client
接下来安装用于 KBS 管理操作的 kbs-client。上面的变量 KBS_CLIENT_ARTIFACT 指向与选定 Trustee 版本匹配的预构建客户端制品。下面的 oras pull 会把它解包到 ${KBS_WORKDIR}/bin 中。
这样可以确保客户端与正在运行的 KBS 服务版本以及管理认证模式匹配。这里还不会修改 KBS 状态;真正的资源写入在后面的 checkpoint 才发生。
command -v oras >/dev/null
mkdir -p "${KBS_WORKDIR}/bin"
oras pull \
--output "${KBS_WORKDIR}/bin" \
"${KBS_CLIENT_ARTIFACT}"
chmod +x "${KBS_WORKDIR}/bin/kbs-client"
KBS_CLIENT="${KBS_WORKDIR}/bin/kbs-client"
"${KBS_CLIENT}" --version
用一个不含敏感信息的探测资源写入再删除,验证 HTTPS 端点和管理员认证是否正常:
printf 'checkpoint2-probe\n' > "${KBS_WORKDIR}/checkpoint2-probe.txt"
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-resource \
--path default/checkpoint2/probe \
--resource-file "${KBS_WORKDIR}/checkpoint2-probe.txt"
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
delete-resource \
--path default/checkpoint2/probe
后面继续操作时,保留当前 shell,或者把 KBS_CLIENT、KBS_WORKDIR、KBS_ADMIN_TOKEN_FILE、KBS_CERT_FILE 和 KBS_URL 私下保存好。不要删除 KBS_WORKDIR,因为里面包含 Trustee 源码、Compose 配置、演示状态以及后续生成的所有教程文件。由于后续 kbs-client 会使用 Compose 生成的管理员 token 调用 KBS,请把它当成管理员凭据妥善保护。
Checkpoint 3:准备 TEE NIM 清单与 sealed secret
这一节会先为运行时 NGC API key 生成一个签名过的 sealed secret,再生成 TEE NIM Pod 清单,但此时还不部署 Pod。
这个清单会使用 SNP GPU runtime class,引用一个 Kubernetes 镜像拉取 Secret、一个承载 sealed runtime secret 的 Kubernetes Secret、一个 可信存储 PVC,以及沿用基准清单中定义的健康检查配置。镜像拉取 Secret 与 PVC 会在后面真正部署前再创建。
之所以在部署前先生成清单,是因为 genpolicy 需要读取 Pod 规格作为输入。后面生成的 Kata agent policy 和 cc_init_data 都依赖这一版清单,测量 checkpoint 也会复用同一份 cc_init_data。
这里还启用了可信镜像存储。由于 guest pull 会在Guest内下载较大的 NIM 模型数据和镜像层,如果全部放在Guest的内存型文件系统中,会迫使机密虚拟机配置更大的内存。把这些数据放到挂载进机密虚拟机的块设备上,会更符合这个场景。
这里有两个和 NGC 相关的 Secret:
ngc-secret-instruct:镜像拉取 Secret,供 Kubernetes 和 CRI 访问认证过的仓库元数据。生产环境中应尽量缩小 token 权限范围,并评估是否能避免把高权限凭据暴露给工作负载集群控制面。这个 Secret 稍后创建。ngc-api-key-sealed-instruct:只保存签名后的 sealed 值。Kubernetes 看到的是 sealed 值;CDH 会在 Guest 内通过从 KBS 读取kbs:///default/ngc-api-key/instruct来解密。该 Secret 之所以已经出现在这里,是因为genpolicy会在 YAML 输入中解析secretKeyRef。
先使用 P-256 生成 sealed secret 签名密钥。私钥 JWK 保留在 KBS 之外,后面只上传公钥 JWK。CDH 会用这个公钥来验证 sealed NIM 运行时 secret 的签名,确认其确实来自期望的密钥,然后再去 KBS 取出对应的明文 secret。生产环境中,这类密钥应在可信发布流水线中生成和保护,只公开经过审核的 sealed 值。
KBS_WORKDIR="${KBS_WORKDIR}" python3 - <<'EOF'
import os
from jwcrypto import jwk
workdir = os.environ["KBS_WORKDIR"]
k = jwk.JWK.generate(
kty='EC', crv='P-256', alg='ES256',
use='sig', kid='sealed-secret-nim-key')
open(f'{workdir}/signing-key-private.jwk', 'w').write(k.export_private())
open(f'{workdir}/signing-key-public.jwk', 'w').write(k.export_public())
EOF
接着生成一个指向 KBS 中 NIM 运行时 API key 的签名 vault sealed secret。正常情况下,应使用 Confidential Containers 项目的 secret CLI 来完成。但由于本文尽量避免要求读者从源码构建 CoCo 组件,而 secret CLI 在写作时还没有单独发布制品,所以这里临时用一个兼容脚本生成等价的 vault sealed-secret 格式。生产环境不要直接复用这段脚本,应改用正式发布并受支持的 CoCo 工具链。
vault sealed secret 本质上是一个 JWS 签名的 JSON 文档,其中包含 KBS 资源 URI 和 provider 元数据;受保护 header 里的 b64 字段是为了与 secret CLI 产出的格式保持一致。
KBS_WORKDIR="${KBS_WORKDIR}" python3 - <<'EOF'
import base64
import json
import os
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec, utils
workdir = os.environ["KBS_WORKDIR"]
signing_jwk = json.loads(open(f"{workdir}/signing-key-private.jwk").read())
def b64url_decode(value):
return base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))
def b64url_encode(value):
return base64.urlsafe_b64encode(value).rstrip(b"=").decode()
payload = {
"version": "0.1.0",
"type": "vault",
"name": "kbs:///default/ngc-api-key/instruct",
"provider": "kbs",
"provider_settings": {},
"annotations": {},
}
protected = {
"b64": True,
"alg": "ES256",
"kid": "kbs:///default/signing-key/sealed-secret",
}
private_value = int.from_bytes(b64url_decode(signing_jwk["d"]), "big")
private_key = ec.derive_private_key(private_value, ec.SECP256R1())
protected_b64 = b64url_encode(json.dumps(
protected, separators=(",", ":")).encode())
payload_b64 = b64url_encode(json.dumps(payload, separators=(",", ":")).encode())
signing_input = f"{protected_b64}.{payload_b64}".encode()
signature_der = private_key.sign(signing_input, ec.ECDSA(hashes.SHA256()))
r, s = utils.decode_dss_signature(signature_der)
signature = r.to_bytes(32, "big") + s.to_bytes(32, "big")
with open(f"{workdir}/ngc-api-key-instruct.sealed", "w") as f:
f.write(f"sealed.{protected_b64}.{payload_b64}.{b64url_encode(signature)}")
EOF
生成 Pod Manifest:
NIM_TEE_MANIFEST="${KBS_WORKDIR}/nvidia-nim-llama-3-1-8b-instruct-tee.yaml"
SEALED_NGC_API_KEY_BASE64="$(
base64 -w0 "${KBS_WORKDIR}/ngc-api-key-instruct.sealed"
)"
cat > "${NIM_TEE_MANIFEST}" <<EOF
apiVersion: v1
kind: Secret
metadata:
name: ngc-api-key-sealed-instruct
type: Opaque
data:
api-key: "${SEALED_NGC_API_KEY_BASE64}"
---
apiVersion: v1
kind: Pod
metadata:
name: nvidia-nim-llama-3-1-8b-instruct
labels:
app: nvidia-nim-llama-3-1-8b-instruct
spec:
securityContext:
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
supplementalGroups: [4, 20, 24, 25, 27, 29, 30, 44, 46]
restartPolicy: Never
runtimeClassName: kata-qemu-nvidia-gpu-snp
imagePullSecrets:
- name: ngc-secret-instruct
containers:
- name: nvidia-nim-llama-3-1-8b-instruct
image: nvcr.io/nim/meta/llama-3.1-8b-instruct:1.13.1
ports:
- containerPort: 8000
name: http-openai
livenessProbe:
httpGet:
path: /v1/health/live
port: http-openai
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
readinessProbe:
httpGet:
path: /v1/health/ready
port: http-openai
initialDelaySeconds: 15
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 3
startupProbe:
httpGet:
path: /v1/health/ready
port: http-openai
initialDelaySeconds: 360
periodSeconds: 10
timeoutSeconds: 1
successThreshold: 1
failureThreshold: 120
env:
- name: NGC_API_KEY
valueFrom:
secretKeyRef:
name: ngc-api-key-sealed-instruct
key: api-key
resources:
limits:
nvidia.com/pgpu: "1"
cpu: "16"
memory: "48Gi"
volumeMounts:
- name: nim-trusted-cache
mountPath: /opt/nim/.cache
volumeDevices:
- name: trusted-storage
devicePath: /dev/trusted_store
volumes:
- name: nim-trusted-cache
emptyDir:
sizeLimit: 64Gi
- name: trusted-storage
persistentVolumeClaim:
claimName: trusted-pvc-instruct
EOF
只做服务端 dry-run 校验,不实际创建资源:
kubectl apply --dry-run=server -f "${NIM_TEE_MANIFEST}"
如果 dry-run 正常,应该看到 Secret 和 Pod 被报告为 dry-run created 或 configured。这里引用的镜像拉取 Secret 和可信存储 PVC 此时还不需要存在,真正部署前再创建即可。
此时Manifest里还没有 cc_init_data 。Checkpoint 5 会通过 nim-initdata.toml 和 genpolicy 把它加上。
Checkpoint 4:创建 Pod initdata 注解
这一节为 Guest 内的 AA 和 CDH 生成配置。生成的 initdata 会包含 KBS 地址、KBS 证书、用于认证仓库访问的 registry credential URI,以及 image signature policy URI。
需要注意的是,这份 initdata 虽然通过 Pod 字段由宿主侧提供,但 Guest 不能因为“收到了”就直接信任它。对于 SNP 启动,Kata 会对生成的 initdata 文档做哈希,然后把摘要写入 QEMU 的 SNP HOST_DATA 启动值中。SNP 会在 attestation evidence 中提供这个值。Guest 拿到 initdata 后,会验证它的哈希是否与 HOST_DATA 一致;证明链路再把校验后的摘要暴露给 KBS。Checkpoint 7 会把这个摘要注入到 KBS 资源策略中。
KBS_URL 必须与 checkpoint 2 中使用的地址一致,证书也必须是该地址实际提供的证书。之所以同时把证书嵌进 aa.toml 和 cdh.toml,是因为 AA 与 CDH 是两个独立的 Guest 组件,它们都会访问 HTTPS KBS 端点:AA 用它做远程证明,CDH 用它拉取资源并解封签名的 sealed secret。
创建 nim-initdata.toml:
INITDATA_FILE="${KBS_WORKDIR}/nim-initdata.toml"
{
cat <<EOF
version = "0.1.0"
algorithm = "sha256"
[data]
"aa.toml" = """
[token_configs]
[token_configs.kbs]
url = "${KBS_URL}"
cert = '''
EOF
cat "${KBS_CERT_FILE}"
cat <<EOF
'''
"""
"cdh.toml" = """
[kbc]
name = "cc_kbc"
url = "${KBS_URL}"
kbs_cert = '''
EOF
cat "${KBS_CERT_FILE}"
cat <<'EOF'
'''
[image]
max_concurrent_layer_downloads_per_image = 1
authenticated_registry_credentials_uri = "kbs:///default/credentials/nvcr"
image_security_policy_uri = "kbs:///default/security-policy/nim"
"""
EOF
} > "${INITDATA_FILE}"
先不要把这个文件直接写入 Pod。Checkpoint 5 会把它作为 genpolicy 的输入,再由 genpolicy 把 Kata agent policy 合入同一份 initdata 文档,并把生成后的 io.katacontainers.config.hypervisor.cc_init_data 注解字段写回Manifest。
Checkpoint 5:生成 Kata agent policy
这一节会对 TEE NIM Pod Manifest 运行 genpolicy,并把生成的 Kata agent policy 写入 Pod initdata。运行 genpolicy 的环境需要能够访问 nvcr.io,因为它在生成策略时会读取镜像元数据。
建议把 genpolicy 看作可信构建或发布流程的一部分。它的输出决定了 shim 到 agent 接口在信任边界上的行为约束,因此生产环境应在可信环境中生成、审核并发布这份策略,而不是像教程这样在工作节点交互式执行。
genpolicy 会按 Docker 风格凭据查找 registry 认证信息。下面的示例会在 genpolicy 工作目录中临时生成一份 nvcr.io 的 Docker config,并显式让 genpolicy 使用它。如果你的可信构建环境已经有合适的 Docker 凭据,也可以直接复用。
先下载与当前 Kata runtime 版本匹配的官方 Kata tools 发布包:
GENPOLICY_WORKDIR="${KBS_WORKDIR}/genpolicy"
NIM_TEE_MANIFEST="${NIM_TEE_MANIFEST:-${KBS_WORKDIR}/nvidia-nim-llama-3-1-8b-instruct-tee.yaml}"
INITDATA_FILE="${INITDATA_FILE:-${KBS_WORKDIR}/nim-initdata.toml}"
NIM_POLICY_MANIFEST="${GENPOLICY_WORKDIR}/nvidia-nim-llama-3-1-8b-instruct-tee-policy.yaml"
GENPOLICY_INITDATA="${GENPOLICY_WORKDIR}/nim-initdata.toml"
GENPOLICY_DOCKER_CONFIG="${GENPOLICY_WORKDIR}/docker-config"
KATA_RUNTIME_VERSION="$(/opt/kata/bin/kata-runtime --version \
| awk '/kata-runtime/ {print $3}')"
KATA_TOOLS_ARCH="amd64"
KATA_TOOLS_NAME="kata-tools-static-${KATA_RUNTIME_VERSION}-${KATA_TOOLS_ARCH}"
KATA_TOOLS_TARBALL="${KATA_TOOLS_NAME}.tar.zst"
KATA_RELEASE_BASE="https://github.com/kata-containers/kata-containers/releases/download"
KATA_TOOLS_URL="${KATA_RELEASE_BASE}/${KATA_RUNTIME_VERSION}/${KATA_TOOLS_TARBALL}"
KATA_TOOLS_EXTRACT_DIR="${GENPOLICY_WORKDIR}/kata-tools"
KATA_TOOLS_DIR="${KATA_TOOLS_EXTRACT_DIR}/opt/kata"
GENPOLICY_BIN="${KATA_TOOLS_DIR}/bin/genpolicy"
GENPOLICY_DEFAULTS="${KATA_TOOLS_DIR}/share/defaults/kata-containers"
mkdir -p "${GENPOLICY_WORKDIR}/genpolicy-settings.d" \
"${GENPOLICY_DOCKER_CONFIG}" \
"${KATA_TOOLS_EXTRACT_DIR}"
curl -fL \
-o "${GENPOLICY_WORKDIR}/${KATA_TOOLS_TARBALL}" \
"${KATA_TOOLS_URL}"
tar --zstd \
-xf "${GENPOLICY_WORKDIR}/${KATA_TOOLS_TARBALL}" \
-C "${KATA_TOOLS_EXTRACT_DIR}"
"${GENPOLICY_BIN}" -v \
-j "${GENPOLICY_DEFAULTS}"
准备 registry 凭据,并把与当前 Kata 版本匹配的 genpolicy 默认文件复制到本地工作目录:
: "${NGC_API_KEY:?set NGC_API_KEY to an NGC API key that can pull the NIM image}"
export NGC_API_KEY
GENPOLICY_AUTH="$(printf '$oauthtoken:%s' "${NGC_API_KEY}" | base64 -w0)"
jq -n \
--arg username '$oauthtoken' \
--arg password "${NGC_API_KEY}" \
--arg auth "${GENPOLICY_AUTH}" \
'{auths: {"nvcr.io": {username: $username, password: $password, auth: $auth}}}' \
> "${GENPOLICY_DOCKER_CONFIG}/config.json"
cp "${GENPOLICY_DEFAULTS}/rules.rego" \
"${GENPOLICY_WORKDIR}/rules.rego"
cp "${GENPOLICY_DEFAULTS}/genpolicy-settings.json" \
"${GENPOLICY_WORKDIR}/genpolicy-settings.json"
cp "${GENPOLICY_DEFAULTS}/drop-in-examples/20-oci-1.3.0-drop-in.json" \
"${GENPOLICY_WORKDIR}/genpolicy-settings.d/20-oci-1.3.0-drop-in.json"
cp "${NIM_TEE_MANIFEST}" "${NIM_POLICY_MANIFEST}"
cp "${INITDATA_FILE}" "${GENPOLICY_INITDATA}"
这里使用 OCI 1.3.0 的 drop-in,是为了和 GPU/SNP 的 CI 配置对齐。它会调整生成策略中预期的 OCI 版本。如果你的 containerd 栈使用其他 OCI 版本,请选择对应的 drop-in 或直接调整设置值。
可选项:如果你想在工作负载运行期间使用 kubectl logs 查看日志,可以在生成的 Kata agent policy 中开启 ReadStreamRequest。这对排障很有帮助,但生产环境一般不建议默认开放宿主侧日志流,除非你明确需要这个能力。
cat > "${GENPOLICY_WORKDIR}/genpolicy-settings.d/99-observation-read-stream.json" <<'EOF'
[
{
"op": "replace",
"path": "/request_defaults/ReadStreamRequest",
"value": true
}
]
EOF
运行 genpolicy。它会原地更新 NIM_POLICY_MANIFEST,为其加入 io.katacontainers.config.hypervisor.cc_init_data 字段;这个注解包含 checkpoint 4 的 initdata 以及新生成的 policy.rego。
(
cd "${GENPOLICY_WORKDIR}"
DOCKER_CONFIG="${GENPOLICY_DOCKER_CONFIG}" \
RUST_LOG=info \
"${GENPOLICY_BIN}" \
-u \
-y "${NIM_POLICY_MANIFEST}" \
-p "${GENPOLICY_WORKDIR}/rules.rego" \
-j "${GENPOLICY_WORKDIR}" \
--initdata-path="${GENPOLICY_INITDATA}"
)
对生成后的清单做 dry-run 校验:
kubectl apply --dry-run=server -f "${NIM_POLICY_MANIFEST}"
检查生成的 cc_init_data 注解:
kubectl create --dry-run=client \
-f "${NIM_POLICY_MANIFEST}" \
-o json \
| jq -r '
select(.kind == "Pod")
| .metadata.annotations["io.katacontainers.config.hypervisor.cc_init_data"]
' \
| base64 -d \
| gzip -d > "${GENPOLICY_WORKDIR}/generated-initdata.toml"
grep -n '^"aa\.toml"[[:space:]]*=' "${GENPOLICY_WORKDIR}/generated-initdata.toml"
grep -n '^"cdh\.toml"[[:space:]]*=' "${GENPOLICY_WORKDIR}/generated-initdata.toml"
grep -n '^"policy\.rego"[[:space:]]*=' "${GENPOLICY_WORKDIR}/generated-initdata.toml"
生成后的 initdata 必须同时包含 aa.toml、cdh.toml 和 policy.rego。保留好 NIM_POLICY_MANIFEST,checkpoint 6 会复用其中的 cc_init_data,而真正部署工作负载时也会直接用到这份清单。
完成这一节后,只需把完整生成的清单留在本地,不要急着部署 NIM 工作负载。应先把参考值、KBS 资源、KBS 策略和可信存储资源都准备好。
Checkpoint 6:收集 SNP 启动参考值
这一节收集 KBS 策略需要的 SNP 启动输入。只有在你还没有现成且已批准的参考值时,才需要执行本节的手工测量。生产环境中,这些值应根据可信发布制品和预期 VM 启动配置预先计算并审核,这样 KBS 策略可以在工作负载部署前就安装好。
如果你已经有批准过的值,可以直接按后文要求把它们写入 ${KBS_WORKDIR}/nim-snp-reference-values.env,然后跳到 checkpoint 7,不必再启动测量 Pod。
测量 Pod 可以很轻:不需要运行 NIM 镜像,也不需要真正对外提供服务。它只需要启动出与目标工作负载同类型的机密虚拟机,以便记录启动输入。因此要尽量保持和 TEE NIM Pod 一致的启动相关设置,特别是 runtime class、生成的 cc_init_data 注解、GPU 请求以及 CPU/内存规格,因为这些因素都会影响 QEMU 命令行,从而影响 SNP launch measurement。容器镜像本身可以只是一个简单的 sleep 容器。
这里的思路是:先让 Kata 真的把测量 Pod 启起来,再从宿主机侧找到对应的 QEMU 进程,提取 SEV-SNP 启动参数,并交给 sev-snp-measure 计算期望的 launch measurement。这样得到的参考值与 Kubernetes+Kata 实际拉起的 VM 形态是对齐的。
生成的 cc_init_data 也会影响测量值。Kata 会对该文档计算哈希,并将摘要写到 SNP HOST_DATA,后面的 KBS 资源策略会把这个摘要固定下来。
先确认节点处于机密 GPU 模式,并从带策略的清单中提取生成好的 cc_init_data:
kubectl get nodes \
-L nvidia.com/cc.mode,nvidia.com/cc.mode.state,nvidia.com/cc.ready.state
MEASUREMENT_POD_NAME="nim-snp-measurement"
MEASUREMENT_POD_MANIFEST="${KBS_WORKDIR}/nim-snp-measurement-pod.yaml"
NODE_NAME="$(
kubectl get nodes \
-l nvidia.com/cc.mode=on,nvidia.com/cc.mode.state=on,nvidia.com/cc.ready.state=true \
-o jsonpath='{.items[0].metadata.name}'
)"
CC_INIT_DATA="$(
kubectl create --dry-run=client \
-f "${NIM_POLICY_MANIFEST}" \
-o json \
| jq -r '
select(.kind == "Pod")
| .metadata.annotations["io.katacontainers.config.hypervisor.cc_init_data"]
'
)"
test -n "${NODE_NAME}"
test -n "${CC_INIT_DATA}"
test "${CC_INIT_DATA}" != "null"
创建一个轻量级度量 Pod。它的 CPU、内存和 GPU 限额与 TEE NIM Pod 保持一致,但镜像使用一个简单的 sleep 容器:
cat > "${MEASUREMENT_POD_MANIFEST}" <<EOF
apiVersion: v1
kind: Pod
metadata:
name: ${MEASUREMENT_POD_NAME}
annotations:
io.katacontainers.config.hypervisor.cc_init_data: "${CC_INIT_DATA}"
spec:
restartPolicy: Never
runtimeClassName: kata-qemu-nvidia-gpu-snp
nodeName: ${NODE_NAME}
containers:
- name: sleep
image: quay.io/prometheus/busybox:latest
imagePullPolicy: IfNotPresent
command: ["sh", "-c", "sleep 600"]
resources:
limits:
nvidia.com/pgpu: "1"
cpu: "16"
memory: "48Gi"
EOF
kubectl delete pod "${MEASUREMENT_POD_NAME}" --ignore-not-found
kubectl apply -f "${MEASUREMENT_POD_MANIFEST}"
这里还会用到两个 Kata 本身不提供的宿主机工具:sev-snp-measure 用来根据 QEMU 启动参数计算期望的 SNP launch measurement,snphost 用来读取宿主机报告的 SNP TCB 值。优先使用发行版包或已批准的 CI 镜像安装它们。若没有现成来源,可使用下面这种常见开发者安装方式。注意:有些发行版会阻止对 externally managed Python 环境执行 pip install --user,此时应改用虚拟环境或发行版包。
snphost 通过 crates.io 安装,需要 Rust 工具链提供的 cargo。cargo install 默认会把二进制写到 ${HOME}/.cargo/bin,因此后面的命令里需要把这个目录加入 PATH。
export PATH="${HOME}/.local/bin:${HOME}/.cargo/bin:${PATH}"
python3 -m pip install --user sev-snp-measure
cargo install --locked snphost
command -v sev-snp-measure
sev-snp-measure --help >/dev/null
command -v snphost
snphost --help >/dev/null
找到属于这个 Pod 的 QEMU 进程。这里用 Pod UID 做匹配,避免在同一宿主机上误抓到其他 Kata VM 的 QEMU 进程:
POD_UID="$(
kubectl get pod "${MEASUREMENT_POD_NAME}" \
-o jsonpath='{.metadata.uid}'
)"
POD_UID_UNDERSCORE="${POD_UID//-/_}"
QEMU_PID=""
for _ in $(seq 1 120); do
QEMU_PID="$(
for pid in $(pgrep -f 'qemu-system-' || true); do
if sudo grep -Eq "pod${POD_UID_UNDERSCORE}|${POD_UID}" \
"/proc/${pid}/cgroup" 2>/dev/null; then
echo "${pid}"
break
fi
done
)"
if test -n "${QEMU_PID}"; then
break
fi
sleep 2
done
test -n "${QEMU_PID}"
echo "${QEMU_PID}"
利用这个 QEMU 进程记录准确的启动输入,并计算 SNP launch measurement。脚本会写出 nim-qemu-launch-inputs.json 和 nim-snp-launch-measurement.txt,同时打印 measurement 和 SNP_HOST_DATA 便于快速核对:
PATH="${PATH}:${HOME}/.local/bin:${HOME}/.cargo/bin" \
QEMU_PID="${QEMU_PID}" \
KBS_WORKDIR="${KBS_WORKDIR}" \
python3 - <<'PY'
import json
import os
from pathlib import Path
import re
import subprocess
pid = os.environ["QEMU_PID"]
cmd = [
item.decode()
for item in Path(f"/proc/{pid}/cmdline").read_bytes().split(b"\0")
if item
]
def value(flag):
index = cmd.index(flag)
return cmd[index + 1]
kernel = value("-kernel")
firmware = value("-bios")
append = value("-append").replace(r"\"", '"')
cpu_model = value("-cpu").split(",", 1)[0]
vcpu_count = re.match(r"\d+", value("-smp")).group(0)
initrd = value("-initrd") if "-initrd" in cmd else None
snp_object = next(
cmd[index + 1]
for index, item in enumerate(cmd)
if item == "-object" and cmd[index + 1].startswith("sev-snp-guest")
)
host_data = re.search(r"(?:^|,)host-data=([^,]+)", snp_object).group(1)
out_dir = Path(os.environ["KBS_WORKDIR"])
inputs = {
"qemu_pid": pid,
"kernel": kernel,
"firmware": firmware,
"initrd": initrd,
"cpu_model": cpu_model,
"vcpu_count": int(vcpu_count),
"append": append,
"sev_snp_object": snp_object,
"host_data": host_data,
}
(out_dir / "nim-qemu-launch-inputs.json").write_text(
json.dumps(inputs, indent=2) + "\n"
)
measure_args = [
"sev-snp-measure",
"--mode=snp",
f"--vcpus={vcpu_count}",
f"--vcpu-type={cpu_model}",
"--output-format=hex",
f"--ovmf={firmware}",
f"--kernel={kernel}",
f"--append={append}",
]
if initrd:
measure_args.append(f"--initrd={initrd}")
measurement = subprocess.check_output(measure_args, text=True).strip()
(out_dir / "nim-snp-launch-measurement.txt").write_text(measurement + "\n")
print(measurement)
print(f"SNP_HOST_DATA={host_data}")
PY
然后从同一台宿主机读取 SNP TCB 值。snphost show tcb 会从 SNP host device 读取平台状态,后面的 awk 再把结果转成 checkpoint 7 直接可用的 shell 变量:
sudo snphost show tcb | tee "${KBS_WORKDIR}/nim-snp-tcb.txt"
awk '
/Reported TCB/ { reported = 1; next }
/Platform TCB/ { reported = 0 }
reported && /Microcode:/ { print "SNP_MICROCODE=" $2 }
reported && /SNP:/ { print "SNP_SNP_SVN=" $2 }
reported && /TEE:/ { print "SNP_TEE_SVN=" $2 }
reported && /Boot Loader:/ { print "SNP_BOOTLOADER=" $3 }
' "${KBS_WORKDIR}/nim-snp-tcb.txt" > "${KBS_WORKDIR}/nim-snp-tcb.env"
{
SNP_HOST_DATA_BASE64="$(
jq -r .host_data "${KBS_WORKDIR}/nim-qemu-launch-inputs.json"
)"
SNP_HOST_DATA_HEX="$(
printf '%s' "${SNP_HOST_DATA_BASE64}" \
| base64 -d \
| od -An -tx1 -v \
| tr -d ' \n'
)"
printf 'SNP_LAUNCH_MEASUREMENT=%s\n' "$(
cat "${KBS_WORKDIR}/nim-snp-launch-measurement.txt"
)"
printf 'SNP_HOST_DATA_BASE64=%s\n' "${SNP_HOST_DATA_BASE64}"
printf 'SNP_HOST_DATA_HEX=%s\n' "${SNP_HOST_DATA_HEX}"
cat "${KBS_WORKDIR}/nim-snp-tcb.env"
} > "${KBS_WORKDIR}/nim-snp-reference-values.env"
cat "${KBS_WORKDIR}/nim-snp-reference-values.env"
再验证在线 Pod 的 cc_init_data 注解哈希,是否与 QEMU 启动时写入 SNP HOST_DATA 的值一致。这一步能确认测量 Pod 实际收到的 initdata 与证明中看到的是同一份内容:
kubectl get pod "${MEASUREMENT_POD_NAME}" \
-o jsonpath='{.metadata.annotations.io\.katacontainers\.config\.hypervisor\.cc_init_data}' \
| base64 -d \
| gzip -d > "${KBS_WORKDIR}/live-generated-initdata.toml"
openssl dgst -sha256 -binary "${KBS_WORKDIR}/live-generated-initdata.toml" \
| base64 -w0
echo
jq -r .host_data "${KBS_WORKDIR}/nim-qemu-launch-inputs.json"
这两个值应该一致。
请保留 nim-snp-reference-values.env、nim-qemu-launch-inputs.json、nim-snp-launch-measurement.txt 和 nim-snp-tcb.txt,后续审核或复现时都可能用到。收集完成后,删除 Pod:
kubectl delete pod "${MEASUREMENT_POD_NAME}" --ignore-not-found
Checkpoint 7:写入 KBS 资源、参考值与策略
这一节会把 Guest 侧需要的 KBS 资源、checkpoint 6 收集到的 SNP 参考值,以及 KBS 资源策略 一并写入 KBS。教程里这些操作直接在工作节点上执行;生产环境应通过正式的 KBS 管理与发布流程完成,并且在工作负载部署前就准备好。
本节会向 KBS 写入以下内容:
- 用于 guest pull 从
nvcr.io拉取镜像的 认证仓库凭据,路径为default/credentials/nvcr - NVIDIA 公钥和容器 镜像签名策略
- NIM 运行时使用的明文 API key,路径为
default/ngc-api-key/instruct,它会被 checkpoint 3 生成的 sealed Kubernetes Secret 引用 - checkpoint 3 生成的 sealed-secret 验证公钥,供 CDH 在读取 KBS 明文 secret 前先验证 sealed 值签名
- checkpoint 6 收集到的 SNP 启动参考值
- 一条要求 CPU 与 GPU 证明都为 affirming,且 initdata 摘要匹配的 KBS 资源策略
如果你在 checkpoint 2 之后换了 shell,需要先恢复 KBS_CLIENT、KBS_WORKDIR、KBS_ADMIN_PRIVATE_KEY_FILE、KBS_CERT_FILE 和 KBS_URL 这些变量。
先加载 checkpoint 6 生成的参考值,或者用同样格式加载已经批准的参考值:
set -a
. "${KBS_WORKDIR}/nim-snp-reference-values.env"
set +a
生成供 CDH 在 guest pull 时使用的 NVCR registry auth 文件。它和 NIM 运行时 API key 是两个不同用途的凭据,即使本教程里两者使用了同一个 NGC API key:
export NGC_API_KEY="<NGC_API_KEY>"
AUTH_VALUE="$(printf '$oauthtoken:%s' "${NGC_API_KEY}" | base64 -w0)"
jq -n --arg auth "${AUTH_VALUE}" '{
auths: {
"nvcr.io": {
auth: $auth
}
}
}' > "${KBS_WORKDIR}/nvcr-auth.json"
printf '%s' "${NGC_API_KEY}" > "${KBS_WORKDIR}/ngc-api-key-instruct"
创建镜像签名策略,并拉取 NVIDIA 公钥:
cat > "${KBS_WORKDIR}/nim-image-policy.json" <<'EOF'
{
"default": [
{
"type": "reject"
}
],
"transports": {
"docker": {
"nvcr.io/nim/meta": [
{
"type": "sigstoreSigned",
"keyPath": "kbs:///default/cosign-public-key/nim",
"signedIdentity": {
"type": "matchRepository"
}
}
],
"nvcr.io/nim/nvidia": [
{
"type": "sigstoreSigned",
"keyPath": "kbs:///default/cosign-public-key/nim",
"signedIdentity": {
"type": "matchRepository"
}
}
]
}
}
}
EOF
curl -fsSL \
-o "${KBS_WORKDIR}/nvidia-cosign.pub" \
https://api.ngc.nvidia.com/v2/catalog/containers/public-key
把资源上传到 KBS。涉及密钥或凭据的上传把输出重定向到 /dev/null,避免终端回显编码后的敏感内容:
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-resource \
--path default/credentials/nvcr \
--resource-file "${KBS_WORKDIR}/nvcr-auth.json" \
>/dev/null
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-resource \
--path default/ngc-api-key/instruct \
--resource-file "${KBS_WORKDIR}/ngc-api-key-instruct" \
>/dev/null
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-resource \
--path default/security-policy/nim \
--resource-file "${KBS_WORKDIR}/nim-image-policy.json"
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-resource \
--path default/cosign-public-key/nim \
--resource-file "${KBS_WORKDIR}/nvidia-cosign.pub"
上传 checkpoint 3 生成的 sealed-secret 公钥。它必须与签名 ngc-api-key-instruct.sealed 时使用的私钥匹配,否则 CDH 无法验证 sealed 值:
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-resource \
--path default/signing-key/sealed-secret \
--resource-file "${KBS_WORKDIR}/signing-key-public.jwk"
写入默认 Trustee sample policy 会使用到的 SNP 参考值:
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-sample-reference-value \
snp_launch_measurement "${SNP_LAUNCH_MEASUREMENT}"
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-sample-reference-value \
--as-integer \
snp_bootloader "${SNP_BOOTLOADER}"
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-sample-reference-value \
--as-integer \
snp_microcode "${SNP_MICROCODE}"
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-sample-reference-value \
--as-integer \
snp_snp_svn "${SNP_SNP_SVN}"
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-sample-reference-value \
--as-integer \
snp_tee_svn "${SNP_TEE_SVN}"
SNP_HOST_DATA_BASE64 是 initdata 摘要写入 SNP HOST_DATA 后的 base64 值。Trustee 在完成 initdata 与 HOST_DATA 的校验后,会把相同字节内容以十六进制字符串 init_data 暴露在 CPU attestation token 中。这里不会用 set-sample-reference-value 去种 SNP_HOST_DATA_HEX,而是直接在 KBS 资源策略中检查 annotated_evidence["init_data"],这样可以把整份 initdata 逐字节固定下来。
安装资源策略。它要求 CPU 和 GPU attestation submodule 都为 affirming,同时检查Guest启动时使用的 initdata 摘要是否等于批准值:
cat > "${KBS_WORKDIR}/nim-kbs-resource-policy.rego" <<EOF
package policy
import rego.v1
default allow = false
cpu0 := input["submods"]["cpu0"]
gpu0 := input["submods"]["gpu0"]
annotated_evidence := cpu0["ear.veraison.annotated-evidence"]
expected_init_data := "${SNP_HOST_DATA_HEX}"
allow if {
cpu0["ear.status"] == "affirming"
gpu0["ear.status"] == "affirming"
annotated_evidence["init_data"] == expected_init_data
}
EOF
"${KBS_CLIENT}" \
--cert-file "${KBS_CERT_FILE}" \
--url "${KBS_URL}" \
config \
--admin-token-file "${KBS_ADMIN_TOKEN_FILE}" \
set-resource-policy \
--policy-file "${KBS_WORKDIR}/nim-kbs-resource-policy.rego"
checkpoint 2 的 Compose 部署会把演示状态保存在 KBS_DATA_DIR,并把配置与身份材料保存在 KBS_CONFIG_DIR。在清理之前不要删除它们。如果 KBS_DATA_DIR 被重置,就需要重新执行本节,把资源、参考值和策略重新写回 KBS。生产环境中,KBS 应使用与其信任边界匹配的持久化存储和受保护身份材料。
Checkpoint 8:准备可信存储资源
这一节会创建 TEE NIM Pod 清单中引用的可信存储资源,但暂时仍不部署 NIM Pod。关于 CoCo 的 受保护存储 模型,以及机密 runtime class 使用的 confidential emptyDir 原语,可参考对应文档。
这里要创建的是 TEE Pod 作为 /dev/trusted_store 挂载的可信存储。启用 guest pull 后,CDH 可以把下载的镜像数据写到这个块设备上,而不是塞进 Guest /run 文件系统。教程里使用的是 loop-backed 本地 PersistentVolume;生产环境应替换为更适合集群的块存储实现。
示例中使用 /tmp 作为 backing file 目录,在很多 CI 系统上它本身就是较大的宿主机 tmpfs。若你的机器空间较小或需要长时间运行,请把 LOOP_FILE 换到磁盘文件系统或单独准备的大容量 tmpfs 上。
NODE_NAME="$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')"
LOOP_FILE="/tmp/trusted-image-storage-instruct.img"
STORAGE_SIZE_MIB="57344"
df -h "$(dirname "${LOOP_FILE}")"
# Match Kata's NVIDIA NIM CI setup: remove any stale loop device and fully
# allocate the backing file before the workload starts. A sparse tmpfs file
# created with truncate can charge pages to the pod cgroup as QEMU writes them.
if LOOP_DEVICE="$(sudo losetup -j "${LOOP_FILE}" | awk -F: 'NR == 1 {print $1}')"; \
test -n "${LOOP_DEVICE}"; then
sudo losetup --detach "${LOOP_DEVICE}"
fi
rm -f "${LOOP_FILE}"
dd if=/dev/zero of="${LOOP_FILE}" bs=1M count="${STORAGE_SIZE_MIB}" status=progress
LOOP_DEVICE="$(sudo losetup --find --show "${LOOP_FILE}")"
echo "NODE_NAME=${NODE_NAME}"
echo "LOOP_DEVICE=${LOOP_DEVICE}"
根据上面的值生成 trusted-storage-instruct.yaml:
cat > trusted-storage-instruct.yaml <<EOF
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: trusted-block-pv-instruct
spec:
capacity:
storage: 57344Mi
volumeMode: Block
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: ${LOOP_DEVICE}
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- ${NODE_NAME}
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: trusted-pvc-instruct
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 57344Mi
volumeMode: Block
storageClassName: local-storage
EOF
应用这些存储对象:
kubectl apply -f trusted-storage-instruct.yaml
kubectl get pv trusted-block-pv-instruct
kubectl get pvc trusted-pvc-instruct
由于这里的 StorageClass 使用 WaitForFirstConsumer,所以在 TEE NIM Pod 真正部署前,PVC 保持 Pending 是正常现象。
Checkpoint 9:运行端到端场景
这一节会创建 TEE NIM Pod 所需的 Kubernetes Secret,部署带策略的 TEE NIM 清单,并验证在 KBS 策略生效的情况下 NIM API 仍然可访问。执行前,KBS 资源、SNP 参考值、KBS 资源策略和可信存储都应已准备完成。
: "${NGC_API_KEY:?set NGC_API_KEY to an NGC API key}"
NIM_POLICY_MANIFEST="${NIM_POLICY_MANIFEST:-${KBS_WORKDIR}/genpolicy/nvidia-nim-llama-3-1-8b-instruct-tee-policy.yaml}"
kubectl create secret docker-registry ngc-secret-instruct \
--docker-server=nvcr.io \
--docker-username='$oauthtoken' \
--docker-password="${NGC_API_KEY}" \
--dry-run=client \
-o yaml | kubectl apply -f -
kubectl apply -f "${NIM_POLICY_MANIFEST}"
kubectl get secret ngc-api-key-sealed-instruct
kubectl get secret ngc-api-key-sealed-instruct \
-o jsonpath='{.data.api-key}' | base64 -d | cut -c1-7
echo
kubectl wait --for=condition=Ready \
--timeout=1000s \
pod/nvidia-nim-llama-3-1-8b-instruct
验证 TEE 工作负载能否通过 NIM API 正常响应:
POD_IP="$(kubectl get pod nvidia-nim-llama-3-1-8b-instruct -o jsonpath='{.status.podIP}')"
curl -fsS "http://${POD_IP}:8000/v1/health/ready" | jq .
curl -fsS "http://${POD_IP}:8000/v1/models" | jq -r '.data[].id'
MODEL_NAME="$(curl -fsS "http://${POD_IP}:8000/v1/models" | jq -r '.data[0].id')"
curl -fsS "http://${POD_IP}:8000/v1/chat/completions" \
-H 'Content-Type: application/json' \
-d "$(jq -n --arg model "${MODEL_NAME}" '{
model: $model,
messages: [{role: "user", content: "Reply with exactly: hello from tee nim"}],
max_tokens: 16,
temperature: 0
}')" | jq -r '.choices[0].message.content'
如果你在 checkpoint 5 启用了可选的 ReadStreamRequest 策略覆盖,那么还可以直接查看容器日志:
kubectl logs nvidia-nim-llama-3-1-8b-instruct
如果 Pod 无法在 KBS 策略下获取资源,优先检查 KBS 日志。参考值缺失或不匹配,通常会表现为 cpu0 子模块不是 affirming,或者 AS 策略日志中提示某个 reference value identifier 未找到。
如果需要进一步排障,请看后面的 附录:故障排查。如果你想把节点恢复到一个可重跑教程的干净状态,可直接使用 附录:故障排查。
展望:生产自动化
前面的 checkpoint 展示了如何在单节点上手动跑通机密 NIM 的完整路径。之所以保留手工步骤,是为了让每个信任边界都能被观察到。真正进入生产环境后,这些结果都应该自动化,不应要求操作者在部署时手改清单或执行宿主机命令。
这个流程不存在“一个人包办全部”的合理生产模型。工作负载平台负责把什么 Pod 交给 Kubernetes;发布流程负责生成带策略的制品;集群与 Trustee 团队负责基础信任设施。下面表格把生产职责拆成五类角色,组织内部可以合并角色,但每项责任都应明确归属。
- 集群平台团队或云平台运维:负责集群 runtime、GPU Operator 集成和 runtime class
- Trustee 运维方:在可信环境中运行 Trustee
- KBS 管理员:负责 KBS 资源、策略和参考值的写入与维护
- 发布工程或可信发布流水线:生成 sealed secret、initdata、agent policy 等发布制品
- 模型服务平台或工作负载操作者:把已批准的工作负载清单提交给 Kubernetes
| 职责项 | 本教程 | 生产角色 | 生产职责 |
|---|---|---|---|
支持 CoCo 的 Kata runtime 与 GPU RuntimeClass |
前提条件及 NVIDIA Confidential Containers 部署指南 | 集群平台团队或云运维 | 安装并升级 Kata、kata-deploy 或 Helm、GPU Operator,并注册 kata-qemu-nvidia-gpu-snp |
| Trustee 服务 | Checkpoint 2:在教程节点上用 Docker Compose 部署 Trustee | Trustee 运维方 | 在可信环境中运行 Trustee,使用持久化存储并审计管理访问 |
| KBS 资源、镜像策略、仓库凭据 | Checkpoint 7:在 worker 上用 kbs-client |
KBS 管理员 | 通过受批准的管理工具配置资源;变更需版本化与评审 |
| 密封运行时密钥(sealed runtime secrets) | Checkpoint 3、7、9:生成 sealed secret、写入 KBS 资源、再应用到 Kubernetes | 发布工程或可信发布流水线 | 使用集群 CoCo 密封密钥进行密封,仅通过受批准发布路径发布密封对象 |
| TEE Pod 清单与存储依赖 | Checkpoint 3、8:手工编写 manifest 与 PVC | 模型服务平台或工作负载操作者 | 基于固定输入为每个模型/版本产出固定 Pod 模板;向 Kubernetes 提交已批准的 manifest |
Guest initdata(aa.toml、cdh.toml) |
Checkpoint 4:在 worker 上生成 nim-initdata.toml |
发布工程或可信发布流水线 | 产出与已批准 KBS URL 和证书绑定的 initdata |
Kata agent policy 与 cc_init_data |
Checkpoint 5:在 worker 上运行 genpolicy |
发布工程或可信发布流水线 | 在模型镜像与 Pod 模板固定后,对已批准 Pod manifest 运行 genpolicy;评审并与发布物一并发布 |
| SNP 参考值与 KBS 策略 | Checkpoint 6–7:在 worker 上测量启动,再配置 KBS | 发布工程或可信发布流水线;KBS 管理员 | 提前从可信启动产物与预期 VM 配置批准参考值;在部署前安装 KBS 策略 |
附录:故障排查
这一节只针对本文这套 NIM 部署路径。建议先看 Kubernetes 状态,再看 KBS 日志。大多数问题通常落在三类:Pod 根本没启动出 sandbox、Guest 启动了但拿不到 KBS 资源,或者 NIM 启动了但迟迟没有 Ready。
先看 Pod 状态和事件:
kubectl get pod nvidia-nim-llama-3-1-8b-instruct -o wide
kubectl describe pod nvidia-nim-llama-3-1-8b-instruct
kubectl get events \
--field-selector involvedObject.name=nvidia-nim-llama-3-1-8b-instruct \
--sort-by=.lastTimestamp
对于 Trustee,先看 Compose 服务状态和日志:
docker compose \
--project-directory "${KBS_TRUSTEE_DIR}" \
--project-name nim-trustee \
ps
docker compose \
--project-directory "${KBS_TRUSTEE_DIR}" \
--project-name nim-trustee \
logs --since 30m kbs as rvps
下面这些 KBS 日志标记最有参考价值:
POST /kbs/v0/auth后面紧跟POST /kbs/v0/attest:说明 Guest 已成功访问 KBS 并开始证明Verifier/endorsement check passed. tee=Snp tee_class="cpu":说明 SNP 证据校验通过Verifier/endorsement check passed. tee=Nvidia tee_class="gpu":说明 GPU 证据校验通过GET /kbs/v0/resource/... 200:说明资源请求通过了资源策略GET /kbs/v0/resource/default/signing-key/sealed-secret 200,随后又有GET /kbs/v0/resource/default/ngc-api-key/instruct 200:说明 Guest 通过 CDH 成功解封了运行时NGC_API_KEY- checkpoint 9 期间如果出现
No reference value found for the given id: snp_launch_measurement:通常表示 checkpoint 7 没有把参考值写入当前 KBS,或者写入到了另一个 KBS 实例,或者演示 KBS 存储被清空了 - 如果
POST /kbs/v0/attest成功但后续资源获取被拒绝,通常就是 KBS 资源策略不匹配,需要检查cpu0、gpu0或 initdata 摘要检查哪一项失败 - 如果
gpu0不是affirming,先确认 NVIDIA GPU 固件和驱动栈是否满足当前平台的机密 GPU 要求。GPU 固件过旧会直接导致 GPU 证明失败 - 如果
credentials/nvcr、security-policy/nim、cosign-public-key/nim这些镜像相关资源都能返回 HTTP 200,但 KBS 从未看到signing-key/sealed-secret或ngc-api-key/instruct请求,那么容器大概率拿到的仍是 sealed 版NGC_API_KEY。这时需要重新生成 sealed secret 和genpolicy输出,并确认 sealed-secret 文件没有多余换行,而且受保护头部格式与 checkpoint 3 一致
有些警告在本教程里并不致命。例如,默认证明策略可能会提示 snp_smt_enabled 或 allowed_vbios_versions 这类可选参考值缺失。真正需要确认的是:记录下来的 SNP launch measurement 和 TCB 值存在,cpu0 与 gpu0 最终都变成 affirming,并且资源请求返回 HTTP 200。
如果 kubectl logs nvidia-nim-llama-3-1-8b-instruct 被拒绝,通常不是 KBS 的问题,而是生成的 Kata agent policy 没允许宿主侧读取日志。若排障期间确实需要日志,请在 checkpoint 5 生成策略前启用可选的 ReadStreamRequest 覆盖。生产策略通常不会默认开放这个能力。
NIM 在绑定 8000 端口之前,startup probe 短暂失败并不罕见,尤其是在模型下载、权重加载和 vLLM graph capture 阶段。只有当 startup probe 达到失败阈值或 Pod 进入失败状态时,才需要把它当成问题处理。
如果你要确认当前在线启动是否仍与参考值一致,可以重复 checkpoint 6 的 QEMU 与 sev-snp-measure 收集流程。重点关注 kernel append 字符串和 SNP HOST_DATA。另外,/opt/kata/share/defaults/kata-containers/runtimes/qemu-nvidia-gpu-snp/config.d 里的遗留配置文件也可能改动 QEMU 命令行,从而改变 SNP launch measurement。
KBS、AS 和 RVPS 使用 Rust tracing。Compose 文件会把宿主机环境中的 RUST_LOG 传给这些服务。如果你想在启动 Trustee 前提高日志级别,可先导出下面的环境变量,再执行 checkpoint 2 里的 docker compose up:
export RUST_LOG=info,kbs=debug,attestation_service=debug,reference_value_provider_service=debug,policy_engine=debug
如果服务已经在运行,可带着这个环境变量重新创建 Compose 服务:
export RUST_LOG=info,kbs=debug,attestation_service=debug,reference_value_provider_service=debug,policy_engine=debug
docker compose \
--project-directory "${KBS_TRUSTEE_DIR}" \
--project-name nim-trustee \
up -d --no-build --force-recreate kbs as rvps
这会重启 Compose 服务。只要 KBS_DATA_DIR 没被删除,教程里用的演示存储在同一台节点上重启容器后仍然会保留 KBS 状态。如果存储目录已重置,就需要重新执行 checkpoint 7 后再测试工作负载。
恢复默认日志级别:
export RUST_LOG=info
docker compose \
--project-directory "${KBS_TRUSTEE_DIR}" \
--project-name nim-trustee \
up -d --no-build --force-recreate kbs as rvps
生产环境不要长期打开 debug 日志,除非你已经确认日志内容和留存路径不会泄露过多 attestation claim、策略决策细节或资源标识信息。
附录:清理
如果你想从 checkpoint 1 重新跑一遍整套教程,可用这一节清理当前节点上的演示资源。它会删除教程工作负载、Trustee Compose 部署、本地 loop 存储以及生成的本地文件。若当前集群是多人共享环境,请先确认这些资源名确实只用于本教程。
KBS_WORKDIR="${KBS_WORKDIR:-${HOME}/nim-kbs}"
KBS_TRUSTEE_DIR="${KBS_TRUSTEE_DIR:-${KBS_WORKDIR}/trustee}"
GENPOLICY_WORKDIR="${GENPOLICY_WORKDIR:-${KBS_WORKDIR}/genpolicy}"
LOOP_FILE="${LOOP_FILE:-/tmp/trusted-image-storage-instruct.img}"
if test "${KBS_WORKDIR}" = "/" \
|| test "${KBS_TRUSTEE_DIR}" = "/" \
|| test "${GENPOLICY_WORKDIR}" = "/"; then
echo "Refusing to remove /"
exit 1
fi
if test -d "${KBS_TRUSTEE_DIR}"; then
docker compose \
--project-directory "${KBS_TRUSTEE_DIR}" \
--project-name nim-trustee \
down --remove-orphans
fi
kubectl delete pod nvidia-nim-llama-3-1-8b-instruct \
--ignore-not-found
kubectl delete pod nim-snp-measurement --ignore-not-found
kubectl delete secret \
ngc-secret-instruct \
ngc-api-key-instruct \
ngc-api-key-sealed-instruct \
--ignore-not-found
kubectl delete pvc trusted-pvc-instruct --ignore-not-found
kubectl delete pv trusted-block-pv-instruct --ignore-not-found
kubectl delete storageclass local-storage --ignore-not-found
if LOOP_DEVICE="$(sudo losetup -j "${LOOP_FILE}" | awk -F: 'NR == 1 {print $1}')" \
&& test -n "${LOOP_DEVICE}"; then
sudo losetup --detach "${LOOP_DEVICE}"
fi
rm -f "${LOOP_FILE}"
rm -rf "${GENPOLICY_WORKDIR}" || sudo rm -rf "${GENPOLICY_WORKDIR}"
rm -rf "${KBS_WORKDIR}" || sudo rm -rf "${KBS_WORKDIR}"
rm -f trusted-storage-instruct.yaml \
nvidia-nim-llama-3-1-8b-instruct.yaml \
nvidia-nim-llama-3-1-8b-instruct-tee.yaml
如果你在实验中额外创建过 runtime 配置 drop-in,请在重跑教程前先检查:
sudo ls -la \
/opt/kata/share/defaults/kata-containers/runtimes/qemu-nvidia-gpu-snp/config.d
只删除你明确为本教程创建的文件。
7 - 使用 SNP 内存加密启动容器
说明: 本文为英文文档的中文译版,英文原版请参见 Container Launch with SNP Memory Encryption。
使用 SNP 内存加密启动容器
启动机密服务
要以 SNP 内存加密方式启动容器,需要显式指定 SNP runtime class,也就是 kata-qemu-snp。这里使用一个提前构建好的 alpine 测试镜像(Dockerfile)。这个镜像已经启用 SSH,并预置了一个用于验证的 SSH 公钥。
下面是一个指定 SNP runtime class 的示例 Service 和 Deployment:
kind: Service
apiVersion: v1
metadata:
name: "confidential-unencrypted"
spec:
selector:
app: "confidential-unencrypted"
ports:
- port: 22
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: "confidential-unencrypted"
spec:
selector:
matchLabels:
app: "confidential-unencrypted"
template:
metadata:
labels:
app: "confidential-unencrypted"
annotations:
io.containerd.cri.runtime-handler: kata-qemu-snp
spec:
runtimeClassName: kata-qemu-snp
containers:
- name: "confidential-unencrypted"
image: ghcr.io/kata-containers/test-images:unencrypted-nightly
imagePullPolicy: Always
把这段 YAML 保存为 confidential-unencrypted.yaml。
启动服务:
kubectl apply -f confidential-unencrypted.yaml
检查是否有错误:
kubectl describe pod confidential-unencrypted
如果 Events 区域没有报错,说明容器已经成功以 SNP 内存加密方式启动。
验证 SNP 内存加密
可以通过容器内的 dmesg 输出来确认 SNP 内存加密是否已经启用。上面的示例镜像内置了对应私钥所匹配的 SSH 公钥,因此可以直接远程登录验证。
先获取 Pod IP:
pod_ip=$(kubectl get pod -o wide | grep confidential-unencrypted | awk '{print $6;}')
下载 SSH 私钥并设置权限:
wget https://github.com/kata-containers/kata-containers/raw/main/tests/integration/kubernetes/runtimeclass_workloads/confidential/unencrypted/ssh/unencrypted -O confidential-image-ssh-key
chmod 600 confidential-image-ssh-key
下面这条命令会通过 SSH 到容器内部执行命令,检查 SNP 内存加密是否生效:
ssh -i confidential-image-ssh-key \
-o "StrictHostKeyChecking no" \
-t root@${pod_ip} \
'dmesg | grep "Memory Encryption Features"'
如果 SNP 已经启用,输出应类似:
[ 0.150045] Memory Encryption Features active: AMD SNP