本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
Kubernetes 中的容器取证检查点
取证式容器检查点技术基于用户空间中的检查点/恢复(Checkpoint/Restore In Userspace,CRIU),允许在容器不知情的情况下为其创建有状态的副本。容器的副本可以在沙盒环境中被多次分析和恢复,而原始容器对此毫不知情。取证式容器检查点技术在 Kubernetes v1.25 中作为 Alpha 功能引入。
它是如何工作的?
借助 CRIU,可以对容器进行检查点操作和恢复。CRIU 已被集成到 runc、crun、CRI-O 和 containerd 中,Kubernetes 中实现的取证式容器检查点功能正是使用了这些现有的 CRIU 集成。
为什么它很重要?
借助 CRIU 及其相应的集成,可以将运行中容器的所有信息和状态保存到磁盘,以供日后进行取证分析。取证分析对于在不停止或影响可疑容器的情况下对其进行检查可能非常重要。如果容器确实受到攻击,攻击者可能会检测到检查容器的尝试。创建一个检查点并在沙盒环境中分析容器,提供了一种在原始容器乃至攻击者可能不知情的情况下检查容器的可能性。
除了取证式容器检查点的用例之外,还可以在不丢失内部状态的情况下将容器从一个节点迁移到另一个节点。特别是对于初始化时间较长的有状态容器,从检查点恢复可以在重启后节省时间,或实现更快的启动时间。
我该如何使用容器检查点功能?
该功能受特性门控保护,因此请确保启用 ContainerCheckpoint
门控后才能使用这项新功能。
容器运行时也必须支持容器检查点功能
containerd:支持正在讨论中。更多细节请参见 containerd 的 PR #6965。
CRI-O:v1.25 版本支持取证式容器检查点。
使用 CRI-O 的示例
要在 CRI-O 中使用取证式容器检查点功能,运行时需要使用命令行选项 --enable-criu-support=true
启动。对于 Kubernetes,你需要启用 ContainerCheckpoint
特性门控来运行你的集群。由于检查点功能由 CRIU 提供,因此也需要安装 CRIU。通常 runc 或 crun 依赖于 CRIU,因此它会被自动安装。
同样需要指出的是,在撰写本文时,检查点功能在 CRI-O 和 Kubernetes 中应被视为 Alpha 级别的特性,其安全影响仍在评估中。
一旦容器和 Pod 开始运行,就可以创建检查点了。检查点功能目前仅在 kubelet 级别公开。要对一个容器设置检查点,你可以在该容器运行的节点上运行 curl
,并触发一个检查点操作。
curl -X POST "https://:10250/checkpoint/namespace/podId/container"
对于一个位于 default 命名空间中名为 counters 的 Pod 内,名为 counter 的容器,其 kubelet API 端点地址为:
curl -X POST "https://:10250/checkpoint/default/counters/counter"
为了完整起见,以下 curl
命令行选项是必需的,以便让 curl
接受 kubelet 的自签名证书并授权使用 kubelet 的 checkpoint
API。
--insecure --cert /var/run/kubernetes/client-admin.crt --key /var/run/kubernetes/client-admin.key
触发这个 kubelet API 将会向 CRI-O 请求创建一个检查点。CRI-O 会向你的底层运行时(例如,runc
)请求一个检查点。收到该请求后,runc
会调用 criu
工具来执行实际的检查点操作。
一旦检查点操作完成,检查点文件应该位于 /var/lib/kubelet/checkpoints/checkpoint-<pod-name>_<namespace-name>-<container-name>-<timestamp>.tar
。
然后,你可以使用该 tar 归档文件在其他地方恢复容器。
在 Kubernetes 之外恢复已设置检查点的容器(使用 CRI-O)
利用检查点 tar 归档文件,可以在 Kubernetes 之外的一个沙盒化的 CRI-O 实例中恢复容器。为了在恢复过程中获得更好的用户体验,我建议你使用 CRI-O GitHub 主分支的最新版本。如果你使用的是 CRI-O v1.25,则需要在启动容器之前手动创建一些 Kubernetes 会创建的目录。
在 Kubernetes 之外恢复容器的第一步是使用 crictl 创建一个 Pod 沙箱:
crictl runp pod-config.json
然后,你可以将之前创建检查点的容器恢复到新创建的 Pod 沙箱中:
crictl create <POD_ID> container-config.json pod-config.json
在 container-config.json
中,你需要指定之前创建的检查点归档文件的路径,而不是指定镜像仓库中的容器镜像:
{
"metadata": {
"name": "counter"
},
"image":{
"image": "/var/lib/kubelet/checkpoints/<checkpoint-archive>.tar"
}
}
接下来,运行 crictl start <CONTAINER_ID>
来启动该容器,然后一个之前被设置检查点的容器的副本就应该在运行了。
在 Kubernetes 内部恢复已设置检查点的容器
要在 Kubernetes 中直接恢复之前设置检查点的容器,需要将检查点归档文件转换为一个可以推送到镜像仓库的镜像。
一种将本地检查点归档文件进行转换的可行方法包括以下步骤,借助 buildah 完成:
newcontainer=$(buildah from scratch)
buildah add $newcontainer /var/lib/kubelet/checkpoints/checkpoint-<pod-name>_<namespace-name>-<container-name>-<timestamp>.tar /
buildah config --annotation=io.kubernetes.cri-o.annotations.checkpoint.name=<container-name> $newcontainer
buildah commit $newcontainer checkpoint-image:latest
buildah rm $newcontainer
生成的镜像并非标准化格式,仅能与 CRI-O 配合使用。请将此镜像格式视为预 Alpha 阶段。目前正在进行讨论,以标准化此类检查点镜像的格式。需要记住的重要一点是,这种尚未标准化的镜像格式只有在 CRI-O 启动时带有 --enable-criu-support=true
参数时才能工作。启动支持 CRIU 的 CRI-O 所带来的安全影响尚不明确,因此应谨慎使用该功能及镜像格式。
现在,你需要将该镜像推送到一个容器镜像仓库。例如:
buildah push localhost/checkpoint-image:latest container-image-registry.example/user/checkpoint-image:latest
要恢复这个检查点镜像(container-image-registry.example/user/checkpoint-image:latest
),该镜像需要在 Pod 的规约中列出。下面是一个示例清单:
apiVersion: v1
kind: Pod
metadata:
namePrefix: example-
spec:
containers:
- name: <container-name>
image: container-image-registry.example/user/checkpoint-image:latest
nodeName: <destination-node>
Kubernetes 将新的 Pod 调度到一个节点上。该节点上的 kubelet 指示容器运行时(本例中为 CRI-O)基于指定的镜像 registry/user/checkpoint-image:latest
创建并启动一个容器。CRI-O 检测到 registry/user/checkpoint-image:latest
是对检查点数据的引用,而不是一个容器镜像。然后,CRI-O 不会执行创建和启动容器的常规步骤,而是获取检查点数据并从该指定的检查点恢复容器。
该 Pod 中的应用程序会继续运行,就好像没有进行过检查点操作一样;在容器内部,应用程序看起来和行为上都像任何其他正常启动而不是从检查点恢复的容器一样。
通过这些步骤,可以将一个节点上运行的 Pod 替换为另一个节点上运行的新的等效 Pod,而不会丢失该 Pod 中容器的状态。
我如何参与?
你可以通过多种方式联系 SIG Node
进一步阅读
关于如何分析容器检查点的详细信息,请参阅后续文章取证式容器分析。