https://lwn.net/Articles/741897/ @一斋 @古二

在KubeCon+CloudNativeCon的文章里面已经提过,用于创建和启动容器的runtime有很多。这些runtime在实现和标准化上都越来越成熟:Docker containerd 1.0、CRI-O 1.0,还有rkt。用户用这些runtime开发容器系统或是Kubernetes的时候可能会有些困惑,本文回顾了这些runtime的前世今生,希望可以帮助大家。 [编辑] 容器是什么?

在更深入介绍runtimes之前,让我们结合容器看看启动时候发生的事情:

  1. 容器由容器镜像生成。容器镜像通常是附带JSON配置文件的tarball,容器镜像可能有嵌套例如Libresonic image包含了一个Tomcat image,而后者又是基于Debian image。容器镜像通常用docker build命令生成。

  2. runtime负责从叫做“registry”的地方下载image,最初registry只有Docker Hub,后来Red Hat的OpenShift project、Microsoft的Azure以及Gitlab也有一个持续集成平台加入。registry是服务docker pull或者push的服务器。

  3. runtime负责把image解析到一个CoW文件系统里面,通常是overlay文件系统。

  4. 最后,还是runtime负责启动container,通知内核利用cgroups、namespaces、capabilities、seccomp、AppArmor、SELinux、Whtnot这样的机制施加资源限制、创建隔离层等。对于Docker来说,创建运行docker的命令是docker run,底层实际调用的是runc。

这样的模式源自Docker Standard Container manifesto,被沿用下来。Open Container Initiative通过一系列规范约定了起来,他们是Image Specification、Runtime Specification和Container Network Interface。

在不同的runtime里面这些标准的实现方式不一样。以及runtime接口的实现也不尽相同,我们将看到不是所有的runtime都是标准化的。 [编辑] Docker和rkt的故事

作为最初的流行容器,Docker是我们绕不开的。最初Docker使用LXC但是它的隔离层不完善,于是出现了libcontainer也就是后来的runc。容器流行之后Docker成为事实上的标准。到了2014年,Kubernetes最初也使用了Docker。Docker公司持续开发新特性,Docker Compose发展到了1.0系统。同时Kubernetes也做了一些重叠的事情。尽管有让这两者兼容的方法Kompose,Docker还是经常被认为做了太多事情。于是CoreOS发布了rkt runtime:

Docker现在干太多的事情了:编译镜像、运行、上传、下载、overlay networking还有启动云服务器,所有这些都在一个可执行文件里面,这不是我们最初希望看到的。

rkt的一个优点是标准的镜像格式,我们曾经在2015年讨论过。根据Brandon Philips(CoreOS CTO)的一封邮件,当时CoreOS甚至没有实现全部的标准接口,在持续开发中,并且不能通过Kubernetes集成测试。

然而Red Hat Container团队负责人Dan Walsh则对CoreOS的努力做了高度评价:没有CoreOS就没有CNI,CRI也仍会处在跟OCI打架的麻烦里,CoreOS被低估了。后来,CoreOS从runtime层加速朝着Kubernetes平台Tectonic和镜像分发服务Quay前进。 [编辑] CRI-O: 最简单的runtime

Red Hat的一些人仍然觉得标准里面的东西太多了,决定做一个只满足Kubernetes需要的runtime。他们的“skunkworks”项目最终发展成CRI-O,只包含精简的CRI接口。KubeCon Austin 2017的一个分享上,Walsh说打造CRI-O的初衷跟Unix哲学是一样的,做一件事就做好。

从2016年年末开始,Intel和SUSE也加入了进来。

CRI-O跟CRI规范兼容,支持OCI和Docker镜像格式、支持镜像GPG签名、使用CNI package支持CNI插件、支持多CoW文件系统。最值得说道的还是CRI-O支持包含“trusted”容器和“untrusted”容器的混合负载。例如CRI-O可以用隔离的Clear Container支持多租户或者运行非可信代码。不过这个功能以什么样的方式进入Kubernetes还不得而知,因为后者认为所有的后端都是一样的。

CRI-O结构很有意思(slides),它重用了runc来启动容器,也利用了其他库例如skopeo项目的containers/image和containers/storage来pull镜像创建文件系统,用oci-runtime-tool配置镜像。CRI-O里面有一个新的deamon conmon,负责监控、TTY分配以及oom的处理。

File:kubecon-crio-sm.png

Conmon daemon在这里需要做所有systemd没有做或者说不想做的事情。 但是,即使CRI-O不直接使用systemd来管理容器,它也会将容器分配给与systemd兼容的cgroup,这样像systemctl这样的常规systemd工具就可以查看容器资源。 因为conmon(不是CRI daemon)是容器的父进程,所以它也允许CRI-O的一部分在不停止容器的情况下重新启动,这就保证了更平滑的升级。 这正是Docker部署所面对的问题,Docker升级是需要重新启动所有的容器。 这对于Kubernetes集群来说通常并不麻烦,因为通过移动容器可以很容易地渐进滚动升级。

CRI-O是第一个通过所有Kubernetes集成测试(除了Docker本身之外)的OCI标准套件的实现。 Patel通过由CRI-O支持的Kubernetes集群演示了这些能力,这似乎是集群功能的常规演示。 Dan Walsh在博客文章中解释了CRI-O的方法,解释了CRI-O如何与Kubernetes进行交互:

CRI-O的首要目标是æ°¸远不会break Kubernetes,这与其他容器runtime不同。 为Kubernetes提供稳定而坚如磐石的容器runtime是CRI-O唯一使命。

根据Patel的说法,CRI-O与基于Docker的普通部署性能相当,团队正致力于优化性能并最终超越Docker。 同时也可以应用Debian和RPM包,像minikube或kubeadm这样的部署工具也支持切换到CRI-O runtime。 在现有的集群上,切换runtime非常简单:只需一个环境变量改变runtime socket,这是Kubernetes用来与runtime进行通信的接口。

CRI-O 1.0于2017年10月发布,支持Kubernetes 1.7。 紧接着,CRI-O 1.8和1.9紧随着Kubernetes 1.8和1.9发布(同步版本号)。 Patel认为CRI-O已经可以投入使用,并且已经在2017年11月发布的OpenShift 3.7中以beta版推出。Red Hat将在OpenShift 3.9版本中将其标记为稳定,并且考虑在OpenShift 3.10版本中设置为默认runtime,同时保留Docker作为后备选项。 后续计划包括集成新的基于虚拟机的Kata Containers runtime,kube-spawn支持以及更多存储后端,比如NFS或GlusterFS。 该小组还讨论了如何支持casync或libtorrent来优化节点间镜像的同步。 [编辑] Containerd: Docker的runtime提供的API

当红帽忙于OCI的实现时,Docker也在研究这个标准,Docker引入了名为containerd的runtime。新的daemon是对内部Docker组件进行重构,以组合OCI特定的操作,如执行、存储和网络接口管理。它已经在1.12 Docker版本中发布了,但是直到在KubeCon上发布containerd 1.0版本才最终完成,而KubeCon将是即将到来的Docker 17.12的一部分(Docker已经将版本号按照年和月设置)。虽然我们将containerd称为“runtime”,但它并不直接实现CRI接口,这部分接口是由另一个名为cri-containerd的daemon所覆盖的。所以containerd需要比CRI-O更多的守护进程(五个,而CRI-O则需要三个)。另外,在编写本文时,cri-containerd组件被标记为beta,但是containerd本身当然已经通过Docker在许多生产环境中使用。

在KubeCon的Node SIG(Special Interest Group)会议上,Stephen Day将containerd描述为“多个解耦组件所围绕的核心组建”。 但是,与CRI-O不同的是,containerd通过Go API支持Kubernetes生态系统之外的工作负载。 为了使API和命令行工具的更改可控,尽管containerd定义了一个清晰的发布过程,但API仍然被认为是不稳定的。 与CRI-O类似,containerd功能开发完毕,并通过了所有Kubernetes测试,但不能与systemd的cgroup进行交互操作。

该项目的下一步是开发更多的测试并提高性能,如内存使用率和延迟。 Containerd的开发者也在努力提高稳定性。 他们希望提供Debian和RPM的支持,以便于软件安装,并将与minikube和kops集成在一起。 还有计划将Kata Containers更加流畅地整合在一起:runc已经可以被Kata取代,但cri-containerd集成尚未实现。 [编辑] 互操作性和默认runtime

可选择的东西太多,这给社区造成了一定程度的混乱。 在KubeCon上,演讲者需要反复提到使用了哪个runtime。 Kubernetes可能会从Docker更改为不同的runtime,因为它不需要Docker提供的所有功能,并且可以确定的是,更改一个新的runtime会导致兼容性问题,因为新的runtime并未实现与Docker完全相同的接口。 例如,日志文件在CRI标准中是不同的。 有些程序也直接监视Docker套接字,它有一些非标准的行为,新的runtime可能有不同的实现,或完全没有。 当试图切换到新的runtime上的时候,所有这些问题会导致新的runtime无法正常工作。

Kubernetes将切换到哪个runtime(如果发生更改)的问题并没有确定,这导致runtime之间的一些竞争。 在KubeCon上有一个小小的争议,因为在CNCF主题演讲中没有提到CRI-O,红帽高级工程师Vincent Batts在Twitter上提到:

  在KubeCon主题中涵盖了CRI实现容器和rktlet,但是没有提到CRI-O,这是一个已经在1.0版本中实际使用的Kubernetes项目。

  当我在KubeCon希望他提供这方面的细节时,Batts解释说:

     健康的竞争是好的,问题是不健康的竞争。 CNCF应该更好地管理他们的项目,不能在压力下,推动某些项目而放弃其他项目。

 Batts进一步解释说,Red Hat“可能正处于一个临界点,一些应用程序可能开始被部署为容器,而不是RPM”,而“安全问题(容器缺乏一种封包格式)是这种转变的主要障碍“。 从Project Atomic项目可以看出,红帽似乎正在向容器转移,这对一个Linux发行版的厂商来说风险还是很大的。

 当我与CNCF的COO Chris Aniszczyk在KubeCon谈话时,他解释说CNCF“目前的政策是优先考虑顶级项目的营销”:

    像CRIO和Helm这样的项目是Kubernetes的一部分,因此也是CNCF的一部分,我们不会像我们已经通过CNCF技术监督委员会批准的顶级项目一样将其推向市场。

    Aniszczyk补充说,“我们想要帮助,我们已经听到了反馈意见,并计划在2018年解决问题”,这表明一个解决方案可能是:申请将CRI-O升级为CNCF的顶级项目。

    在一个容器runtime的记者会上,Philips解释说,社区将通过共识决定Kubernetes默认运行的runtime。 他将runtime与网页浏览器进行类比,并建议将容器的OCI规范与HTML5和Javascript标准进行类比:都是不同实现推动的不断发展的标准。 他认为这æ ·的竞争是健康的,意味着更多的创新。

    参与编写这些新runtime的许多人最初都是Docker的贡献者:Patel是最初的OCI实现的初始maintainer,领导开发了runc,而Philips在启动rkt项目之前也是Docker的核心贡献者。 这些人与Docker开发人员一起积极协作,将这些接口标准化,并希望看到Kubernetes稳定和改进。 据Docker公司的Patrick Chazenon说,目标是“让容器的runtime变得无聊而稳定,人们在这个平台上进行创新”。 出席新闻发布会的开发者对他们所取得的成就感到高兴和自豪:他们已经成功地为容器创建了一个单一的、可互操作的规范,并且规范正在扩大。

    目前容器标准化方面的较大的主题不像镜像分发(即容器Registriy)那样多,而且可能要在基于Docker分发系统的规范中进行标准化。还需要遵循Linux内核的更改,例如cgroups v2。

    实际情况是,每个runtime都有自己的优势:containerd有一个API,所以它可以用来构建定制的平台,而CRI-O是一个更简单的runtime,专门针对Kubernetes。 Docker和rkt在另一个层面上,不仅仅提供了runtime,还提供了其他的功能,比如:构建容器或推送到Registry。
    [编辑] 合并和标准化在2018年继续推进

    目前,大多数公共云基础架构仍然使用Docker作为runtime。 事实上,即使CoreOS也在其Tectonic平台上使用Docker代替rkt。 根据Philips的说法,这是因为“我们的客户依赖Docker Engine周围的整个集成生态环境,它是所有现有Kubernetes产品中经过最充分测试的选择。” Philips表示,CoreOS可能会考虑为Tectonic提供备用runtime,“如果替代容器runtime为Kubernetes用户提供重大改进”:

       Containerd和CRI-O今年都开发了大量的新代码,就这一点来说,两者都是非常初期的项目。 此外,他们需要达到足够的成熟度,使得整个生态系统可以进行第三方集成,包括日志记录,监控,安全性等多个方面。

       Philips在此博客中进一步解释了CoreOS的立场:

          到目前为止,CRI对于Kubernetes社区的主要好处是更好的代码组织和提升了Kubelet本身的代码覆盖率,从而使代码库的质量更高,并且做了比以往更彻底的测试工作。 但对于几乎所有的部署,我们预计Kubernetes社区将在近期继续使用Docker引擎。

	  在新闻发布会上的讨论中,Patel也表示“我们不希望Kubernetes用户知道runtime是什么”。 事实上,只要它有效,用户就不应该在意。 此外,OpenShift,Tectonic和其他平台将runtime决策抽象出来,用户可以根据需求选择自己的最佳runtime。 因此,Kubernetes默认选择何种runtime的问题对于那些喜欢在标准规范上达成共识的开发人员来说并不是真正的问题。 在这个充满冲突的世界里,看到这些开发者亲切地合作,绝对是一股清新的气息。