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 共用了一套管理模型;如果需求主要依赖丰富镜像、跨运行时兼容或成熟的容器编排工具,它未必是更轻量的替代品。












暂无评论内容