Skip to content

Latest commit

 

History

History
104 lines (86 loc) · 5.72 KB

containerd.org

File metadata and controls

104 lines (86 loc) · 5.72 KB

containerd

目录

crictl & ctr

在 containerd 的实现中,容器会区分为 container 和 task,其中 container 是容器的元信息,而 task 是实际运行的容器进程。

对应到常用的命令行工具:

  • crictl - 匹配 cri 实现的命令行工具,可以查看 Pod、容器信息,但是看不到 task 这一级别
  • ctr - containerd 的工具,可以查看 container 和 task 信息

同时,containerd 也有 namespace 的概念,而 k8s 的容器统一在 k8s.io ns 下,是写死的常量,因此,有如下命令:

ctr -n k8s.io t ls # 列出节点上正在运行的容器进程
ctr -n k8s.io c ls # 列出节点上的容器
ctr -n k8s.io t exec -t --exec-id 1 <tskid> sh  # 进入容器执行命令

参考:containerd/docs/getting-started.md at main · containerd/containerd

containerd & containerd-shim & runc

containerd 通过 containerd-shim 进程来操作容器进程,因为容器进程是需要一个父进程来做状态收集、维持 stdin 等 fd 打开等工作,假如这个父进程就是 containerd,那如果 containerd 挂掉的话,这些功能就都不可用了,通过 containerd-shim 这个中间层可以避免这些问题。

然后创建容器需要做一些 namespaces 和 cgroups 的配置,以及挂载 root 文件系统等操作,这些操作其实已经有了标准的规范,那就是 OCI(开放容器标准),runc 就是它的一个参考实现,这个标准其实就是一个文档,主要规定了容器镜像的结构、以及容器需要接收哪些操作指令, 比如 create、start、stop、delete 等这些命令。runc 就可以按照这个 OCI 文档来创建一个符合规范的容器,既然是标准肯定就有其他 OCI 实现,比如 Kata、gVisor 些容器运行时都是符合 OCI 标准的。

所以真正启动容器是通过 containerd-shim 去调用 runc 来启动容器的,runc 启动完容器后本身会直接退出,containerd-shim 则会成为容器进程的父进程, 负责收集容器进程的状态, 上报给 containerd, 并在容器中 pid 为 1 的进程退出后接管容器中的子进程进行清理, 确保不会出现僵尸进程。

参考:OCI 与 CRI - Kubernetes 进阶训练营(第2期)

创建容器

https://github.com/containerd/containerd/blob/v1.6.19/pkg/cri/server/container_create.go

配置例:

# Kubernetes doesn't use containerd restart manager.
root = "/var/lib/containerd"
state = "/run/containerd"

[plugins.linux]
  shim = "/usr/bin/containerd-shim"
  runtime = "/usr/bin/runc"

[plugins.cri.containerd.default_runtime]
  runtime_type = "io.containerd.runtime.v1.linux"
  runtime_engine = ""
  runtime_root = ""

步骤:

  1. 获取 Sandbox 元信息,生成容器 ID
  2. 创建容器 root 目录,这里会用到 /etc/containerd/config.toml 中的 root 配置
    // Create container root directory.
    containerRootDir := c.getContainerRootDir(id)
    if err = c.os.MkdirAll(containerRootDir, 0755); err != nil {
      return nil, fmt.Errorf("failed to create container root directory %q: %w",
        containerRootDir, err)
    }
        
  3. 创建容器临时目录,这里会用到 /etc/containerd/config.toml 中的 state 配置
    volatileContainerRootDir := c.getVolatileContainerRootDir(id)
    if err = c.os.MkdirAll(volatileContainerRootDir, 0755); err != nil {
      return nil, fmt.Errorf("failed to create volatile container root directory %q: %w",
        volatileContainerRootDir, err)
    }
        
  4. 获取容器 RuntimeClass,生成容器 spec 信息
    ociRuntime, err := c.getSandboxRuntime(sandboxConfig, sandbox.Metadata.RuntimeHandler)
    spec, err := c.containerSpec(id, sandboxID, sandboxPid, sandbox.NetNSPath, containerName, containerdImage.Name(), config, sandboxConfig,
      &image.ImageSpec.Config, append(mounts, volumeMounts...), ociRuntime)
        
  5. 创建 ContainerIO 对象,看着会关联 tty 和 stdin,会在容器临时目录创建 fifoset 相关文件
    containerIO, err := cio.NewContainerIO(id,
        cio.WithNewFIFOs(volatileContainerRootDir, config.GetTty(), config.GetStdin()))
        
  6. 调用 ContainerService(感觉是 containerd-shim)创建容器,同时保存容器信息到 containerstore
    if cntr, err = c.client.NewContainer(ctx, id, opts...); err != nil {
      return nil, fmt.Errorf("failed to create containerd container: %w", err)
    }
    
    container, err := containerstore.NewContainer(meta,
      containerstore.WithStatus(status, containerRootDir),
      containerstore.WithContainer(cntr),
      containerstore.WithContainerIO(containerIO),
    )
        

启动容器

https://github.com/containerd/containerd/blob/v1.6.19/pkg/cri/server/container_start.go

containerd 中的逻辑其实不多,就是根据 sandbox、container、runtime 等元信息调用 TaskService 来创建 Task,同时更新 containerstore 中的容器状态信息。