05 什么是runtime

Docker、Containerd、RunC 间的联系和区别

先说答案

为了防止docker一家独大,docker当年的实现被拆分出了几个标准化的模块,标准化的目的是模块是可被其他实现替换的,不由任何一个厂商控制。
docker由 docker-client ,dockerd,containerd,docker-shim,runc组成,所以containerd是docker的基础组件之一

下面是从containerd引过来的一张图
file
从k8s的角度看,可以选择 containerd 或 docker 作为运行时组件:Containerd 调用链更短,组件更少,更稳定,占用节点资源更少

调用链
Docker 作为 k8s 容器运行时,调用关系如下:
kubelet –> docker shim (在 kubelet 进程中) –> dockerd –> containerd
Containerd 作为 k8s 容器运行时,调用关系如下:
kubelet –> cri plugin(在 containerd 进程中) –> containerd

推荐阅读
https://containerd.io/
https://cloud.tencent.com/document/product/457/35747
https://jiajunhuang.com/articles/2018_12_22-docker_components.md.html

详解请看下述章节

一 什么是OCI

当大家都意识到容器时代的到来,很多组织或个人都参与到了容器的开发工作中来,导致容器由很多不同的实现,长此以往必然会引起混乱、不兼容。为了解决这一问题

Docker、Google、CoreOS 和其他供应商创建了开放容器计划 (OCI),目前主要有两个标准文档:

1、容器运行时标准 (runtime spec)

2、容器镜像标准(image spec)。

OCI 对容器 runtime 的标准主要是指定容器的运行状态,和 runtime 需要提供的命令。下图可以是容器状态转换图:

  • init 状态:这个是我自己添加的状态,并不在标准中,表示没有容器存在的初始状态
  • creating:使用 create 命令创建容器,这个过程称为创建中
  • created:容器创建出来,但是还没有运行,表示镜像和配置没有错误,容器能够运行在当前平台
  • running:容器的运行状态,里面的进程处于 up 状态,正在执行用户设定的任务
  • stopped:容器运行完成,或者运行出错,或者 stop 命令之后,容器处于暂停状态。这个状态,容器还有很多信息保存在平台中,并没有完全被删除

Docker的完整构成(非常重要,务必仔细阅读!!!!!!!!!!!)

Docker 于 2013 年发布,解决了开发人员在端到端运行容器时遇到的许多问题。这里是他包含的所有东西:

  • 容器镜像格式
  • 一种构建容器镜像的方法(Dockerfile/docker build);
  • 一种管理容器镜像(docker image、docker rm等);
  • 一种管理容器实例的方法(docker ps, docker rm 等);
  • 一种共享容器镜像的方法(docker push/pull);
  • 一种运行容器的方式(docker run);

当时,Docker 是一个单体系统,并没有考虑到要与其他系统比如k8s对接,所以docker本身提供了尽可能完善的功能。

但是,这些功能中没有一个是真正相互依赖的。这些中的每一个都可以在可以一起使用的更小、更集中的工具中实现。每个工具都可以通过使用一种通用格式、一种容器标准来协同工作。

从 Docker 1.11 之后,Docker Daemon 被分成了多个模块以适应 OCI 标准。拆分之后,结构分成了以下几个部分:

如图所示,你应该知道docker由很多层组成

1、客户端命令

2、dockerd守护进程,全称docker daemon

3、containerd服务

4、由containerd服务创建出的container-shim进程

5、由container-shim进程调用runc创建出的容器进程

其中,containerd 独立负责容器运行时和生命周期(如创建、启动、停止、中止、信号处理、删除等),其他一些如镜像构建、卷管理、日志等由 dockerd(docker daemon) 的其他模块处理。

Docker 的模块块拥抱了开放标准,希望通过 OCI 的标准化,容器技术能够有很快的发展,最早docker把RunC 单拿出来,捐赠给 OCI 作为OCI 容器运行时标准的参考实现,即runc就是安装oci标准实现的,使用runc可以创建一个符合oci规范的容器,详见4.1小节

现在创建一个docker容器的时候(注意我说的是docker容器而不是其他种类的容器),Docker Daemon 并不能直接帮我们创建了,而是请求 containerd 服务来创建一个容器。当containerd 收到请求后,也不会直接去操作容器,而是创建一个叫做 containerd-shim 的进程。让这个进程去操作容器,我们指定容器进程是需要一个父进程来做状态收集、维持 stdin 等 fd 打开等工作的,假如这个父进程就是 containerd,那如果 containerd 挂掉的话,整个宿主机上所有的容器都得退出了,而引入 containerd-shim 这个垫片就可以来规避这个问题了,就是提供的live-restore的功能。这里需要注意systemd的 MountFlags=slave。

然后创建容器需要做一些 namespaces 和 cgroups 的配置,以及挂载 root 文件系统等操作。runc 就可以按照这个 OCI 文档来创建一个符合规范的容器。

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

层级关系总结如下

dockerd
    containerd
        containerd-shim----runc---->容器进程
        containerd-shim----runc---->容器进程
        containerd-shim----runc---->容器进程

二 总结什么是runtime

往小了说,runtime所处的位置就是上一小节docker分层图中的紫色方块

往大了说,runtime指的是管理镜像或容器的软件,翻译成中文叫做运行时,往大了说,runtime可以分为两类:

1.低级别运行时(low-level runtime),如上一小节的紫色方块所示,比如lxc、runc、gvisor、kata等
对于低级别的运行时来说,只能简单的管理容器,却是不能管理镜像的。

2.高级别运行时(high-level runtime),比如docker、containerd、podman等
对于高级别运行时来说,他们是通过调用低级别运行时来管理容器(可以简单的理解为高级别是在低级别基础上的上层封装),一般可以是runc作为低级别运行时,他们还可以管理镜像。

三 k8s为何抛弃docker

联系管理员微信tutu19192010,注册账号

上一篇
下一篇
Copyright © 2022 Egon的知识星球 egonlin.com 版权所有 帮助IT小伙伴学到真正的技术