在过去几年中,容器技术不仅仅在开发者中成为热门话题,许多企业也参与其中。这种对容器兴趣的日益增加,使得对其安全提升和加固的需求不断提升,同时也对可扩展性和互操作型有了更高的要求。这些工作都是大工程,本文介绍了红帽在企业级容器支持上所做的工作。
当我在 2013 年秋季首次遇到 Docker 公司(Docker.io)的代表时,我们还在研究如何在红帽企业版(Red Hat Enterprise Linux,RHEL)中使用 Docker 容器。(现在 Docker 项目的一部分已经被改名为 Moby。)在将这项技术融入到 RHEL 时,我们遇到了许多问题。首先遇到的最大障碍是找到一个支持写时复制(Copy On Write,COW)的文件系统来处理容器镜像分层。红帽最终为包括 Device Mapper 、 btrfs 和最初版本的 OverlayFS 贡献了部分 COW 实现。对于 RHEL,我们默认使用 Device Mapper,虽然当时对 OverlayFS 的支持已经快完成了。
另一个大障碍是启动容器的工具。当时,上游 docker 使用 LXC 工具来启动容器,但是我们不希望在 RHEL 中支持 LXC 工具集。在与上游 docker 公司合作之前,我和 libvirt 团队合作,制作了名为 virt-sandbox 的工具,它能够通过libvirt-lxc来启动容器。
当时,红帽公司一些人认为,将 LXC 工具集移除,在 Docker 守护进程和 libvirt 之间通过libvirt-lxc来桥接启动容器是个不错的想法。但是这种实现方式令人担忧。使用该方式通过 Docker 客户端(docker-cli)来启动容器,客户端和容器进程(pid1OfContainer)之间的调用层次有:
docker-cli → docker-daemon → libvirt-lxc → pid1OfContainer
我不太赞同启动容器的工具和实际运行容器之间有两个守护进程。
我的团队开始和上游 docker 开发者一起,通过原生 Go 语言实现了一个名为容器运行时库: libcontainer 。该库最终作为 OCI 运行时规范runc 的初始实现发布。
docker- cli → docker-daemon @ pid1OfContainer
有许多人误以为当他们执行一个容器的时候,容器进程是 docker-cli 的子进程。事实上,docker 是一个典型的 C/S 架构,容器进程运行在一个完全独立的环境中。这样的 C/S 架构,可能导致不稳定和潜在的安全隐患,另外还会导致一些系统特性无法使用。例如, systemd 有一个称为套接字激活(socket activation)的特性,用户可以设置守护进程仅在进程连接到套接字时才运行。这样可以降低系统内存使用,让服务按需运行。套接字激活的实现原理是 systemd 监听一个 TCP 套接字,当该套接字接收到数据包的时候,systemd 激活需要监听该套接字的服务。当这个服务激活之后,systemd 将套接字转交给新启动的守护进程。将这个守护进程放到基于 Docker 的容器中之后,就会产生问题。通过在 systemd 的 unit 文件中使用 Docker 客户端命令可以启动容器,但是 systemd 无法简单的通过 Docker 客户端命令将套接字转移到守护进程。
类似这样的问题,让我们意识到,有必要换一种运行容器的方式。
容器编排问题
上游 docker 项目致力于让容器使用变得更加方便,它一直是学习 Linux 容器的好工具。我们可以通过简单的使用类似docker run -ti fedora sh
命令来启动容器并进入容器中。
容器的真正优势在于同时启动大量组件,并将它们组装成一个强大的应用程序。设置一个多容器应用的难点在于其复杂度的指数级增长,以及通过简单的 docker 命令将分散的各个部分串联起来。如何在资源有限的集群节点中管理容器布局?如何管理这些容器的生命周期?类似问题还有很多。
在第一届 DockerCon 上,至少有 7 个公司 / 开源项目展示了容器编排的方式。我们演示的是红帽 OpenShift 的项目 geard ,它松散的基于 OpenShift v2 容器。红帽决定我们需要在容器编排上重新审视,也可能和其他开源社区的人员合作。
Google 演示的是 Kubernetes 容器编排工具,它是基于 Google 内部开发的编排工具所积累的经验。OpenShift 决定放弃 Gear 项目,开始和 Google 一起合作 Kubernetes。Kubernetes 目前已经称为 GitHub 上最大的社区项目之一。
Kubernetes
Kubernetes 最初使用 Google 的 lmctfy 作为其容器运行时库。2014 年夏天,lmctfy 被移植支持 Docker。Kubernetes 会在集群的每个节点运行一个名为kubelet 的守护进程。这意味着原始的Kubernetes 和Docker 1.8 工作流程看上去是这样的:
kubelet → dockerdaemon @ PID1
这又回到了两个守护进程的模式。
还有更糟糕的。每次 Docker 发布,Kubernetes 都会被破坏。Docker 1.10 修改了后端存储,导致所有镜像都需要重新构建。Docker 1.11 开始通过 runc 来启动容器:
kubelet → dockerdaemon @ runc @PID1
Docker 1.12 增加了一个容器守护进程来启动容器。这样的主要目的是满足 Docker Swarm(一个 Kubernetes 的竞争对手)的需求:
kubelet → dockerdaemon → containerd @runc @ pid1
前面提到了,每次 Docker 发布新版本都会破坏 Kubernetes 的功能,因此对于 Kubernetes 和 OpenShift 等工具都需要适配旧版本 Docker。
现在,已经进化成三守护进程系统,这种架构下守护进程中的任何一部分出错了,整个系统都会分崩离析。
走向容器标准化
CoreOS、rkt 其他运行时环境
由于 Docker 运行时库的各种问题,多个组织都在寻求其替代方案。其中一个组织是 CoreOS。CoreOS 为上游 docker 提供了一个替代的容器运行时环境 rkt(rocket)。同时,CoreOS 还引入了一个标准容器规范:appc(App Container)。简单的说,他们希望大家在处理容器镜像存储的时候可以使用统一的标准规范。
这是一个对社区的警示。在刚开始和上游 docker 合作容器项目的时候,我最大的担忧是最终会有多个标准。我不希望会出现类似 RPM 格式和 Debian 格式(deb)之间的战争,这场战争影响了后来 20 年的 Linux 软件分发。appc 提出的一个利好是它说服了上游 docker 和开源社区一起建立一个标准体: Open Container Initiative (OCI)。
OCI 一直在制定两种规范:
- OCI 运行时规范:OCI 运行时规范“旨在定义配置、执行环境和容器的生命周期。”它定义了容器在硬盘上存储的方式,用于描述容器中应用程序的 JSON 文件和如何创建和运行容器。上游 docker 贡献了 libcontainer,并且提供了 runc 作为 OCI 运行时规范的默认实现。
* OCI 镜像格式规范:镜像格式规范基本上基于上游 docker 镜像格式,同时定义了容器镜像在容器仓库中的格式。该标准允许应用程序开发者将他们的应用程序标准化成同样的格式。appc 规范描述的一些特性已经合并如 OCI 镜像格式规范。这两个 OCI 规范都已经接近 1.0 正式版本。(1.0 正式版本已经于 2017 年 7 月 20 日发布,译者注)上游 docker 已经统一在其正式发布之后支持 OCI 镜像规范。rkt 现在同时支持 OCI 镜像和传统 docker 镜像格式。
通过提供容器镜像和运行时的行业规范,OCI 帮助了容器相关工具和编排框架上的革新。
抽象运行时接口
Kubernetes 编排工具是这些标准的受益者之一。作为 Kubernetes 的一大支持者,CoreOS 提交了一系列的补丁为 Kubernetes 增加了通过 rkt 运行容器的支持。Google 和 Kubernetes 社区发现应用了这些补丁之后,将来要为 Kubernetes 增加新的容器运行时支持会使得 Kubernetes 代码变得复杂和臃肿。因此 Kubernetes 团队决定实现一套名为容器运行时接口(Container Runtime Interface,CRI)的 API 协议规范。然后他们重构了 Kubernetes,使其直接调用 CRI,而不用调用 Docker 引擎。如果以后需要添加新的容器运行时接口,只需要实现 CRI 的服务端接口即可。同时,Kubernetes 为 CRI 开发者提供了大量测试集用来验证这些实现是否兼容 Kubernetes。CRI 的抽象,还有一项需要持续进行工作:将原先 Kubernetes 中对 Docker 引擎的直接调用去除,移动到称为 docker-shim 的适配层中去。
容器工具创新
镜像仓库创新——skopeo
几年前我们在 Atomic 项目中开发 atomic 命令行接口。其中一个功能是,我们希望能够有一个工具,当镜像还在镜像仓库的时候,就可以验证其中的内容。当时,唯一的变通方式是通过容器镜像关联的 JSON 文件将镜像拉取到本地,然后再通过docker inspect
命令来读取镜像信息。这些镜像可能非常大,占用上 G 空间。有了这项功能,就能够让用户能够提前检查镜像内容,以确定是否需要拉取镜像,因此我们为docker inspect
命令增加了一个--remote
的参数。但是上游 docker 拒绝了这个 pull request,原因是他们不希望将 Docker 命令行接口弄的复杂,并且用户可以通过自己的小工具来实现同样的功能。
由 Antonio Murdaca 领导的我们团队,最终完成了名为 skopeo 的工具。Antonio 将这个工具定位不仅局限于拉取镜像的配置文件,他还实现了将容器镜像从镜像仓库拉取到本地服务器,从本地服务器推送镜像到镜像仓库的双向协议。
目前,skopeo 已经重度融入在 atomic 命令行接口中,包括检查容器更新、集成入 atomic scan 命令实现中等。Atomic 也使用 skopeo 来拉取和推送镜像,而非使用上游的 docker 守护进程。
Containers/image 库
我们一直在和 CoreOS 沟通关于在 rkt 中使用 skopeo 的可行性,但是对方一直不愿意通过一个可以执行的帮助应用程序,而是希望能够将 skopeo 功能作为依赖库直接集成进去。因此我们决定将 skopeo 拆分成库和可执行程序,最终创建了 image 工程。
containers/image 库和 skopeo 已经被一些上游项目和云服务基础设施工具使用。二者已经能够支持除 Docker 外的其他多种存储后端,并且它们提供了诸如在镜像仓库之间移动镜像等许多特性。skopeo 的一个优势在于,它的工作不依赖任何守护进程。containers/image 库的突破,也让类似容器镜像签名等增强功能的实现成为了可能。
镜像处理和扫描的创新
前文提到了 atomic 命令行接口,该工具用来实现 docker 命令行接口不兼容的特性和一些我们觉得上游 docker 不会接受的特性。另外我们还希望它能够可扩展的支持其他容器运行时、工具和存储。前面提到的 skopeo 就验证了这点。
其中一个我们想加入到 atomic 的特性是atomic mount
。该命令的需求来源是能够从 Docker 镜像存储(上游 docker 称之为图驱动,graph driver)获取数据,并将其挂载到某个地方,这样可以使用其他工具来检查这个镜像。目前如果使用原生 docker,查看镜像内容的唯一方式是启动容器。如果镜像中包含不受信内容,仅仅为了查看镜像内容而运行其代码是非常危险的。启动容器然后验证内容的另外一个问题是,用于检查的工具通常不会包含在容器镜像中。
大部分容器镜像扫描工具的工作流程通常如下:它们连接到 Docker 套接字,执行docker save
命令创建一个压缩包,然后将其解压缩到硬盘上,最后检查这些内容。这个操作流程很慢。
有了atomic mount
命令,我们可以直接通过 Docker 图驱动(graph driver)来挂载镜像。如果 Docker 守护进程使用设备映射器(device mapper),它可以挂载这个设备;如果使用的是 overlay 文件系统,它可以挂载 overlay。这样可以快速的满足我们的需求。现在只需要这样做:
# atomic mount fedora /mnt # cd /mnt
然后就可以开始检查内容。当检查完毕之后,执行:
# atomic umount /mnt
我们将该特性融入到了atomic scan
命令中,这样就可以构建一个快速镜像扫描仪了。
工具协调问题
atomic mount
命令使用过程中一个大问题是它独立工作。Docker 守护进程无法感知到还有其他进程在使用镜像。这可能导致一些问题(例如,有人首先用前面提到的命令挂载 Fedora 镜像,然后其他人执行了docker rmi fedora
命令,Docker 守护进程在试图删除 Fedora 镜像的时候会因为设备忙而失败)。此时 Docker 守护进程会进入不可预知的状态。
Container/storage 库
为了解决这个问题,我们开始将上游 docker daemon 代码中图驱动相关代码拉取到自己的仓库。Docker 守护进程的图驱动将所有锁相关操作都在自己的内存中完成。我们希望能够将锁相关实现移动到文件系统中,这样不同的进程能够同时操作容器存储,而无需都通过守护进程这个单点。
最终该项目被命名为 container/storage ,它实现了容器在运行、构建和存储时所需要的所有写时复制(COW)特性,而无需使用一个进程来控制和监控(即不需要守护进程)。现在 skopeo 和其他一些工具和项目都能够更好的使用存储。还有一些其他开源项目开始使用 containers/storage 库,在某个时间点我们希望这个项目能够合并回上游的 docker 项目。
启航,进行创新
下面让我们来看下 Kubernetes 在一个节点上通过 Docker 守护进程运行一个容器时,到底发上了什么。首先 Kubernetes 执行一个命令:
kubelet run nginx –image=nginx
该命令告诉 kubelet 在节点上运行 nginx 应用程序。kubelet 调用容器运行时接口,要求其启动 nginx 应用程序。此时,容器运行时接口的实现需要完成以下步骤:
- 检查本地存储中名为 nginx 的镜像。如果本地不存在,容器运行时将会在镜像仓库中查找对应的镜像。
- 如果镜像在本地存储中不存在,将其从镜像仓库下载到本地系统中。
- 将下载的容器镜像释放到容器存储中(通常是一个写时复制存储),并挂载到适当位置。
- 使用标准化的容器运行时来运行容器。
让我们来看看上述过程中依赖的特性:
- OCI 镜像格式:用于定义镜像在仓库中的标准存储格式。
- Containers/image 库:用于实现从镜像仓库拉取镜像所需要的所有特性。
- Containers/storage 库:提供 OCI 镜像格式解压缩到写时复制存储上所需要的功能。
- OCI 运行时规范和
runc
:提供运行容器所需要的工具(和 Docker 守护进程用来运行容器的工具相同)。
这意味着我们可以使用这些工具和库实现使用容器所需要的能力,而无需依赖一个大型容器守护进程。
在一个中等到大型规模基于 DevOps 的持续集成 / 持续交付环境中,效率、速度和安全性是非常重要的。而一旦相关的工具符合 OCI 规范,那么开发和运维就能够在持续集成 / 持续交付管道和生产环境中使用最佳的工具。大部分容器操作工具都隐藏在容器编排框架或者其他上层容器平台技术之下。可以预见到未来容器运行时和镜像工具的选择会成为容器平台的一个安装选项。
系统(独立)容器
Atomic 项目中,我们引入了atomic host
,这是一个构建操作系统的新方式,其中的软件可以“原子的”更新,并且大部分应用程序都以容器的方式运行。使用该平台的目的是为了证明大部分软件能够被移植到 OCI 镜像格式,并能够使用标准的协议从镜像仓库下载并安装到系统中。将软件以容器镜像的方式提供,可以让用户的操作系统和应用软件以不同的速度进行更新。传统的 RPM/yum/DNF 方式分发软件包会将应用程序版本限定在主机操作系统的生命周期中。
这种将基础设置作为容器来分发的一个问题是,有的时候应用程序需要在容器运行时守护进程启动前执行。让我们来看一个使用 Docker 守护进程的 Kubernetes 例子:Kubernetes 在启动前需要先完成网络设置,这样它才能够将 pods 分配到独立的网络环境中。该场景下目前我们使用的默认守护进程是 flanneld ,它必须在 Docker 守护进程启动之前运行,以设置 Docker 守护进程的网络接口。同时,flanneld 使用 etcd 作为数据存储。该守护进程需要在 flanneld 之前启动。
如果我们将 etcd 和 flanneld 通过镜像分发,就会遇到先有鸡还是先有蛋的问题。在启动容器化应用程序之前需要一个容器运行时守护进程,但是这些应用程序又需要在容器运行时守护进程之前启动。为了解决这个问题,我找到了一些 hack 的方式,但是没有一种方式是彻底的。同时,Docker 守护进程目前还没有一个像样的方式来设置容器启动的优先级。我看见过这方面的建议,但是目前的实现都是使用类似于老的 SysVInit 方式来启动服务(当然我也知道这带来的复杂性。)
systemd
使用 systemd 来替代 SysVInit 的一个原因是用来处理启动服务的优先级和顺序,为什么容器不能利用这项技术呢?在 Atomic 项目中,我们决定在主机上运行容器的时候不再需要容器运行时守护进程,尤其是那些启动早期需要的服务。因此我们增强了 atomic 命令行接口,允许用户安装容器镜像。当用户执行atomic install --system etcd
时,atomic 会使用 skopeo 从镜像仓库拉取 etcd 的 OCI 镜像,然后将镜像释放到 OSTree 存储中。因为我们在生产环境中运行 etcd,因此该镜像是只读的。下一步atomic
命令从容器镜像中抓取 systemd 的 unit 文件模板,并在硬盘上创建 unit 文件用以启动镜像。该 unit 文件通常使用runc
在主机上启动容器(虽然 runc 不是必须的)。
如果运行atomic install --system flanneld
命令会发生类似的事情,除了这次生成 flanneld 的 unit 文件将会指定 etcd 需要在它启动前运行。
当系统启动时,systemd 确保 etcd 会在 flanneld 之前运行,并且容器运行时会在 flanneld 启动之后再运行。该特性可以让用户将 Docker 守护进程和 Kubernetes 放到系统容器中。这意味着用户可以启动一个 atomic host 或者传统基于 rpm 的操作系统,其中的整个容器编排框架以容器的方式运行。这项功能非常强大,因为我们知道客户希望能够独立于这些组件,持续给他们的容器主机打补丁。此外,它能够将主机操作系统占用空间最小化。
当然,关于将传统应用程序放到容器中使其既能够作为独立 / 系统容器运行,又能够成为可编排容器仍然有所争论。考虑一个 Apache 容器,我们可以使用atomic install --system httpd
命令安装。这个容器镜像可以和基于 rpm 的 httpd 服务一样的启动(systemctl start httpd
,除了 httpd 进程将会启动在容器中)。存储仍然可以使用本地的,意味着可以将主机的 /var/www 目录挂载到容器中,这个容器也监听这本地网络的 80 端口。这表明我们可以在主机的容器中运行传统工作负载,而无需一个容器运行时守护进程。
构建容器镜像
在我看来,最近 4 年来容器创新中最令人悲伤的事情,就是构建容器镜像构建机制缺乏创新。一个容器镜像是一个由镜像内容的压缩包和一些 JSON 文件组成的压缩包。容器的基础镜像是一个完成的根文件系统和一些描述用的 JSON 文件。然后用户可以在上面增加层,每个增加的层会形成一个压缩包和记录变化的 JSON 文件。这些层和基础镜像一起打包组合成了容器镜像。
基本上所有人都通过docker build
和 Dockerfile 格式文件来构建镜像。上游 docker 在几年前就已经停止接受修改和增强 Dockerfile 格式和构建方式的 pull request。在容器进化过程中,Dockerfile 扮演了重要的角色。开发和运维可以简单直接的方式构建镜像。然而在我看来,Dockerfile 只是一个简化的 bash 脚本,到目前为止还有好多问题没有解决。例如:
- 为了构建容器镜像,Dockerfile 必须依赖 Docker 守护进程
- 目前为止还没有人创建出标准的工具,可以独立于 Docker 命令来创建 OCI 格式镜像。
- 诸如
ansible-containers
和 OpenShift S2I(Source2Image)等工具,仍然在底层使用了 Docker 引擎。
- Dockerfile 中的每一行都会创建一个新的镜像,这能够提升开发阶段构建镜像的效率,因为工具可以知道 Dockerfile 中的每一行是否有修改,如果没有修改,之前构建的镜像可以被复用而无需重新构建。但是这会导致产生大量的层。
- 因为这个问题许多人都要求提供一个合并层的方式以限制层的数量。最终上游 docker 已经接受了部分建议满足了该需求。
- 为了能够从安全的站点拉取镜像内容,通常用户需要一些安全措施。例如用户需要有 RHEL 认证和订阅授权才能够在镜像中增加 RHEL 内容。
- 这些密钥最终会保存在镜像的层中,开发者需要在这些层中移除它们。
- 为了能够在构建镜像的时候能够挂载卷,我们在自己分发的 atomic 项目和 docker 包中增加了
-v
参数,但是上游 docker 没有接受这些补丁。
- 构建出来的组件最终被放置在容器镜像中。因此,虽然 Dockerfile 对于刚开始构建的开发者,可以更好的了解整个构建过程,但是这对于大规模企业环境来说不是一个高效的方式。另外,在使用自动化容器平台之后,用户不会关注构建 OCI 标准镜像的方式是否高效。
出发吧 buildah
在 2017 年 DevConf.cz 上,我让我们团队的 Nalin Dahyabhai 看看称之为 containers-coreutils 的构建工具,本质上这是一个使用了 containers/storage 库和 containers/image 库的一系列命令行工具,它们可以模仿 Dockerfile 的语法。Nalin 将其命令为 buildah ,用来取笑我的波士顿口音。使用一些 buildah 原语,我们就可以构建一个容器镜像:
- 关于安全性的一个主要思想是确保操作系统的镜像尽可能小,以限制不需要的工具。该想法的来源是黑客需要依赖工具来攻破应用程序,如果注入 gcc、make、dnf 等工具不存在,攻击可能会停止或者受到限制。
- 另外,由于这些镜像的拉取和推送都需要经过互联网,缩小镜像尺寸总是没错的。
- Docker 构建镜像的主要方式是通过命令将软件安装或者编译到容器的
buildroot
中。 - 执行
run
命令需要这些可执行程序包含在容器镜像中。例如在镜像中使用dnf
命令需要安装整个 Python 栈,即使最终的应用程序不会使用 Python。 ctr=$(buildah from fedora)
:- 使用 containers/image 库从镜像仓库拉取 Fedora 镜像
- 返回一个容器 ID(
ctr
)
mnt=$(buildah mount $ctr)
:- 挂载刚创建的容器镜像(
$ctr
)。 - 返回挂载点路径。
- 现在可以使用这个挂载点写入内容。
- 挂载刚创建的容器镜像(
dnf install httpd –installroot=$mnt
:- 我们可以使用主机系统上的命令将内容写入到容器中,这意味着我们可以把密钥放在主机上,而不用放入到容器中,另外构建工具也可以保存在主机上。
- dnf 等命令也不用事先安装到容器中,Python 等依赖也是,除非应用程序依赖。
cp foobar $mnt/dir
:- 我们可以使用 bash 支持的任何命令来填充容器。
buildah commit $ctr
:- 我们可以在需要的时候创建层。层的创建由用户控制而不是工具。
buildah config --env container=oci --entrypoint /usr/bin/httpd $ctr
:- 在 Dockerfile 中支持的命令也可以指定。
buildah run $ctr dnf -y install httpd
:- buildah 同样支持
run
命令,它不依赖容器运行时守护进程,而是通过执行runc
直接在锁定的容器中执行命令。
- buildah 同样支持
buildah build-using-dockerfile -f Dockerfile .
:- buildah 同样支持使用 Dockerfile 来构建镜像。
我们希望将类似ansible-containers
和 OpenShift S2I 这样的工具改成buildah
,而非依赖一个容器运行时守护进程。
构建容器镜像和运行容器采用相同的运行时环境,对于生产环境还会遇到一个大问题,既针对容器安全需要同时满足二者的权限需求。通常构建容器镜像的时候需要远多于运行容器所需要的权限。例如,默认情况下我们会允许mknod
能力。mknod
能力允许进程能够创建设备节点,但是在生产环境几乎没有应用程序需要这个能力。在生产环境中移除mknod
能力能够让系统变得更加安全。
另一个例子是我们会默认给容器镜像以读写权限,因为安装进程需要将软件包安装到/usr
目录中。但是在生产环境中,我个人建议应该将所有容器运行在只读模式。容器中的进程应该只能允许写入tmpfs
或者挂载到容器内的卷上。通过将构建镜像和运行容器分离,我们可以修改这些默认设置,让运行环境更加安全。
Kubernetes 的运行时抽象:CRI-O
Kubernetes 增加了一套用于插拔任何 pods 运行时的 API,称为容器运行时接口(Container Runtime Interface,CRI)。我不愿意在我的系统上运行太多的守护进程,但是我们基于此增加了一种。由我们团队中 Mrunal Patel 领导的小组,在 2016 年晚些时候开始实现 CRI-O 守护进程。这是一个用于运行基于 OCI 应用程序的容器运行时接口守护进程。理论上,未来我们可以将 CRI-O 代码直接编译如 kubelet,以减少一个守护进程。
和其他容器运行时不同的是,CRI-O 的唯一目的是满足 Kubernetes 的需求。回忆一下前面提到的关于 Kubernetes 运行一个容器所需要的步骤。
Kubernetes 向 kubelet 发送一个消息,通知它需要启动一个 nginx 服务:
- kubelet 调用 CRI-O 告知其运行 nginx。
- CRI-O 响应 CRI 请求。
- CRI-O 在镜像仓库中找到对应的 OCI 镜像。
- CRI-O 使用 containers/image 将镜像从仓库拉取到本地。
- CRI-O 使用 containers/storage 将镜像解压到本地存储中。
- CRI-O 使用 OCI 运行时规范启动容器,通常使用
runc
。我前面提到过,这和 Docker 守护进程使用runc
来启动容器的方式相同。 - 如果需要,kubelet 也可以使用其他的运行时来启动容器,例如 Clear Containers 的
runv
CRI-O 旨在成为运行 Kubernetes 的稳定平台,我们只会在通过了所有 Kubernetes 的测试集之后才会发布新的版本。所有提交到 https://github.com/Kubernetes-incubator/cri-o 上的 pull request 都需要运行整个 Kubernetes 测试集。开发者不能提交一个无法通过这些测试的 pull request。CRI-O 是一个完全开放的项目,目前已经有来自 Intel、SUSE、IBM、Google、Hyper.sh 等公司的贡献者。只要 CRI-O 的大多数维护者同意,pull request 就会被接受,即使这些补丁功能不是红帽公司需要的。
总结
我希望本文的深入探讨能够帮助读者了解 Linux 容器的演化史。Linux 一度处于每个厂商有自己标准的情形。Docker 公司专注于建立镜像构建的事实标准,并且简化了容器的相关工具。Open Container Initiative 的建立意味着行业正在围绕着核心镜像格式和运行时制定规范,围绕着让工具更有效率、更安全、高可扩展性和可用性进行创新。容器允许我们能够用新颖的方式验证软件安装,无论其是运行在主机上的传统软件还是运行在云端经过编排的微服务。在很多方面,这还仅仅是个开始。
查看英文原文: https://opensource.com/article/17/7/how-linux-containers-evolved
感谢徐川对本文的审校。
评论