容器已经风靡全球。无论您想到 Kubernetes、Docker、CoreOS、Silverblue 还是 Flatpak,都很明显,现代应用程序都在容器中运行,以实现便利性、安全性和可扩展性。
但是,容器可能很难理解。在容器中运行是什么意思?容器中的进程如何与它们运行所在的计算机的其余部分进行交互?开源不喜欢神秘,因此本文解释了容器技术的后端,正如我关于 Flatpak 的文章解释了一个常见的前端。
命名空间
命名空间在编程世界中很常见。如果您沉浸在计算机世界的高度技术性领域,那么您可能已经看过这样的代码
using namespace std;
或者您可能在 XML 中看到过这样的代码
<book xmlns="http://docbook.org/ns/docbook" xml:lang="en">
这些短语为源代码文件中稍后使用的命令提供了上下文。例如,C++ 知道程序员在键入 cout 时是什么意思的唯一原因是,C++ 知道 cout 命名空间是一个有意义的词。
如果这对您来说太技术性而难以想象,您可能会惊讶地发现,我们在日常生活中也每天都在使用命名空间。我们不称它们为命名空间,但我们一直都在使用这个概念。例如,“我是 Enterprise 的粉丝”这句话在服务于大型企业(通常称为“企业”)的 IT 公司中有一种含义,但在科幻小说大会上可能具有不同的含义。“它运行的是什么引擎?”这个问题在车库中有一种含义,在 Web 开发中则有不同的含义。我们并非总是在随意对话中声明命名空间,因为我们是人类,我们的大脑可以快速适应以确定上下文,但对于计算机而言,必须显式声明命名空间。
对于容器,命名空间定义了进程对其周围正在运行的其他进程的“感知”边界。
lsns
您可能没有意识到,但您的 Linux 机器悄悄地维护着特定于给定进程的不同命名空间。通过使用最新版本的 util-linux 软件包,您可以列出机器上现有的命名空间
$ lsns
NS TYPE NPROCS PID USER COMMAND
4026531835 cgroup 85 1571 seth /usr/lib/systemd/systemd --user
4026531836 pid 85 1571 seth /usr/lib/systemd/systemd --user
4026531837 user 80 1571 seth /usr/lib/systemd/systemd --user
4026532601 user 1 6266 seth /usr/lib64/firefox/firefox [...]
4026532928 net 1 7164 seth /usr/lib64/firefox/firefox [...]
[...]
如果您的 util-linux 版本未提供 lsns 命令,您可以在 /proc 中查看命名空间条目
$ ls /proc/*/ns
1571
6266
7164
[...]
$ ls /proc/6266/ns
ipc net pid user uts [...]
在您的 Linux 机器上运行的每个进程都使用进程 ID (PID) 进行枚举。每个 PID 都分配有一个命名空间。同一命名空间中的 PID 可以相互访问,因为它们被编程为在给定的命名空间内运行。默认情况下,不同命名空间中的 PID 无法相互交互,因为它们在不同的上下文或命名空间中运行。这就是为什么在一个命名空间下的“容器”中运行的进程无法访问其容器外部的信息或在不同容器内部运行的信息的原因。
创建新的命名空间
处理容器的软件的常用功能是自动命名空间管理。启动新的容器化应用程序或环境的人工管理员不必使用 lsns 来检查存在哪些命名空间,然后手动创建一个新的命名空间;使用 PID 命名空间的软件会在 Linux 内核的帮助下自动执行此操作。但是,您可以手动模拟该过程,以便更好地了解幕后发生的事情。
首先,您需要确定一个未在您的计算机上运行的进程。对于此示例,我将使用 Z shell (Zsh),因为我在我的机器上运行 Bash shell。如果您在计算机上运行 Zsh,则使用 Bash 或 tcsh 或您当前未运行的其他 shell。目标是找到您可以证明未运行的东西。您可以使用 pidof 命令证明某项内容未运行,该命令查询您的系统以发现您命名的任何应用程序的 PID
$ pidof zsh
$ sudo pidof zsh
只要未返回 PID,您查询的应用程序就不会运行。
Unshare
unshare 命令在与其父进程取消共享的命名空间中运行程序。有许多类型的命名空间可用,因此请阅读 unshare 手册页以了解所有可用选项。
要为您的测试命令创建新的命名空间
$ sudo unshare --fork --pid --mount-proc zsh
%
由于 Zsh 是一个交互式 shell,因此它会在启动时方便地将您带入其命名空间。并非所有进程都这样做,因为某些进程在后台运行,让您在其本机命名空间中的提示符下。 只要您保持在 Zsh 会话中,您就可以通过查看新的 fork 进程的 PID 来看到您已离开常用命名空间
% pidof zsh
pid 1
如果您了解 Linux 进程 ID,那么您就会知道 PID 1 始终是保留的,主要是由于启动过程的性质,用于初始化应用程序(Slackware、Devuan 之外的大多数发行版上的 systemd,以及可能是一些 Arch 的自定义安装)。对于 Zsh 或任何不是启动初始化应用程序的应用程序来说,成为 PID 1 几乎是不可能的(因为没有 init 系统,计算机将不知道如何启动)。然而,就您的 shell 在此演示中所知,Zsh 占据了 PID 1 插槽。
尽管您的 shell 现在告诉您什么,但您系统上的 PID 1 尚未被替换。在计算机上打开第二个终端或终端选项卡,并查看 PID 1
$ ps 1
init
然后找到 Zsh 的 PID
$ pidof zsh
7723
如您所见,您的“主机”系统看到了全局,并了解到 Zsh 实际上是以某个高编号 PID 运行的(除非巧合,否则它可能不是您计算机上的 7723)。Zsh 将自身视为 PID 1 仅仅是因为其范围仅限于(或包含在)其命名空间内。一旦您将进程 fork 到其自己的命名空间中,其子进程将从 1 开始编号,但仅在该命名空间内。
命名空间以及 cgroups 等其他技术构成了容器化的基础。了解命名空间存在于主机环境的更广泛命名空间的上下文中(在本演示中,它是您的计算机,但在现实世界中,主机通常是服务器或混合云)可以帮助您了解容器化应用程序的行为方式和原因。例如,运行 Wordpress 博客的容器“不”知道它不是在容器中运行;它知道它可以访问内核和一些 RAM 以及您提供的任何配置文件,但它可能无法访问您的主目录或您未明确授予其访问权限的任何目录。此外,该博客软件中的失控进程不会影响您系统上的任何其他进程,因为就它所知,PID“树”仅回溯到 1,而 1 是它正在运行的容器。
容器是一项强大的 Linux 功能,并且每天都越来越受欢迎。现在您已经了解了它们的工作原理,请尝试探索 Kubernetes、Silverblue 或 Flatpak 等容器技术,看看您可以使用容器化应用程序做什么。容器是 Linux,所以启动它们,仔细检查它们,并在实践中学习。
6 条评论