systemd-nspawn (简体中文)
systemd-nspawn 就像是 chroot 命令, 但是是 吃了类固醇的chroot(chroot on steroids).
systemd-nspawn 可用于在一个轻量命名空间容器中运行命令或操作系统。它比 chroot 更强大在于它完全虚拟化了文件系统层次结构、进程树、各种 IPC 子系统以及主机和域名。
systemd-nspawn 将容器中各种内核接口的访问限制为只读,像是 /sys, /proc/sys 和 /sys/fs/selinux。 网络接口和系统时钟或许不能从容器内更改,不能创建设备节点。主机系统也无法重新启动,并且可能无法从容器内加载内核模块。
安装
systemd-nspawn 是被打包进 包的一部分。
用例
在容器中创建和启动最小 Arch Linux 发行版
首先安装 .
然后,创建一个目录来保存容器。在这个用例中我们将使用 。
接下来,我们使用 pacstrap 在容器中安装一个基本的arch系统。我们至少需要安装 包组.
# pacstrap -i -c ~/MyContainer base [additional pkgs/groups]
一旦安装完成后,chroot至容器中并设置root密码:
# systemd-nspawn -D ~/MyContainer # passwd # logout
最后, 启动容器:
# systemd-nspawn -b -D ~/MyContainer
参数 将会启动这个容器(比如:以 PID=1 运行 systemd), 而不是仅仅启动一个 shell, 而参数 指定成为容器根目录的目录。
容器启动后,输入密码以"root"身份登录。
可以在容器内运行 来关闭容器。在主机端,容器可以通过 machinectl 工具进行控制。
创建 Debian 或 Ubuntu 环境
安装 和 与 中的一个或两个(当然要安装你想要的发行版的keyrings)。
systemd-container 包。在这里很容易设置 Debian 或 Ubuntu 环境:
# cd /var/lib/machines # debootstrap --include=systemd-container --components=main,universe codename container-name repository-url
对于 Debian,有效的要么是滚动式名称像是 "stable" 或 "testing",要么是发行名称如 "stretch" 或 "sid", 对于 Ubuntu,应使用"xenial"或者"zesty"等。 代码名称的完整列表位于 中。对于 Debian 镜像,"repository-url" 可以是 https://deb.debian.org/debian/ 。 对于 Ubuntu 镜像, "repository-url" 可以是 http://archive.ubuntu.com/ubuntu/ 。"repository-url" 不 应该包含尾随斜线。
与 Arch 相同,Debian 和 Ubuntu 不会让您在首次登录时无密码登录。为了设置root密码登录,要不使用"-b"参数并设置密码:
# cd /var/lib/machines # systemd-nspawn -D ./container-name # passwd # logout
编译与测试包
请参阅 Creating packages for other distributions 寻找更多用例.
管理
位于 的容器可以被 machinectl 命令所控制,它可以内部控制 单元的实例。 下的子目录对应着容器的名字, 比如 /var/lib/machines/container-name/。
默认 systemd-nspawn 选项
要明白非常重要的一点是通过 machinectl 与 启动的容器所使用的默认选项与通过 systemd-nspawn 命令手动启动的有所不同。 通过服务启动所使用的额外选项有:
- / – 管理的容器会自动搜索一个init程序,并以PID 1的形式调用它。
- 关联于 – 管理的容器获得一个虚拟网络接口,并与主机网络断开连接。 详情看 #网络 。
- – 如果内核支持,管理的容器默认使用 user_namespaces(7) 特性。 解释请看 #无特权容器 。
这些行为可以在每个容器配置文件中被覆盖, 详情看 #配置 。
machinectl
容器可以被 命令管理, 比如说,启动容器:
$ machinectl start container-name
相似的, 还有其他的子命令 , , 和 show. 详细解释请看 。
其他常见命令:
- – 显示现在正在运行的容器
- – 在一个容器中打开一个交互式登录会话
- – 在容器中打开一个交互式shell会话(这将立即调用一个用户进程,而不需要通过容器中的登录进程)
machinectl enable container-name与 – 启用或禁用容器的随开机启动,详见 #启用容器的随开机启动
machinectl 也有管理容器(或虚拟机)镜像和镜像传输的子命令。 详情见 与 。
systemd 工具链
大部分核心的 systemd 工具链已更新为对容器有效。工具们通常提供了 选项并把容器名称作为参数。 用例:
查看特定容器的 journal 日志:
# journalctl -M MyContainer
显示控制组的内容:
$ systemd-cgls -M MyContainer
查看容器的启动时间:
$ systemd-analyze -M MyContainer
查看资源使用情况的概况:
$ systemd-cgtop
配置
容器前设置
要指定容器前设置而不是全局覆盖,可以使用.nspawn文件。 详情见 systemd.nspawn(5) 。
启用容器的随开机启动
当频繁使用一个容器时,你可能希望在系统启动时启动它。
首先确保 已经 enabled。
容器被 machinectl 的可发现可以 enabled 或者 disabled。
$ machinectl enable container-name
资源控制
您可以使用控制组来使用 实现对容器的限制和资源管理,请参阅 。例如,您可能希望限制内存量或 CPU 使用率。要将容器的内存消耗限制为 2 GiB:
# systemctl set-property systemd-nspawn@myContainer.service MemoryMax=2G
或者将 CPU 时间使用率限制为大约相当于 2 个内核:
# systemctl set-property systemd-nspawn@myContainer.service CPUQuota=200%
这将在 /etc/systemd/system.control/systemd-nspawn@myContainer.service.d/ 中创建常驻文件}。
根据文档, 是控制内存消耗的首选方法,但也不会像 那样进行强制限制。您可以使用这两个选项将 作为您最后的一道防线。还要考虑到如果您不会限制容器可以“看到”的 CPU 数量,但通过限制容器相对于总 CPU 时间的最大时间,也可以获得类似的结果。
网络
systemd-nspawn 容器可以使用 主机网络 或者 私有网络:
- 在主机网络模式下,容器可以完全访问主机网络。这意味着容器将能够访问主机上的所有网络服务,来自容器的数据包将在外部网络中显示为来自主机(即共享同一IP地址)。
- 在私有网络模式下,容器与主机的网络断开连接,这使得容器无法使用所有网络接口,但环回设备和明确分配给容器的接口除外。为容器设置网络接口有多种不同的方法:
- 可以将现有接口分配给容器(例如,如果您有多个以太网设备)。
- 可以创建一个与现有接口(即VLAN接口)相关联的虚拟网络接口,并将其分配给容器。
- 可以创建主机和容器之间的虚拟以太网链接。
- 在后一种情况下,容器的网络是完全隔离的(与外部网络以及其他容器),由管理员来配置主机和容器之间的网络。这通常涉及创建一个网桥network bridge来连接多个(物理或虚拟)接口,或者在多个接口之间设置一个NATNetwork Address Translation。
主机网络模式适用于应用程序容器,它不运行任何网络软件来配置分配给容器的接口。当你从shell运行systemd-nspawn时,主机联网是默认模式。
另一方面,私有网络模式适用于应与主机系统隔离的 "系统容器"。创建虚拟以太网链路是一个非常灵活的工具,可以创建复杂的虚拟网络。这是由machinectl或启动的容器的默认模式。
下面的小节描述了常见的情况。关于可用的systemd-nspawn选项,请参见。
使用主机网络
要禁用私有网络和创建由machinectl启动的容器使用的虚拟以太网链接,请添加.nspawn文件,其中包含以下选项:
这将覆盖在中使用的-n/选项,新启动的容器将使用主机网络模式。
使用虚拟以太网链接
如果使用-n/选项启动容器,systemd-nspawn将在主机和容器之间创建一个虚拟的以太网链接。链接的主机侧将作为名为的网络接口提供。链路的容器侧将被命名为。请注意,这个选项意味着 。
当您启动容器时,必须为两个接口(主机上和容器中)分配一个 IP 地址。如果您在主机上和容器中都使用systemd-networkd,这就是开箱即用:
- 主机上的文件与接口相匹配,并启动一个DHCP服务器,该服务器为主机接口和容器分配IP地址,
- 容器中的文件与接口相匹配,并启动一个DHCP客户端,该客户端从主机接收一个IP地址。
如果不使用 systemd-networkd,可以配置静态 IP 地址或在主机接口上启动 DHCP 服务器,在容器中启动 DHCP 客户端。详情请参见Network configuration。
要让容器访问外部网络,您可以按照 Internet sharing#Enable NAT 中的描述配置 NAT。如果您使用 systemd-networkd,这将通过 中的 IPMasquerade=yes 选项自动(部分)完成。然而,这只会发出一个iptables规则,比如说:
-t nat -A POSTROUTING -s 192.168.163.192/28 -j MASQUERADE
表必须手动配置,如Internet sharing#Enable NAT所示。您可以使用通配符来匹配所有以开头的接口:
# iptables -A FORWARD -i ve-+ -o internet0 -j ACCEPT
补充,规则可能会像Internet sharing#Enable NAT中描述的那样不起作用。如果是这种情况,请尝试-A FORWARD -i ve-+ -j ACCEPT。
使用网络桥接
如果您已在主机系统上配置了网桥network bridge,则可以为容器创建一个虚拟以太网链路,并将其主机侧添加到网桥中。这可以通过选项来完成。请注意,意味着,即虚拟以太网链路是自动创建的。然而,链路的主机端将使用前缀而不是,因此用于启动DHCP服务器和IP伪装的systemd-networkd选项将不会被应用。
网桥的管理由管理员负责。例如,网桥可以用一个物理接口连接虚拟接口,也可以只连接几个容器的虚拟接口。参见systemd-networkd#Network bridge with DHCP和systemd-networkd#Network bridge with static IP addresses,了解使用systemd-networkd进行配置的例子。
还有一个--network-zone=zone-name选项,它与类似,但网桥由systemd-nspawn和systemd-networkd自动管理。当第一个用配置的容器启动时,会自动创建名为--network-zone=zone-name的网桥接口,当最后一个用--network-zone=zone-name配置的容器退出时,会自动删除。因此,这个选项可以方便地将多个相关的容器放置在一个共同的虚拟网络上。请注意,接口由systemd-networkd管理的方式与接口相同,使用文件中的选项。
使用 "macvlan" 或者 "ipvlan" 接口
您可以在现有的物理接口(即VLAN接口)上创建一个虚拟接口,并将其添加到容器中,而不是创建一个虚拟的以太网链路(其主机端可能被添加到桥接中,也可能没有)。该虚拟接口将与底层主机接口进行桥接,从而使容器暴露在外部网络中,从而使其能够通过DHCP从与主机相连的同一局域网中获得一个独特的IP地址。
systemd-nspawn 提供两个选项:
- – 虚拟接口的MAC地址将与底层物理不同,并被命名为。
--network-ipvlan=interface– 虚拟接口的MAC地址将与底层物理相同,并命名为。
所有选项都意味着 .
使用现有接口
如果主机系统有多个物理网络接口,可以使用将分配给容器(并使它在容器启动时对主机不可用)。请注意,意味着。
端口映射
当启用私有网络时,可以使用-p/选项或使用.nspawn文件中的设置将主机上的各个端口映射到容器上的端口。这可以通过向表发出iptables规则来完成,但需要手动配置表中的链,如#使用虚拟以太网链接所示。
例如,将主机上的TCP端口8000映射到容器中的TCP端口80:
域名解析
容器中的域名解析Domain name resolution可以通过systemd-nspawn的--resolv-conf选项或.nspawn文件的相应选项来配置。有很多可能的值,这些值在 中有描述。
默认值是,即:
- 如果启用了,就会保持容器中的原样。
- 否则,如果systemd-resolved在主机上运行,它的存根文件将被复制或绑定到容器中。
- 否则, 文件就会被从主机复制或绑定到容器上。
在后两种情况下,如果容器根部是可写的,则复制文件,如果是只读的,则绑定挂载。
提示和技巧
无特权容器
systemd-nspawn支持无特权的容器,不过容器需要以root身份启动。
最简单的方法是通过选项让systemd-nspawn自动选择一个未使用的UID/GID范围。
# systemd-nspawn -bUD ~/MyContainer
如果内核支持用户命名空间,选项相当于。这意味着当容器启动时,容器中的文件和目录会被chown到选定的私有 UID/GID 范围。详情请参见。
一旦用私有UID/GID范围启动了一个容器,就需要一直这样使用它,以避免权限错误。另外,也可以撤销的效果。(或)在文件系统中,通过指定一个从0开始的ID范围:
# systemd-nspawn -D ~/MyContainer --private-users=0 --private-users-chown
使用 X 环境
详情见 Xhost 和 Change root#Run graphical applications from chroot.
您需要设置您容器会话中 以连接到外部 X 服务器。
X 在 文件夹中存储一些必要的文件。为了使容器能显示任何内容,它需要访问这些文件。为此,当启动容器时,请追加 选项。
Avoiding 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",值,这会使输入匹配你的每一个显示。 更多信息请参考 。
运行 Firefox
以PID 1运行
# systemd-nspawn --setenv=DISPLAY=:0 \
--setenv=XAUTHORITY=~/.Xauthority \
--bind-ro=$HOME/.Xauthority:/root/.Xauthority \
--bind=/tmp/.X11-unix \
-D ~/containers/firefox \
firefox
或者你可以启动容器,让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
访问主机文件系统
请见 和 于 .
如果主机和容器都是 Arch Linux,则例如,可以共享 pacman 缓存:
# systemd-nspawn --bind=/var/cache/pacman/pkg
或许你还可以使用文件来进行指定的先于容器的绑定:
/etc/systemd/nspawn/''my-container''.nspawn
[Files] Bind=/var/cache/pacman/pkg
详情见 #容器前设置.
要将该目录绑定到容器内的不同路径,请添加路径,并用冒号分隔。例如:
# systemd-nspawn --bind=/path/to/host_dir:/path/to/container_dir
在非systemd系统上运行
详情见 Init#systemd-nspawn。
使用Btrfs子卷作为容器的根
要使用 Btrfs subvolume 作为容器根目录的模板,请使用 标志。这将获取子卷的快照,并以它填充容器的根目录。
例如,要使用位于的快照:
# systemd-nspawn --template=/.snapshots/403/snapshots -b -D my-container
其中是将为容器创建的目录的名称。关机后,会保留新创建的子卷。
使用容器的临时Btrfs快照
可以使用或-x标志来创建容器的临时btrfs快照,并将其作为容器的根。在容器中启动时所作的任何更改都将丢失。例如:
# systemd-nspawn -D my-container -xb
其中my-container是现有容器或系统的目录。例如,如果是一个btrfs子卷,我们可以通过以下操作创建一个当前运行的主机系统的短暂容器:
# systemd-nspawn -D / -xb
关闭容器电源后,创建的btrfs子卷会立即被删除。
在 systemd-nspawn 中运行 docker
Docker 需要 的 权限去运行容器, 由于cgroup命名空间的原因,它默认被systemd-nspawn挂载为只读。但是,通过从主机系统绑定挂载并启用必要的能力和权限,可以在systemd-nspawn容器中运行Docker。
首先,通过禁用cgroup命名空间。
然后, 编辑(或创建) 并加入下方配置。
/etc/systemd/nspawn/myContainer.nspawn
[Exec] Capability=all SystemCallFilter=add_key keyctl PrivateUsers=no [Files] Bind=/sys/fs/cgroup
这将授予容器所有的功能,禁用用户名称间隔,将两个系统列入调用和的白名单。(与内核键环有关,被Docker 所需要),以及从主机到容器的绑定挂载。编辑这些文件后,您需要关机并重新启动您的容器,它们才能生效。如果您的容器在此更改之前启用了用户命名空间(如果使用 单元,则为默认值),您还需要撤销由用户命名空间引起的权限更改以避免权限错误。详情请参见#无特权容器。
- 在systemd-nspawn里面启动Docker之前,你可能需要在主机上加载
overlay模块,才能正确使用overlay2存储驱动(Docker的默认存储驱动)。如果没有加载驱动,会导致Docker选择效率低下的驱动vfs,该驱动会为每一层Docker容器复制一切。请参考Kernel modules#Automatic module loading with systemd,了解如何自动加载模块。 - 从2020年11月起,cgroups v2 似乎会破坏systemd-nspawn内的Docker。如果你想以这种方式使用Docker,不要设置内核参数
systemd.unified_cgroup_hierarchy=1。
在无root权限的情况下使用machinectl
machined 启用了 Polkit,这就允许创建polkit规则,允许在不成为root用户的情况下执行某些操作。不同的权限在中进行了描述,所有权限都在下。
要允许名为 "foo "的用户在没有root权限的情况下执行所有操作,请添加一个规则:
另外,用户需要有管理单元的权限,才能启动和停止nspawn容器,添加这个规则就可以了:
疑难解答
Root登录失败
如果您在尝试登录时(即使用)得到以下错误:
arch-nspawn login: root Login incorrect
并且 journalctl 显示:
pam_securetty(login:auth): access denied: tty 'pts/0' is not secure !
删除容器文件系统中的和。可以选择将它们添加到中的NoExtract,以防止它们被重新安装。详情请参见。
execv(...) failed: Permission denied
当试图通过来启动容器时(或在容器中执行一些东西),出现以下错误:
execv(/usr/lib/systemd/systemd, /lib/systemd/systemd, /sbin/init) failed: Permission denied
即使有关文件的权限(即 /lib/systemd/systemd)是正确的,这也可能是由于以非root用户的身份挂载容器所在的文件系统造成的。例如,如果您在fstab中手动挂载了具有选项的磁盘,systemd-nspawn将不允许执行文件,即使它们是由root用户拥有的。
TERM中的终端类型不正确(破损的颜色)
当通过登录容器时,容器内的终端中的颜色和按键可能会被破坏。这可能是由于环境变量中的终端类型不正确。除非明确配置,否则环境变量不会从主机上的 shell 继承,而是回到 systemd 中固定的默认值 ()。要配置,在容器内为 systemd 服务创建一个配置覆盖,启动 的登录 getty,并将 设置为与您登录的主机终端匹配的值:
/etc/systemd/system/container-getty@.service.d/term.conf
[Service] Environment=TERM=xterm-256color
或者使用。它正确地继承了终端的环境变量。
在容器内挂载NFS共享
目前(2019年6月)不可能