systemd-nspawn 中文手册

译者:金步国


版权声明

本文译者是一位开源理念的坚定支持者,所以本文虽然不是软件,但是遵照开源的精神发布。

其他作品

本文译者十分愿意与他人分享劳动成果,如果你对我的其他翻译作品或者技术文章有兴趣,可以在如下位置查看现有的作品集:

联系方式

由于译者水平有限,因此不能保证译文内容准确无误。如果你发现了译文中的错误(哪怕是错别字也好),请来信指出,任何提高译文质量的建议我都将虚心接纳。


手册索引 · 指令索引systemd-241

名称

systemd-nspawn — 在轻量级容器中运行命令或操作系统

大纲

systemd-nspawn [OPTIONS...] [COMMAND [ARGS...] ]

systemd-nspawn --boot [OPTIONS...] [ARGS...]

描述

systemd-nspawn 可用于在一个轻量级的名字空间容器中运行一条命令、甚至一个操作系统。 它与 chroot(1) 很相似, 但是功能更为强大:它能够完全的虚拟化文件系统层次结构、进程树、各种进程间通信(IPC)子系统、 主机名与域名。

systemd-nspawn 能够 通过 --directory= 选项运行在任何包含操作系统的目录树上。通过使用 --machine= 选项, 可以自动在特定的位置(特别是保存容器镜像的默认目录 /var/lib/machines) 搜索操作系统目录树。

chroot(1) 相比, systemd-nspawn 的强大之处还体现在它能够在容器内启动一个完整的Linux操作系统。

systemd-nspawn 会对容器内部的进程作如下限制:(1)仅允许以只读方式访问例如 /sys, /proc/sys, /sys/fs/selinux 这样的内核接口。 (2)禁止修改主机的网络接口以及系统时钟。(3)禁止创建设备节点。 (4)禁止重启主机操作系统。 (5)禁止加载内核模块。

可以使用例如 dnf(8), debootstrap(8), pacman(8) 这样的工具为 systemd-nspawn 容器安装一个操作系统目录树。 详见后文的"例子"小节以了解更多有关如何使用这些命令的示例。

出于安全考虑, systemd-nspawn 会在启动容器之前,首先检查容器中是否存在 /usr/lib/os-release/etc/os-release 文件(参见 os-release(5))。 因此对于那些不包含 os-release 文件的容器镜像来说, 有必要手动添加 os-release 文件。

systemd-nspawn 既可以在交互式命令行上直接调用,也可以作为系统服务在后台运行。 当作为后台服务运行时,每一个容器实例就是一个以容器自己的名字命名的服务实例。 为了简化容器实例的创建,提供了一个默认的 systemd-nspawn@.service 模版单元, 它以容器的名字作为单元实例的名字。注意,当 systemd-nspawn 被模版单元调用时, 命令行选项的默认值与从交互式命令行调用时的默认值是不一样的。最重要的差别在于:模版单元会默认使用 --boot 选项, 而交互式命令行默认并不使用它。 其他的差别在后文对每一个命令行选项的详细解释中会有所说明。

可以使用 machinectl(1) 工具来管理容器。 它提供了许多方便的命令来基于 systemd-nspawn@.service 模版 以系统服务的方式运行容器。

对于每一个容器,都可以存在一个同名的 .nspawn 配置文件, 用于针对个别容器做特别的设置(详见 systemd.nspawn(5) 手册)。 配置文件中的设置可以覆盖 systemd-nspawn@.service 模版的默认设置, 从而可以避免直接对模版进行修改。

注意, systemd-nspawn 会挂载专属于容器内部私有的例如 /dev, /run 之类的虚拟文件系统。 它们在容器之外不可见,并且随着容器的退出而自动消亡。

注意,即使在同一个目录上运行两个 systemd-nspawn 容器,这两个容器内的进程也是互相不可见的。 两个容器的 PID 名字空间是完全互相隔离的,除了共享相同的底层文件系统之外, 两个容器之间几乎不再共享其他对象(除了几个极个别的运行时对象)。 machinectl(1)loginshell 命令可以 在一个正在运行的容器中打开一个额外的登录会话。

systemd-nspawn 遵守 Container Interface 规范。

systemd-nspawn 启动的容器将会被注册到 systemd-machined.service(8) 服务中(如果它正在运行的话),并且被该服务持续跟踪,此外,该服务还提供了与这些容器交互的编程接口。

选项

如果使用了 --boot 选项,那么命令行参数(ARGS)将被原封不动的传递给 init 程序。 否则,将创建一个容器并在其中启动 COMMAND 程序, 同时,命令行参数(ARGS)将被原封不动的传递给此程序。 如果既没有使用 --boot 选项, 也没有设置命令行参数(ARGS), 那么将创建一个容器并在其中启动一个 shell 。

可以使用的命令行选项(OPTIONS)如下:

-D, --directory=

容器的 根文件系统目录。

如果 --directory=--image= 都没有设置, 那么将会搜索与容器名称(也就是 --machine= 选项的值)同名的目录, 并将其用作容器的根目录。参见 machinectl(1) 手册的"文件与目录"小节,以了解具体的搜索路径。

如果 --directory=, --image=, --machine= 都没有设置,那么将会把当前目录用作容器的根目录。 此选项不可与 --image= 同时使用。

--template=

容器根目录的模版。必须是一个目录(或 "btrfs" 子卷)。 如果指定了此选项并且容器的根目录(--directory=)尚不存在, 那么将会首先创建一个空的根目录(或 "btrfs" 快照), 然后将模版目录中的内容原封不动的复制一份填充进去。 如果模版是一个 "btrfs" 子卷的根, 那么只需创建一个COW(copy-on-write)快照,即可瞬间完成容器根目录的填充。 如果模版不是一个 "btrfs" 子卷的根(甚至不是 "btrfs" 文件系统), 那么必须使用低效的复制操作(如果文件系统支持的话,也可能实际上是通过 copy-on-write 操作), 这样就有可能需要花费大量的时间才能完成容器根目录的填充。 此选项不可与 --image=--ephemeral 同时使用。

注意, 模版中 "hostname", "machine ID" 之类的系统标识设置, 将会被原封不动的复制到容器根目录去。

-x, --ephemeral

以无痕模式运行容器。使用此选项之后,容器将会运行在文件系统的临时快照之上, 当容器结束运行时,这个临时快照将会被立即删除,因此不会留下任何痕迹。 此选项不可与 --template= 同时使用。

注意,使用此选项启动的容器, 原有的 "hostname", "machine ID" 之类的系统标识设置, 将会原封不动的保持不变。

-i, --image=

容器的镜像。 参数必须是一个普通文件或者块设备节点。 指定的镜像(普通文件或块设备)将被挂载为容器的根文件系统。 用作镜像的文件或块设备必须满足如下条件:

  • 仅包含一个 MBR 分区表, 其中仅含有一个 0x83 类型的分区(Linux), 并被标记为引导分区(bootable)。

  • 仅包含一个 GPT 分区表, 其中仅含有一个 0fc63daf-8483-4772-8e79-3d69d8477de4 类型的分区(Linux数据)。

  • 仅包含一个 GPT 分区表, 其中仅含有一个根分区(将被挂载为容器的根文件系统)。 可选的,还可以含有一个HOME分区(将被挂载到 /home 目录)、 一个服务器数据分区(将被挂载到 /srv 目录)。 所有这些分区的类型标记(GUID)必须遵守 Discoverable Partitions Specification 规范。

  • 不包含任何分区表,整个文件或块设备本身就是一个单纯的文件系统(直接挂载为容器的根文件系统)。

对于GPT镜像,如果存在ESP(EFI System Partition)分区,并且根文件系统上的 /efi 目录存在且为空, 那么ESP分区将会被自动挂载到 /efi 目录。若 /efi 目录不存在或不为空, 并且根文件系统上的 /boot 目录存在且为空,那么ESP分区将会被自动挂载到 /boot 目录。

LUKS加密分区将会被自动解密。如果使用 --root-hash= 给出了GPT镜像内受 dm-verity(数据完整性校验技术)保护的分区的"root hash", 那么将在挂载该分区之前自动校验分区数据的正确性。

其他无关的分区(例如交换分区)将不会被挂载。 此选项不可与 --directory=--template= 同时使用。

--root-hash=

用于校验分区数据完整性(dm-verity)的"root hash"(十六进制字符串)。 如果容器镜像中包含基于 dm-verity 技术的分区数据完整性元数据,此选项的值将用于校验分区数据的正确性。 例如在使用 SHA256 算法的情况下,"root hash"一般是一个256位的二进制数(表现为一个64字符的十六进制字符串)。 如果没有使用此选项,但是容器的镜像文件含有 "user.verity.roothash" 扩展属性(参见 xattr(7)), 那么将从此扩展属性中提取分区的"root hash"(十六进制字符串),并将其用于校验分区数据的正确性。 如果容器的镜像文件不含此扩展属性,但是在镜像文件的所在目录中存在文件名相同且后缀名为 .roothash 的文件, 那么将会从此文件中读取分区的"root hash"(十六进制字符串), 并将其用于校验分区数据的正确性。

-a, --as-pid2

以 PID=2 运行指定的程序。 如果既没有使用此选项,也没有使用 --boot 选项,那么将以 PID=1 运行指定的程序(一般是 init 或 shell)。 注意,在UNIX系统上,PID=1 的进程(init)必须满足一些特殊的要求, 例如,它必须能够收集所有孤儿进程,还必须要实现与 sysvinit 兼容的信号处理器(特别是在收到 SIGINT 信号时重启、 收到 SIGTERM 信号时重新执行、收到 SIGHUP 信号时重新加载配置、等等)。使用了 --as-pid2 选项之后, 将会以 PID=1 运行一个极度简化的 init 进程,同时以 PID=2 运行指定的程序(不需要满足 init 进程的特殊要求)。 这个极度简化的 init 进程,仅能够满足UNIX系统对 init 进程的最低要求(收集孤儿进程以及处理 sysvinit 信号)。 强烈建议使用此选项在容器中运行绝大多数普通程序。 换句话说,除非运行的是能够满足UNIX系统 PID=1 进程的特殊要求的 init 或者 shell 程序, 否则应该明确使用此选项。 此选项不可与 --boot 同时使用。

-b, --boot

自动搜索 init 程序并以 PID=1 运行它(而不是 shell 或用户指定的程序)。 使用此选项之后,命令行上的参数(ARGS)将会被原封不动的传递给 init 程序。 此选项不可与 --as-pid2 同时使用。

下面的表格解释了 不同调用模式之间的差异:

表 1. 调用模式

选项解释
--as-pid2--boot 都没有使用在容器中以 PID=1 运行 COMMAND 进程,并将 ARGS 原封不动的作为命令行参数传递给 COMMAND 进程。
仅使用了 --as-pid2 首先在容器中以 PID=1 运行一个极度简化的 init 进程,然后以 PID=2 运行 COMMAND 进程,并将 ARGS 原封不动的作为命令行参数传递给 COMMAND 进程。
仅使用了 --boot 自动搜索 init 程序并在容器中以 PID=1 运行它,同时将 ARGS 原封不动的作为命令行参数传递给此 init 程序。

注意,在使用 systemd-nspawn@.service 模版的情况下, 将默认以 --boot 模式运行 systemd-nspawn

--chdir=

在容器内启动进程之前,首先将工作目录切换到此选项指定的目录。 必须设为一个以容器内的文件系统名字空间为基准的绝对路径。

--pivot-root=

在容器内将根目录切换为此选项指定的目录,同时将容器内原来的根目录卸载或挂载到其他目录。 如果将此选项设为一个单独的路径,那么表示将容器的根目录切换为此选项指定的目录,同时卸载原来的根目录。 如果将此选项设为冒号分隔的两个路径("新目录:旧目录"), 那么表示将容器的根目录切换为"新目录", 同时将容器内原来的根目录挂载到"旧目录"。 注意,所有的路径都必须是以容器内的文件系统名字空间为基准的绝对路径。

此选项仅可用于包含多个不同启动目录的容器(例如包含多个 OSTree 部署的容器)。 此选项模拟了 initrd(initial RAM disk) 的功能:选取特定的目录作为根目录, 并在完成根目录的切换之后,再启动 PID=1 的 init 进程。

-u, --user=

进入容器之后, 将进程的用户身份切换为此选项指定的用户(必须是在容器内确实存在的用户)。 注意,此选项仅能用于预防某些粗心大意的操作可能造成的破坏, 它并非是一个特别坚固的安全特性, 可能无法抵挡某些精心设计的破坏。

-M, --machine=

设置容器的名称。 此名称可以用于 在运行时引用该容器(例如 machinectl(1) 之类的工具)。 此外,该名称还被用作容器的初始主机名(hostname)(容器启动之后可以修改)。 如果未指定此选项,那么将使用容器根目录路径的末尾部分, 同时, 如果使用了 --ephemeral 模式的话, 还可能会再加上一个随机字符串后缀。 如果容器的根目录就是主机的根目录, 那么容器的初始主机名将使用主机的主机名。

--hostname=

设置容器的初始主机名, 默认使用 --machine= 的值。 容器名(--machine=)用于从外部标识容器, 而主机名(--hostname=)则用于从内部标识容器自身。 为了避免不必要的混淆,明智的做法是将两者始终保持一致。 所以应该尽量避免使用此选项,仅使用 --machine= 即可。 注意,无论容器的初始主机名是 --hostname= 还是 --machine= , 容器内的进程都可以在运行中对主机名进行修改。

--uuid=

设置容器的UUID("machine ID")。 容器的初始化系统将会使用此处的设置填充 /etc/machine-id 文件(如果确实不存在)。 注意,仅在容器中的 /etc/machine-id 确实不存在的情况下, 此选项才有意义。

-S, --slice=

将此容器添加到指定的 slice 单元中,而不是默认的 machine.slice 单元。 仅当此容器运行在自己的 scope 单元内时(也就是未使用 --keep-unit 选项), 此选项才有意义。

--property=

为容器所属的 scope 单元设置一个单元属性 仅当此容器运行在自己的 scope 单元内时(也就是未使用 --keep-unit 选项),此选项才有意义。 属性的赋值语法与 systemctl set-property 命令完全相同。 此选项主要用于修改容器的资源控制(例如内存限制)。

--private-users=

控制容器的用户名字空间。启用之后,容器将拥有自己私有的用户与组(UID与GID), 容器内的私有 UID/GID(从 root 的 UID=0,GID=0 开始向上递增) 将会被映射到宿主系统上一段未使用的 UID/GID 范围(通常是高于 65536 的范围)。 此选项可以接受如下几种设置:

  1. 设为一个或两个正整数(冒号分隔),表示开启用户名字空间。 第一个数字表示宿主系统上分配给容器的 UID/GID 起点,第二个数字表示宿主系统上分配给容器的 UID/GID 数量。 如果省略第二个数字,那么表示分配 65536 个。

  2. 使用了此选项但是未设置选项值,或者设为布尔值 yes ,表示开启用户名字空间。 在这种情况下,为容器分配的 UID/GID 数量是固定的 65536 个, 而 UID/GID 范围的起点则是容器根目录自身的 UID/GID 。 要使用此设置,必须确保:(1)在启动容器之前先准备好完整的容器目录树内容; (2)目录树中的所有文件与目录的 UID/GID 都没有超出允许的范围; (3)所有 ACL 中涉及的 UID/GID 都没有超出允许的范围; (4)容器根目录的 UID/GID 必须是 65536 的整数倍。

  3. 未使用此选项,或者设为布尔值 no ,表示彻底关闭用户名字空间。

  4. 设为特殊值 "pick" ,表示开启用户名字空间。 在这种情况下,为容器分配的 UID/GID 数量是固定的 65536 个, 而 UID/GID 范围的起点则按如下规则确定: 如果容器根目录自身的 UID/GID 既未被宿主系统使用也未被其他容器使用, 那么 UID/GID 范围的起点就是容器根目录自身的 UID/GID (这与上文的"yes"类似), 否则(也就是已被宿主或其他容器占用), 将会自动在宿主系统 UID/GID 的 524288-1878982656 范围内随机选择一个未被占用的值作为起点(必须是 65536 的整数倍)。 此设置隐含的设置了下文的 --private-users-chown 选项, 从而可以确保容器中的文件和目录的 UID/GID 不会超出自动选择的范围。 此设置让用户名字空间的行为变得完全自动化。 在这种情况下,第一次启动一个先前从未使用过的容器镜像,可能会导致为该容器重新分配 UID/GID 范围, 从而导致非常耗时的调整 UID/GID 的操作。 不过以后再次启动此容器就很轻松了(除非又遇到需要重新分配 UID/GID 范围的情况)。

建议为每个容器都分配 65536 个 UID/GID ,以完整覆盖常用的16位 UID/GID 范围。 出于安全考虑,切不可为不同的容器分配重叠的 UID/GID 范围。 应该将32位 UID/GID 的高16位用作容器标识符、低16位用作容器内的用户标识符。 事实上, --private-users=pick 就隐含了这个规则。

当开启了用户名字空间之后,分配给每个容器的 GID 范围将始终保持与 UID 范围完全相同。

在绝大多数场合, --private-users=pick 都是首选的设置。 因为它增强了容器的安全性并且在绝大多数场合都能自动完成必要的操作。

注意,被选中的 UID/GID 范围并不会被记录在 /etc/passwd/etc/group 文件中(事实上根本不会记录在任何地方)。 这个范围仅在启动容器的当时,根据容器根目录的 UID/GID 进行推算。

注意,当启用了用户名字空间之后, 文件系统将会遵循容器与宿主之间的 UID/GID 映射规则。 这就意味着在容器与宿主之间来回复制文件的时候, 需要根据映射规则自动修改文件的 UID/GID 。

--private-users-chown

调整容器内所有文件与目录的 UID/GID 与 ACL 以确保遵循容器与宿主之间的 UID/GID 映射规则(见上文)。 注意, 使用此选项有可能会导致巨大的性能损失。

--private-users=pick 隐含的设置了此选项。 如果关闭了用户名字空间,那么此选项将被忽略(不起任何作用)。

-U

如果内核支持用户名字空间,那么此选项等价于 --private-users=pick --private-users-chown ,否则等价于 --private-users=no

注意,在使用 systemd-nspawn@.service 模版的情况下, -U 是默认设置。

注意,如果想要在文件系统上撤销 --private-users-chown (或 -U) 造成的影响,可以通过将容器的 UID/GIU 起点重置为"0"来实现:

systemd-nspawn … --private-users=0 --private-users-chown
--private-network

将容器的网络从宿主的网络断开。 这样,在容器中,除了 loopback 设备、 --network-interface= 指定的设备、 --network-veth 配置的设备, 其他所有网络接口都将变为不可见。 使用此选项之后, CAP_NET_ADMIN capability 将被添加到容器现有的 capabilities 集合中。 当然,你也可以明确的使用 --drop-capability= 去掉它。 如果没有明确设置此选项(但可能被其他选项隐含的设置了), 那么该容器将能够完全访问宿主机的全部网络。

--network-namespace-path=

接受一个内核网络名字空间的文件路径, 表示将该容器运行在指定的网络名字空间中。 路径应该指向一个 (可能是绑定挂载的)网络名字空间文件(位于 /proc/$PID/ns/net)。 一个典型的用法是指向 /run/netns/ 目录下一个由 ip-netns(8) 创建的网络名字空间文件(例如 --network-namespace-path=/run/netns/foo)。 注意,此选项不能与其他网络选项(例如 --private-network--network-interface=)一起使用。

--network-interface=

为容器分配指定的网络接口。 这将会从宿主系统删除指定的网络接口, 并将其转移到容器中。 当容器终止之后,此接口将会被交还给宿主系统。 注意, --network-interface= 隐含的设置了 --private-network 选项。 可以多次使用此选项 以分配多个网络接口。

--network-macvlan=

为指定的以太网接口创建一个 "macvlan" 接口, 并将其添加到容器中。 所谓 "macvlan" 接口, 是指在一个现有的物理以太网接口上添加一个新的MAC地址而创建的虚拟接口。 添加到容器内的这个 "macvlan" 接口, 其名称将由宿主系统内对应的以太网接口名称再加上 "mv-" 前缀组成。 注意,--network-macvlan= 隐含的设置了 --private-network 选项。 可以多次使用此选项以添加多个 "macvlan" 接口。

--network-ipvlan=

为指定的以太网接口创建一个 "ipvlan" 接口, 并将其添加到容器中。 所谓 "ipvlan" 接口, 是指在一个现有的物理以太网接口上添加一个新的IP地址而创建的虚拟接口。 添加到容器内的这个 "ipvlan" 接口, 其名称将由宿主系统内对应的以太网接口名称再加上 "iv-" 前缀组成。 注意,--network-ipvlan= 隐含的设置了 --private-network 选项。 可以多次使用此选项以添加多个 "ipvlan" 接口。

-n, --network-veth

在容器与宿主之间创建一个虚拟以太网连接("veth")。 宿主端看到的以太网连接的名称就是容器的名称(也就是 --machine= 的值)再加上 "ve-" 前缀。 容器端看到的以太网连接的名称则是 "host0" 。注意, --network-veth 隐含的设置了 --private-network 选项。

注意, systemd-networkd.service(8) 默认包含 /usr/lib/systemd/network/80-container-ve.network , 此文件匹配所有通过该选项创建的虚拟以太网连接的宿主端接口, 此文件不但为这些接口启用了 DHCP 功能,而且还为这些接口设置了通向宿主机外部网络的路由(从而可以连通外网)。 该服务还默认包含 /usr/lib/systemd/network/80-container-host0.network , 此文件匹配所有通过该选项创建的虚拟以太网连接的容器端接口,并且为这些接口启用了 DHCP 功能。 如果在宿主与容器内同时运行了 systemd-networkd 服务, 那么无须额外的配置,即可自动实现在容器与宿主之间进行 IP 通信, 并且可以连接到外部网络。

注意, --network-veth 是使用 systemd-nspawn@.service 模版时的默认选项。

--network-veth-extra=

在容器与宿主之间添加一个额外的虚拟以太网连接。 接受一对冒号分隔的网络接口名称("宿主网络接口名称:容器网络接口名称")。 如果省略了后一个名称, 那么表示使用同一个名称来命名宿主和容器的网络接口。 此选项与 --network-veth 没有任何关系。 此选项不但可以多次使用,而且还可以明确的指定网络连接的名称。 注意, --network-bridge= 对于 --network-veth-extra= 创建的网络连接没有任何作用。

--network-bridge=

--network-veth 创建的虚拟以太网连接("veth")的宿主端添加到此选项指定的以太网桥上。 接受一个有效的网桥设备的网络接口名称。 注意,--network-bridge= 隐含的设置了 --network-veth 选项。 如果使用了此选项,那么太网连接的宿主端名称将使用 "vb-" 前缀(而不是 "ve-" 前缀)。

--network-zone=

在容器中创建一个虚拟以太网连接("veth"),并将其添加到一个自动管理的以太网桥上。 以太网桥的接口名称就是此选项的值再加上 "vz-" 前缀。 此网桥接口将在当第一个配置了此名称的容器启动时自动启动, 并在最后一个配置了此名称的容器退出时自动移除。 因此,使用此选项配置的网桥接口,只会在至少有一个引用了此网桥的容器处于运行状态时才会存在。 此选项与 --network-bridge= 很相似, 不同之处在于此选项所配置的网桥会被自动创建与删除(无须人为干预)。

使用此选项,可以方便的将一组相关的本地容器,添加到基于虚拟以太网的同一个广播域(也就是同一子网)之中。 这样的广播域就被称为"区域"(zone)。每一个容器都只能是某个 zone 的一部分,而每个 zone 则可以包含多个容器。 zone 的名称(也就是 --network-zone= 的值)可以自由选择, 但是必须确保加上 "vz-" 前缀之后,可以构成一个合法的网络接口名称。 使用了相同 --network-zone= 值的多个运行中的容器, 将会自动加入到同一个 zone 当中。

注意, systemd-networkd.service(8) 默认包含 /usr/lib/systemd/network/80-container-vz.network , 此文件匹配所有通过该选项创建的网桥接口,此文件不但为这些接口启用了 DHCP 功能, 而且还为这些接口设置了通向宿主机外部网络的路由(从而可以连通外网)。 因此,在绝大多数情况下,通过使用 --network-zone= 选项,无须额外的配置, 即可自动实现将多个本地容器加入同一个广播域(也就是同一子网)、并与宿主之间互相连通, 而且可以通过宿主机连接到外部网络。

-p, --port=

如果为容器开启了私有网络, 那么可以使用此选项在宿主的一个IP端口与容器的一个IP端口之间建立映射。 选项值必须满足"[协议:]宿主端口[:容器端口]"格式。 其中的"协议"必须是 "tcp" 或 "udp" 、 "宿主端口"与"容器端口"都必须是一个 1-65535 之间的某个端口号。 如果省略"协议:"部分, 那么等价于设为"tcp:"; 如果省略":容器端口"部分, 那么等价于使用与"宿主端口"相同的端口号。 注意,仅在容器确实使用了私有网络的情况下,才可以使用此选项。 也就是说仅在确实使用了例如 --network-veth, --network-zone=, --network-bridge= 这些选项的时候,才可以使用此选项。

-Z, --selinux-context=

用于容器内进程的 SELinux 安全上下文标签。

-L, --selinux-apifs-context=

用于容器内 虚拟内核文件系统中的文件的 SELinux 安全上下文标签。

--capability=

给容器额外赋予指定的 capabilities 。 接受一个逗号分隔的 capabilities(7) 列表。容器默认拥有的 capabilities 集合包括: CAP_AUDIT_CONTROL, CAP_AUDIT_WRITE, CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_FOWNER, CAP_FSETID, CAP_IPC_OWNER, CAP_KILL, CAP_LEASE, CAP_LINUX_IMMUTABLE, CAP_MKNOD, CAP_NET_BIND_SERVICE, CAP_NET_BROADCAST, CAP_NET_RAW, CAP_SETFCAP, CAP_SETGID, CAP_SETPCAP, CAP_SETUID, CAP_SYS_ADMIN, CAP_SYS_BOOT, CAP_SYS_CHROOT, CAP_SYS_NICE, CAP_SYS_PTRACE, CAP_SYS_RESOURCE, CAP_SYS_TTY_CONFIG 。 此外,如果使用了 --private-network 选项,那么将会自动包含 CAP_NET_ADMIN 。 特殊值 "all" 表示赋予全部的 capabilities 。

--drop-capability=

从容器中 删除指定的 capabilities 。 这将导致容器以少于默认 capabilities 集合(见上文)的方式 运行。

--no-new-privileges=

接受一个布尔值。为容器中的进程设置 PR_SET_NO_NEW_PRIVS 标记的值。 默认值为 no 。设为 yes 表示禁止容器中的进程获取新特权, 也就是,文件的 "setuid" 位以及文件系统 capabilities 将会失效。 详见 prctl(2) 手册。

--system-call-filter=

设置容器的系统调用过滤器。接受一个空格分隔的列表, 列表中的每一项都是一个系统调用名称或系统调用组名称(组名称带有 "@" 前缀,可以使用 systemd-analyze(1)syscall-filter 命令列出所有系统调用组名称)。 如果列表前面带有 "~" 前缀,那么表示黑名单列表(禁止容器使用列表中的系统调用), 否则表示白名单列表(允许容器使用列表中的系统调用)。 如果多次使用此选项,那么表示将多个列表组合在一起。 如果某个系统调用既在白名单中又在黑名单中,那么以黑名单为准(也就是黑名单的优先级更高)。 注意,systemd-nspawn 始终隐含着一个默认的系统调用白名单, 此选项仅仅是在这个隐含的默认白名单基础上利用白名单添加或利用黑名单删除某些系统调用。 注意,容器的系统调用过滤器还隐含的受到 --capabilities= 命令行选项的影响。

--rlimit=

为容器设置特定的 POSIX 资源限制。 可以接受两种格式: (1) "LIMIT=SOFT:HARD" (2) "LIMIT=VALUE" 。其中, LIMIT 是一个资源限制类型(例如 RLIMIT_NOFILERLIMIT_NICE)、 SOFTHARD 分别是软限制与硬限制的数值。 如果使用(2)格式,那么 VALUE 将同时表示软限制与硬限制的数值。 特殊值 "infinity" 表示取消该资源类型的限制。 可以多次使用此选项,从而对多种类型的资源进行限制。 如果多次对同一种资源类型设置限制,那么以最后一个设置为准。 有关资源限制的更多详情,参见 setrlimit(2) 手册。 默认情况下,容器中 init 进程(PID=1) 的资源限制将被设置为与 Linux 内核最初传递给宿主 init 进程的值相同。 注意,针对单个用户设置的资源限制(特别是 RLIMIT_NPROC),仍然会作用于该用户的容器。这就意味着,除非使用 --private-users= 开启了用户名字空间, 否则,针对同一用户设置的任何资源限制,都将作用于该用户的所有本地容器与宿主进程。 因此,必须特别注意这种限制,因为它们可能由不可信任的代码设置。例如: "--rlimit=RLIMIT_NOFILE=8192:16384"

--oom-score-adjust=

设置该容器的 OOM ("Out Of Memory") 计分调整值(也就是 /proc/self/oom_score_adj 的值)。 该值会影响因为内存不足而需要杀死进程时,杀死该容器的优先级。详见 proc(5) 手册。 取值范围是 -1000…1000 之间的一个整数(数值越大越容易被优先杀死)。

--cpu-affinity=

设置该容器的CPU关联性。 接受一个逗号分隔的CPU编号与CPU范围("编号下限-编号上限")的列表。详见 sched_setaffinity(2) 手册。

--kill-signal=

systemd-nspawn 自身收到 SIGTERM 信号时,要给容器内的 init 进程(PID=1)发送什么信号(以触发容器的有序关闭)。 如果使用了 --boot 选项, 那么默认为 SIGRTMIN+3 信号(对于与 systemd 兼容的 init 来说,表示正常有序的关闭)。 如果没有使用 --boot 选项,那么默认为 SIGKILL 信号(粗暴的强制关闭)。 更多有关可用信号的解释,可参见 signal(7) 手册。

通过软连接或绑定挂载控制容器内的日志对宿主系统的可见性。 若开启,则表示允许从宿主系统查看容器内的日志文件。 注意,宿主系统的日志在容器内是永远不可见的。 可将此选项设为 "no", "host", "try-host", "guest", "try-guest", "auto" 之一。 "no" 表示不作任何连接(不可见)。 "host" 表示将容器的日志文件直接存储在宿主系统的 /var/log/journal/machine-id 目录中, 也就是,将宿主系统上对应的日志目录绑定挂载到容器内对应的日志目录上。 "guest" 表示将容器的日志文件直接存储在容器自身的 /var/log/journal/machine-id 目录中, 同时,在宿主系统的相同路径上创建一个软连接,并将其指向容器内对应的日志目录。 "try-host"/"try-guest" 与 "host"/"guest" 类似, 不同之处在于:当宿主系统没有启用持久日志时(/var/log/journal 不存在或不可写),不会导致容器启动失败。 默认值 "auto" 表示如果宿主系统的 /var/log/journal 目录下与该容器对应的日志目录确实存在, 那么就将其绑定挂载到容器内对应的日志目录上, 否则不作任何连接(相当于设为 "no")。 实际上,只要成功使用 "guest" 或 "host" 启动过一次容器, 并且后来又使用 "auto" 再次启动该容器, 那么就会在宿主系统的日志目录中创建该容器专用的持久日志目录。

注意,在使用 systemd-nspawn@.service 模版的情况下, --link-journal=try-guest 是默认设置。

-j

等价于 --link-journal=try-guest

--resolv-conf=

如何处理容器内的 /etc/resolv.conf 文件(也就是如何处理宿主系统与容器之间的DNS同步)。 可设为 "off", "copy-host", "copy-static", "bind-host", "bind-static", "delete", "auto" 之一。 "off" 表示保持容器内的 /etc/resolv.conf 文件原封不动, 就好像此文件是容器镜像内的固有文件一样(既不修改它、也不使用绑定挂载覆盖它)。 "copy-host" 表示把宿主系统的 /etc/resolv.conf 直接复制到容器中。 "bind-host" 表示把宿主系统的 /etc/resolv.conf 绑定挂载到容器中。 "copy-static"/"bind-static" 表示把 systemd-resolved.service(8) 提供的静态 resolv.conf 文件直接复制/绑定挂载到容器中。 "delete" 表示删除容器内的 /etc/resolv.conf 文件(若存在)。 "auto" 表示:(1)如果开启了私有网络(--private-network),那么设为 "off" ; (2)否则如果 systemd-resolved.service 可用,那么设为 "copy-static"(可写镜像) 或 "bind-static"(只读镜像); (3)否则设为 "copy-host"(可写镜像) 或 "copy-static"(只读镜像)。 如果容器有可能自己修改 /etc/resolv.conf 文件,那么应该使用 "copy-*" 以确保与宿主系统分离(从而允许在容器内修改),否则应该使用 "bind-*" 以确保无法在容器内修改 /etc/resolv.conf 文件(因为使用了只读绑定挂载)(不过如果容器具有足够的特权, 有可能卸载掉已绑定的挂载)。注意,无论绑定挂载还是复制, 在完成一次性的早期初始化之后, 通常不会再进一步传播配置(因为该文件通常已经被复制与重命名)。 默认值为 "auto"

--timezone=

如何处理容器内的 /etc/localtime 文件(也就是如何处理宿主系统与容器之间的本地时区同步)。 可设为 "off", "copy", "bind", "symlink", "delete", "auto" 之一。 "off" 表示保持容器内的 /etc/localtime 文件原封不动, 就好像此文件是容器镜像内的固有文件一样(既不修改它、也不使用绑定挂载覆盖它)。 "copy" 表示把宿主系统的 /etc/localtime 直接复制到容器中。 "bind" 表示把宿主系统的 /etc/localtime 绑定挂载到容器中。 "symlink" 表示在容器内创建 /etc/localtime 软连接, 并将其指向容器内对应于宿主时区的时区文件。 "delete" 表示删除容器内的 /etc/localtime 文件(若存在)。 "auto" 表示如果宿主系统的 /etc/localtime 是软连接,那么设为 "symlink" ,否则设为 "copy"(可写镜像) 或 "bind"(只读镜像)。 默认值为 "auto"

--read-only

以只读模式 挂载容器的根文件系统。

--bind=, --bind-ro=

将宿主机上的一个文件或目录绑定挂载到容器内指定的目录上。 如果参数是一个单独的路径,那么它必须是一个目录,表示从宿主系统绑定挂载到容器内的相同路径上。 如果参数是冒号分隔的一对路径("源路径:目标路径"),那么第一个源路径表示宿主系统上的源路径(既可以是文件也可以是目录)、 第二个目标路径表示在容器内的挂载目录(必须是目录)。如果还想指定挂载选项,那么可以使用"源路径:目标路径:挂载选项"这样格式的参数。 如果在源路径前加上 "+" 字符前缀,那么表示此源路径是容器内的源路径, 从而可以实现容器内部的绑定挂载。 如果源路径是一个空字符串,那么表示此源路径实际上是宿主系统 /var/tmp 目录下的一个临时目录,并且在容器关闭之后,此临时目录将会被自动删除。 "挂载选项"是一系列逗号分隔的挂载选项,但实际上,目前仅允许使用 rbindnorbind 两个选项,用于控制是否以递归的方式绑定挂载(默认是"rbind")。 如果要在路径中包含冒号,那么必须使用 "\:" 进行转义。 可以多次使用此选项以创建多个互不相关的绑定挂载点。 使用 --bind-ro= 表示仅创建只读的绑定挂载点。

注意,当与 --private-users 一起使用时, 产生的挂载点将被 nobody 用户所拥有。 这是因为挂载点下的文件和目录的用户与组仍然位于宿主系统中,而非位于容器中, 因此在容器内就会呈现为 UID 65534 (nobody)。 推荐使用 --bind-ro= 以只读方式创建这种挂载点。

--tmpfs=

在容器内挂载一个 tmpfs 内存文件系统。 如果设为一个单独的绝对路径, 那么表示容器内的挂载点。 如果还想同时指定挂载选项, 那么可以使用"挂载路径:挂载选项"这样格式的参数。 除非在挂载选项中专门进行了设置, 否则挂载点默认被 root/root 拥有,并且默认的访问权限是 0755 。 此选项在启动无状态容器(也就是将 /var 挂载到内存),特别是与 --read-only 组合使用时,非常有用。 如果要在路径中包含冒号, 那么必须使用 "\:" 进行转义。

--overlay=, --overlay-ro=

将多个目录树依次组合成一个 overlay 文件系统,并将其挂载到容器中。 接受一系列冒号分隔的目录路径列表,除了最后一个路径表示容器内的挂载点之外, 前面的所有路径都表示宿主机上用于组成 overlay 文件系统的一个个目录树(从左到右依次叠加)。

如果要在路径中包含冒号, 那么必须使用 "\:" 进行转义。

如果设置了三个或更多路径, 那么最后一个路径表示容器内的挂载点, 前面的所有路径都表示宿主机上用于组成 overlay 文件系统的一个个目录树(从左到右依次叠加)。 最左边的路径位于 overlay 文件系统最底层, 倒数第二个路径位于 overlay 文件系统最上层。 使用 --overlay-ro= 表示创建只读的 overlay 文件系统。 如果使用 --overlay= 创建了可读写的 overlay 文件系统, 那么所有对 overlay 文件系统的写入都将仅作用于最上层的目录, 也就是倒数第二个路径所在的目录。

如果仅设置了两个路径, 那么第二个路径既是宿主机上 overlay 文件系统的最上层目录, 同时又是容器内 overlay 文件系统的挂载点。 此二选项都要求必须设置至少两个路径。

如果在组成 overlay 文件系统的源路径前面加上 "+" 字符前缀,那么表示此源路径是容器内的源路径。 如果将组成 overlay 文件系统的最上层目录的源路径设为空字符串, 那么表示此源路径实际上是宿主系统 /var/tmp 目录下的一个临时目录, 并且在容器关闭之后,此临时目录将会被自动删除。 这个特性经常用于让只读的容器目录在运行时变为可写。 例如,使用 "--overlay=+/var::/var" 可以自动在只读的 /var 目录上叠加一个可写的临时目录。

更多关于 overlay 文件系统的介绍,可参见 overlayfs.txt 文档。 注意,overlay 文件系统与常规的文件系统在基本语义上有着很大的不同, 例如,进程在写入一个文件的时候, 可能会看到该文件的所属设备与 inode 信息在写入前后发生了变化, 或者有时候进程会看到某个文件已经过期的老旧版本。 注意,因为此选项会从最上层的目录树自动获得 "workdir=" 目录的 overlay 文件系统挂载选项(原封不动的搬运过来), 再加上 "workdir=" 必须与最上层的目录树位于同一个文件系统上, 所以,最上层的目录树本身其实并不是一个真正的挂载点。 还需要注意的是,"lowerdir=" 目录的挂载选项 来自于 此选项中的路径栈(以反向顺序排列)。

-E NAME=VALUE, --setenv=NAME=VALUE

向容器内的 init 进程传递一个环境变量(以 "NAME=VALUE" 格式)。 此选项既可以用于覆盖一个已有环境变量的默认值,也可以用于添加一个新的环境变量。 可以多次使用此选项以添加多个环境变量。

--register=

控制是否将该容器注册到 systemd-machined.service(8) 服务中。 接受一个布尔值,默认值为 "yes" 。 当容器中运行的是一个完整操作系统的时候(准确的说是当 PID=1 的进程是系统与服务管理器的时候),应该开启此选项。 开启此选项之后,该容器即可被 machinectl(1) 工具控制,并且被 ps(1) 工具显示出来。 如果该容器内的 PID=1 进程不是服务管理器,那么应该将此选项设为 "no" 。

--keep-unit

不是在一个临时创建的 scope 单元中运行此容器, 而是直接在调用 systemd-nspawn 命令的 service 或 scope 单元中运行此容器。 如果同时还设置了 --register=yes ,那么该 service 或 scope 单元将被注册到 systemd-machined.service(8) 服务中。 如果从一个服务单元内部调用 systemd-nspawn 命令,那么必须使用此选项, 并且该服务单元的唯一作用必须是仅仅只运行一个单独的 systemd-nspawn 容器。 禁止在用户会话环境中使用此选项。

注意,使用了 --keep-unit 选项之后, --slice=--property= 将会失效。同时使用 --keep-unit--register=no 可以防止将调用 systemd-nspawn 命令的 service 或 scope 单元注册到 systemd-machined 中。

--personality=

设置 容器内 uname(2) 所报告的体系架构。目前仅支持 "x86" 与 "x86-64" 两个值。 此选项对于在64位宿主机上运行32位容器来说才有实际意义。 如果未设置此选项, 那么将使用与宿主机相同的值。

-q, --quiet

静默模式。 也就是关闭状态报告。 使用此选项之后, 将仅输出容器内操作系统自身的控制台内容。

--volatile, --volatile=MODE

控制以何种"易失"模式启动容器。 --volatile(等价于 --volatile=yes) 表示以完全无状态模式(完全隐私模式)启动容器, 也就是将内存("tmpfs")用作容器的根文件系统, 同时仅以只读模式挂载容器镜像内的 /usr 目录, 这样,将以只读模式使用容器镜像, 所有对容器内文件系统的修改都将在容器关闭后丢失。 --volatile=state 表示以常规方式挂载容器镜像内的根目录, 但是仅将 /var 挂载到内存中("tmpfs"), 这样,容器将以用户自定义的配置启动, 但是运行过程中的状态都将在容器关闭之后丢失。 --volatile=no 是默认值, 表示以常规的读写模式挂载容器镜像内的文件系统。

此选项为容器提供了类似于宿主机 "systemd.volatile=" 内核引导选项的功能。详见 kernel-command-line(7) 手册。

注意,要使用此选项,容器内的操作系统必须满足以下条件:(1)可以在仅挂载 /usr 的条件下启动;(2)可以自动填充 /var 目录;(3)当 "--volatile=yes" 时, 还必须能够自动填充 /etc 目录。

--settings=MODE

控制 systemd-nspawn 是否搜索并使用专门针对每个容器的 .nspawn 配置文件。 接受一个布尔值或者特殊值 overridetrusted

默认值 yes 表示在 /etc/systemd/nspawn//run/systemd/nspawn/ 目录中搜索与容器名称(来自于 --machine= 选项或者容器镜像目录/文件的名称)相同且后缀名为 .nspawn 的配置文件并应用这些配置文件。 如果没有找到对应的配置文件, 那么将会进一步在容器镜像文件的所在目录、或容器根目录的所在父目录中搜索。 如果找到了对应的配置文件,那么将会仅应用其中的非特权指令, 而所有特权指令都将被忽略。 注意,命令行上的设置比容器配置文件的优先级更高, 也就是 .nspawn 文件中的配置将会被命令行上的设置所覆盖。 这里所说的特权指令, 是指有可能造成权限提升或者要求访问主机资源(例如主机的文件或目录)的配置指令。 更多关于 .nspawn 文件的说明,参见 systemd.nspawn(5) 手册。

override 与 yes 类似, 不同之处在于,容器配置文件的优先级将会高于命令行上的设置。 也就是 .nspawn 文件中的配置将会 覆盖命令行上的设置。

trusted 与 yes 类似, 不同之处在于,将会搜索所有位于 /etc/systemd/nspawn/ 目录、 /run/systemd/nspawn/ 目录、 容器镜像文件的所在目录、容器根目录的所在父目录 中的 .nspawn 文件,并应用这些配置文件。 注意,这些文件中的配置依然会被命令行上的设置所覆盖。

设为 no 表示 完全忽略 .nspawn 文件。

--notify-ready=

配置容器内的 init 进程是否支持通知机制, 仅接受一个布尔值(noyes)。 设为 no 表示 systemd-nspawn 将会在启动容器内 init 进程的同时, 向 systemd 发送一条 "READY=1" 消息。 设为 yes 表示 systemd-nspawn 将会等待容器内 init 进程主动发送 "READY=1" 消息,然后再将此消息转发给 systemd 。 有关通知机制的详情, 参见 sd_notify(3) 手册。

-h, --help

显示简短的帮助信息并退出。

--version

显示简短的版本信息并退出。

例子

例 1. 下载 Fedora 镜像并在其中启动 shell

# machinectl pull-raw --verify=no \
      https://mirrors.163.com/fedora/releases/30/Cloud/x86_64/images/Fedora-Cloud-Base-30-1.2.x86_64.raw.xz
# systemd-nspawn -M Fedora-Cloud-Base-30-1.2.x86_64.raw

这将使用 machinectl(1) 下载镜像并在其中启动一个 shell


例 2. 创建一个最小化的 Fedora 系统并在容器中启动它

# dnf -y --releasever=30 --installroot=/var/lib/machines/f30 \
      --disablerepo='*' --enablerepo=fedora --enablerepo=updates install \
      systemd passwd dnf fedora-release vim-minimal
# systemd-nspawn -bD /var/lib/machines/f30

这将在 /var/lib/machines/f30 目录中安装一个最小化的 Fedora 系统,并在名字空间容器中启动该系统。 因为此 Fedora 系统安装在标准目录 /var/lib/machines/ 之中,所以还可以使用 systemd-nspawn -M f30 命令来启动。


例 3. 创建一个最小化的 Debian unstable 系统并在容器中启动一个 shell

# debootstrap unstable ~/debian-tree/
# systemd-nspawn -D ~/debian-tree/

这将在 ~/debian-tree/ 目录中安装一个最小化的 Debian unstable 系统, 然后在一个容器中启动此系统中的 shell 。

debootstrap 支持 Debian, Ubuntu, Tanglu 开箱即用,所以对于这三种发行版可以使用相同的安装命令。 对于其他 Debian 系发行版,必须明确指定一个镜像,详见 debootstrap(8) 手册。


例 4. 创建一个最小化的 Arch Linux 系统并在容器中启动它

# pacstrap -c -d ~/arch-tree/ base
# systemd-nspawn -bD ~/arch-tree/

这将在 ~/arch-tree/ 目录中安装一个最小化的 Arch Linux 系统, 然后在一个容器中启动此系统。


例 5. 安装 OpenSUSE Tumbleweed 滚动发行版

# zypper --root=/var/lib/machines/tumbleweed ar -c \
      https://download.opensuse.org/tumbleweed/repo/oss tumbleweed
# zypper --root=/var/lib/machines/tumbleweed refresh
# zypper --root=/var/lib/machines/tumbleweed install --no-recommends \
      systemd shadow zypper openSUSE-release vim
# systemd-nspawn -M tumbleweed passwd root
# systemd-nspawn -M tumbleweed -b

例 6. 在容器中启动一个宿主系统的临时快照

# systemd-nspawn -D / -xb

这将在容器中运行当前宿主系统的一个临时快照,并在容器退出后立即销毁此快照。 在容器运行期间对文件系统所做的任何更改都将在容器关闭后丢失。


例 7. 运行带有 SELinux 沙盒安全上下文的容器

# chcon system_u:object_r:svirt_sandbox_file_t:s0:c0,c1 -R /srv/container
# systemd-nspawn -L system_u:object_r:svirt_sandbox_file_t:s0:c0,c1 \
      -Z system_u:system_r:svirt_lxc_net_t:s0:c0,c1 -D /srv/container /bin/sh

例 8. 运行含有一个 OSTree 部署的容器

# systemd-nspawn -b -i ~/image.raw \
      --pivot-root=/ostree/deploy/$OS/deploy/$CHECKSUM:/sysroot \
      --bind=+/sysroot/ostree/deploy/$OS/var:/var

退出状态

等于 在容器中执行的程序的退出状态

参见

systemd(1), systemd.nspawn(5), chroot(1), dnf(8), debootstrap(8), pacman(8), zypper(8), systemd.slice(5), machinectl(1), btrfs(8)