Linux 容器的演变历程

容器在过去几年中取得了长足的进步。我们将回顾其发展历程。
708 位读者喜欢这篇文章。
How Linux containers have evolved

Daniel Ramirez。CC BY-SA 4.0

在过去几年中,容器不仅在开发者中,而且在企业中都已成为热门话题。这种日益增长的兴趣导致对安全改进和加固,以及为可扩展性和互操作性做准备的需求增加。这需要大量的工程工作,这里讲述的是 Red Hat 在企业层面完成了多少工程工作的故事。

当我在 2013 年秋季首次会见 Docker 公司 (Docker.io) 的代表时,我们正在研究如何使 Red Hat Enterprise Linux (RHEL) 使用 Docker 容器。(Docker 项目的一部分后来被更名为 Moby。)我们将这项技术引入 RHEL 时遇到了几个问题。第一个也是最大的障碍是获得受支持的写时复制 (COW) 文件系统来处理容器镜像分层。Red Hat 最终贡献了几个 COW 实现,包括 Device Mapperbtrfs 和第一个版本的 OverlayFS。对于 RHEL,我们默认使用 Device Mapper,尽管我们在 OverlayFS 支持方面越来越接近。

下一个主要障碍是启动容器的工具。当时,上游 docker 正在使用 LXC 工具来启动容器,而我们不希望在 RHEL 中支持 LXC 工具集。在与上游 docker 合作之前,我一直在与 libvirt 团队合作开发一个名为 virt-sandbox 的工具,该工具使用 libvirt-lxc 来启动容器。

当时,Red Hat 的一些人认为,替换 LXC 工具并添加一个桥接器,以便 Docker 守护程序可以使用 libvirt-lxc 与 libvirt 通信以启动容器,这是一个好主意。但这种方法存在严重的担忧。请考虑以下使用 Docker 客户端 (docker-cli) 启动容器的示例,以及容器进程 (pid1OfContainer) 启动之前的调用层级

docker-cli → docker-daemon → libvirt-lxc → pid1OfContainer

我不喜欢在启动容器的工具和最终运行的容器之间有两个守护程序的想法。

我的团队与上游 docker 开发者一起努力开发了一个原生的 Go 编程语言 容器运行时实现,名为 libcontainer。该库最终与 runc 一起作为 OCI Runtime Specification 的初始实现发布。

docker- cli → docker-daemon @ pid1OfContainer

尽管大多数人错误地认为,当他们执行容器时,容器进程是 docker-cli 的子进程,但实际上他们执行的是客户端/服务器操作,容器进程是在一个完全独立的环境中作为子进程运行的。这种客户端/服务器操作可能会导致不稳定性和潜在的安全问题,并且会阻止有用的功能。例如,systemd 具有一个名为套接字激活的功能,您可以在其中设置一个守护程序,使其仅在进程连接到套接字时运行。这意味着您的系统使用更少的内存,并且只有在需要时才执行服务。套接字激活的工作方式是 systemd 监听 TCP 套接字,当数据包到达套接字时,systemd 会激活通常监听该套接字的服务。服务激活后,systemd 会将套接字传递给新启动的守护程序。将此守护程序移动到基于 Docker 的容器中会导致问题。单元文件将使用 Docker CLI 启动容器,并且 systemd 无法通过 Docker CLI 将连接的套接字传递给 Docker 守护程序。

诸如此类的问题使我们意识到,我们需要其他运行容器的方法。

容器编排问题

上游 docker 项目使容器的使用变得容易,并且它仍然是学习 Linux 容器的绝佳工具。您可以通过运行一个简单的命令(如 docker run -ti fedora sh)快速体验启动容器,并立即进入容器环境。

容器的真正威力在于您开始同时运行多个容器并将它们连接起来形成更强大的应用程序时。设置多容器应用程序的问题在于复杂性迅速增加,并且使用简单的 Docker 命令进行连接会变得难以处理。如何在资源有限的节点集群中管理容器应用程序的部署或编排?如何管理它们的生命周期等等?

在第一届 DockerCon 上,至少有七家不同的公司/开源项目展示了如何编排容器。Red Hat 的 OpenShift 有一个名为 geard 的项目,它大致基于 OpenShift v2 容器(称为“gears”),我们正在演示该项目。Red Hat 认为我们需要重新审视编排,并可能与开源社区中的其他人合作。

Google 正在演示 Kubernetes 容器编排,这基于 Google 在编排其自身内部架构方面开发的所有知识。OpenShift 决定放弃我们的 Gear 项目,开始与 Google 合作开发 Kubernetes。Kubernetes 现在是 GitHub 上最大的社区项目之一。

Kubernetes

Kubernetes 的开发目的是使用 Google 的 lmctfy 容器运行时。Lmctfy 在 2014 年夏天被移植为与 Docker 配合使用。Kubernetes 在 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)。他们还引入了一个标准的容器规范,称为 appc (App Container)。基本上,他们希望让每个人都使用一个标准规范来规定如何将应用程序存储在容器镜像包中。

这引起了警觉。当我第一次开始与上游 docker 合作开发容器时,我最大的担心是我们最终会得到多个规范。我不希望 RPM 与 Debian 之间的战争影响未来 20 年的 Linux 软件交付。appc 引入的一个好处是,它说服了上游 docker 与开源社区合作创建一个名为 Open Container Initiative (OCI) 的标准组织。

OCI 一直在制定两个规范

OCI Runtime Specification:OCI 运行时规范“旨在指定容器的配置、执行环境和生命周期”。它定义了容器在磁盘上的外观、描述容器内将运行的应用程序的 JSON 文件,以及如何生成和执行容器。上游 docker 贡献了 libcontainer 工作,并将 runc 构建为 OCI Runtime Specification 的默认实现。

OCI Image Format Specification:镜像格式规范主要基于上游 docker 镜像格式,并定义了位于容器注册表中的实际容器镜像包。此规范允许应用程序开发人员将其应用程序标准化为单一格式。appc 中描述的一些想法(尽管它仍然存在)已添加到 OCI Image Format Specification 中。这两个 OCI 规范都已接近 1.0 版本发布。上游 docker 已同意在 OCI Image Specification 最终确定后对其进行支持。Rkt 现在支持运行 OCI 镜像以及传统的上游 docker 镜像。

Open Container Initiative 通过为行业提供一个围绕容器镜像和运行时进行标准化的场所,帮助释放了工具和编排领域的创新。

抽象运行时接口

利用这种标准化的创新之一是在 Kubernetes 编排领域。作为 Kubernetes 工作的坚定支持者,CoreOS 向 Kubernetes 提交了一系列补丁,以添加通过 rkt 以及上游 docker 引擎进行通信和运行容器的支持。Google 和上游 Kubernetes 认为,添加这些补丁以及未来可能添加新的容器运行时接口将使 Kubernetes 代码过于复杂。上游 Kubernetes 团队决定实施一个名为 Container Runtime Interface (CRI) 的 API 协议规范。然后他们将重构 Kubernetes 以调用 CRI 而不是 Docker 引擎,这样任何想要构建容器运行时接口的人都可以只实现 CRI 的服务器端,他们就可以支持 Kubernetes。上游 Kubernetes 为 CRI 开发人员创建了一个大型测试套件,用于进行测试以证明他们可以为 Kubernetes 提供服务。目前正在努力从 Kubernetes 中删除所有 Docker-engine 调用,并将它们放在一个名为 docker-shim 的 shim 后面。

容器工具创新

使用 skopeo 进行容器注册表创新

几年前,我们正在与 Project Atomic 团队合作开发 atomic CLI。我们希望能够在容器镜像位于容器注册表时对其进行检查。当时,查看容器注册表中与容器镜像关联的 JSON 数据的唯一方法是将镜像拉取到本地服务器,然后您可以使用 docker inspect 读取 JSON 文件。这些镜像可能非常大,高达数 GB。因为我们希望允许用户检查镜像并决定不拉取它们,所以我们希望为 docker inspect 添加一个新的 --remote 接口。上游 docker 拒绝了拉取请求,告诉我们他们不想使 Docker CLI 复杂化,并且我们可以轻松地构建自己的工具来做同样的事情。

我的团队在 Antonio Murdaca 的领导下,采用了这个想法并创建了 skopeo。Antonio 不仅停留在拉取与镜像关联的 JSON 文件上,他还决定实现从容器注册表到本地主机以及从本地主机到容器注册表的容器镜像拉取和推送的完整协议。

Skopeo 现在在 atomic CLI 中被广泛使用,用于检查容器的新更新以及 atomic scan 内部。Atomic 也使用 skopeo 来拉取和推送镜像,而不是使用上游 docker 守护程序。

Containers/image

我们一直在与 CoreOS 讨论是否可以将 skopeo 与 rkt 一起使用,他们表示他们不想 exec 到辅助应用程序,但会考虑使用 skopeo 使用的库。我们决定将 skopeo 分割成一个库和一个可执行文件,并创建了 image

containers/image 库和 skopeo 在其他几个上游项目和云基础设施工具中使用。Skopeo 和 containers/image 已经发展到除了 Docker 之外还支持多个存储后端,并且它具有在容器注册表之间移动容器镜像的能力以及许多很酷的功能。skopeo 的一个优点 是它不需要任何守护程序来完成其工作。containers/image 库的突破也使我们能够添加增强功能,例如 容器镜像签名

镜像处理和扫描方面的创新

我在本文前面提到了 atomic CLI 命令。我们构建这个工具是为了向容器添加 Docker CLI 中不适用的功能,以及我们认为无法进入上游 docker 的功能。我们还希望允许灵活性来支持其他容器运行时、工具和存储的开发。Skopeo 就是一个例子。

我们想要添加到 atomic 的一个功能是 atomic mount。基本上,我们想获取存储在 Docker 镜像存储(上游 docker 称之为图驱动程序)中的内容,并将镜像挂载到某个位置,以便工具可以检查镜像。目前,如果您使用上游 docker,查看镜像的唯一方法是启动容器。如果您有不受信任的内容,则在容器内执行代码以查看镜像可能很危险。通过启动容器来检查镜像的第二个问题是,用于检查容器的工具可能不在容器镜像中。

大多数容器镜像扫描器似乎都具有以下模式:它们连接到 Docker 套接字,执行 docker save 创建一个 tarball,然后在磁盘上解压 tarball,最后检查内容。这是一个缓慢的操作。

使用 atomic mount,我们想要进入 Docker 图驱动程序并挂载镜像。如果 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 守护程序可能会进入奇怪的状态。

容器存储

为了解决这个问题,我们开始考虑将图驱动程序代码从上游 docker 守护程序中提取到其自己的存储库中。Docker 守护程序在其图驱动程序的所有锁定都在内存中完成。我们希望将此锁定移动到文件系统中,以便我们可以让多个不同的进程能够同时操作容器存储,而无需通过单个守护程序进程。

我们创建了一个名为 container/storage 的项目,它可以执行运行、构建和存储容器所需的所有 COW 功能,而无需一个进程来控制和监视它(即,不需要守护程序)。现在 skopeo 和其他工具和项目可以利用该存储。其他开源项目也开始使用 containers/storage,在某个时候,我们希望将此项目合并回上游 docker 项目。

解除 Docker 的束缚,让我们创新

如果您考虑一下当 Kubernetes 在具有 Docker 守护程序的节点上运行容器时会发生什么,首先 Kubernetes 会执行类似这样的命令

kubelet run nginx –image=nginx

此命令告诉 kubelet 在节点上运行 NGINX 应用程序。kubelet 调用 CRI 并要求它启动 NGINX 应用程序。此时,实现 CRI 的容器运行时必须执行以下步骤

  1. 检查本地存储中是否有名为 nginx 的容器。如果本地没有,容器运行时将在容器注册表中搜索标准化的容器镜像。
  2. 如果镜像不在本地存储中,则从容器注册表将其下载到本地系统。
  3. 将下载的容器镜像解压到容器存储之上——通常是 COW 存储——并挂载它。
  4. 使用标准化的容器运行时执行容器。

让我们看一下上面描述的功能

  1. OCI Image Format Specification 定义了存储在容器注册表中的镜像的标准镜像格式。
  2. Containers/image 是一个库,它实现了将容器镜像从容器注册表拉取到容器主机所需的所有功能。
  3. Containers/storage 提供了一个库,用于将 OCI Image Format 解压到 COW 存储上,并允许您处理镜像。
  4. OCI Runtime Specification 和 runc 提供了用于执行容器的工具(与 Docker 守护程序用于运行容器的工具相同)。

这意味着我们可以使用这些工具来实现使用容器的能力,而无需大型容器守护程序。

在中等到大规模的基于 DevOps 的 CI/CD 环境中,效率、速度和安全性非常重要。只要您的工具符合 OCI 规范,开发人员或运维人员就应该使用最佳工具来自动化 CI/CD 管道并将其投入生产。大多数容器工具都隐藏在编排或更高级别的容器平台技术之下。我们设想,运行时或镜像包工具的选择可能会成为容器平台的一个安装选项。

系统(独立)容器

在 Project Atomic 上,我们推出了 atomic host,这是一种构建操作系统的新方法,其中软件可以“原子地”更新,并且在其上运行的大多数应用程序将作为容器运行。我们在这个平台上的目标是证明,将来大多数软件都可以以 OCI Image Format 交付,并使用标准协议从容器注册表获取镜像并将它们安装在您的系统上。以容器镜像的形式提供软件允许您以与在其上运行的应用程序不同的速度更新主机操作系统。传统的 RPM/yum/DNF 包分发方式将应用程序锁定在主机操作系统的生命周期中。

我们看到将大多数基础设施作为容器交付的一个问题是,有时您必须在容器运行时守护程序执行之前运行应用程序。让我们看一下我们的 Kubernetes 示例,它与 Docker 守护程序一起运行:Kubernetes 需要设置网络,以便它可以将其 pod/容器放入隔离的网络中。我们目前为此使用的默认守护程序是 flanneld,它必须在 Docker 守护程序启动之前运行,以便将网络接口交给 Docker 守护程序以运行 Kubernetes pod。此外,flanneld 使用 etcd 作为其数据存储。此守护程序需要在 flanneld 启动之前运行。

如果我们想将 etcd 和 flanneld 作为容器镜像交付,我们就会遇到先有鸡还是先有蛋的问题。我们需要容器运行时守护程序来启动容器化应用程序,但是这些应用程序需要在容器运行时守护程序启动之前运行。我已经看到几种尝试处理这种情况的 hacky 设置,但没有一种是干净的。此外,Docker 守护程序目前没有体面的方法来配置容器启动的优先级顺序。我已经看到了一些关于这方面的建议,但它们看起来都像旧的 SysVInit 服务启动方式(我们知道这会导致复杂性)。

systemd

用 systemd 替换 SysVInit 的一个原因是处理启动服务的优先级和顺序,那么为什么不利用这项技术呢?在 Project Atomic 中,我们决定我们希望在主机上运行容器,而无需容器运行时守护程序,尤其是在早期启动时。我们增强了 atomic CLI,允许您安装容器镜像。如果您执行 atomic install --system etcd,它将使用 skopeo 连接到容器注册表并拉取 etcd OCI Image。然后,它将镜像解压(或展开)到 OSTree 后端存储上。由于我们在生产环境中运行 etcd,因此我们将镜像视为只读。接下来,atomic 命令从容器镜像中获取 systemd 单元文件模板,并在磁盘上创建一个单元文件以启动镜像。单元文件实际上使用 runc 在主机上启动容器(尽管 runc 不是必需的)。

如果您执行 atomic install --system flanneld,也会发生类似的事情,但这次 flanneld 单元文件指定它需要在 etcd 单元运行之后才能启动。

当系统启动时,systemd 确保 etcd 在 flanneld 之前运行,并且容器运行时在 flanneld 启动之后才启动。这允许您将 Docker 守护程序和 Kubernetes 移动到系统容器中。这意味着您可以启动 atomic host 或传统的基于 rpm 的操作系统,该操作系统将整个容器编排堆栈作为容器运行。这很强大,因为我们知道客户希望继续独立于这些组件修补他们的容器主机。此外,它还最大限度地减少了主机操作系统的占用空间。

甚至有人讨论将传统应用程序放入容器中,这些容器可以作为独立/系统容器或作为编排容器运行。考虑一个 Apache 容器,您可以使用 atomic install --system httpd 命令安装它。此容器镜像的启动方式与启动基于 rpm 的 httpd 服务 (systemctl start httpd,只是 httpd 将在容器中启动) 的方式相同。存储可以是本地的,这意味着来自主机的 /var/www 被挂载到容器中,并且容器在本地网络端口 80 上监听。这表明您可以在容器内的主机上运行传统工作负载,而无需容器运行时守护程序。

构建容器镜像

从我的角度来看,过去四年容器创新中最令人遗憾的事情之一是缺乏构建容器镜像的机制创新。容器镜像只不过是一个 tarball 和一些 JSON 文件的 tarball。容器的基础镜像是一个 rootfs 以及描述基础镜像的 JSON 文件。然后,当您添加层时,层之间的差异会与 JSON 文件的更改一起被 tar 压缩。这些层和基础文件被 tar 压缩在一起以形成容器镜像。

几乎每个人都在使用 docker build 和 Dockerfile 格式进行构建。上游 docker 在几年前停止接受修改或改进 Dockerfile 格式和构建的拉取请求。Dockerfile 在容器的演变中发挥了重要作用。开发人员或管理员/运维人员可以以简单明了的方式构建容器;但是,在我看来,Dockerfile 实际上只是一个简陋的 bash 脚本,并且会产生一些从未解决的问题。例如

  • 要构建容器镜像,Dockerfile 需要 Docker 守护程序正在运行。
    • 除了执行 Docker 命令之外,没有人构建标准的工具来创建 OCI 镜像。
    • 即使是 ansible-containers 和 OpenShift S2I (Source2Image) 等工具也在底层使用 docker-engine
  • Dockerfile 中的每一行都会创建一个新镜像,这有助于创建容器的开发过程,因为该工具足够智能,可以知道 Dockerfile 中的行没有更改,因此可以使用现有镜像,而无需重新处理这些行。这可能会导致大量层。
    • 由于这些原因,一些人已经要求提供压缩镜像以消除层的机制。我认为上游 docker 最终已经接受了一些东西来满足这一需求。
  • 为了从安全站点拉取内容以放入您的容器镜像中,通常您需要某种形式的密钥。例如,您需要访问 RHEL 证书和订阅才能将 RHEL 内容添加到镜像中。
    • 这些密钥最终可能会出现在存储在镜像中的层中。开发人员需要费尽周折才能删除这些密钥。
    • 为了允许在 Docker 构建期间挂载卷,我们在我们交付的 projectatomic/docker 包中添加了一个 -v 卷开关,但上游 docker 尚未接受这些补丁。
  • 构建工件最终会出现在容器镜像内部。因此,尽管 Dockerfile 非常适合入门或在笔记本电脑上构建容器以尝试了解您可能想要构建的镜像,但它们实际上并不是在高规模企业环境中构建镜像的有效或高效手段。在自动化的容器平台背后,您不应关心您是否正在使用更有效的方式来构建符合 OCI 标准的镜像。

使用 Buildah 解除 Docker 的束缚

在 DevConf.cz 2017 上,我要求我团队中的 Nalin Dahyabhai 研究构建我称之为 containers-coreutils 的东西,基本上是使用 containers/storage 和 containers/image 库并构建一系列可以模仿 Dockerfile 语法的命令行工具。Nalin 决定将其命名为 buildah,取笑我的波士顿口音。使用一些 buildah 原语,您可以构建容器镜像

  • 安全性的主要概念之一是尽可能减少操作系统镜像内部的内容量,以消除不必要的工具。其理念是,黑客可能需要工具来突破应用程序,如果 gccmakednf 等工具不存在,则可以阻止或限制攻击者。
  • 因为这些镜像正在互联网上被拉取和推送,所以缩小容器的大小始终是一个好主意。
  • Docker 构建的工作方式是将安装软件或编译软件的命令放在容器的 uildroot 中。
  • 执行 run 命令要求所有可执行文件都位于容器镜像内部。即使您在应用程序中从未使用过 Python,仅在容器镜像内部使用 dnf 也需要存在整个 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,但 buildah 不是依赖于容器运行时守护程序,而是执行 runc 以在锁定的容器内运行命令。
  • buildah build-using-dockerfile -f Dockerfile .:

    我们希望将 ansible-containers 和 OpenShift S2I 等工具迁移到使用 buildah,而不是要求容器运行时守护程序。

    在与生产环境中用于运行容器的相同容器运行时中构建的另一个大问题是,在安全性方面,您最终会得到最低的共同标准。构建容器往往比运行容器需要更多的特权。例如,默认情况下我们允许 mknod 功能。mknod 功能允许进程创建设备节点。某些软件包安装尝试创建设备节点,但在生产环境中几乎没有应用程序这样做。从生产环境中的容器中删除 mknod 功能将使您的系统更加安全。

    另一个例子是,我们将容器镜像默认设置为读/写,因为安装过程意味着将软件包写入 /usr。然而在生产环境中,我认为您真的应该以只读模式运行所有容器。仅允许容器写入 tmpfs 或已卷挂载到容器中的目录。通过将容器的运行与构建分开,我们可以更改默认设置,从而创建一个更加安全的环境。

    • 是的,buildah 可以使用 Dockerfile 构建容器镜像。

CRI-O:Kubernetes 的运行时抽象

Kubernetes 添加了一个 API,用于为 pod 插入任何运行时,称为 Container Runtime Interface (CRI)。我不太喜欢在我的系统上运行大量守护程序,但我们又添加了一个。我的团队在 Mrunal Patel 的领导下,于 2016 年底开始开发 CRI-O 守护程序。这是一个用于运行基于 OCI 的应用程序的 Container Runtime Interface 守护程序。从理论上讲,将来我们可以将 CRI-O 代码直接编译到 kubelet 中,以消除第二个守护程序。

与其他容器运行时不同,CRI-O 的唯一目的是满足 Kubernetes 的需求。请记住上面描述的 Kubernetes 运行容器所需的步骤。

Kubernetes 发送消息给 kubelet,告知其需要运行 NGINX 服务器。

  1. kubelet 调用 CRI-O,告知其运行 NGINX。
  2. CRI-O 响应 CRI 请求。
  3. CRI-O 在容器镜像仓库中找到一个 OCI 镜像。
  4. CRI-O 使用 containers/image 从镜像仓库拉取镜像到主机。
  5. CRI-O 使用 containers/storage 将镜像解压到本地存储。
  6. CRI-O 启动 OCI 运行时规范,通常是 runc,并启动容器。正如我之前所说,Docker 守护进程以完全相同的方式使用 runc 启动其容器。
  7. 如果需要,kubelet 也可以使用替代运行时启动容器,例如 Clear Containers runv

CRI-O 旨在成为运行 Kubernetes 的稳定平台,除非它通过整个 Kubernetes 测试套件,否则我们不会发布新版本的 CRI-O。所有提交到 https://github.com/Kubernetes-incubator/cri-o 的拉取请求都针对整个 Kubernetes 测试套件运行。不通过测试,您无法将拉取请求提交到 CRI-O。CRI-O 是完全开源的,我们有来自多家不同公司的贡献者,包括 Intel、SUSE、IBM、Google、Hyper.sh。只要大多数维护者同意对 CRI-O 的补丁,它就会被接受,即使该补丁不是 Red Hat 想要的。

结论

我希望这次深入探讨能帮助您理解 Linux 容器是如何演进的。曾经,Linux 容器处于各自为政的状态。Docker 帮助大家专注于镜像创建的事实标准,并简化了用于容器的工具。开放容器计划 (Open Container Initiative) 现在意味着整个行业围绕核心镜像格式和运行时开展工作,这促进了围绕使工具更高效地用于自动化、更安全、高度可扩展且更易于使用方面的创新。容器使我们能够以新颖的方式审视软件安装——无论是运行在主机上的传统应用程序,还是在云中运行的编排微服务。在许多方面,这仅仅是开始。

User profile image.
Daniel Walsh 在计算机安全领域工作了近 30 年。Dan 于 2001 年 8 月加入 Red Hat。

6 条评论

“……而且我们不想在 RHEL 中支持 LXC 工具集”,这是什么原因?为什么 RH 不投资 LXC?

标题简直是错误的。你指的不是 Linux 容器,而是 Docker,仅此而已。Docker 是唯一的 Linux 容器吗?

Linux 容器大约从 2001 年左右就出现了,远早于 Docker 流行起来。在 Red Hat 听说这个概念之前,我就已经在使用 Virtuozzo 容器作为功能齐全的多用途 Linux 服务器。

与 OpenVZ 相比,Docker 和 LXD 非常不成熟。它们缺乏最基本的功能,而且是一种倒退。

如果您正在寻找构建仅限应用程序容器的有效方法,请查看 https://habitat.sh

非常感谢您花时间清楚地解释您的选择及其背后的动机。

“(……)并且如果 gcc、make、dnf 等工具不存在,则可以阻止或限制攻击者”

我非常同意这一点!

Creative Commons License本作品采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可。
© . All rights reserved.