跳转至内容

systemd-nspawn

来自 ArchWiki

systemd-nspawn 类似于 chroot 命令,但它是“ 强化的 chroot”。

systemd-nspawn 可用于在轻量级命名空间容器中运行命令或操作系统。它比 chroot 更强大,因为它完全虚拟化了文件系统层次结构,以及进程树、各种 IPC 子系统以及主机和域名。

systemd-nspawn 将容器对各种内核接口的访问限制为只读,例如 /sys/proc/sys/sys/fs/selinux。不能从容器内部更改网络接口和系统时钟。不能创建设备节点。宿主机不能从容器内部重新启动,也不能加载内核模块。

systemd-nspawnLXCLibvirt 更易于配置。

安装

systemd-nspawnsystemd 的一部分,并随其打包。

示例

创建并启动一个最小化的 Arch Linux 容器

创建一个目录来存放容器,在本例中我们将使用 ~/MyContainer

使用 pacstrap(来自 arch-install-scripts 包)将一个基本的 Arch 系统安装到容器中。至少我们需要安装 base 软件包。

# pacstrap -K -c ~/MyContainer base [additional packages/groups]
提示 base 软件包不依赖于 linux 内核软件包,并且已为容器做好准备。
注意 如果从其他操作系统创建(其中 pacstrap 不可用),可以使用 bootstrap tarball 作为容器镜像。pacman 密钥环需要在容器内部进行初始化,请参阅 Install Arch Linux from existing Linux#Initializing pacman keyring

安装完成后,进入容器并设置 root 密码

# systemd-nspawn -D ~/MyContainer
# passwd
# logout
提示 设置 root 密码是可选的。您可以通过运行 machinectl shell root@MyContainer 直接在已启动的容器中获得一个 root shell,而无需登录。请参阅 #machinectl

最后,启动容器

# systemd-nspawn -b -D ~/MyContainer

-b 选项将启动容器(即运行 systemd 作为 PID=1),而不是仅仅运行一个 shell,而 -D 指定将成为容器根目录的目录。

容器启动后,使用您的密码以“root”身份登录。

容器可以通过在容器内运行 poweroff 来关闭。从宿主机,可以使用 machinectl 工具来控制容器。

注意 要从容器内部终止会话,请按住 Ctrl 并快速按 ] 三次。

创建 Debian 或 Ubuntu 环境

安装 debootstrap,以及 debian-archive-keyringubuntu-keyring(取决于您想要的发行版)。

然后使用以下结构调用 deboostrap

# debootstrap [OPTIONS...] SUITE TARGET [MIRROR]
  • SUITE(必需)是在 脚本目录中找到的所需发行版特定版本的代号或别名。
    • 对于 Debian,有效的 suite 名称是稳定版别名 stabletestingunstable,或者是 bookwormsid 等发行版名称:有关列表,请参阅 [1]
    • 对于 Ubuntu,只能使用版本名称,如 jammynoble,而不是版本号:有关代号到版本号的表格,请参阅 [2][3]
    • 在脚本目录中存在其他 Debian 衍生发行版(如 Devuan、eLxr、Kali Linux、Pardus、PureOS、Trisquel 和 Tanglu(已于 2017 年停用))的参考。调用这些通常需要获取其特定的 keyring 并将其传递给 --keyring 选项,或使用 --no-check-sig 禁用对检索到的 Release 文件的 OpenPGP 签名检查。
  • TARGET(必需)是包含 debootstrapped 系统的目录;如果它尚不存在,则会被创建。
  • MIRROR(可选):应从中下载软件包的归档 URL。对于当前的 Debian 版本,可以是任何 有效镜像,例如 CDN 支持的 https://deb.debian.org/debian(默认),对于 Ubuntu,可以是来自 [4] 的任何镜像,例如参考的 https://archive.ubuntu.com/ubuntu(也用作默认)。
注意 Debian 的存档版本(截至 2025-01,这是 Debian 10/Buster 之前的所有版本)使用特殊的 debian-archive 镜像 URL https://archive.debian.org/debian/。对于 Ubuntu,脚本使用 https://old-releases.ubuntu.com/ubuntu/。但请注意,对于未包含在上述 keyring 包中的发行版,会遇到“未知签名密钥”的问题,例如,Debian 的 9(“Stretch”)之前的任何发行版[5],使得后者成为无需获取不同 keyring 或禁用签名检查即可安装的最旧发行版。

debootstrap 无法解析虚拟包的依赖项[6],因此它默认不安装 systemddbuslibpam-systemd 推荐依赖项[7][8]。因此,一些 systemd/dbus 相关的功能(例如 localectl)以及使用 #machinectl 管理容器的功能将无法开箱即用。

为了在基于 systemd 的系统上获得完整的功能,请在 debootstrap 调用中添加 --include=dbus,libpam-systemd,或在容器中一次性安装这些软件包。

# debootstrap --include=dbus,libpam-systemd,libnss-systemd stable /path/to/machine
注意启动使用 -b, --boot 选项)非 systemd 作为其 init 的容器时,可能会出现其他困难。自 Debian 8(“jessie”)[9] 和 Ubuntu 自 15.04(“vivid”)[10] 以来,systemd 一直是 Debian 的默认 init 系统。无论 init 系统如何,获得 shell 都应该可以工作。

与 Arch 类似,Debian 和 Ubuntu 在没有密码的情况下不允许您登录。要设置 root 密码,请在不带 -b 选项的情况下运行 systemd-nspawn

# systemd-nspawn -D /path/to/machine
# passwd
# logout

使用 Podman 或 Docker 创建 RHEL 衍生环境

这些说明应适用于任何使用 dnf 的发行版。您将需要 PodmanDocker

# mkdir -p /var/lib/machines/my-machine
# podman pull centos:stream9
# podman run --rm -it -v /var/lib/machines/my-machine:/machine centos:stream9

从该容器内部,您可以构建您的机器的根环境

本文或本节需要在语言、wiki 语法或风格方面进行改进。请参阅 Help:Style 获取参考。

原因:安装/首次设置说明应外包。(请在 Talk:Systemd-nspawn 中讨论)
bash-5.1# dnf update -y
bash-5.1# dnf --repo=baseos \
              --releasever=9 \
              --best \
              --installroot=/machine \
              install \
              systemd-udev \
              hostname \
              yum \
              dnf \
              centos-gpg-keys \
              centos-stream-release \
              rootfiles \
              shadow-utils \
              util-linux
bash-5.1# sed -i 's/^root:[^:]*:/root::/' /machine/etc/shadow # remove root password for initial login
bash-5.1# exit

从那里,您应该能够启动机器

# systemd-nspawn --machine=my-machine --boot

创建 Fedora 或 AlmaLinux 环境

安装 dnf,并编辑 /etc/dnf/dnf.conf 文件以添加所需的 Fedora 存储库。

/etc/dnf/dnf.conf
[fedora]
name=Fedora $releasever - $basearch
metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-$releasever&arch=$basearch
gpgkey=https://getfedora.org/static/fedora.gpg

[updates]
name=Fedora $releasever - $basearch - Updates
metalink=https://mirrors.fedoraproject.org/metalink?repo=updates-released-f$releasever&arch=$basearch
gpgkey=https://getfedora.org/static/fedora.gpg

fedora.gpg 文件包含最新 Fedora 发行版的 gpg 密钥 https://getfedora.org/security/。要设置一个最小化的 Fedora 42 容器

# mkdir /var/lib/machines/container-name
# dnf5 --releasever=42 --best --use-host-config --setopt=install_weak_deps=False --repo=fedora --repo=updates --installroot=/var/lib/machines/container-name install dhcp-client dnf fedora-release glibc glibc-langpack-en iputils less ncurses passwd systemd systemd-networkd systemd-resolved util-linux vim-default-editor
注意 如果您想安装不同的 Fedora 版本,请记住不同的版本将有不同的 软件包要求

如果您使用 btrfs 文件系统,请创建一个子卷而不是创建一个目录。

像 AlmaLinux 这样的 Enterprise Linux 衍生版默认启用了三个存储库:BaseOS(包含所有安装的基础核心集)、AppStream(包含附加应用程序、语言包等)和 Extras(包含未包含在 RHEL 中的软件包)。因此,对于一个最小化的容器,我们只需要将 BaseOS 存储库添加到 /etc/dnf/dnf.conf

/etc/dnf/dnf.conf
[baseos]
name=AlmaLinux $releasever - BaseOS
mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/baseos
gpgkey=https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-$releasever

创建 AlmaLinux 9 最小化容器

# dnf --repo=baseos --releasever=9 --best --installroot=/var/lib/machines/container-name --setopt=install_weak_deps=False install almalinux-release dhcp-client dnf glibc-langpack-en iproute iputils less passwd systemd vim-minimal

这将安装 AlmaLinux 9 的最新次要版本,您可以选择安装一个特定的发行点版本,但您需要更改 gpgpkey 条目以手动指向 RPM-GPG-KEY-AlmaLinux-9

与 Arch、Fedora 或 AlmaLinux 一样,它们在没有密码的情况下不允许您以 root 身份登录。要设置 root 密码,请在不带 -b 选项的情况下运行 systemd-nspawn

# systemd-nspawn -D /var/lib/machines/container-name passwd

构建和测试软件包

有关示例用法,请参阅 为其他发行版创建软件包

管理

位于 /var/lib/machines/ 的容器可以通过 machinectl 命令进行控制,该命令内部控制 systemd-nspawn@.service 单元的实例。/var/lib/machines/ 中的子目录对应于容器名称,即 /var/lib/machines/container-name/

注意 如果由于某种原因容器无法移动到 /var/lib/machines/,可以创建符号链接。有关详细信息,请参阅 machinectl(1) § FILES AND DIRECTORIES

默认 systemd-nspawn 选项

请注意,通过 machinectlsystemd-nspawn@.service 启动的容器使用与手动通过 systemd-nspawn 命令启动的容器不同的默认选项。服务使用的额外选项是:

  • -b/--boot – 已管理的容器会自动搜索 init 程序并将其作为 PID 1 调用。
  • --network-veth,它隐含 --private-network – 已管理的容器会获得一个虚拟网络接口,并与宿主机网络断开连接。有关详细信息,请参阅 #Networking
  • -U – 如果内核支持,已管理的容器默认使用 user_namespaces(7) 功能。有关影响,请参阅 #Unprivileged containers
  • --link-journal=try-guest

此行为可以在每个容器的配置文件中覆盖。有关详细信息,请参阅 #Configuration

machinectl

注意 machinectl 工具要求容器中安装 systemddbus。有关详细讨论,请参阅 [11]

容器可以通过 machinectl subcommand container-name 命令进行管理。例如,要启动一个容器

$ machinectl start container-name
注意 machinectl 要求容器名称仅包含 ASCII 字母、数字和连字符,以便它们是有效的 主机名。例如,如果容器名称包含下划线,它将根本不会被识别,运行 machinectl start container_name 将导致错误 Invalid machine name container_name。有关更多详细信息,请参阅 [12][13]

同样,还有 poweroffrebootstatusshow 等子命令。有关详细说明,请参阅 machinectl(1) § Machine Commands

提示 可以使用容器内的 poweroffreboot 命令执行关机和重启操作。

其他常用命令是

  • machinectl list – 显示当前正在运行的容器列表
  • machinectl login container-name – 在容器中打开一个交互式登录会话
  • machinectl shell [username@]container-name – 在容器中打开一个交互式 shell 会话(这会立即调用用户进程,而无需经过容器内的登录过程)
  • machinectl enable container-namemachinectl disable container-name – 启用或禁用容器在启动时启动,有关详细信息,请参阅 #Enable container to start at boot

machinectl 还有用于管理容器(或虚拟机)镜像和镜像传输的子命令。有关详细信息,请参阅 machinectl(1) § Image Commandsmachinectl(1) § Image Transfer Commands。截至 2023Q1,machinectl(1) § EXAMPLES 中的前三个示例演示了镜像传输命令。machinectl(1) § FILES AND DIRECTORIES 讨论了在哪里可以找到合适的镜像。

systemd 工具链

核心 systemd 工具链的许多部分已更新为与容器一起使用。提供 -M, --machine= 选项的工具通常会接受容器名称作为参数。

示例

查看特定机器的 journal 日志

# journalctl -M container-name

显示 cgroup 内容

$ systemd-cgls -M container-name

查看容器的启动时间

$ systemd-analyze -M container-name

概述资源使用情况

$ systemd-cgtop

配置

每个容器的设置

要指定每个容器的设置而不是全局覆盖,可以使用 .nspawn 文件。有关详细信息,请参阅 systemd.nspawn(5)

  • 当您运行 machinectl remove 时,/etc/systemd/nspawn/ 中的 .nspawn 文件可能会意外删除。[14]
  • --settings=override(在 systemd-nspawn@.service 文件中指定)存在时,在 .nspawn 文件和命令行上指定的网络选项的交互不会正确工作。[15] 作为一种变通方法,您需要包含选项 VirtualEthernet=on,即使服务指定了 --network-veth

启用容器在启动时启动

当频繁使用容器时,您可能希望在启动时启动它。

首先确保 machines.target启用

可被 machinectl 发现的容器可以启用或禁用

$ machinectl enable container-name
  • 这会产生启用 systemd-nspawn@container-name.service systemd 单元的效果。
  • #Default systemd-nspawn options 中所述,由 machinectl 启动的容器会获得一个虚拟以太网接口。要禁用私有网络,请参阅 #Host networking

资源控制

您可以使用 cgroups 来实现对容器的限制和资源管理,使用 systemctl set-property,请参阅 systemd.resource-control(5)。例如,您可能想限制内存量或 CPU 使用率。将容器的内存消耗限制为 2 GiB

# systemctl set-property systemd-nspawn@container-name.service MemoryMax=2G

或者将 CPU 时间使用量限制为大约相当于 2 个核心

# systemctl set-property systemd-nspawn@container-name.service CPUQuota=200%

这将创建永久文件在 /etc/systemd/system.control/systemd-nspawn@container-name.service.d/

根据文档,MemoryHigh 是控制内存消耗的首选方法,但它不会像 MemoryMax 那样被硬限制。您可以使用这两个选项,将 MemoryMax 作为最后一道防线。同时也要考虑到您不会限制容器可以看到的 CPU 数量,但通过限制容器最多能获得多少时间(相对于总 CPU 时间)来实现类似的结果。

提示 如果您只想临时更改,可以传递 --runtime 选项。您可以使用 systemd-cgtop 检查其结果。

网络

systemd-nspawn 容器可以使用 宿主机网络私有网络

  • 在宿主机网络模式下,容器可以完全访问宿主机网络。这意味着容器将能够访问宿主机上的所有网络服务,并且来自容器的流量将出现在外部网络上,就像来自宿主机一样(即共享相同的 IP 地址)。
  • 在私有网络模式下,容器与宿主机的网络断开连接。这使得除回环设备和明确分配给容器的网络接口之外的所有网络接口都无法用于容器。有多种方法可以为容器设置网络接口:
在后一种情况下,容器网络是完全隔离的(与外部网络以及其他容器隔离),由管理员负责配置宿主机和容器之间的网络。这通常涉及在多个接口之间使用 NAT 网络,或使用网络桥接来连接多个(物理或虚拟)接口。

宿主机网络模式适用于应用程序容器,这些容器不运行任何网络软件来配置分配给容器的接口。当您从 shell 运行 systemd-nspawn 时,宿主机网络是默认模式。

另一方面,私有网络模式适用于应该与宿主机隔离的系统容器。创建虚拟以太网链路是非常灵活的工具,允许创建复杂的虚拟网络。这是由 machinectlsystemd-nspawn@.service 启动的容器的默认模式。

以下子部分描述了常见场景。有关可用的 systemd-nspawn 选项的详细信息,请参阅 systemd-nspawn(1) § Networking Options

宿主机网络

要禁用私有网络和为使用 machinectl 启动的容器创建虚拟以太网链路,请添加一个具有以下选项的 .nspawn 文件

/etc/systemd/nspawn/container-name.nspawn
[Network]
VirtualEthernet=no

这将覆盖 systemd-nspawn@.service 中使用的 -n/--network-veth 选项,新启动的容器将使用宿主机网络模式。

私有网络

如果容器使用 -n/--network-veth 选项启动,systemd-nspawn 将在宿主机和容器之间创建一个虚拟以太网链路。链路的宿主机端将作为名为 ve-container-name 的网络接口可用。链路的容器端将命名为 host0。请注意,此选项隐含 --private-network

  • 如果容器名称太长,接口名称将被缩短(例如,ve-long-conKQGh 而不是 ve-long-container-name),以适应15 个字符的限制。完整名称将设置为接口的 altname 属性(请参阅 ip-link(8)),并且仍然可以用于引用接口。
  • 当使用 ip link 检查接口时,接口名称将显示一个后缀,例如 ve-container-name@if2host0@if9@ifN 实际上不是接口名称的一部分;相反,ip link 会附加此信息,以指示虚拟以太网线连接到另一端的哪个“插槽”。
例如,一个显示为 ve-foo@if2 的宿主机虚拟以太网接口连接到容器 foo,而在容器内部连接到第二个网络接口——在容器内运行 ip link 时显示为索引 2 的接口。类似地,容器中命名为 host0@if9 的接口连接到宿主机上的第 9 个网络接口。

当您启动容器时,必须为两个接口(在宿主机上和在容器中)分配 IP 地址。如果您在宿主机和容器中都使用 systemd-networkd,这可以开箱即用:

  • 宿主机上的 /usr/lib/systemd/network/80-container-ve.network 文件匹配 ve-container-name 接口并启动一个 DHCP 服务器,该服务器为宿主机接口和容器分配 IP 地址。
  • 容器中的 /usr/lib/systemd/network/80-container-host0.network 文件匹配 host0 接口并启动一个 DHCP 客户端,该客户端从宿主机接收 IP 地址。
提示启动宿主机上的 systemd-networkd 将提供所述的容器网络自动配置,而不会干扰现有的网络设置,因为现有的接口(或接口组)将保持未被 systemd-networkd 管理,直到另行配置。因此,它与已管理的网络设置(例如与 NetworkManager)完全兼容,并且启动 systemd-networkd 是获得容器内部互联网连接(以及能够从宿主机访问它们)所需的一切,而不会干扰宿主机上的任何内容。

如果您不使用 systemd-networkd,您可以在宿主机接口上配置静态 IP 地址或启动 DHCP 服务器,并在容器中启动 DHCP 客户端。有关详细信息,请参阅 网络配置

使用 NAT 网络

要使容器能够访问外部网络,您可以按照 Internet sharing#Enable NAT 中的描述配置 NAT。如果您使用 systemd-networkd,这可以通过 `/usr/lib/systemd/network/80-container-ve.network` 中的 IPMasquerade=both 选项(部分)自动完成。但是,这只发出一条 iptables(或 nftables)规则,例如

-t nat -A POSTROUTING -s 192.168.163.192/28 -j MASQUERADE

filter 表需要手动配置,如 Internet sharing#Enable NAT 中所示。您可以使用通配符来匹配所有以 ve- 开头的接口。

# iptables -A FORWARD -i ve-+ -o internet0 -j ACCEPT
注意 systemd-networkdsystemd-nspawn 可以与 iptables(使用 libiptc 库)以及 nftables [16][17] 进行交互。在这两种情况下都支持 IPv4 和 IPv6 NAT。

此外,您需要为 ve-+ 接口打开 UDP 端口 67,以接收来自 DHCP 服务器(由 systemd-networkd 运行)的传入连接。

# iptables -A INPUT -i ve-+ -p udp -m udp --dport 67 -j ACCEPT
使用网络桥接

如果您在宿主系统上配置了 网络桥接,您可以为容器创建一个虚拟以太网链路,并将其宿主端添加到网络桥接中。这可以通过 --network-bridge=bridge-name 选项来完成。请注意,--network-bridge 暗示 --network-veth,即虚拟以太网链路会自动创建。但是,该链路的宿主端将使用 vb- 前缀而不是 ve-,因此 systemd-networkd 用于启动 DHCP 服务器和 IP 伪装的选项将不会被应用。

桥接的管理留给管理员。例如,桥接可以将虚拟接口与物理接口连接,或者只连接多个容器的虚拟接口。使用 systemd-networkd 的示例配置请参见 systemd-networkd#Network bridge with DHCPsystemd-networkd#Network bridge with static IP addresses

还有一个 --network-zone=zone-name 选项,它与 --network-bridge 类似,但网络桥接由 systemd-nspawnsystemd-networkd 自动管理。当启动的第一个配置了 --network-zone=zone-name 的容器时,会自动创建一个名为 vz-zone-name 的桥接接口,当最后一个配置了 --network-zone=zone-name 的容器退出时,会自动删除。因此,此选项可以轻松地将多个相关容器放在一个公共的虚拟网络上。请注意,vz-* 接口由 systemd-networkd 管理,方式与 ve-* 接口使用 /usr/lib/systemd/network/80-container-vz.network 文件中的选项相同。

使用 "macvlan" 或 "ipvlan" 接口

与创建虚拟以太网链路(其宿主端可能已添加到桥接中,也可能未添加)相反,您可以创建位于现有物理接口(即 VLAN 接口)上的虚拟接口,并将其添加到容器中。该虚拟接口将与底层的宿主接口进行桥接,从而使容器暴露给外部网络,允许它从与宿主连接的同一 LAN 通过 DHCP 获取独立的 IP 地址。

systemd-nspawn 提供 2 个选项

  • --network-macvlan=interface – 虚拟接口将具有与底层物理 interface 不同的 MAC 地址,并将命名为 mv-interface
  • --network-ipvlan=interface – 虚拟接口将具有与底层物理 interface 相同的 MAC 地址,并将命名为 iv-interface

这两个选项都隐含 --private-network

注意 要允许宿主与容器通信,请在宿主上创建一个 macvlan 或 ipvlan 接口,将其连接到容器使用的同一物理接口,并在其上设置网络连接。确保虚拟接口名称与 systemd-nspawn 创建的虚拟接口名称不冲突。使用 systemd-networkd 的示例请参见 systemd-networkd#MACVLAN bridge
使用现有接口

如果宿主系统有多个物理网络接口,您可以使用 --network-interface=interfaceinterface 分配给容器(并在容器启动时将其从宿主系统移除)。请注意,--network-interface 暗示 --private-network

提示systemd-nspawn(1) v256 起,支持将无线网络接口传递给 systemd-nspawn 容器。

端口映射

启用私有网络时,可以使用 -p/--port 选项或 .nspawn 文件中的 Port 设置将宿主的单个端口映射到容器的端口。例如,将宿主的 TCP 端口 8000 映射到容器的 TCP 端口 80

/etc/systemd/nspawn/container-name.nspawn
[Network]
Port=tcp:8000:80

这通过向 nat 表发出 iptables(或 nftables)规则来实现,但 filter 表中的 FORWARD 链需要手动配置,如 #Use a virtual Ethernet link 中所示。此外,如果您按照 Simple stateful firewall 进行配置,请运行以下命令以允许建立到宿主 wan_interface 在转发端口上的新连接。

# iptables -A FORWARD -i wan_interface -o ve-+ -p tcp --syn --dport 8000 -m conntrack --ctstate NEW -j ACCEPT
注意 systemd-nspawn 会明确排除 loopback 接口进行端口映射。因此,对于上面的示例,localhost:8000 连接到宿主而不是容器。只有连接到其他接口的流量才会受到端口映射的影响。有关详细信息,请参见 [18]

域名解析

域名解析 在容器中的配置方式与在宿主系统上相同。此外,systemd-nspawn 提供了管理容器内 /etc/resolv.conf 文件的选项。

  • --resolv-conf 可在命令行中使用。
  • ResolvConf= 可在 .nspawn 文件中使用。

这些相应的选项有许多可能的值,可在 systemd-nspawn(1) § Integration Options 中找到。默认值为 auto,这意味着:

  • 如果启用了 --private-network,则 /etc/resolv.conf 在容器中保持不变。
  • 否则,如果宿主上运行了 systemd-resolved,则其存根 resolv.conf 文件将被复制或绑定挂载到容器中。
  • 否则,/etc/resolv.conf 文件将从宿主复制或绑定挂载到容器中。

在最后两种情况下,如果容器根目录可写,则文件将被复制;如果容器根目录是只读的,则会进行绑定挂载。

对于第二种情况,即宿主上运行 systemd-resolvedsystemd-nspawn 希望它也在容器中运行,以便容器可以使用宿主的存根符号链接文件 /etc/resolv.conf。如果不是,默认值 auto 将不再有效,您应该使用 replace-* 选项之一替换符号链接。

技巧与提示

运行非 shell/init 命令

来自 systemd-nspawn(1) § Execution Options

[选项] --as-pid2 [调用] shell 或指定的程序作为进程 ID (PID) 2,而不是 PID 1 (init)。[...] 建议使用此模式在容器中调用任意命令,除非它们已被修改为可以正确作为 PID 1 运行。换句话说:此开关几乎适用于所有命令,除非命令指的是 init 或 shell 实现 [...] 此选项不能与 --boot 结合使用。

无特权容器

systemd-nspawn 支持无特权容器,尽管容器需要以 root 用户启动。

本文或本节需要在语言、wiki 语法或风格方面进行改进。请参阅 Help:Style 获取参考。

原因: Linux Containers#Enable support to run unprivileged containers (optional) 中很少有内容适用于 systemd-nspawn。(在 Talk:Systemd-nspawn 中讨论)

最简单的方法是使用 -U 选项,让 systemd-nspawn 自动选择一个未使用的 UID/GID 范围。

# systemd-nspawn -bUD ~/MyContainer

如果内核支持用户命名空间,-U 选项等同于 --private-users=pick --private-users-ownership=auto。有关详细信息,请参见 systemd-nspawn(1) § User Namespacing Options

注意 您也可以手动指定容器的 UID/GID 范围,但这很少有用。

如果容器使用 --private-users-ownership=chown 选项(或在需要 --private-users-ownership=chown 的文件系统上)以私有 UID/GID 范围启动,则需要继续保持此设置以避免权限错误。或者,可以通过指定从 0 开始的 ID 范围来撤销 --private-users-ownership=chown 对容器文件系统的影响。

# systemd-nspawn -D ~/MyContainer --private-users=0 --private-users-ownership=chown

使用 X 环境

本文或本章节的准确性存在争议。

原因: 本节末尾关于 systemd 版本的注释似乎已过时。对我来说(systemd 版本 239),即使 /tmp/.X11-unix 以读写方式绑定,X 应用程序也正常工作。(在 Talk:Systemd-nspawn#/tmp/.X11-unix contents have to be bind-mounted as read-only - still relevant? 中讨论)

请参见 XhostChange root#Run graphical applications from chroot

您需要在容器会话中设置 DISPLAY 环境变量,以便连接到外部 X 服务器。

X 将一些必需的文件存储在 /tmp 目录中。为了让您的容器显示任何内容,它需要访问这些文件。为此,在启动容器时附加 --bind-ro=/tmp/.X11-unix 选项。

注意 自 systemd 版本 235 起,/tmp/.X11-unix 的内容 必须以只读方式绑定挂载,否则它们将从文件系统中消失。只读挂载标志不会阻止使用 connect() 系统调用访问套接字。如果您也绑定了 /run/user/1000,则可能需要显式地以只读方式绑定 /run/user/1000/bus 以防止 dbus 套接字被删除。

避免使用 xhost

xhost 只提供对 X 服务器的相对粗粒度的访问权限。通过 $XAUTHORITY 文件可以实现更细粒度的访问控制。不幸的是,仅仅将 $XAUTHORITY 文件提供给容器是不够的:您的 $XAUTHORITY 文件特定于您的宿主,但容器是不同的宿主。可以使用以下技巧(改编自 stackoverflow)让您的 X 服务器接受容器内运行的 X 应用程序的 $XAUTHORITY 文件。

$ XAUTH=/tmp/container_xauth
$ xauth nextract - "$DISPLAY" | sed -e 's/^..../ffff/' | xauth -f "$XAUTH" nmerge -
# systemd-nspawn -D myContainer --bind=/tmp/.X11-unix --bind="$XAUTH" -E DISPLAY="$DISPLAY" -E XAUTHORITY="$XAUTH" --as-pid2 /usr/bin/xeyes

上面第二行将连接族设置为 "FamilyWild",值为 65535,这将导致该条目匹配所有显示。有关更多信息,请参见 Xsecurity(7)

使用 X 嵌套/Xephyr

另一种运行 X 应用程序并避免共享 X 桌面的风险的简单方法是使用 X 嵌套。其优点是完全避免容器内应用程序与非容器应用程序之间的交互,并且能够运行不同的 桌面环境窗口管理器。缺点是性能较低,并且在使用 Xephyr 时缺乏硬件加速。

在容器外部使用以下命令启动 Xephyr:

# Xephyr :1 -resizeable

然后使用以下选项启动容器:

--setenv=DISPLAY=:1 --bind-ro=/tmp/.X11-unix/X1

不需要其他绑定。

在某些情况下(主要是与 -b 一起使用时),您可能仍然需要在容器内手动设置 DISPLAY=:1

运行 Firefox

# systemd-nspawn --setenv=DISPLAY=:0 \
              --setenv=XAUTHORITY=~/.Xauthority \
              --bind-ro=$HOME/.Xauthority:/root/.Xauthority \
              --bind=/tmp/.X11-unix \
              -D ~/containers/firefox \
              --as-pid2 \
              firefox
注意 因此,firefox 是以 root 用户身份运行的,如果未使用 #Unprivileged containers,这会带来风险。在这种情况下,您可能首先选择在容器内 添加用户,然后在 systemd-nspawn 调用中添加 --user <username> 选项。

或者,您可以启动容器,让例如 systemd-networkd 设置虚拟网络接口。

# systemd-nspawn --bind-ro=$HOME/.Xauthority:/root/.Xauthority \
              --bind=/tmp/.X11-unix \
              -D ~/containers/firefox \
              --network-veth -b

容器启动后,像这样运行 Xorg 二进制文件:

# systemd-run -M firefox --setenv=DISPLAY=:0 firefox

3D 图形加速

本文章或章节需要扩充。

原因: VulkanOpenGL 的 3D 加速后继者,如何进入场景?(在 Talk:Systemd-nspawn 中讨论)

要启用加速的 3D 图形,可能需要将 /dev/dri 绑定挂载到容器中,方法是在 .nspawn 文件中添加以下行:

Bind=/dev/dri

上述技巧是从 patrickskiba.com 采用的。这显著解决了

libGL error: MESA-LOADER: failed to retrieve device information
libGL error: Version 4 or later of flush extension not found
libGL error: failed to load driver: i915

您可以通过运行 glxinfoglxgears 来确认它已启用。

NVIDIA GPU

如果无法在容器中安装与宿主相同的 NVIDIA 驱动程序版本,您可能还需要绑定驱动程序库文件。您可以在宿主上运行 pacman -Ql nvidia-utils 来查看它包含的所有文件。您不需要复制所有内容。当容器通过 machinectl start container-name 运行时,以下 systemd 覆盖文件将把所有必需的文件绑定过来。

本文或本章节的准确性存在争议。

原因: 没有理由将 /usr/lib/ 绑定到 /usr/lib/x86_64-linux-gnu/。(在 Talk:Systemd-nspawn 中讨论)
/etc/systemd/system/systemd-nspawn@.service.d/nvidia-gpu.conf
[Service]
ExecStart=
ExecStart=systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --machine=%i \
--bind=/dev/dri \
--bind=/dev/shm \
--bind=/dev/nvidia0 \
--bind=/dev/nvidiactl \
--bind=/dev/nvidia-modeset \
--bind=/usr/bin/nvidia-bug-report.sh:/usr/bin/nvidia-bug-report.sh \
--bind=/usr/bin/nvidia-cuda-mps-control:/usr/bin/nvidia-cuda-mps-control \
--bind=/usr/bin/nvidia-cuda-mps-server:/usr/bin/nvidia-cuda-mps-server \
--bind=/usr/bin/nvidia-debugdump:/usr/bin/nvidia-debugdump \
--bind=/usr/bin/nvidia-modprobe:/usr/bin/nvidia-modprobe \
--bind=/usr/bin/nvidia-ngx-updater:/usr/bin/nvidia-ngx-updater \
--bind=/usr/bin/nvidia-persistenced:/usr/bin/nvidia-persistenced \
--bind=/usr/bin/nvidia-powerd:/usr/bin/nvidia-powerd \
--bind=/usr/bin/nvidia-sleep.sh:/usr/bin/nvidia-sleep.sh \
--bind=/usr/bin/nvidia-smi:/usr/bin/nvidia-smi \
--bind=/usr/bin/nvidia-xconfig:/usr/bin/nvidia-xconfig \
--bind=/usr/lib/gbm/nvidia-drm_gbm.so:/usr/lib/x86_64-linux-gnu/gbm/nvidia-drm_gbm.so \
--bind=/usr/lib/libEGL_nvidia.so:/usr/lib/x86_64-linux-gnu/libEGL_nvidia.so \
--bind=/usr/lib/libGLESv1_CM_nvidia.so:/usr/lib/x86_64-linux-gnu/libGLESv1_CM_nvidia.so \
--bind=/usr/lib/libGLESv2_nvidia.so:/usr/lib/x86_64-linux-gnu/libGLESv2_nvidia.so \
--bind=/usr/lib/libGLX_nvidia.so:/usr/lib/x86_64-linux-gnu/libGLX_nvidia.so \
--bind=/usr/lib/libcuda.so:/usr/lib/x86_64-linux-gnu/libcuda.so \
--bind=/usr/lib/libnvcuvid.so:/usr/lib/x86_64-linux-gnu/libnvcuvid.so \
--bind=/usr/lib/libnvidia-allocator.so:/usr/lib/x86_64-linux-gnu/libnvidia-allocator.so \
--bind=/usr/lib/libnvidia-cfg.so:/usr/lib/x86_64-linux-gnu/libnvidia-cfg.so \
--bind=/usr/lib/libnvidia-egl-gbm.so:/usr/lib/x86_64-linux-gnu/libnvidia-egl-gbm.so \
--bind=/usr/lib/libnvidia-eglcore.so:/usr/lib/x86_64-linux-gnu/libnvidia-eglcore.so \
--bind=/usr/lib/libnvidia-encode.so:/usr/lib/x86_64-linux-gnu/libnvidia-encode.so \
--bind=/usr/lib/libnvidia-fbc.so:/usr/lib/x86_64-linux-gnu/libnvidia-fbc.so \
--bind=/usr/lib/libnvidia-glcore.so:/usr/lib/x86_64-linux-gnu/libnvidia-glcore.so \
--bind=/usr/lib/libnvidia-glsi.so:/usr/lib/x86_64-linux-gnu/libnvidia-glsi.so \
--bind=/usr/lib/libnvidia-glvkspirv.so:/usr/lib/x86_64-linux-gnu/libnvidia-glvkspirv.so \
--bind=/usr/lib/libnvidia-ml.so:/usr/lib/x86_64-linux-gnu/libnvidia-ml.so \
--bind=/usr/lib/libnvidia-ngx.so:/usr/lib/x86_64-linux-gnu/libnvidia-ngx.so \
--bind=/usr/lib/libnvidia-opticalflow.so:/usr/lib/x86_64-linux-gnu/libnvidia-opticalflow.so \
--bind=/usr/lib/libnvidia-ptxjitcompiler.so:/usr/lib/x86_64-linux-gnu/libnvidia-ptxjitcompiler.so \
--bind=/usr/lib/libnvidia-rtcore.so:/usr/lib/x86_64-linux-gnu/libnvidia-rtcore.so \
--bind=/usr/lib/libnvidia-tls.so:/usr/lib/x86_64-linux-gnu/libnvidia-tls.so \
--bind=/usr/lib/libnvidia-vulkan-producer.so:/usr/lib/x86_64-linux-gnu/libnvidia-vulkan-producer.so \
--bind=/usr/lib/libnvoptix.so:/usr/lib/x86_64-linux-gnu/libnvoptix.so \
--bind=/usr/lib/modprobe.d/nvidia-utils.conf:/usr/lib/x86_64-linux-gnu/modprobe.d/nvidia-utils.conf \
--bind=/usr/lib/nvidia/wine/_nvngx.dll:/usr/lib/x86_64-linux-gnu/nvidia/wine/_nvngx.dll \
--bind=/usr/lib/nvidia/wine/nvngx.dll:/usr/lib/x86_64-linux-gnu/nvidia/wine/nvngx.dll \
--bind=/usr/lib/nvidia/xorg/libglxserver_nvidia.so:/usr/lib/x86_64-linux-gnu/nvidia/xorg/libglxserver_nvidia.so \
--bind=/usr/lib/vdpau/libvdpau_nvidia.so:/usr/lib/x86_64-linux-gnu/vdpau/libvdpau_nvidia.so \
--bind=/usr/lib/xorg/modules/drivers/nvidia_drv.so:/usr/lib/x86_64-linux-gnu/xorg/modules/drivers/nvidia_drv.so \
--bind=/usr/share/X11/xorg.conf.d/10-nvidia-drm-outputclass.conf:/usr/share/X11/xorg.conf.d/10-nvidia-drm-outputclass.conf \
--bind=/usr/share/dbus-1/system.d/nvidia-dbus.conf:/usr/share/dbus-1/system.d/nvidia-dbus.conf \
--bind=/usr/share/egl/egl_external_platform.d/15_nvidia_gbm.json:/usr/share/egl/egl_external_platform.d/15_nvidia_gbm.json \
--bind=/usr/share/glvnd/egl_vendor.d/10_nvidia.json:/usr/share/glvnd/egl_vendor.d/10_nvidia.json \
--bind=/usr/share/licenses/nvidia-utils/LICENSE:/usr/share/licenses/nvidia-utils/LICENSE \
--bind=/usr/share/vulkan/icd.d/nvidia_icd.json:/usr/share/vulkan/icd.d/nvidia_icd.json \
--bind=/usr/share/vulkan/implicit_layer.d/nvidia_layers.json:/usr/share/vulkan/implicit_layer.d/nvidia_layers.json \
DeviceAllow=/dev/dri rw
DeviceAllow=/dev/shm rw
DeviceAllow=/dev/nvidia0 rw
DeviceAllow=/dev/nvidiactl rw
DeviceAllow=/dev/nvidia-modeset rw
注意 每次升级宿主上的 NVIDIA 驱动程序时,都需要重启容器,并可能需要在容器内运行 ldconfig 来更新库。

访问宿主文件系统

请参见 systemd-nspawn(1) 中的 --bind--bind-ro

如果宿主和容器都是 Arch Linux,那么例如,可以共享 pacman 缓存。

# systemd-nspawn --bind=/var/cache/pacman/pkg

或者,您可以通过以下文件指定每个容器的绑定:

/etc/systemd/nspawn/my-container.nspawn
[Files]
Bind=/var/cache/pacman/pkg

请参见 #Per-container settings

要将目录绑定到容器内的不同路径,请在路径后添加冒号分隔。例如:

# systemd-nspawn --bind=/path/to/host_dir:/path/to/container_dir

#Unprivileged containers 的情况下,生成的挂载点将由 nobody 用户拥有。这可以使用 idmap 挂载选项进行修改。

# systemd-nspawn --bind=/path/to/host_dir:/path/to/container_dir:idmap

在非 systemd 系统上运行

请参见 Init#systemd-nspawn

使用 Btrfs 子卷作为容器根目录

要使用 Btrfs 子卷 作为容器根目录的模板,请使用 --template 标志。该标志会拍摄子卷的快照,并用它来填充容器的根目录。

注意 如果指定的模板路径不是子卷的根目录,则会复制**整个**树。这将非常耗时。

例如,要使用位于 /.snapshots/403/snapshot 的快照:

# systemd-nspawn --template=/.snapshots/403/snapshot -b -D my-container

其中 my-container 是为容器创建的目录名称。关机后,新创建的子卷将被保留。

使用容器的临时 Btrfs 快照

可以使用 --ephemeral-x 标志来创建容器的临时 btrfs 快照并将其用作容器根目录。在容器内运行时所做的任何更改都将丢失。例如:

# systemd-nspawn -D my-container -xb

其中 my-container 是**现有**容器或系统的目录。例如,如果 / 是一个 btrfs 子卷,可以通过执行以下操作来创建当前运行的宿主系统的临时容器:

# systemd-nspawn -D / -xb

关闭容器后,创建的 btrfs 子卷将立即删除。

在 systemd-nspawn 中运行 docker

Docker 20.10 起,可以在启用了 cgroups v2(Arch Linux 中的默认设置)的无特权 systemd-nspawn 容器中运行 Docker 容器,而不会损害安全措施,方法是禁用 cgroups 和用户命名空间。为此,请编辑 /etc/systemd/nspawn/myContainer.nspawn(如果不存在则创建),并添加以下配置。

/etc/systemd/nspawn/myContainer.nspawn
[Exec]
SystemCallFilter=@keyring bpf

然后,Docker 应该可以在容器中正常工作。

注意 上述配置会将系统调用 add_keykeyctlbpf 暴露给容器,这些调用不是命名空间的。这仍然可能存在安全风险,尽管比之前禁用用户命名空间(就像之前必须做的那样)风险低得多。

本文或本章节的准确性存在争议。

原因:/proc/sys 以读写方式绑定挂载到无特权容器中不安全。(在 Talk:Systemd-nspawn 中讨论)

使用较新版本的 systemd,您还需要以下解决方法:

/etc/systemd/nspawn/myContainer.nspawn
[Files]
Bind=/proc:/run/proc
Bind=/sys:/run/sys

有关更多详细信息,请参见 [19]

由于 overlayfs 不支持用户命名空间,并且在 systemd-nspawn 中不可用,因此默认情况下,Docker 会回退到使用效率低下的 vfs 作为其存储驱动程序,这会在每次启动容器时复制映像。可以通过使用 fuse-overlayfs 作为其存储驱动程序来解决此问题。为此,我们首先需要将 fuse 暴露给容器。

/etc/systemd/nspawn/myContainer.nspawn
[Files]
Bind=/dev/fuse

然后允许容器读取和写入设备节点。

# systemctl set-property systemd-nspawn@myContainer DeviceAllow='/dev/fuse rwm'

最后,在容器内安装 fuse-overlayfs 包。您需要重启容器才能使所有配置生效。

映射本地用户并将其主目录绑定挂载到容器中

/var/lib/machines/MyContainer/ 中创建容器,如 #Create and boot a minimal Arch Linux container 中所述。

创建带有 BindUser= 的配置文件将选定的本地用户名映射到容器中。请注意,这需要 PrivateUsers=,有关详细信息,请参见 systemd-nspawn(1)。在绑定挂载的主目录中创建的文件,无论是在容器内部还是外部,都将具有相同的 UID 和 GID。

/etc/systemd/nspawn/MyContainer.nspawn
[Exec]
User=username
PrivateUsers=pick

[Files]
BindUser=username

在配置文件中拥有 User= 会指定用于在容器内运行命令(例如交互式 shell)的默认用户。

# systemd-nspawn -M MyContainer bash

这有助于在另一个 Linux 发行版中测试 Arch Linux 包。

故障排除

execv(...) 失败:权限被拒绝

当尝试通过 systemd-nspawn -bD /path/to/container 启动容器(或在容器中执行某些操作)时,并出现以下错误:

execv(/usr/lib/systemd/systemd, /lib/systemd/systemd, /sbin/init) failed: Permission denied

即使相关文件(例如 /lib/systemd/systemd)的权限正确,这可能是因为存储容器的文件系统是以非 root 用户挂载的。例如,如果您使用 fstab 中的条目手动挂载磁盘,该条目具有 noauto,user,... 选项,systemd-nspawn 将不允许执行文件,即使它们属于 root。

TERM 中的终端类型不正确(颜色损坏)

通过 machinectl login 登录容器时,容器内的终端颜色和按键可能损坏。这可能是由于 TERM 环境变量中的终端类型不正确。环境变量不会从宿主上的 shell 继承,而是回退到 systemd 中固定的默认值(vt220),除非另有配置。要进行配置,请在容器中为 container-getty@.service systemd 服务创建一个配置覆盖,并将 TERM 设置为与您登录的宿主终端匹配的值。

/etc/systemd/system/container-getty@.service.d/term.conf
[Service]
Environment=TERM=xterm-256color

或者使用 machinectl shell。它会正确继承终端的 TERM 环境变量。

在容器内挂载 NFS 共享

本文章或章节需要扩充。

原因: 讨论页面添加了一个部分,声称有一种部分解决方法(2023 年 1 月)(在 Talk:Systemd-nspawn#A trick way to mount a NFS share with the container 中讨论)

目前(2019 年 6 月)无法实现。

参见

© . This site is unofficial and not affiliated with Arch Linux.

Content is available under GNU Free Documentation License 1.3 or later unless otherwise noted.