低级别 Linux 容器运行时历史

“容器运行时”是一个含义过多的术语。
565 位读者喜欢这个。
Why containers are the best way to test software performance

Rikki Endsley。CC BY-SA 4.0

在红帽,我们喜欢说,“容器就是 Linux——Linux 就是容器。” 这句话的意思如下。传统容器是系统上的进程,通常具有以下三个特征

1. 资源约束

当您在系统上运行大量容器时,您不希望任何容器垄断操作系统,因此我们使用资源约束来控制 CPU、内存、网络带宽等。Linux 内核提供了 cgroups 功能,可以配置该功能来控制容器进程资源。

2. 安全约束

通常,您不希望您的容器能够互相攻击或攻击主机系统。我们利用 Linux 内核的几个功能来设置安全隔离,例如 SELinux、seccomp、capabilities 等。

3. 虚拟隔离

容器进程不应看到容器外部的任何进程。它们应该在自己的网络上。容器进程需要能够绑定到不同容器中的端口 80。每个容器都需要查看其自身的镜像,需要其自身的根文件系统 (rootfs)。在 Linux 中,我们使用内核命名空间来提供虚拟隔离。

因此,在 cgroup 中运行、具有安全设置并在命名空间中运行的进程可以称为容器。查看 Red Hat Enterprise Linux 7 系统上的 PID 1 systemd,您会看到 systemd 在 cgroup 中运行。

# tail -1 /proc/1/cgroup
1:name=systemd:/

ps 命令显示系统进程具有 SELinux 标签...

# ps -eZ | grep systemd
system_u:system_r:init_t:s0     	1 ?    	00:00:48 systemd

和 capabilities。

# grep Cap /proc/1/status
...
CapEff:	0000001fffffffff
CapBnd:	0000001fffffffff
CapBnd:    0000003fffffffff

最后,如果您查看 /proc/1/ns 子目录,您将看到 systemd 运行所在的命名空间。

ls -l /proc/1/ns
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 mnt -> mnt:[4026531840]
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 net -> net:[4026532009]
lrwxrwxrwx. 1 root root 0 Jan 11 11:46 pid -> pid:[4026531836]
...

如果 PID 1(以及系统上的所有其他进程)都具有资源约束、安全设置和命名空间,我认为系统上的每个进程都在容器中。

容器运行时工具只是修改这些资源约束、安全设置和命名空间。然后 Linux 内核执行这些进程。容器启动后,容器运行时可以监控容器内部的 PID 1 或容器的 stdin/stdout——容器运行时管理这些进程的生命周期。

容器运行时

您可能会对自己说,systemd 听起来与容器运行时非常相似。好吧,在就为什么容器运行时不使用 systemd-nspawn 作为启动容器的工具进行了多次电子邮件讨论之后,我决定有必要讨论容器运行时并提供一些历史背景。

Docker 通常被称为容器运行时,但“容器运行时”是一个含义过多的术语。当人们谈论“容器运行时”时,他们实际上是在谈论更高级别的工具,如 Docker、CRI-ORKT,它们带有开发者功能。它们是 API 驱动的。它们包括从容器注册表中拉取容器镜像、设置存储以及最终启动容器等概念。启动容器通常涉及运行一个专用工具来配置内核以运行容器,这些工具也被称为“容器运行时”。我将把它们称为“低级别容器运行时”。像 Docker 和 CRI-O 这样的守护程序,以及像 PodmanBuildah 这样的命令行工具,可能应该被称为“容器管理器”。

当 Docker 最初编写时,它使用 lxc 工具集启动容器,该工具集早于 systemd-nspawn。红帽最初在 Docker 方面的工作是尝试将 libvirt (libvirt-lxc) 集成到 Docker 中,作为 lxc 工具的替代方案,这些工具在 RHEL 中不受支持。libvirt-lxc 也没有使用 systemd-nspawn。当时,systemd 团队表示 systemd-nspawn 只是一个用于测试的工具,而不是用于生产。

与此同时,包括我的红帽团队的一些成员在内的上游 Docker 开发人员决定,他们想要一种 golang 原生的方式来启动容器,而不是启动单独的应用程序。libcontainer 的工作开始了,它是一个用于启动容器的原生 golang 库。红帽工程部门认为这是最佳前进方向,并放弃了 libvirt-lxc

后来,成立了 开放容器倡议组织 (OCI),部分原因是人们希望能够以更多方式启动容器。传统的命名空间隔离容器很受欢迎,但人们也渴望虚拟机级别的隔离。英特尔和 Hyper.sh 正在研究 KVM 隔离容器,而微软正在研究基于 Windows 的容器。OCI 希望有一个标准规范来定义什么是容器,因此 OCI 运行时规范 应运而生。

OCI 运行时规范定义了一种 JSON 文件格式,该格式描述了应运行哪个二进制文件、应如何包含它以及容器的 rootfs 的位置。工具可以生成此 JSON 文件。然后其他工具可以读取此 JSON 文件并在 rootfs 上执行容器。Docker 的 libcontainer 部分被分离出来并捐赠给 OCI。上游 Docker 工程师和我们的工程师帮助创建了一个新的前端工具,用于读取 OCI 运行时规范 JSON 文件并与 libcontainer 交互以运行容器。这个名为 runc 的工具也被捐赠给了 OCI。虽然 runc 可以读取 OCI JSON 文件,但用户需要自己生成它。runc 此后已成为最流行的低级别容器运行时。几乎所有容器管理工具都支持 runc,包括 CRI-O、Docker、Buildah、Podman 和 Cloud Foundry Garden。从那时起,其他工具也实现了 OCI 运行时规范以执行符合 OCI 标准的容器。

Clear Containers 和 Hyper.sh 的 runV 工具都是为了使用 OCI 运行时规范来执行基于 KVM 的容器而创建的,它们正在一个新的名为 Kata 的项目中整合他们的努力。去年,Oracle 创建了一个名为 RailCar 的 OCI 运行时工具的演示版本,该工具是用 Rust 编写的。自 GitHub 项目上次更新以来已经过去两个月了,因此尚不清楚它是否仍在开发中。几年前,Vincent Batts 致力于添加一个工具 nspawn-oci,该工具解释 OCI 运行时规范文件并启动 systemd-nspawn,但没有人真正接受它,它也不是一个原生实现。

如果有人想实现一个原生的 systemd-nspawn --oci OCI-SPEC.json 并使其被 systemd 团队接受以获得支持,那么 CRI-O、Docker 以及最终的 Podman 将能够除了 runc 和 Clear Container/runV (Kata) 之外还能使用它。(我的团队中没有人正在研究这个。)

底线是,在三四年前,上游开发人员想要编写一个低级别的 golang 工具来启动容器,而这个工具最终变成了 runc。当时的那些开发人员有一个基于 C 的工具来做到这一点,称为 lxc,并放弃了它。我非常确定,在他们决定构建 libcontainer 时,他们对 systemd-nspawn 或任何其他非原生 (golang) 的运行“命名空间”隔离容器的方式不感兴趣。

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

评论已关闭。

Creative Commons License本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.