聊聊容器中的名词

开门见山的说,本文讲回顾容器发展历史,理清各种名词之间的关系,包含切不限于以下

  • docker
  • containerd
  • runc
  • shim
  • OCI
  • CRI
  • ......

docker起源

最初docker是在2013年开发,使用了LXC【Linux 内核容器虚拟化】技术, LXC是linux基金会对 namespace和Cgroup的官方封装,相当于提供了更高一层的库来避免手动操作namespace和Cgroup, 然后docker使用了LXC并在基础上提供了方便的镜像管理和容器运行时管理。

后来docker自己用go语言实现了自己的LXC, 称之为 libcontainer。

最初的docker就是c/s架构, 即用户使用cli命令行作为客户端 和 docker Daemon 服务端通信,此时的docker Daemon还是一坨代码并没有做切分。

$ sudo docker version
Client:
Version:      18.03.1-ce
API version:  1.37
Go version:   go1.9.5
Git commit:   9ee9f40
Built:        Thu Apr 26 07:17:20 2018
OS/Arch:      linux/amd64
Experimental: false
Orchestrator: swarm

Server:
Engine:
Version:      18.03.1-ce
API version:  1.37 (minimum version 1.12)
Go version:   go1.9.5
Git commit:   9ee9f40
Built:        Thu Apr 26 07:15:30 2018
OS/Arch:      linux/amd64
  Experimental: false
复制代码

docker拆分

时间来到了2015年, 此时的docker如日中天,而且也出现了越来越多的容器项目,于是由 Docker,CoreOS 和Google其他容器行业的领导者成立了OCI组织【The Open Containers Initiative】。 它维护了运行时和镜像规范。它的目的是围绕容器行业制定标准,规定了创建一个容器需要实现哪些api,至于api内部实现由项目本身决定,这样一来不同的容器项目只要实现了OCI标准就可以互相兼容。

2015 年,Docker 宣布使用轻量级的、可移植的运行时 runC,它基本上是一个直接使用 libcontainer 的命令行工具,而无需通过 Docker 引擎。runC只实现了容器运行时,并不提供镜像相关的功能。

runc 的前身是 libcontainer, runc = libcontainer + client ,所以官方说的是符合 OCI 规范的管理容器的 CLI 工具。容器这里对应的是 libcontainer。

注意,基于 OCI 标准实现的运行时,不止有 runc,还有虚拟机运行 runv: Hypervisor-based Runtime for OCI 以及 huawei-openlabl: oct 等。这就是指定标准的好处。

再注意,runc 是独立于 Docker 的,它是比 Docker 更轻量级的,而且是符合 OCI 标准化的,Docker 不具备这些。

runC 的目标是使标准容器随处可用,该项目转赠给了 OCI。

2016年,Docker搞了Swarm项目,并做了架构切分,拆分出 containerd ,让他做之前docker做的事情,剩下的Docker Daemon 专门负责上层的封装编排,containerd 从核心的 Docker 引擎中移出,并变成一个单独的守护进程。

由此Docker 从一个单一的程序转变成了一组独立的组件和项目。

可以看到图上出现了 shim 组件, 这个组件的作用其实是相当于一个中间件, 解藕containerd和底层容器运行时之间的关系。当containerd出现异常终止时不会影响正在运行的容器。

此时Docker 创建容器的步骤

  1. Docker 引擎创建镜像

  2. 传递给 containerd

  3. containerd 创建 containerd-shim

  4. containerd-shim 使用 runC 运行一个容器

  5. containerd-shim 允许运行时(这种情况下是 runC)在启动后推出

    Client: Version: 19.03.8 API version: 1.40 Go version: go1.12.17 Git commit: afacb8b Built: Wed Mar 11 01:21:11 2020 OS/Arch: darwin/amd64 Context: default Experimental: true

    Server: Engine: Version: 19.03.8 API version: 1.40 (minimum version 1.12) Go version: go1.12.17 Git commit: afacb8b Built: Wed Mar 11 01:29:16 2020 OS/Arch: linux/amd64 Experimental: true containerd: Version: v1.2.13 GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429 runc: Version: 1.0.0-rc10 GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd docker-init: Version: 0.18.0 GitCommit: fec3683

容器编排

随着容器技术被越来越多的人使用, 能够自动化处理部署、管理、弹性伸缩、容器网络管理的需求的呼声也越来越高。

Swarm 是 Docker 自己开发的编排工具。起初是一个独立的工具,在 1.12 版本之后包含在 Docker 中。它使用 Docker CLI 创建 swarm 集群、部署和管理应用程序和服务。

公司在用的k8s就不用说了, google主导开发的容器编排项目, 初期在代码里写死了对docker的支持,随后在容器编排的竞争中战胜了swarm,成为了事实上的标准。

K8S 和 docker

早期为了支持多个容器引擎,是在Kubernetes内部对多个容器引擎做兼容,例如kubelet启动一个docker-manager的进程直接调用docker的api进行容器的创建。

后来k8s为了隔离各个容器引擎之间的差异,在docker分出containerd后,k8s也搞出了自己的容器运行时接口(CRI),CRI的出现是为了统一k8s与不同容器引擎之间交互的接口,与OCI的容器运行时规范不同,CRI更加适合k8s,不仅包含对容器的管理,还引入了k8s中Pod的概念及对Pod生命周期的管理。 k8s开始把containerd接入CRI标准。kubelet通过CRI接口调用docker-shim,进一步调用docker api。此时在每个k8s节点上kubelet大致按下图流程启动容器:

为了更好的将containerd接入到CRI标准中,k8s又搞出了cri-containerd项目,cri-containerd是一个守护进程用来实现kubelet和containerd之间的交互,此时k8s节点上kubelet大致按下图流程启动容器:

在上图中cri-containerd和containerd还是两个独立的进程,他们之间通过gRPC通信,后来在Containerd 1.1时,将cri-containerd改成了Containerd的CRI插件,CRI插件位于containerd内部,这让k8s启动Pod时的通信更加高效,此时k8s节点上kubelet大致按下图流程启动容器:

早期: kubelet --> docker-manager --> docker
中期: kubelet -CRI-> docker-shim --> docker --> containerd --> runc
中期: kubelet -CRI-> cri-containerd --> containerd --> runc
当前: kubelet -CRI-> containerd(CRI plugin) --> runc
复制代码

PS

CRI出现的背景

起初k8s在代码层面内置并且默认docker的支持, 后来出现了rkt【coreos推出的容器项目】,并且rkt也想合并到k8s代码中【是写死的那种】,可能是为了削弱docker的影响,最终这个方案通过了,支持rkt的代码就合并到了k8s中。

但是这肯定不是可持续发展的模式,后面想支持其他的容器实现再写一套代码?而且还会有各种兼容性问题,所以k8s在 1.5版本 推出了 CRI 标准。想要接入k8s的容器项目不必合并到k8s主干代码中,只需要实现CRI即可。

猜你喜欢

转载自juejin.im/post/7108667202110390286