本文发布已超过一年。较早的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已不再准确。

Kubernetes 中的取证容器检查点

容器取证检查点基于 用户空间中的检查点/恢复 (CRIU),允许创建运行中容器的状态化副本,且容器本身并不知道正在被创建检查点。该容器副本可以在沙箱环境中多次分析和恢复,而原始容器并不会感知到。容器取证检查点作为 Alpha 特性在 Kubernetes v1.25 中引入。

工作原理是什么?

借助 CRIU 可以对容器进行检查点创建和恢复。CRIU 已集成到 runc、crun、CRI-O 和 containerd 中,Kubernetes 中实现的容器取证检查点功能就使用了这些现有的 CRIU 集成。

为什么它很重要?

借助 CRIU 及相应的集成,可以将运行中容器的所有信息和状态保存到磁盘上,以便后续进行取证分析。取证分析对于检查可疑容器而不停止或影响它可能很重要。如果容器确实受到攻击,攻击者可能会检测到检查容器的尝试。在沙箱环境中创建检查点并分析容器,可以在原始容器甚至攻击者不知情的情况下检查容器。

除了容器取证检查点的用例外,还可以将容器从一个节点迁移到另一个节点,而不会丢失其内部状态。特别是对于初始化时间较长的有状态容器,从检查点恢复可以在重启后节省时间,或显著加快启动速度。

如何使用容器检查点功能?

该特性位于一个特性门控之后,因此在使用新特性之前,请务必启用 ContainerCheckpoint 门控。

运行时也必须支持容器检查点功能

  • containerd:支持目前正在讨论中。详情请参阅 containerd 拉取请求 #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://localhost:10250/checkpoint/namespace/podId/container"

对于 default 命名空间中名为 counters 的 Pod 中名为 counter 的容器,可以通过以下地址访问 kubelet API 端点

curl -X POST "https://localhost: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 归档文件,可以在 CRI-O 的沙箱实例中将容器恢复到 Kubernetes 外部。为了获得更好的恢复用户体验,建议使用来自 CRI-O GitHub main 分支的最新版本 CRI-O。如果使用 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 结合使用时才起作用。请将此镜像格式视为 pre-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 的规约中。这是一个示例 manifest

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

延伸阅读

请参阅后续文章容器取证分析,了解如何分析容器检查点。