containerd CRI 简要分析

概述

Containerd 在 release1.5 之后内置了 cri。通过暴露 CRIService 供 kubelet 调用。CRI 的封装并不复杂,都是利用了 containerd 本身的功能模块。通过 CRI 管理 pod 主要分为三个模块:

  • Sandbox 的管理:RunPodSandbox、StopPodSandbox、RemovePodSandbox、PodSandboxStatus、ListPodSandbox
  • Container 的管理:CreateContainer、StartContainer、StopContainer、RemoveContainer、ListContainers、UpdateContainerResources、ContainerStats、ListContainerStats、UpdateRuntimeConfig、Status
  • 容器其他方面的管理:ReopenContainerLog、ExecSync、Exec、Attach、PortForward

Sandbox 的管理

Sandbox 和 Container 在实现上类似,通过启动一个特殊的 pause 容器来创建一个沙箱环境。通常情况下,这个沙箱环境具有和主机隔离的 pid,uts,mount,network,ipc,user。

以 RunPodSandbox 为例,containerd 会执行以下操作:

  1. Containerd CRI 在 RunPodSandbox 时,会先保证 sandbox 的镜像是否存在。如果不存在则会进行 pull 操作。
  2. 创建 pod network namespace,并调用 CNI
  3. 使用 container task 来创建容器
  4. 启动 container task,也就是执行二进制文件 pause
  5. 更新 sandbox 的状态,包括 pid 置为 task pid,state 置为 ready 以及更新创建时间。
  6. 在新的 goroutine 中启动 sandbox exit monitoring。这样就可以在 sandbox 进程退出时,执行清理工作。然后更新 sandbox 的状态。

以 StopPodSandbox 为例,containerd 会执行以下操作:

  1. 使用 containerStore 来 list 出所有 container,并使用 container.SandboxID 过滤出属于当期 sandbox 的 container
  2. 依次停止 sandbox 下的 container
  3. 清理 sandbox 的文件,比如 unmount dev shm
  4. 如果 sandbox container(pause) 的状态是 Ready 或 Unknown,使用 SiGKILL 终止 sandbox container
  5. 调用 cni 清理 network namespace,然后移除。

Container 管理

在 sandbox 创建好之后,kubelet 就可以通过 CRI 来创建 container 了。CreateContainer 的逻辑如下:

  1. 获取 sandbox 的信息,后面创建的容器需要使用和 sandbox 同样的 runtime,namespace。
  2. 获取 container mounts,包括 /etc/hosts/etc/resolv.condev shm。然后设置
  3. 设置 container log path。
  4. 设置 container io,包括 stdin, stdout, stderr, terminal。
  5. 在 container store(metadata) 中创建容器记录。此时 container 处于 CREATED 状态。

创建好 container 后,调用 StartContainer 即可运行容器。步骤如下:

  1. 更新 container 状态为 Running,防止重复 start。
  2. 设置 container stdout,stderr 到 log 文件上。
  3. 启动 task 来运行 container 进程
  4. 更新 container status 的 Pid 和 StartedAt
  5. 在新的 goroutine 中启动 sandbox exit monitoring 来监控 container 的进程状态。

StopContainer 的步骤如下:

  1. 如果 container 设置了 stop timeout,使用 task.Kill 发送 SIGTERM 信号。然后等待 timeout 时间
  2. 使用 task.Kill 发送 SIGKILL 信号。

RemoveConrtainer 的步骤如下:

  1. 如果当前 container 处于 RUNNING 或者 UNKNOWN 状态,则使用 timeout 为 0 的 stopContainer 来强制停止容器。
  2. 设置 container 的状态为 removing。
  3. 从 store 中删除 container,checkpoint 以及一些缓存信息。

容器管理

Containerd CRI 除了实现了 sandbox 和 container 的生命周期管理,也提供了对容器其他方面的管理。比如:

  1. 在 kubelet 对 logfile rotate 之后,调用 ReopenContainerLog 来将 container log 输出到新的日志文件中
  2. 通过 GRPC 提供 GetAttach,返回 attach http endpoint 和 token,供 kubelet 通过 http stream 连接到 process 的 stdin,stdout 和 stderr 上。
  3. 通过 GRPC 提供 GetExec,返回 exec http endpoint 和 token,供 kubelet 通过 http stream 在容器命名空间内执行命令。
  4. 通过 GRPC 提供 GetPortforward,返回 portforward http endpoint 和 token。kubelet 通过 http stream 连接上后,使用 netns 在 sandbox network namespace 下 dial 容器内的端口,使用 io.Copy 转发输入输出流。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据