Skip to content

Instantly share code, notes, and snippets.

@Alceatraz
Last active July 19, 2025 10:30
Show Gist options
  • Select an option

  • Save Alceatraz/b704a7f74cd79181a42121d811ad9dd1 to your computer and use it in GitHub Desktop.

Select an option

Save Alceatraz/b704a7f74cd79181a42121d811ad9dd1 to your computer and use it in GitHub Desktop.
systemd-container (nspawn) - 不会被平台绑架的容器化

本教程基于以下共识:

  • 任何对容器化工具的非标准封装都是平台绑定
  • 任何对容器化工具的标准封装都是在发明另一个Kubernetes
  • NAS是拿来用的而不是拿来玩的。天天拆装重启是不正常的使用方法

基于NAS是拿来用的,默认此教程的读者:

  • 拥有真实的存算需求
  • 拥有合理的基础设施:稳定的供电、通畅的网络、合理的性能等
  • 拥有合理的备用设备:足够的磁盘、321备份机制等

WHAT

nspawn 是 systemd 套件中 systemd-container 组件的一个工具(程序)。用于启动一个Linux容器。

WHY

你一定遇到以下的某一个问题:

  1. GUI不支持某些设置必须手动改cfg,手动改过的cfg被又被GUI覆盖掉,导致不敢重启/不敢改
  2. 高层封装的GUI工具因为某些取舍有某些奇怪的设计,看不懂具体做什么,要查文档甚至问论坛
  3. 各家系统都基于自己的GUI设计了不同的逻辑,得去学习各家的设计逻辑,操作还都不一样
  4. 系统灾难性损坏后 docker 的 overlay 难以救援 (如果机器死了用不了docker命令)
  5. 系统灾难性损坏后各家NAS的文件散乱一地,不知道哪些有用
  6. 各家系统为了留住用户故意设计不通用的工具,并且应用逻辑都不同,完全无法跨平台无感迁移

相对于以上又笨又蠢还坏的东西 systemd-container 拥有以下绝对优势:

无心智负担

nspawn的所有选项都可以以参数形式传递,而且参数所见即所得。不存在 1 / 2 / 3 问题。

  systemd-nspawn \
  --keep-unit \
  --boot \
  --machine=demo \
  --directory=/opt/demo/rootfs \
  --network-bridge=xxxxxx

无平台绑定

  1. 作为systemd套件的一部分,任何正常的Linux发行版中都内置了软件包。
  2. 基于rootfs的容器化,不存在docker的坑爹网络、compose的套件绑定、品牌NAS的操作限制。

Docker不支持网桥,要么忍着docker的烂NAT,要么macvlan在交换机里绕圈,要么就得上K8s通过LB解决 比如TrueNAS的AppChart,以前基于k3s+Helm的还有通用性,24.10换成compose以后彻底锁死 比如品牌NAS,直接把控制台做的跟桌面一样,App呈现就是个窗口,换个平台根本用不了

故不存在 4 问题。

无管理门槛

nspawn 不引入 overlay 层:

  1. 无性能开销,比如PVE的LXC默认只能使用qcow虚拟磁盘,改成rootfs需要手动改配置文件
  2. 不需要任何转换工具就可以迁移/备份/救援,可以只对特定文件备份,缩减备份体积和耗时、提高备份原子性

nspawn 可以使用自家 systemd 套件的功能:

  1. 直接使用systemd的unit功能,实现诸如后台启动、服务依赖、日志查询等功能
  2. 直接使用systemd-network,容器内网络配置极为容易。不需要iproute2那样写命令,也不需要逆天nmcli的daemon常驻

故不存在 5 / 6 问题。


开始之前

你必须先了解以下知识

  • rootfs 是什么
  • cgroup 是什么
  • cgroup 的 UID/GID 映射机制
  • 网桥 / 交换 / 路由 的网络知识
  • systemd-run / unit / service 的基础用法

相关资料

关于本教程

  • 本教程基于 Debian 12 演示,安装了Proxmox的内核和OVS,但不安装Proxmox-VE
  • nspawn支持多种网络。此处只用 --network-bridge= 网桥模式
  • nspawn支持多种user-namespacing。此处只用 --private-users=no 模式

注意: --private-users=no 不安全,只有可信的情况下才可以使用


基本启停

  1. 安装 nspawn apt install systemd-container
  2. 下载 debian bookworm x64 最新的 rootfs https://jenkins.linuxcontainers.org/view/Images/job/image-debian/lastStableBuild/architecture=amd64,release=bookworm,variant=default/
root@BTS-SRV-005:/opt/nspawn# ls -Alh
total 92M
-rw-r--r-- 1 root root 92M Jul 19 15:11 rootfs.tar.xz

root@BTS-SRV-005:/opt/nspawn/demo# ls
bin  boot  dev	etc  home  lib	lib64  media  mnt  opt	proc  root  run  sbin  srv  sys  tmp  usr  var

简单启动

执行以下命令以启动一个最简单的nspawn容器,启动完成后会进入容器的shell。

systemd-nspawn \
  --directory=/opt/nspawn/demo

在不指定boot参数的情况下,会自动在容器中启动bash,即PID 1。退出shell会导致容器停止

root@demo:~# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   7592  3596 pts/0    Ss   15:40   0:00 -bash

正常启动

正常Linux容器是当作虚拟机使用的,而不是docker风格。使用--boot参数会传递ARGS作为init的参数(也同时启动init)。启动完成后会进入容器的login

systemd-nspawn \
  --boot \
  --directory=/opt/nspawn/demo

因为没有修改密码所以无法登录。按住ctrl后按三次]以结束nspawn。

注意:按 X + EEE 或者输入 2887 没有用

修改密码

两种方法:

  1. 无 --boot 参数启动后直接 passwd 修改
  2. rootfs的优势在于外部可以直接修改内部的文件,修改 etc/shadow 文件

现在容器已经可以登录了

注册启动

systemd-container 还附带另一个工具machinectl。使用--register=yes将这个容器注册到machined,同时可以用使用--machine=指定注册的名字

systemd-nspawn \
  --boot \
  --register=yes \
  --directory=/opt/nspawn/demo

打开另一个shell。machinectl以列出容器。machinectl login demo 以终端形式链接到容器的tty。如果有init则可以直接machinectl shell demo 以跳过登录。

root@BTS-SRV-005:~# machinectl 
MACHINE CLASS     SERVICE        OS     VERSION ADDRESSES
demo    container systemd-nspawn debian 12      -        

1 machines listed.
root@BTS-SRV-005:~# machinectl shell demo
Connected to machine demo. Press ^] three times within 1s to exit session.
root@LXCNAME:~# hostname
LXCNAME

后台启动

正常使用中不可能使用用户shell来启动容器,nspawn本身不包含类似docker-daemon的设计。因为systemd本身有unit(或者systemd-run)功能,直接以临时service的形式运行。

systemd-run \
  --unit=nspawn-demo \
  -- \
systemd-nspawn \
  --keep-unit \
  --boot \
  --register=yes \
  --directory=/opt/nspawn/demo
Running as unit: nspawn-demo.service
root@BTS-SRV-005:~# journalctl -u nspawn-demo
Jul 20 20:00:00 BTS-SRV-005 systemd[1]: Started nspawn-demo.service - ......
Jul 20 20:00:00 BTS-SRV-005 systemd-nspawn[4351]: Spawning container d......

注:准确来说nspawn实现了machined,而不是附带。machined是虚拟机的管理框架,nspawn将启动的容器适配machined的接口,通过machined管理。

服务启动

同样的,改写成service文件也很简单。但是注意必须是After=network.target否则在主机网络准备好之前就启动会导致容器内网络异常。

[Unit]
After=network.target nss-lookup.target
[Install]
WantedBy=multi-user.target
[Service]
User=root
LimitNPROC=10000
LimitNOFILE=1000000
WorkingDirectory=/opt/nspawn/demo
ExecStart=systemd-nspawn \
--keep-unit \
--boot \
--machine=demo \
--directory=/opt/nspawn/demo

常见配置

我们已经成功的以服务形式启动了容器,但是还没有做任何配置。

主机网桥

一个网桥的例子

auto enp1s0
iface enp1s0 inet manual

auto enp2s0
iface enp2s0 inet manual

auto enp3s0
iface enp3s0 inet manual

auto vmbr0
iface vmbr0 inet static
    bridge_fd 0
    bridge_stp off
    bridge_waitport 0
	bridge-ports enp1s0 enp2s0 enp3s0
	address 10.0.0.10/8
	gateway 10.0.0.1

一个基于OVS的网桥例子

auto enp1s0
iface enp1s0 inet manual
	ovs_type OVSPort
	ovs_bridge vmbr0

auto enp2s0
iface enp2s0 inet manual
	ovs_type OVSPort
	ovs_bridge vmbr0

auto enp3s0
iface enp3s0 inet manual
	ovs_type OVSPort
	ovs_bridge vmbr0

auto vmbr0
iface vmbr0 inet static
	ovs_type OVSBridge
	ovs_ports enp1s0 enp2s0 enp3s0
	address 10.0.0.10/8
	gateway 10.0.0.1

接入网桥

使用--network-bridge=xxxx即可给容器分配桥接网卡

systemd-run \
  --unit=nspawn-demo \
  -- \
systemd-nspawn \
  --keep-unit \
  --boot \
  --register=yes \
  --directory=/opt/nspawn/demo \
  --network-bridge=vmbr0

nspawn网桥模式会生成一个虚拟网卡,容器内他的名字叫做 host0 (除非手动指定)

?: host0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> .......

host0的对端则是在主机系统创建了一块虚拟网卡并接入网桥

root@BTS-SRV-005:~# ip l
?: vb-demo@if2: <BROADCAST,MULTICAST,UP,LOWER_UP> ......

root@BTS-SRV-005:~# brctl show
bridge name	bridge id		    STP enabled	interfaces
vmbr0       8000.????????????   no	        eno0
                                            vb-demo

linux-container社区提供的镜像使用systemd-network配置网络。修改 /etc/systemd/network/80-container-host0.network

systemd-network的具体使用与本文无关,进一步资料自行查阅。

[Match]
Name=host0
[Network]
Address=10.0.0.10/8
Gateway=10.0.0.1

映射目录

使用 --bind--bind-ro 可以方便的将主机目录穿透进容器。

注意:此处未使用 --private-user 此时容器将不会隔离UID/GID,这是一个安全风险。

systemd-run \
  --unit=nspawn-demo \
  -- \
systemd-nspawn \
  --keep-unit \
  --boot \
  --register=yes \
  --directory=/opt/nspawn/demo \
  --network-bridge=vmbr0 \
  --bind-ro=/opt/acme:/nginx/cert \
  --bind=/opt/nginx:/nginx \
  --bind=/host-folder:/in-contianer

增加权限

如果想在nspawn内嵌套使用cgroup,比如运行docker或者k3s之类的工具。则需要:

  1. 穿透 cgroup
  2. 增加 capabilities

注意:此处使用了 --capability=all 而不是最小权限,这是一个安全风险。

systemd-run \
  --unit=nspawn-demo \
  -- \
systemd-nspawn \
  --keep-unit \
  --boot \
  --register=yes \
  --directory=/opt/nspawn/demo \
  --network-bridge=vmbr0 \
  --capability=all \
  --system-call-filter='add_key keyctl bpf'
  --bind=/dev/kmsg \
  --bind-ro=/sys/module \
  --inaccessible=/sys/module/apparmor
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment