容器以迅雷不及掩耳之势流行起来。当你想到Kubernetes、Docker、CoreOS、Silverblue、或Flatpak时,听到各种术语的时候,这表明,现代应用正在容器中运行,因为它方便、安全和具有可伸缩性。
在容器里运行是什么意思?在容器中如何处理与电脑其余部分的互动?本文带你一探容器技术的究竟。
命名空间
命名空间在编写程序时很常见。你很可能会看到这样的代码:
using namespace std;
或者在XML中看到:
<book xmlns="http://docbook.org/ns/docbook" xml:lang="en">
这类短语为以后在源代码文件中使用的命令提供上下文。例如,C++知道程序员键入cout意味着什么的唯一原因是因为C++知道cout命名空间是一个有意义的词。
你可能会惊讶地发现,在现实生活中,我们每天都在使用命名空间。可能不称之为命名空间,但我们一直使用这个概念。在随意的对话中,我们并不总是声明一个命名空间,因为我们是人类,我们的大脑可以快速适应来确定上下文,但是对于计算机,命名空间必须显式声明。
对于容器,命名空间定义了进程“感知”其周围运行的其他内容的边界。
lsns
你可能并没有意识到你的Linux机器在静静地维护着特定于给定进程的不同命名空间。通过使用近期版本的util-linux包,你可以列出现有的命名空间:
$ lsns NS TYPE NPROCS PID USER COMMAND4026531835 cgroup 85 1571 seth /usr/lib/systemd/systemd --user4026531836 pid 85 1571 seth /usr/lib/systemd/systemd --user4026531837 user 80 1571 seth /usr/lib/systemd/systemd --user4026532601 user 1 6266 seth /usr/lib64/firefox/firefox [...]4026532928 net 1 7164 seth /usr/lib64/firefox/firefox [...][...]
如果你的util-linux版本没有提供lsns命令,那么可以在/proc中看到命名空间条目:
$ ls /proc/*/ns157162667164[...]$ ls /proc/6266/nsipc 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会话中,通过查看新分叉进程的PID,就可以看到你已经离开了通常的命名空间:
% pidof zshpid 1
如果你知道有关Linux进程ID的任何信息,那么你就知道PID 1始终是为初始化应用程序预留的。Zsh或任何不是启动初始化应用程序的应用程序,几乎不可能是PID 1。然而,在本演示中,Zsh占据了PID 1。
不管你的shell现在告诉你什么,你系统上的PID 1没有被替换。打开计算机上的第二个终端或终端选项卡,查看PID 1:
$ ps 1
init
然后找到Zsh的PID:
$ pidof zsh7723
可以看到,“主机”系统看到了全局,并理解Zsh实际上是作为一个高编号的PID运行的。Zsh将自己视为PID 1,只是因为它的作用域局限于(或包含在)它的命名空间中。一旦你将一个进程分叉到它自己的命名空间中,它的子进程将从1开始编号,但只是在该命名空间内部。
命名空间,连同其他技术(如cgroups等),构成了容器化的基础。理解命名空间存在于主机环境的更广泛命名空间的上下文中(在这个演示中,是你的计算机,但在真实世界中,主机通常是服务器或混合云),可以帮助你理解容器化应用程序如何运行以及为什么以这种方式运行。
例如,一个运行WordPress博客的容器并不“知道”它不是在容器中运行的;它知道它可以访问内核和一些RAM以及你提供的任何配置文件,但它可能无法访问你的主目录或你没有特别授予它访问权限的任何目录。此外,博客软件中的runaway进程不能影响系统中的任何其他进程,因为据它所知,PID“树”只返回到1,而1是它在其中运行的容器。
容器是一个强大的Linux功能,越来越流行。现在你已经了解了它们的工作原理,不妨尝试探索容器技术,如Kubernetes、Silverblue或Flatpak,并了解如何使用容器化应用程序。