过完年休假回来的一天,刷到一条新闻,说 RUNC 有一个致命的漏洞: cve-2019-5736,需要及时更新 Docker 来解决,让我很诧异。Docker 不是直接构建在 Linux 之上的么?怎么还有这个叫作 RunC 的什么事?于是我就顺便找了找相关的信息,发现自己的知识有点落伍了,Docker 的发展完全可以跟大前端圈子匹敌了,都快学不动了!这其中得发现也是千丝万缕的联系,各种爱恨情仇,利益纠葛,完全都可以写一部小说了,名字就叫《鲸鱼奇遇记》,不过经过一年多的大浪淘沙之后,似乎也稳定下来了,Google 的 Kubernetes 力压群雄完成了这场容器变革。

背景

OK,我就先不准备扯这么远了,还是回来看看 RUNC 是什么东西,并且怎么应用在 Docker 中的吧。在当初我看《自己动手写 Docker》的时候,这里面的 Docker 实现还是比较老的版本,相对来说还是比较原始的基于 LXC 的实现,里面的 init 都还是自己通过调用系统调用来实现的,Docker 最初也许是这么实现的,但是随着 Docker 的流行,事情发生了一些转变,为啥呢?因为一个东西流行之后,必定会有很多相关的东西被实现,例如 Docker 自己收购的 Compose,还有现在流行的 Kubernetes 等等,那么只要有外围应用,那么对核心的东西就是一个强依赖,如果没有一些约束的话,就会受到核心的应用的制约,例如如果不制定一个规范对 Docker 进行规范化,那么其他应用就等于把自己的生命放在 Docker 公司的手里,哪天 Docker 东西不开心了,改动一下一两个接口,大家都得完蛋。

在这样的情景下,有识之士肯定都是不满的,第一个跳出来的就是 Docker 的昔日好机油 CoreOS,我们知道 CoreOS 也是一家技术公司,它自己有自己的 CoreOS 操作系统,并且还有我们熟悉的好几个开源项目,例如 Etcd 和 flannel,它为了防范 Docker 的竞争,因为 Docker 作为一个基础组件肯定是不怎么赚钱的,为了打入企业级市场,那么一个很明显的发展方向就是 Paas,所以这时 CoreOS 感到了威胁,于是乎投资培养了 rkt,另外一个容器引擎,在2014年底,CoreOS 的 CEO Alex Polvi 正式发布了 CoreOS 的开源容器引擎 Rocket(rkt)。如果只有 CoreOS 一家肯定是掀不起什么风浪的,但是,没想到的是,有此担心的还有 Google,这家软件巨擎,作为一个有着多年(据说几十年?)容器使用经验的老司机,Google 对于容器的理解和沉淀应该没有哪一家可以比拟的。Google 在它的杀手级应用 Kubernets(borg) 上宣称支持 rkt,并且在 2015 年 4 月领投 CoreOS 1200万美元,这无疑是对外暴露我支持 CoreOS 的意思。

毕竟容器是个好东西,大家都是认可的,这么一个好牌总不能就这么玩坏了吧,于是乎,2015 年 6 月,在 Linux 基金会的牵头之下,大家决定好好得处理这件事情,于是乎由 Docker 公司牵头成立了 OCI(Open Container initiative/ 开放容器倡议),OCI 主要做了两项重要的工作,其实就是定义了两份规范,分别是:

然而,这规范不是随便定义的,既然 Docker 已经那么流行了,那么这两份规范大部分都是基于 Docker 来定义的。其中, OCI Image 规范定义了 Image 的规范,通过的这个规范,一个 OCI 的实现可以将符合规范的 Image 解压为一个 filesystem bundle,而这个 filesystem bundler 是可以被 Runtime 规范所加载并执行,这个就是 OCI 的意义。

虽然 OCI 是由 Docker 公司发起并且维护的,但是,可以从 OCI 的官网上看到,其实并没有怎么维护,因为我个人看来,这时其实 CoreOS 和 Google 等公司的计谋已经达到了,虽然 Docker 是事实上的容器规范,但是只要有 OCI 这么个规范在,我基于容器的企业级应用便后顾无忧了,毕竟 CoreOS 和 Google 都是在企业级上拥有比 Docker 强得多的实力和经验,所以 Docker 公司在维护 OCI 的规范上也是显得无力。于是乎,以 Google 公司为核心的一系列公司,又成立了一个新的基金会 CNCF,以 Kubernetes 为核心打造企业级的容器生态,到现在可以发现,Docker 公司越来越边缘化,于是乎,在 2017 年初,Docker 公司将 Docker 项目改名为 moby,叫给社区维护,并创建了 docker-ce 和 docker-ee 项目,他们的区别是:

但是,从代码提交了修改上可以发现,moby 项目似乎没有怎么维护,反而像是 docker-ce 项目的 pick 从项目;从这个举动我个人的看法是,Docker 公司放弃了容器的发展话语权,转而作为一家容器服务公司,其实对于一家这样的初创公司,未毕这不是一条除了被收购(Docker 曾经拒绝了微软的收购)之外的好选择。

RunC

那么前边扯了这么多背景之后,和 RunC 有什么关系呢?其实在 CoreOS 叛逃之前,Docker 就开发了一个组件 Libcontainer,它试图定义一个核心的容器底层,然后让其他人可以根据 Libcontainer 来构建自己的容器引擎,但是,因为大家对 Libcontainer 的实现以及意见反馈充满了诸多意见,并逐渐演化出了后面的容器标准之争,于是当 OCI 成立之后,Docker 公司就将 Libcontainer 捐赠给了 OCI,并且重命名为 runc(run container?),后面的 Docker 就基于 RunC 进行实现,下面我就尝试以 RunC 为工具进行运行 Container 的示例:

这里有必要解释一下其中有两行的意思,分别是:

至于什么是 rootfs,这个留待以后讲吧,需要明白的是,Docker Container 是一个进程,那么进程有一个运行时环境,而这个 rootfs 提供了这个运行时环境的独特依赖,除此之外还有共同依赖,那就是系统内核,这个也后续再说。

当我们创建完这两样东西之后就可以使用 runc 运行了:

如果此时你另开一个终端,并且查看 runc list,你会发现居然可以看到运行中的 runc 容器:

其实这也响应了 runc 封装了 Docker 运行时的大部分功能的描述,根据说明,runc 可以加载并运行符合 OCI Image 规范的容器。而 runc 的玄妙之处在你查看 config.json 配置之后你将明白了大半:

OK,这里就先看到这吧,后续我会继续玩一下 runc,并且尝试查看一下 docker-ce 是如何和 runc 结合起来的。

Reference

  1. A Brief History of Containers: From the 1970s to 2017