systemd-nspawn 容器的挂载、网络与自启配置

systemd-nspawn 容器的挂载、网络和自启配置要同时核对。挂载必须写成 --bind=SOURCE:DEST,并确保源目录存在、目标目录正确且容器进程有权限访问;需要长期保留的绑定应写入 .nspawn 文件。使用 veth 时,网络配置不能只看接口是否出现,必须把接口接入 bridge,再由 DHCP 或静态地址配置补齐地址、路由和 DNS。开机自启主要依赖宿主机上的 systemd-nspawn@.service,容器内部的应用服务则由容器内的 systemd 单独管理。

排查时,宿主机、容器内和 unit 日志要一起看。以下命令可按检查结果缩小故障范围:

# 宿主机
systemd-nspawn --version
sudo machinectl list
sudo systemctl status systemd-nspawn@demo.service --no-pager
sudo journalctl -u systemd-nspawn@demo.service -b --no-pager

# 容器内
findmnt
ip -br addr
ip route
getent hosts example.com

如果“目录为空”,先核对 --bind 的源路径、目标路径和实际挂载结果;如果“只有 lo”,先查网络模式、veth 和 bridge;如果“有 IP 无域名”,单独检查 DNS;如果 unit 启动失败,直接读 journal,不要先重建容器。

本文的 .nspawn 字段以 systemd 254 手册中的 systemd-nspawn(1)、systemd.nspawn(5) 和 systemd-nspawn@.service(5) 为基准;不同发行版可能 backport、裁剪或使用不同的软件包名、网络管理器和 SELinux 策略,应以本机 systemd-nspawn --version 与 man systemd.nspawn 的结果为准。 参考资料中的 2025 年文章以命令巡览的方式整理 nspawn,适合用来查参数;排障仍要沿着“挂载、地址、路由、DNS、unit 日志”逐层缩小范围。

--bind 后目录还是空的,先查哪几处?

绑定挂载共享同一份目录内容,不会把目录复制到目标位置。源路径应使用宿主机上的绝对路径,目标路径应使用容器内的绝对路径:

sudo test -d /srv/app
sudo stat -c '%u:%g %a %n' /srv/app
sudo mkdir -p /path/to/rootfs/workspace/app

sudo systemd-nspawn -D /path/to/rootfs \
  --bind=/srv/app:/workspace/app

进入容器后,不要只执行 ls,先确认目标是否真的成为挂载点:

findmnt -T /workspace/app -o SOURCE,TARGET,FSTYPE,OPTIONS
ls -ld /workspace/app

findmnt 没有显示这个目标,说明当前启动方式没有应用该绑定,或者 .nspawn 中没有对应设置;如果显示了宿主机 /srv/app,但目录为空,通常是源目录本身为空,而不是绑定未生效。

权限问题也容易被误判为“挂载为空”。宿主机的普通用户能够读取,不代表容器内的 root 或普通用户也能读取。目录的每个父目录都需要具备执行权限,文件所有权则取决于容器进程的 UID、GID 以及是否启用了用户命名空间。分别比较宿主机和容器内的 UID、GID:

# 宿主机
stat -c '%u:%g %a %n' /srv/app

# 容器内
stat -c '%u:%g %a %n' /workspace/app

只读绑定适合配置文件:

sudo systemd-nspawn -D /path/to/rootfs \
  --bind-ro=/srv/app/config:/etc/app

只读挂载会让需要写配置、生成缓存或创建临时文件的应用失败;可写绑定中的修改则会直接反映到宿主机源目录。部署前应根据应用的实际写入路径选择,而不是把所有目录都设成可写。

Fedora 等启用 SELinux 的系统还要单独检查标签。普通的 chmod 无法解决 SELinux 拒绝访问的问题:

getenforce
ls -Zd /srv/app
sudo ausearch -m AVC -ts recent

如果策略数据库已经为该路径定义了适用的文件上下文,可以重新应用:

sudo semanage fcontext -l | grep -F '/srv/app'
sudo restorecon -Rv /srv/app

若查询不到对应规则,就需要按目标 SELinux 策略为容器文件指定受支持的上下文,不能把 container_file_t 或其他类型当成所有发行版通用的答案。restorecon 也只对已有策略规则生效。

精简 rootfs 时还有一个容易造成事故的习惯:删除或替换 usr/、var/、etc/ 时必须先进入解包目录,并使用相对路径。带前导 / 的路径指向的是宿主机根目录,不是 rootfs 内部:

cd /path/to/rootfs || exit 1
test "$(pwd -P)" = "/path/to/rootfs" || exit 1
rm -rf -- ./usr ./var ./etc

上面只适用于明确要替换这些目录的重建流程,不应直接用于现有容器;很多 /usr 内容是动态链接器、shell 和 systemd 启动所必需的。

veth 有了却拿不到 IP,先看网络模式还是 bridge?

先确定容器需要共享宿主机网络,还是加入独立网络。不同模式对应不同的隔离程度、连接方式和配置前提,应根据容器是否需要独立网络命名空间以及是否经由 bridge 上网来选择参数。

需求 常用参数 主要限制
直接共享宿主机网络 --network-host 容器可以看到宿主机网络接口,隔离最弱
独立网络命名空间,经 bridge 上网 --network-veth --network-bridge=br0 bridge、地址、网关和 DNS 都要另行配置
接入物理网卡所在的二层网络 --network-macvlan=eth0 宿主机通常不能直接访问 macvlan 子接口,Wi-Fi 场景需验证
使用 ipvlan --network-ipvlan=eth0 宿主机回连能力取决于模式和内核网络配置
使用命名的 network zone --network-zone=zone-name 需要匹配目标网络管理器对 zone 的配置

典型的 bridge 模式是:

sudo systemd-nspawn -D /path/to/rootfs \
  --network-veth --network-bridge=br0

这个命令的前提是 br0 已经由宿主机选定的网络管理器创建并配置。不要同时让 NetworkManager、systemd-networkd 和手工的 ip link 脚本争抢同一个 bridge。先在宿主机检查:

ip -br link show br0
bridge link show

bridge 本身只负责转发二层帧,不会自动给容器分配地址,也不会自动提供 NAT。容器里只有 eth0 却没有 IP,通常是 DHCP 客户端没有运行或没有收到租约,或者 .network 文件没有匹配到实际网卡名。

如果容器使用 systemd-networkd,可以在 rootfs 中创建例如 /etc/systemd/network/20-eth0.network:

[Match]
Name=eth0

[Network]
DHCP=yes

然后确认容器内的 networkd 已启用:

sudo machinectl shell demo /usr/bin/systemctl enable --now systemd-networkd

这要求 rootfs 本身包含 systemd-networkd 和 systemd。没有可用的 DHCP 服务时,可以改用静态配置,例如:

[Match]
Name=eth0

[Network]
Address=10.20.0.10/24
Gateway=10.20.0.1
DNS=10.20.0.1

这里的地址、网关和 DNS 必须按目标 bridge 的实际配置替换,10.20.0.0/24 只是格式示例。

进入容器后按地址、路由、DNS 的顺序检查:

ip -br link
ip -br addr
ip route

只有 lo 说明网卡没有创建、没有被放进容器网络命名空间,或者容器内网络服务尚未配置;有 IP 但没有默认路由,说明静态配置缺 Gateway,或者 DHCP 没有下发路由;有地址和路由但域名失败,则应转到 DNS 分支。

“容器有 IP,但宿主机或外部机器访问不了”也不能直接归因于 nspawn。bridge 场景要继续检查宿主机是否拥有通往容器网段的路由、防火墙是否放行,以及外部访问是否需要 NAT。macvlan 下宿主机通常无法直接访问容器接口,这是该模式常见的回连限制。Wi-Fi 网关对额外 MAC、macvlan 和 bridge 的支持取决于 AP 与驱动,不能直接照搬有线网络方案,必须在目标 Wi-Fi 环境验证。

IP 正常但域名解析失败,DNS 不要和路由混在一起

“能取得 IP”“能访问 IP”和“能解析域名”要分开检查。前两层已经正常时,不必继续改 bridge 或路由,直接检查解析链路:

cat /etc/resolv.conf
readlink -f /etc/resolv.conf
resolvectl status
getent hosts example.com

resolvectl 只有在宿主机或容器实际运行 systemd-resolved 时才有意义;容器没有该服务时,命令不存在或没有有效输出并不奇怪。

最常见的坑是复制了宿主机 /etc/resolv.conf,却把其中的 127.0.0.53 也带进容器。这个地址通常指向 systemd-resolved 的本地 stub。如果容器内没有运行 resolved,它就不是可用的 DNS 服务器。Ubuntu rootfs 的临时做法是先检查宿主机文件,再复制其中包含可用 nameserver 的内容:

cat /etc/resolv.conf
sudo rm -f /path/to/rootfs/etc/resolv.conf
sudo cp -L /etc/resolv.conf /path/to/rootfs/etc/resolv.conf

这适合临时验证,不是可靠的长期方案:宿主机 resolv.conf 可能由 DHCP、resolved 或其他工具动态生成,内容也会过期。长期配置应优先让容器自己的 DHCP 或静态网络配置提供 DNS,并在容器内运行 resolved 时使用正确的 stub 配置。参考资料中的 ARM Ubuntu 示例也采用了复制宿主机 resolv.conf 的做法,但只能视为临时排障步骤。

systemd 254 的 .nspawn 手册提供了 [Network] ResolvConf= 字段,用于控制 nspawn 对容器解析配置的接管方式;它和 DNS=、DNSSearch= 等字段的含义不同,具体取值应按当前版本手册确认,不要混用旧教程里的字段和值。

每次都敲参数容易漏,怎样把配置放进 .nspawn?

.nspawn 用来保存机器的长期配置,machinectl 用来观察和操作已经注册的容器,两者职责不同。

systemd 254 手册列出的配置搜索路径包括 /etc/systemd/nspawn/、/run/systemd/nspawn/ 和 /var/lib/machines/.nspawn/。生产配置通常放在 /etc/systemd/nspawn/<机器名>.nspawn;同名设置会跨文件合并,而不是简单地用后一个文件覆盖整个前一个文件。目录之间有固定优先级,同一目录内重复设置则由后加载的文件覆盖前值,具体合并顺序以本机手册为准。

例如创建:

sudo install -d -m 0755 /etc/systemd/nspawn
sudoedit /etc/systemd/nspawn/demo.nspawn

使用 systemd 254 的字段,可以写成:

[Files]
BindPaths=/srv/app:/workspace/app
BindReadOnlyPaths=/srv/app/config:/etc/app

[Network]
VirtualEthernet=yes
Bridge=br0

这里的 BindPaths=、BindReadOnlyPaths=、VirtualEthernet= 和 Bridge= 分别对应相应的命令行参数。不要把配置文件名和机器名混用:例如机器名为 demo,文件通常就应命名为 demo.nspawn。

手工启动时,可以显式给出 rootfs 和机器名:

sudo systemd-nspawn \
  --machine=demo \
  --directory=/var/lib/machines/demo \
  --boot

随后检查 systemd 是否已注册它:

sudo machinectl list
sudo machinectl shell demo

machinectl shell 能进入容器,只证明机器注册和基本通信正常,不代表容器内的应用服务已经启动。

命令行参数和 .nspawn 同时存在时,最终优先级还受 --settings= 模式影响,不能简单假设“命令行一定覆盖文件”。最可靠的检查方法是同时看 unit 实际启动参数和容器内状态。服务已经启动后,可以这样读取 nspawn 进程的命令行:

pid="$(systemctl show -p MainPID --value systemd-nspawn@demo.service)"
sudo tr '\0' ' ' </proc/"$pid"/cmdline

再用 findmnt -T 和 ip -br addr 验证挂载与网卡是否真的生效。配置里写了 BindPaths=,但实际挂载不存在,通常就是机器名、配置路径、参数覆盖模式或启动参数不一致。

systemctl start 后容器没常驻或挂载丢失,先查 unit 与启动参数

排查容器启动故障时,先区分三个层次:宿主机 systemd 管理 systemd-nspawn@demo.service,该 unit 管理 nspawn 进程和容器生命周期;容器内部的 systemd 则负责管理应用 unit。

先确认目标发行版是否提供模板 unit,并查看它在本机究竟怎样启动:

systemctl list-unit-files 'systemd-nspawn@*'
systemctl cat systemd-nspawn@demo.service
sudo systemctl status systemd-nspawn@demo.service --no-pager
sudo journalctl -u systemd-nspawn@demo.service -b --no-pager

官方模板通常把实例名映射为机器名,并从 /var/lib/machines/<实例名> 查找 rootfs。若目录实际位于其他位置,应先查看本机 unit 的 ExecStart,再使用该版本支持的 service drop-in 或配置方式调整,不要直接抄一段通用 ExecStart 把模板原有的 --boot、--link-journal、--settings 等参数覆盖掉。

标准部署路径下可以这样启用:

sudo systemctl enable --now systemd-nspawn@demo.service
sudo systemctl status systemd-nspawn@demo.service --no-pager

手工执行 systemd-nspawn 成功而 unit 失败时,优先比较两边的实际参数、rootfs 路径、工作目录、运行用户和挂载时序。unit 运行时通常比交互式 shell 启动得更早,此时源目录所在文件系统如果尚未挂载,绑定就会失败。

需要等待网络或挂载就绪时,可以增加 drop-in:

sudo systemctl edit systemd-nspawn@demo.service

写入:

[Unit]
Wants=network-online.target
After=network-online.target
RequiresMountsFor=/srv/app

RequiresMountsFor=/srv/app 会为 /srv/app 对应的挂载建立依赖关系;network-online.target 只有在系统确实启用了相应的 wait-online 机制,并且目标 bridge 被网络管理器视为 online 时才有意义。它不是“等待任意网卡出现”的通用开关。

容器启动后立即退出时,先读宿主机 journal,判断是 nspawn 本身退出、rootfs 内没有可用的 PID 1,还是容器启动后内部服务失败。容器内应用由容器自己的 systemd 管理,可以在宿主机上直接操作:

sudo machinectl shell demo /usr/bin/systemctl enable --now app.service
sudo journalctl -M demo -u app.service -b --no-pager

宿主机 unit 反复重启时,journalctl -u systemd-nspawn@demo.service 用来定位容器生命周期问题;journalctl -M demo 才是查看容器内部日志的入口。machinectl shell 成功也不等于业务服务健康,最终仍要检查目标 unit 的状态和日志。

Exec format error 在跨架构运行时出现,先查 binfmt_misc

这个问题只在宿主机与容器架构不同时排查。原生 ARM 宿主机运行 ARM 容器不需要安装 QEMU 解释器。

Ubuntu、Debian 系可以安装参考资料中的软件包:

sudo apt install -y systemd-container qemu-user-static binfmt-support

参考做法还会把静态 QEMU 解释器放进 rootfs:

sudo cp /usr/bin/qemu-aarch64-static /path/to/rootfs/usr/bin/

随后检查 binfmt_misc 注册结果。参考资料中的真实输出是:

$ cat /proc/sys/fs/binfmt_misc/qemu-aarch64
interpreter /usr/bin/qemu-aarch64-static
magic 7f454c460201010000000000000000000200b7

如果输出文件不存在、解释器路径在容器内失效,或者 magic 与目标二进制不匹配,应先修复 binfmt 注册,再验证容器内程序。Ubuntu、Debian 系还可以检查负责注册解释器的服务:

sudo systemctl status systemd-binfmt.service --no-pager

systemd-container、qemu-user-static 和 binfmt-support 是参考资料中的 Debian、Ubuntu 系用法,其他发行版不能照搬包名;有些系统需要单独启用或安装 qemu-user-binfmt,有些则由其他机制完成注册。

systemd-nspawn 的一个特点是与宿主机 systemd 共用了一套管理模型;如果需求主要依赖丰富镜像、跨运行时兼容或成熟的容器编排工具,它未必是更轻量的替代品。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容