容器化初步——浅析 runc,containerd 和 docker
容器化技术很多人都不陌生,docker 作为一个运维和部署工具,已经快成为广大工程师的必备技能了。但是剖析docker 内部组件的文章却并不多。很多人可能不知道 docker 背后是由多个组件构成的,本文试图浅显地解释一下 docker 的几个核心组件,以及它们之间的关系。
runc
runc 是一个用来运行容器的命令行工具,没错,如果你只想运行一个“容器”,其实你并不需要 docker ,runc 就足够了,而且步骤相当简单。
为了介绍下面的部分,首先我们得理解一下容器是如何被描述的。为了被避免一个或多个商业公司垄断,容器的标准是由一个开放组织维护的,就是大名鼎鼎的 OCI(Open Container Initiative)。OCI 在 runtime-spec 这个仓库里描述了容器有关的具体标准(specification),各大厂家按照这个统一的标准做出来的各种组件,理论上就是可以通用的。统一标准而不是统一实现,这个思想和 Java 的 Servlet 以及 Python 的 WSGI 有异曲同工之妙。
OCI 定义的容器标准内容非常多,超出了这篇文章所想涉及的范畴,我们还是专注于实操。简单说一个容器的内容由两部分组成,一部分是config.json用来存放描述性信息,另一部分是 rootfs 根文件系统,包含容器内部的实际内容。用 runc spec 可以生成一个config.json
目录下会生成一个config.json,内容如下:
这个JSON里面就是容器的一些描述性信息。熟悉Linux的同学应该应该对rootfs不陌生,我们使用下面这个命令,获取一个docker镜像内部的rootfs:
然后我们就可以使用下面这个命令运行容器了:
当然这只是最基础的容器运行方法,不管是runc的配置还是命令行工具,都有大量的配置选项可供选择。通过这个部分我们应该了解了,总结一下,runc是一个运行容器的命令行工具,在底层为容器的运行提供了支持。
前面提到过,runc面向的是OCI的spec。因此实现并不是唯一的,和runc实现了相同接口的,还有包括谷歌的 gvisor 等其他工具等,感兴趣的可以了解一下。
Containerd
说完了runc,往上一层是containerd。熟悉Linux的同学应该知道,以d为结尾的程序,一般是守护进程。containerd就是一个管理容器的守护进程,它向下使用runc完成容器的执行,向上提供GPRC的API,方便完成各种管理操作。这里引用一张containerd官网的图片,用来描述containerd的架构:

还是进入实操,到contaienrd这个级别,我们已经可以直接进行image和container的管理了。containerd实现了OCI的镜像标准,也就是说我们不用自己拼rootfs,而是可以直接使用现成的alpine镜像了。
我们使用containerd的管理工具ctr,可以直接这样操作:
当然运行container也不在话下:
查看当前运行的container:
可以看到containerd的操作实际上和docker已经比较接近了,只不过containerd相对要更底层一些,对上层调用来说会更方便。例如containerd同时支持了Kubernetes的CRI接口标准,因此Kubernetes可以直接使用containerd作为容器runtime。
Docker
说了这么多终于到了大家最熟悉的docker。docker实际上背后也是使用了containerd,然后在上层封装了自己的命令行工具和其他组件。docker的操作就是最方便的了:
docker在containerd的基础增加了什么呢?简单说有这么几块:
- 方便的管理方式。docker在container的基础上,提供了cp,exec等命令,并且封装了volume和mount等操作,让我们无需接触底层的各种spec,也可以方便地完成容器有关的任务。
- 高级网络管理。docker中可以完成container和主机之间的各种端口映射操作,背后依托的是一个叫docker-proxy的额外程序。
- 提供Docker Engine API。Docker本身自己也是有一套API标准的,还有一些docker-py这样的SDK方便大家调用。
这篇文章到这就结束了,希望能让大家对docker背后的组件能有所了解。
