Systemd-nspawn使用记录
参考:
https://wiki.archlinuxcn.org/wiki/Systemd-nspawn
为什么我不用 Docker?
因为我的需求是在我个人主机上跑一个完整的 Linux 小系统,里面运行几个服务,并不是作为生产环境部署微服务,所以 Docker 抽象出来的分层镜像对我的场景没啥太大的作用,我觉得反而还是一种负担 ();我也不愿意 Docker 修改我主机上的任何配置即使我能够修好 (启动Docker后会破坏KVM的桥接网络)
介绍
Systemd-nspawn 跟 chroot 命令类似,是个终极版的 chroot。
systemd-nspawn 可以在轻量的命名空间容器中运行命令或系统。它比 chroot 强大的地方是,它完全虚拟了文件系统层次结构、进程树、各种 IPC 子系统以及主机名。
systemd-nspawn 将容器中各种内核接口的访问限制为只读,像是 /sys, /proc/sys 和 /sys/fs/selinux。网络接口和系统时钟不能从容器内更改,不能创建设备节点。不能从容器中重启宿主机,也不能加载内核模块。
相比 LXC 或 Libvirt, systemd-nspawn 更容易配置。
创建并启动一个最小的 Arch Linux 容器
需要先下载 arch-install-scripts 用于下载一个基本的 Arch 系统。其中至少需要安装包组 base 包。后续需要啥包可以进入容器完成安装。(最好下个 nvim)
1 | sudo pacstrap -K -c ~/MyContainer base [additional packages/groups] |
一旦安装完成后,进入容器中并设置 root 密码:
1 | sudo systemd-nspawn -D ~/MyContainer |
最后, 启动容器:
1 | sudo systemd-nspawn -b -D ~/MyContainer |
参数 -b 将会启动这个容器(比如:以 PID=1 运行 systemd), 而不是仅仅启动一个 shell, 而参数 -D 指定成为容器根目录的目录。
容器启动后,输入密码以 “root” 身份登录。
可以在容器内运行 poweroff 来关闭容器。在主机端,容器可以通过 machinectl 工具进行控制。
注意:要从容器内终止 session,请按住 Ctrl 并快速地按 ] 三下。
管理
默认 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
这些行为可以在每个容器配置文件中被覆盖, 详情看 #配置。
配置
网络
systemd-nspawn 容器可以使用 主机网络 或者 私有网络 _:
- 在主机网络模式下,容器可以完全访问主机网络。这意味着容器将能够访问主机上的所有网络服务,来自容器的数据包将在外部网络中显示为来自主机(即共享同一 IP 地址)。
- 在私有网络模式下,容器与主机的网络断开连接,这使得容器无法使用所有网络接口,但环回设备和明确分配给容器的接口除外。为容器设置网络接口有多种不同的方法:
- 可以将现有接口分配给容器(例如,如果您有多个以太网设备)。
- 可以创建一个与现有接口(即 VLAN 接口)相关联的虚拟网络接口,并将其分配给容器。
- 可以创建主机和容器之间的虚拟以太网链接。
在后一种情况下,容器的网络是完全隔离的(与外部网络以及其他容器),由管理员来配置主机和容器之间的网络。这通常涉及创建一个网桥 network bridge 来连接多个(物理或虚拟)接口,或者在多个接口之间设置一个 NATNetwork Address Translation。
主机网络模式适用于应用程序容器 ,它不运行任何网络软件来配置分配给容器的接口。当你从 shell 运行 systemd-nspawn 时,主机联网是默认模式。
另一方面,私有网络模式适用于应与主机系统隔离的 “系统容器”。创建虚拟以太网链路是一个非常灵活的工具,可以创建复杂的虚拟网络。这是由 machinectl 或 systemd-nspawn@.service 启动的容器的默认模式。
不同的启动方式,会导致网络的模式不同
使用 “macvlan” 或者 “ipvlan” 接口
您可以在现有的物理接口(即 VLAN 接口)上创建一个虚拟接口,并将其添加到容器中,而不是创建一个虚拟的以太网链路(其主机端可能被添加到桥接中,也可能没有)。该虚拟接口将与底层主机接口进行桥接,从而使容器暴露在外部网络中,从而使其能够通过 DHCP 从与主机相连的同一局域网中获得一个独特的 IP 地址。
systemd-nspawn 提供两个选项:
--network-macvlan=<interface>– 虚拟接口的 MAC 地址将与底层物理interface不同,并被命名为mv-interface。--network-ipvlan=<interface>– 虚拟接口的 MAC 地址将与底层物理interface相同,并命名为iv-interface。
所有选项都意味着 --private-network.
实践
我用的无线网卡,想通过 ipvlan 模式为容器配置独立网络。用的 shell 启动,所以需要显式设置参数。
先查看网卡名:
1 | ip addr |
假设无线网卡名为 wlp4s0,启动容器:
1 | sudo systemd-nspawn \ |
进入容器后,查看网络接口。会发现一个名为 iv-wlp4s0(格式通常为 iv-<父接口名>)的接口已存在,但处于 unmanaged 状态,未被 systemd-networkd 管理:
1 | ip link show |
1 | ip addr |
1 | networkctl status iv-wlp4s0 |
开始配置网络:
先用宿主机查看这个局域网网关是哪个:(假设网关地址为 192.168.233.43)
1 | ip route |
在容器内创建并编辑网络配置文件,例如 /etc/systemd/network/80-ipvlan.network:
1 | nvim /etc/systemd/network/80-ipvlan.network |
重启 systemd-networkd 让它接管接口,重载配置:
1 | systemctl restart systemd-networkd |
有时候可能会需要一个静态的 ipv4/ipv6,依旧可以在网络配置文件配置:
1 | [Match] |
重载配置后查看,已经分到静态 ip,可以尝试 ping 下网关与其他局域网设备,但要注意此时宿主机与容器是 ping 不通的(宿主机与 ipvlan 容器不通,首要原因是 ipvlan 的内核驱动设计,无线环境的特点加剧并固化了这一现象。详细原因与解决方案见文章末):
1 | networkctl status iv-wlp4s0 |
1 | ip addr |
以同样的方式开启第二个容器,检测容器之间是否能互通:
1 | ping -c 3 192.168.233.105 |
答案是可以的。
总结
使用 ipvlan 的话,容器与宿主机之间是不通的,但是容器与容器之间可以相通。
原因:
- ipvlan 的设计限制:ipvlan 子接口与父接口共享同一个 MAC 地址但处于不同的网络命名空间。内核的 ipvlan 驱动明确禁止了从子接口发往父接口自身 IP 地址的流量,以防止网络栈中出现回环和状态混乱。此限制与物理介质(有线或无线)无关,在有线网络下同样存在。
- 无线环境的叠加限制:在 Wi-Fi 环境中,大多数无线网卡驱动和接入点(AP)会默认启用“客户端隔离”并禁用“Hairpin Mode”。这进一步阻止了同一无线客户端上不同 IP(宿主机 IP 和容器 IP)之间通过无线射频路径通信,使得连通即使通过底层驱动允许也变得困难。
为什么容器之间可以直接互通?
内核为每个容器创建一个关联到主接口的 ipvlan 子接口。多个容器(网络命名空间)直接共享主机的一个物理接口,无需创建额外的桥接设备,子接口间的流量转发由内核 ipvlan 驱动直接处理,完全绕过了物理网卡的外部收发队列,从而实现了极高的转发效率。具体工作模式有两种:
- L2 模式(默认):二层交换。
- 所有 ipvlan 子接口处于同一个二层网络(相同 IP 子网)。它们共享主接口的 MAC 地址,依靠不同的 IP 地址进行区分。
- 同一主机上的容器通信:数据包在内核中直接从源容器的 ipvlan 子接口转发到目标容器的 ipvlan 子接口,不经过主接口的物理层,性能极高。
- 与外部通信:出口流量由主接口直接发出,但源 MAC 地址会被特殊处理(使用主接口 MAC)。
- L3 模式:三层路由。
- 每个 ipvlan 子接口配置在不同的三层子网。主机内核的 ipvlan 驱动自动充当这些子网间的路由器。
- 所有通信(包括同一主机上的容器间通信)都是三层路由。数据包在内核的 ipvlan 路由逻辑中完成转发,同样不经过物理网卡的对外发包环节。这提供了更好的网络隔离,并避免了二层模式下的某些 ARP 限制。
但是想要容器与宿主机连通的话也不难,加上 veth 就好了。
最后的话展示一下资源消耗:
1 | systemd-cgtop machine.slice |