你所热爱的,就是你的生活背景音乐 - 关关于友链

基于 systemd-nspawn 的轻量化容器搭建与网络配置

swwind

有时候需要搞一个 Ubuntu 的环境来做一些实验,或者需要一个沙箱来跑毒瘤程序,相比于搬出 VirtualBox 等大家伙,使用基于 systemd-nspawn 的轻量化的容器在性能上会优秀不少。

创建容器

可以参考 Arch Wiki 上的样例,在 /var/lib/machines/ 中创建容器。

如果你不想创建在 /var/lib/machines/ 里面也没事,但是后面的程序会默认在这个地方找文件,所以在别的地方创建完了之后必须软链接到这里。下文均以在 /var/lib/machines/ 中创建 <container-name> 容器为例,请将 <container-name> 替换为自定义名称。

Arch

需要先安装 arch-install-scripts

sudo mkdir /var/lib/machines/<container-name>
sudo pacstrap -K -c /var/lib/machines/<container-name> base

Debian / Ubuntu

需要先安装 debootstrapdebian-archive-keyring(或者 ubuntu-keyring)。

cd /var/lib/machines
sudo debootstrap \
  --include=dbus-broker,systemd-container \
  --components=main,universe \
  <codename> <container-name> <repository-url>

其中 codename 是发行版代号,可以在其他地方找到。

repository-url 可以设置为 tuna 的镜像地址(去掉最后的 /)。

https://mirrors.tuna.tsinghua.edu.cn/debian/
https://mirrors.tuna.tsinghua.edu.cn/ubuntu/

配置网络

网络配置是一个很复杂的事情,很多时候我也不知道发生了什么,不过事情看起来都可以正常工作。

我的目标配置如下:主机上创建一个虚拟网卡,掌管 192.168.26.0/24 的 NAT 网络,所有容器通过该网卡进行上网和与其他容器通信。

主机创建虚拟网卡

使用 ip 工具创建一个 nat0 虚拟网卡并给予地址。

sudo ip link add nat0 type bridge
sudo ip addr add 192.168.26.1/24 dev nat0
sudo ip link set nat0 up

配置 iptables 路由

首先开启系统的 IPv4 转发功能。

sudo sysctl -w net.ipv4.conf.all.forwarding=1
sudo sysctl -w net.ipv4.conf.default.forwarding=1

然后写入 iptables 规则。

# 允许 DHCP 和 DNS 数据包进入本机(不过好像默认都是开的)
sudo iptables -I INPUT -i nat0 -p udp --dport 67 -j ACCEPT
sudo iptables -I INPUT -i nat0 -p tcp --dport 67 -j ACCEPT
sudo iptables -I INPUT -i nat0 -p udp --dport 53 -j ACCEPT
sudo iptables -I INPUT -i nat0 -p tcp --dport 53 -j ACCEPT
# 允许转发
sudo iptables -I FORWARD -i nat0 -j ACCEPT
sudo iptables -I FORWARD -o nat0 -j ACCEPT
# 设置网络地址转换
sudo iptables -t nat -A POSTROUTING -s 192.168.26.0/24 -j MASQUERADE

开启 DHCP & DNS 服务器

首先需要安装 dnsmasq

# 开启 DHCP & DNS 服务
sudo dnsmasq --interface=nat0 --except-interface=lo \
  --dhcp-range=192.168.26.20,192.168.26.100,12h \
  --listen-address=192.168.26.1 --port=53 \
  --pid-file=/var/run/nat0-dnsmasq.pid

dnsmasq 会自动读取 /etc/resolv.conf 并将 DNS 请求转发到上游的 DNS 服务器。

配置 nspawn 容器

编辑 /etc/systemd/nspawn/<container-name>.nspawn 文件,添加以下内容。

[Network]
Bridge=nat0

启动

我们默认 root 是没有设置密码的,如果想要设置 root 密码可以如下操作:

# 进入容器但不启动(类似 chroot)
sudo systemd-nspawn -D /var/lib/machines/<container-name>

# 设置 root 密码
passwd

完成之后可以连按三次 Ctrl + ] 退出。

管理工具

可以使用 machinectl 来管理所有的容器。

# 启动
sudo machinectl start <container-name>
# 停止
sudo machinectl stop <container-name>
# 打开终端(类似 TTY)
sudo machinectl login <container-name>
# 免密码打开 shell(root 就是可以为所欲为)
sudo machinectl shell <user>@<container-name>

容器内网络配置

使用 ip a 来查看容器内的所有网络设备,如果没有任何问题,你将可以看到 lo 和另外一个 host0@if5 的设备(数字可能不太一样)。

一般来说,直接启动 systemd-networkd.service 就可以自动配置 ip 地址。

sudo systemctl enable --now systemd-networkd.service

DNS 服务器可能需要手动配置,直接编辑 /etc/resolv.conf 并添加 nameserver 192.168.26.1 即可。

echo "nameserver 192.168.26.1" > /etc/resolv.conf

最后直接使用 ping www.bilibili.com,应该可以正常 DNS 解析与路由数据包。

其他问题

IPv6

参考阅读 SLAAC 环境下的 IPv6 桥接与中继 - Menci's Blog

取决于你本机获取的 IPv6 地址段大小,有下面几种情况:

  1. 本机获取的是一整段 /64 地址,即上层路由器会将整段地址的数据包都路由到本机,比较常见在家庭宽带的路由器中;
  2. 本机获取到的只有一个 /128 地址,即路由器只会将目标地址完全匹配的数据包路由到本机,比较常见在校园网和其他环境中。

如果你不知道你是什么情况,可以主动尝试添加一个 IPv6 地址,并检查数据包能否回到本机。

假设你使用 eno1 网卡进行上网,使用 ip addr show dev eno1 | grep inet6 可以查看到该设备的 IPv6 地址。

inet6 2001:db8:1234:5678:1111:2222:3333:4444/64 scope global dynamic noprefixroute
inet6 fe80::6b12:c8e3:92d1:9ced/64 scope link noprefixroute

可以看到上面的 2001:db8:1234:5678:1111:2222:3333:4444/64 即是本机的公网 IPv6 地址。

之后保留前面的 64 位地址不变,将后 64 位地址替换成任意其他数字,添加到该网卡中。

sudo ip addr add 2001:db8:1234:5678::1/64 dev eno1

然后使用 ping -I <addr> <domain> 验证使用该地址的数据包能否返回。

ping -I 2001:db8:1234:5678::1 www.bilibili.com

如果能够返回,则说明分配给本机的是整个网段,否则说明分配给本机的只是单独的一个地址。

当然你也可以直接假定是第二种情况,除非你在路由器上面跑 nspawn。

可以用整个网段

针对第一种情况,我们直接在 dnsmasq 中启用 RA 功能分发本网段的地址即可。

我没有折腾过,你可能需要自己查阅文档。

当然,如果你不想给容器分配公网地址,你也可以用下面的方式。

只能用一个地址

针对这种情况也有很多解决办法,这里给出一种基于 NAT6 的地址分配方式。

首先开启系统的 IPv6 转发功能。

sudo sysctl -w net.ipv6.conf.all.forwarding=1
sudo sysctl -w net.ipv6.conf.default.forwarding=1

然后随便找一个 IPv6 的保留地址段(以 fc00::/7 开头,例如 fd23::/64),在创建 nat0 的时候顺便给予其该地址段。

sudo ip addr add fd23::1/64 dev nat0

之后配置 iptables 的时候加入关于 IPv6 的网络地址转换功能。

# 允许转发
sudo ip6tables -I FORWARD -i nat0 -j ACCEPT
sudo ip6tables -I FORWARD -o nat0 -j ACCEPT
# 设置网络地址转换
sudo ip6tables -t nat -A POSTROUTING -s fd23::/64 -j MASQUERADE

最后启动 dnsmasq 的时候加入以下参数来自动分配 IPv6 地址。

--dhcp-range=fd23::/64,ra-stateless,ra-names

之后重启容器,就可以看到容器被自动分配了 fd23::/64 的地址,并且可以通过网络地址转换功能接入 IPv6 网络。

当然,你可以删除上文中的 IPv4 相关内容,就可以获得一个 IPv6 Only 的容器。但是没有什么用

ping 不能用

Arch Linux 下的 ping 工具貌似有些小问题,可能会像这样报错:

ping: socktype: SOCK_RAW
ping: socket: Operation not permitted
ping: => missing cap_net_raw+p capability or setuid?

可以使用 setcap/usr/bin/ping 添加缺少的 capability。

sudo setcap 'cap_net_raw+p' /usr/bin/ping

留言

给文章一个评价吧!

如果过程中有遇到任何问题,可以留言讨论。

少女祈祷中...