systemd-nspawn
systemd-nspawn 跟 chroot 命令类似,是个终极版的 chroot。
systemd-nspawn 可以在轻量的命名空间容器中运行命令或系统。它比 chroot 强大的地方是,它完全虚拟了文件系统层次结构、进程树、各种 IPC 子系统以及主机名。
systemd-nspawn 将容器中各种内核接口的访问限制为只读,像是 /sys
, /proc/sys
和 /sys/fs/selinux
。网络接口和系统时钟不能从容器内更改,不能创建设备节点。不能从容器中重启宿主机,也不能加载内核模块。
相比 LXC 或 Libvirt, systemd-nspawn 更容易配置。
安装[编辑 | 编辑源代码]
systemd-nspawn 是 systemd 的一部分并被打进 systemd包 软件包中。
用例[编辑 | 编辑源代码]
创建并启动最小 Arch Linux 容器[编辑 | 编辑源代码]
首先安装 arch-install-scripts包。
然后,创建一个目录来保存容器。在这个用例中我们将使用 ~/MyContainer
。
接下来,我们使用 pacstrap 在容器中安装一个基本的Arch系统。其中至少需要安装包组 base包。
# pacstrap -K -c ~/MyContainer base [additional packages/groups]
一旦安装完成后,进入容器中并设置root密码:
# systemd-nspawn -D ~/MyContainer # passwd # logout
machinectl shell root@MyContainer
直接获取一个 root shell ,并不需要登录。最后, 启动容器:
# systemd-nspawn -b -D ~/MyContainer
参数 -b
将会启动这个容器(比如:以 PID=1 运行 systemd
), 而不是仅仅启动一个 shell, 而参数 -D
指定成为容器根目录的目录。
容器启动后,输入密码以"root"身份登录。
securetty
TTY 设备白名单导致的。 请查看#Root登录失败。可以在容器内运行 poweroff
来关闭容器。在主机端,容器可以通过 machinectl 工具进行控制。
Ctrl
并快速地按 ]
三下。创建 Debian 或 Ubuntu 环境[编辑 | 编辑源代码]
安装 debootstrap包 以及 debian-archive-keyring包 或 ubuntu-keyring包 中的一个或两个(当然要安装你想要的发行版的keyring)。
然后通过以下结构式调用 deboostrap:
# debootstrap codename container-name repository-url
- codename:对于 Debian,有效的代码名称要不然是稳定性别名如
stable
、testing
或unstable
,要不然就是版本名称如bookworm
或sid
。你可以从这里获取名称列表:[1]。对于 Ubuntu:只有像是jammy
或noble
这样的名称应该被使用而不是版本号。你可以从 [2] 或者 [3] 获取一份代码名称与版本号的对应表格。
- container-name 是会包含操作系统文件树的目录,如果它不存在的话将会被创建。
- repository-url:下载操作系统文件树的镜像地址。目前 Debian 发布在任何有效镜像站,比如说 CDN 支持的 https://deb.debian.org/debian 。 Ubuntu 的镜像站在 [4],比如说 http://archive.ubuntu.com/ubuntu 。
- 如果你需要一个以归档的 Debian 版本(指的是任何在2024年8月,Debian 10/Buster 之前的版本),请使用一个特殊的 debian-archive 镜像地址:https://archive.debian.org/debian/ 。Ubuntu 应该使用 https://old-releases.ubuntu.com/ubuntu/ 。
- systemd-nspawn 要求容器中的操作系统使用 systemd 作为其 init 程序(以 PID 1 运行)。从 Debian 8 (“jessie”) [5] 以及 Ubuntu 15.04 (“vivid”)[6] 之后,这是两者的默认 init 程序。但请注意,对于不包含在上述密钥包中的版本,会出现与 “未知签名密钥(unknown signing key)”相关的问题,比如任何早于 Debian 9 (“Stretch”)[7] 的发行版本。这使得后者成为成为无需额外操作即可安装的最老版本。
与 Arch 相同,Debian 和 Ubuntu 不会让您在首次登录时无密码登录。为了设置root密码登录,要不使用"-b"参数并设置密码:
# cd /var/lib/machines # systemd-nspawn -D ./container-name # passwd # logout
systemd-container
时使用 --include=
选项,这始终是 dbus 和 systemd 的依赖项:
# debootstrap --include=systemd-container codename /var/lib/machines/container-name repository-url
Create a Fedora or AlmaLinux environment[编辑 | 编辑源代码]
Install dnf包, and edit the /etc/dnf/dnf.conf
file to add the required Fedora repositories.
/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
The fedora.gpg file contain the gpg keys for the latest Fedora releases https://getfedora.org/security/. To set up a minimal Fedora 37 container:
# mkdir /var/lib/machines/container-name # dnf --releasever=37 --best --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
If you are using btrfs filesystem create a subvolume instead of creating a directory.
An Enterprise Linux derivative like AlmaLinux has three repositories enabled by default, BaseOS wich contains a core set that provides the basis for all installations, AppStream that includes additional applications, language packages, etc and Extras that contains packages not included in RHEL. So for a minimal container we only need to add the BaseOS repository to /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
To create an AlmaLinux 9 minimal container:
# 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
This will install the latest minor version of AlmaLinux 9, you can choose to install a specific point release, but you will need to change the gpgpkey entry to manually point to RPM-GPG-KEY-AlmaLinux-9
Just like Arch, Fedora or AlmaLinux will not let you log in as root without a password. To set up the root password, run systemd-nspawn without the -b
option:
# systemd-nspawn -D /var/lib/machines/container-name passwd
编译与测试包[编辑 | 编辑源代码]
请参阅 Creating packages for other distributions 寻找更多用例.
管理[编辑 | 编辑源代码]
位于 /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 选项[编辑 | 编辑源代码]
要明白非常重要的一点是通过 machinectl 与 systemd-nspawn@.service
启动的容器所使用的默认选项与通过 systemd-nspawn 命令手动启动的有所不同。 通过服务启动所使用的额外选项有:
-b
/--boot
– 管理的容器会自动搜索一个init程序,并以PID 1的形式调用它。--network-veth
关联于--private-network
– 管理的容器获得一个虚拟网络接口,并与主机网络断开连接。 详情看 #网络。-U
– 如果内核支持,管理的容器默认使用 user_namespaces(7) 特性。 解释请看 #无特权容器。--link-journal=try-guest
这些行为可以在每个容器配置文件中被覆盖, 详情看 #配置。
machinectl[编辑 | 编辑源代码]
容器可以被 machinectl subcommand container-name
命令管理, 比如说,启动容器:
$ machinectl start container-name
machinectl start container_name
时将会报错 Invalid machine name container_name
。详情请看 [9] 与 [10] 。相似的, 还有其他的子命令 poweroff
, reboot
, status
和 show
. 详细解释请看 machinectl(1) § Machine Commands 。
poweroff
and reboot
命令执行。其他常见命令:
machinectl list
– 显示现在正在运行的容器machinectl login container-name
– 在一个容器中打开一个交互式登录会话machinectl shell [username@]container-name
– 在容器中打开一个交互式shell会话(这将立即调用一个用户进程,而不需要通过容器中的登录进程)machinectl enable container-name
与machinectl disable container-name
– 启用或禁用容器的随开机启动,详见 #启用容器的随开机启动
machinectl 也有管理容器(或虚拟机)镜像和镜像传输的子命令。 详情见 machinectl(1) § Image Commands 与 machinectl(1) § Image Transfer Commands 。
systemd 工具链[编辑 | 编辑源代码]
大部分核心的 systemd 工具链已更新为对容器有效。工具们通常提供了 -M, --machine=
选项并把容器名称作为参数。
用例:
查看特定容器的 journal 日志:
# journalctl -M MyContainer
显示控制组的内容:
$ systemd-cgls -M MyContainer
查看容器的启动时间:
$ systemd-analyze -M MyContainer
查看资源使用情况的概况:
$ systemd-cgtop
配置[编辑 | 编辑源代码]
容器前设置[编辑 | 编辑源代码]
要指定容器前设置而不是全局覆盖,可以使用.nspawn文件。 详情见 systemd.nspawn(5) 。
启用容器的随开机启动[编辑 | 编辑源代码]
当频繁使用一个容器时,你可能希望在系统启动时启动它。
首先确保 machines.target
已经 enabled。
容器被 machinectl 的可发现可以 enabled 或者 disabled。
$ machinectl enable container-name
- 这样做的效果是启用
systemd-nspawn@container-name.service
systemd单元。 - 如#默认 systemd-nspawn 选项中提到的,由machinectl启动的容器会获得一个虚拟的以太网接口。要禁用私有网络,请参见#使用主机网络。
资源控制[编辑 | 编辑源代码]
您可以使用控制组来使用 systemctl set-property
实现对容器的限制和资源管理,请参阅 systemd.resource-control(5)。例如,您可能希望限制内存量或 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/
中创建常驻文件}。
根据文档,MemoryHigh
是控制内存消耗的首选方法,但也不会像 MemoryMax
那样进行强制限制。您可以使用这两个选项将 MemoryMax
作为您最后的一道防线。还要考虑到如果您不会限制容器可以“看到”的 CPU 数量,但通过限制容器相对于总 CPU 时间的最大时间,也可以获得类似的结果。
--runtime
使更改成为临时性的。您可以使用 systemd-cgtop
检查其结果。网络[编辑 | 编辑源代码]
systemd-nspawn 容器可以使用主机网络或者私有网络:
- 在主机网络模式下,容器可以完全访问主机网络。这意味着容器将能够访问主机上的所有网络服务,来自容器的数据包将在外部网络中显示为来自主机(即共享同一IP地址)。
- 在私有网络模式下,容器与主机的网络断开连接,这使得容器无法使用所有网络接口,但环回设备和明确分配给容器的接口除外。为容器设置网络接口有多种不同的方法:
- 可以将现有接口分配给容器(例如,如果您有多个以太网设备)。
- 可以创建一个与现有接口(即VLAN接口)相关联的虚拟网络接口,并将其分配给容器。
- 可以创建主机和容器之间的虚拟以太网链接。
- 在后一种情况下,容器的网络是完全隔离的(与外部网络以及其他容器),由管理员来配置主机和容器之间的网络。这通常涉及创建一个网桥network bridge来连接多个(物理或虚拟)接口,或者在多个接口之间设置一个NATNetwork Address Translation。
主机网络模式适用于应用程序容器,它不运行任何网络软件来配置分配给容器的接口。当你从shell运行systemd-nspawn时,主机联网是默认模式。
另一方面,私有网络模式适用于应与主机系统隔离的 "系统容器"。创建虚拟以太网链路是一个非常灵活的工具,可以创建复杂的虚拟网络。这是由machinectl或systemd-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-容器名称
的网络接口提供。链路的容器侧将被命名为host0
。请注意,这个选项意味着 --private-network
。
- 如果容器名称太长,接口名称将被缩短(例如
ve-long-conKQGh
而不是ve-long-container-name
),以适应15个字符的限制。全名将被设置为接口的altname
属性(见ip-link(8)),并且仍然可以用来引用接口。 - 当检查带有
ip link
的接口时,接口名会显示一个后缀,例如ve-容器名@if2
和host0@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,可以配置静态 IP 地址或在主机接口上启动 DHCP 服务器,在容器中启动 DHCP 客户端。详情请参见网络配置。
要让容器访问外部网络,您可以按照 Internet sharing#Enable NAT 中的描述配置 NAT。如果您使用 systemd-networkd,这将通过 /usr/lib/systemd/network/80-container-ve.network
中的 IPMasquerade=yes
选项自动(部分)完成。然而,这只会发出一个iptables规则,比如说:
-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
并且,为了接收DHCP服务器的入站连接,你需要在 ve-+
接口上开放 UDP 67 端口(由 systemd-networkd 操控):
# iptables -A INPUT -i ve-+ -p udp -m udp --dport 67 -j ACCEPT
使用网络桥接[编辑 | 编辑源代码]
如果您已在主机系统上配置了网桥network bridge,则可以为容器创建一个虚拟以太网链路,并将其主机侧添加到网桥中。这可以通过--network-bridge=bridge-name
选项来完成。请注意,--network-bridge
意味着--network-veth
,即虚拟以太网链路是自动创建的。然而,链路的主机端将使用vb-
前缀而不是ve-
,因此用于启动DHCP服务器和IP伪装的systemd-networkd选项将不会被应用。
网桥的管理由管理员负责。例如,网桥可以用一个物理接口连接虚拟接口,也可以只连接几个容器的虚拟接口。参见systemd-networkd#Network bridge with DHCP和systemd-networkd#Network bridge with static IP addresses,了解使用systemd-networkd进行配置的例子。
还有一个--network-zone=zone-name
选项,它与--network-bridge
类似,但网桥由systemd-nspawn和systemd-networkd自动管理。当第一个用vz-zone-name
配置的容器启动时,会自动创建名为--network-zone=zone-name
的网桥接口,当最后一个用--network-zone=zone-name
配置的容器退出时,会自动删除。因此,这个选项可以方便地将多个相关的容器放置在一个共同的虚拟网络上。请注意,vz-*
接口由systemd-networkd管理的方式与ve-*
接口相同,使用/usr/lib/systemd/network/80-container-vz.network
文件中的选项。
使用 "macvlan" 或者 "ipvlan" 接口[编辑 | 编辑源代码]
您可以在现有的物理接口(即VLAN接口)上创建一个虚拟接口,并将其添加到容器中,而不是创建一个虚拟的以太网链路(其主机端可能被添加到桥接中,也可能没有)。该虚拟接口将与底层主机接口进行桥接,从而使容器暴露在外部网络中,从而使其能够通过DHCP从与主机相连的同一局域网中获得一个独特的IP地址。
systemd-nspawn 提供两个选项:
--network-macvlan=interface
– 虚拟接口的MAC地址将与底层物理interface
不同,并被命名为mv-interface
。--network-ipvlan=interface
– 虚拟接口的MAC地址将与底层物理interface
相同,并命名为iv-interface
。
所有选项都意味着 --private-network
.
使用现有接口[编辑 | 编辑源代码]
如果主机系统有多个物理网络接口,可以使用--network-interface=interface
将interface
分配给容器(并使它在容器启动时对主机不可用)。请注意,--network-interface
意味着--private-network
。
端口映射[编辑 | 编辑源代码]
当启用私有网络时,可以使用-p
/-port
选项或使用.nspawn文件中的Port
设置将主机上的各个端口映射到容器上的端口。这可以通过向nat
表发出iptables规则来完成,但需要手动配置forward
表中的filter
链,如#使用虚拟以太网链接所示。
例如,将主机上的TCP端口8000映射到容器中的TCP端口80:
/etc/systemd/nspawn/container-name.nspawn
[Network] Port=tcp:8000:80
域名解析[编辑 | 编辑源代码]
容器中的域名解析 Domain name resolution 可以以主机系统中相同的方式配置。另外,systemd-nspawn 提供了管理容器中 /etc/resolv.conf
文件的选项:
--resolv-conf
can be used on command-lineResolvConf=
can be used in .nspawn files
这些相应的选项有许多可能的值,在 systemd-nspawn(1) § Integration Options 的描述中会提到。 默认值为 auto
,这意味着:
- 如果启用了
--private-network
,/etc/resolv.conf
就会保持容器中的原样。 - 否则,如果systemd-resolved在主机上运行,它的存根文件
resolv.conf
将被复制或绑定到容器中。 - 否则,
/etc/resolv.conf
文件就会被从主机复制或绑定到容器上。
在后两种情况下,如果容器根部是可写的,则复制文件,如果是只读的,则绑定挂载。
对于第二种 systemd-resolved 在宿主机上运行的情况, systemd-nspawn 希望它也能在容器中运行,所以容器将会从宿主中 /etc/resolv.conf
文件建立桩符号链接。如果不是这样,那么默认值 auto
将不会生效,你应该使用任意 replace-*
选项来替换这个链接。
提示和技巧[编辑 | 编辑源代码]
运行 non-shell/init 命令[编辑 | 编辑源代码]
来自 systemd-nspawn(1) § Execution_Options:
[选项]
--as-pid2
[调用] shell 或以进程ID (PID) 2 而不是 PID 1 (init) 来运行特定程序。 [...] 建议使用这种模式来调用容器中的任意命令,除非它们已经被修改为以PID 1的形式正确运行。或者换句话说:这个改变应该被用于几乎所有的命令,除非该命令是指一个init或shell的实现。
无特权容器[编辑 | 编辑源代码]
systemd-nspawn支持无特权的容器,不过容器需要以root身份启动。
最简单的方法是通过-U
选项让systemd-nspawn自动选择一个未使用的UID/GID范围。
# systemd-nspawn -bUD ~/MyContainer
如果内核支持用户命名空间,-U
选项相当于--private-users=pick --private-users-ownership=auto
。详情请参见systemd-nspawn(1) § User Namespacing Options。
如果一个容器曾使用 --private-users-ownership=chown
选项(或者在一个 -U
需要 --private-users-ownership=chown
的文件系统中)在私有UID/GID范围启动,就需要一直这样使用它以避免权限错误。另外,也可以在一个ID范围从0开始的容器文件系统中撤销--private-users-ownership=chown
的效果:
# systemd-nspawn -D ~/MyContainer --private-users=0 --private-users-ownership=chown
使用 X 环境[编辑 | 编辑源代码]
详情见 Xhost 和 Change root#Run graphical applications from chroot.
您需要设置您容器会话中 DISPLAY
以连接到外部 X 服务器。
X 在 /tmp
文件夹中存储一些必要的文件。为了使容器能显示任何内容,它需要访问这些文件。为此,当启动容器时,请追加 --bind-ro=/tmp/.X11-unix
选项。
/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",GID为65535
,会使输入匹配你的每一个显示。 更多信息请参考 Xsecurity(7) 。
使用 X 嵌套/Xephyr[编辑 | 编辑源代码]
另一个运行X应用程序并避免共享X桌面风险的简单方法是使用X嵌套。 这里的优点是完全避免了容器内应用程序和非容器内应用程序之间的交互,并且能够运行不同的 desktop environment 或 window manager,缺点是使用 Xephyr 时性能较差,并且缺乏硬件加速。 在容器外启动 Xephyr:
# Xephyr :1 -resizeable
然后在使用下面的参数启动容器:
--setenv=DISPLAY=:1 --bind-ro=/tmp/.X11-unix/X1
不需要其他绑定。
在某些情况下,你可能仍然需要在容器中手动设置 DISPLAY=:1
(主要是在与 b
一起使用时)。
运行 Firefox[编辑 | 编辑源代码]
以PID 1运行
# systemd-nspawn --setenv=DISPLAY=:0 \ --setenv=XAUTHORITY=~/.Xauthority \ --bind-ro=$HOME/.Xauthority:/root/.Xauthority \ --bind=/tmp/.X11-unix \ -D ~/containers/firefox \ --as-pid2 \ firefox
--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图形加速[编辑 | 编辑源代码]
为了启用加速的3D图形,可能有必要在 .nspawn 文件中加入以下一行,将 /dev/dri
挂载到容器中。
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
你可以通过运行 glxinfo
或 glxgears
来确认它已经被启用。
Nvidia GPUs[编辑 | 编辑源代码]
如果你不能在容器上安装与主机上相同的nvidia驱动版本,你可能需要同时绑定驱动库文件。你可以在主机上运行 pacman -Ql nvidia-utils
来查看它包含的所有文件。你不需要把所有文件都复制过来。当容器通过 machinectl start container-name
运行时,下面的systemd覆盖文件将把所有必要的文件绑定过来。
/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
ldconfig
以更新库。访问主机文件系统[编辑 | 编辑源代码]
请见 --bind
和 --bind-ro
于 systemd-nspawn(1).
如果主机和容器都是 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
如果是 #无特权容器,产生的挂载点将由nobody用户拥有。这可以通过 idmap
挂载选项来修改。
# systemd-nspawn --bind=/path/to/host_dir:/path/to/container_dir:idmap
在非systemd系统上运行[编辑 | 编辑源代码]
详情见 Init#systemd-nspawn。
使用Btrfs子卷作为容器的根[编辑 | 编辑源代码]
要使用 Btrfs subvolume 作为容器根目录的模板,请使用 --template
标志。这将获取子卷的快照,并以它填充容器的根目录。
例如,要使用位于/.snapshots/403/snapshot
的快照:
# systemd-nspawn --template=/.snapshots/403/snapshots -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=add_key keyctl bpf
然后,Docker 应该在容器内按原样工作。
由于 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包 包。你需要重新启动容器以使所有配置生效。
疑难解答[编辑 | 编辑源代码]
Root登录失败[编辑 | 编辑源代码]
如果您在尝试登录时(即使用machinectl login <name>
)得到以下错误:
arch-nspawn login: root Login incorrect
并且容器中journal
显示:
pam_securetty(login:auth): access denied: tty 'pts/0' is not secure !
只删除容器文件系统中的/etc/securetty
[16]和/usr/share/factory/etc/securetty
,或者也可以根据需要只简单地向容器文件系统中 /etc/securetty
添加所需的pty终端设备(如 pts/0
)。
但是任何改变都会在下一次启动时被覆盖,因此有必要同时在容器文件系统上的 /usr/lib/tmpfiles.d/arch.conf
文件中删除 /etc/securetty
条目,见FS#63236。如果你选择删除,也可以在 /etc/pacman.conf
中选择将这些文件列入黑名单(NoExtract),以防止它们被重新安装。详见 FS#45903。
execv(...) failed: Permission denied[编辑 | 编辑源代码]
当试图通过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 服务创建一个配置覆盖,启动 machinectl login
的登录 getty,并将 TERM
设置为与您登录的主机终端匹配的值:
/etc/systemd/system/container-getty@.service.d/term.conf
[Service] Environment=TERM=xterm-256color
或者使用machinectl shell
。它正确地继承了终端的TERM
环境变量。
在容器内挂载NFS共享[编辑 | 编辑源代码]
目前(2019年6月)还不可能
曲线救国的方式是使用主机挂载NFS后通过目录映射到容器目录,NFS挂载方式请查看 NFS#Client。挂载后会遇到权限错误,这是因为容器的用户ID是通过主机映射过去的,详情请查看:[17], 解决这个问题的方法有很多种,如下面几种:
- 在主机上使用管理员权限将NFS挂载目录所属用户ID修改为容器所对应的实际用户ID。
- 在容器启动时使用
--private-users
参数指定使用用户作为特权用户启动。 - 在挂载目录时使用
--bind-user
参数指定NFS目录所属用户映射到容器中。