目录 
 
- 前言
 
 - 需求
 
 - 精简容器体积
 
 - 创建目录结构
 
 - 测试容器是否正常启动
 
 - 创建并测试容器的独立网络
 
 - 以systemd服务来管理
 
 - 通过wifi连接网关的容器配置
 
 - 其他说明
 
 
  
  
 
 
前言 
 
以前我的树莓派服务是放docker容器中的,但是后来docker访问受限,于是就用systemd-nspawn容器替代。systemd-nspawn容器的功能相对docker而言没那么丰富,但是胜在简单、轻量、便利。 
 本博文是作者“进取有乐”原创,仅发布于博客园,其他网站转载请注明出处。 
  
 
需求 
 
容器大小:我不喜欢太大的容器,所以容器要精简,经过我精简后的镜像只有几MB大小。我的服务有花生壳、小米球、vlmcsd(KMS服务器) 
 容器网络:我希望不使用宿主机网络空间,使用容器独立的网络。 
  
 
精简容器体积 
 
为了精简容器,开始时,我想要用alphine,但是后来发现我的某个服务在alpine下面无法运行,所以我使用了debian的initrd.gz,initrd是linux系统启动过程中的一个最小化内存盘系统。 
 !!注意: 下面操作容器的usr var etc等目录的时候,不要在路径前面加/, 否则会损坏宿主机的系统!! 
cd /tmp; wget https://mirrors.tuna.tsinghua.edu.cn/debian/dists/bookworm/main/installer-arm64/current/images/cdrom/initrd.gz  #下载适用于arm64架构的debian12的initrd.gz。最近稳定版是debian13 https://mirrors.tuna.tsinghua.edu.cn/debian/dists/stable/main/installer-arm64/current/images/cdrom/initrd.gz 
 mkdir -p /opt/unsafe/base; cd /opt/unsafe/base; # 创建一个目录,用于后续操作 
 gzip -dc /tmp/initrd.gz |cpio -idm;  # 解包 
 rm -v bin sbin lib; mv -v usr/{bin,sbin,lib} ./; rm -rfv initrd usr/* var/* # 删掉根下的bin sbin lib三个软连接,把usr/下的bin、sbin、lib三个目录移动到根下。删掉initrd/目录,删掉usr/ var/下的所有数据(而不删除目录本身) 
 cd bin; ls -l |grep '^-' |awk '{print $NF}' # 查看bin/下有很多软连接和常规文件,除了busybox这个常规文件外(busybox保证基本shell),其他常规文件用处不大,下面一条命令会删除它们。 
 rm -v $(ls -l |grep '^-' |awk '!/busybox/{print $NF}' |xargs) # bin/下除了busybox,其他的常规文件都删除 
 cd ../sbin; rm -v $(ls -l |grep '^-' |awk '{print $NF}') # sbin/下所有常规文件删掉,保留软连接 
 ldd /tmp/phtunnel; ldd /tmp/xiaomiqiu; ldd /tmp/vlmcsd-armv7el-uclibc-static # 查看我的花生壳、小米球、vlmcsd需要依赖什么动态库。执行后发现,后两者是静态可执行文件,花生壳(phtunnel)依赖的动态库如下-  linux-vdso.so.1 libpthread.so.0 libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6 /lib/ld-linux-aarch64.so.1 libm.so.6 => /lib/aarch64-linux-gnu/libm.so.6 
 
  复制代码 cd ../lib; find . -regextype egrep -regex ".*(linux-vdso|libpthread|libc|ld-linux-aarch64|libm)\.so.*" # 在lib/下找一下这几个文件 
 find . -type f -regextype egrep ! -regex ".*(linux-vdso|libpthread|libc|ld-linux-aarch64|libm)\.so.*" -delete # find命令增加取反"!"操作,删除lib/下除了上面依赖库之外的所有文件 
 cd ../etc; find . -regextype egrep ! -regex ".*(passwd|group|fstab)" -delete # 删除etc/下除了passwd、group、fstab之外的所有文件或目录 
 cp -v /etc/os-release ./ # 把宿主机的os-release 复制到容器etc目录,没有这个文件,容器启动不了。 
 cd ../; while :; do x="$(find . -mindepth 2 -type d -empty |xargs)"; if [ -n "$x" ]; then rm -rfv $x; else break; fi; done # 回到容器的根,删除深度至少是2层的空目录,因为有些子目录删除后会导致父目录也变成空目录,所以要循环删除 
 du -sh # 查看容器的根,发现占用空间是3M。这个体积已经满意了。但是精简后也要缺点,就是容器启动的时候不能使用-b参数(boot),一些网络配置文件无法使用,只能容器启动后动态执行网络配置命令。 
  
 
创建目录结构 
 
useradd -d /opt/unsafe unsafe # 创建unsafe用户,家目录是/opt/unsafe 
 cd /var/lib/machines; ln -s /opt/unsafe/base hsk; ln -s /opt/unsafe/base xmq; ln -s /opt/unsafe/base vlmcsd # 在/var/lib/machines目录下创建hsk、xmq、vlmcsd三个软连接,三个软连接都指向/opt/unsafe/base的容器文件根系统。 
 mkdir /opt/unsafe/base/app # 在容器根下面创建一个app目录用于绑定三个服务的二进制路径 
 mkdir /opt/unsafe/{hsk,xmq,vlmcsd} # 在/opt/unsafe目录下创建hsk、xmq、vlmcsd目录用来存放这三个服务的二进制文件和配置文件。 
 chown -R unsafe:unsafe /opt/unsafe; chmod 755 /opt/unsafe # 递归修改unsafe目录属主。修改家目录权限 
  
 
测试容器是否正常启动 
 
systemd-nspawn --machine hsk --bind=/opt/unsafe/hsk:/app --read-only --private-users=2048:65535 # 启动容器,这里的--machine hsk 也可以写成--directory /var/lib/machines/hsk, 因为/var/lib/machines下有了hsk目录,所以这里才可以写成--machine hsk, 否则必须指明容器根目录路径。 --bind表示把宿主机的目录绑定到容器的/app路径下, --private-users 2048:65535表示容器是以这个范围内的uid来启动,宿主机执行ps auxf就能看到进程的uid是大于等于2048的。执行了这个命令后,就进入了容器的shell界面。 
 /app/phtunnel -c /app/phtunnel.json # 在容器的shell命令下启动花生壳,发现正常。同样的步骤,小米球和vlmcsd测试都正常。(我是直接测试,建议先在宿主机测试好,调好配置文件,然后再在容器内测试)。敲Ctrl+]组合键3次退出容器 
  
 
创建并测试容器的独立网络 
 
我的树莓派连接的网关是192.168.1.1;宿主系统的网络是用NetworkManager管理网络的,NetworkManager执行命令后会在/etc/NetworkManager/system-connections/形成配置文件。现在我的树莓派是使用有线连接的(如果是wifi连接,需要特殊处理,见后文)。这里使用桥接网络给容器使用。 
 nmcli connection add type bridge con-name br1 ifname br1 ipv4.method manual ipv4.addresses "192.168.1.101/24" ipv4.gateway "192.168.1.1" ipv4.dns "223.5.5.5,119.29.29.29" # 创建一个桥接网络接口br1 
 nmcli connection add type bridge-slave con-name br1-slave-eth0 ifname eth0 master br1 # 把eth0接口加入br1 
 nmcli connection show # 看看eth0对应的连接的NAME字段是什么,一般是"Wired connection 1",有那么就删掉它 nmcli connection delete "Wired connection 1",防止该连接和br1的slave网口冲突 
 nmcli connection up br1 # 使br1处于up状态。此时nmcli connection show br1发现它是自动连接的。 
 systemd-nspawn --machine hsk --bind=/opt/unsafe/hsk:/app --read-only --private-users=2048:65535 --network-bridge=br1 # 启动hsk容器,进入容器shell,容器的host0网口和宿主机的br1是桥接在一起的 
 ip link set host0 up; ip a a 192.168.1.102/24 dev host0 #在容器内的shell中启用host0网口,并配上ip地址。大概在配好ip后30多秒才能ping通网关192.168.1.1 
 ip route add default via 192.168.1.1 dev host0 # 添加默认路由,ping baidu.com通了。 
  
 
以systemd服务来管理 
 
上面的测试通过后,形成服务文件,服务文件的形式有两种,一种是常规的"服务名.service"文件,一种是systemd-nspwan容器的"服务名.nspawn"文件。注意在/etc/systemd/nspawn/目录下的 .nspawn配置文件是被信任的,/var/lib/machines/下的.nspawn文件是不被信任的(不信任场景下,执行systemd-nspawn -M name时,会忽略一些系统级命令)。.nspawn服务是使用类似"systemctl start systemd-nspawn@服务名.service"来管理服务的。我使用的是常规的.service文件。 
 以花生壳的.service服务文件为例: cat /lib/systemd/system/hsk.service-  [Unit] Description=hsk(phtunnel) service After=network-online.target Wants=network-online.target [Service] # 如果想要服务启动过程及运行日志,请执行journalctl -feu hsk ,下面启动过程使用循环ping检查网关连通性,通了之后再启动服务。 ExecStart=systemd-nspawn --bind=/opt/unsafe/hsk:/app --read-only --private-users=2048:65535\ --machine hsk --network-bridge=br1 /bin/sh -c 'ip link set host0 up; ip addr add 192.168.1.111/24 dev host0;\ ip route add default via 192.168.1.1 dev host0; echo "等待网络..."; \ while :; do ping -c1 -w1 -W1 192.168.1.1 >/dev/null 2>&1 && break; done;\ /app/phtunnel -c /app/phtunnel.json;' Restart=always RestartSec=30s ProtectSystem=yes [Install] WantedBy=multi-user.target 
 
  复制代码 执行systemctl start hsk 启动花生壳服务的容器,执行systemctl enable hsk 使花生壳服务开机启动。小米球、vlmcsd服务也用类似步骤完成。只是容器ip地址和命令行不同: 
 /app/xiaomiqiu -log stdout -log-level error -config /app/xiaomiqiu.conf 
 /app/vlmcsd-armv7el-uclibc-static -D -l syslog 
  
 
通过wifi连接网关的容器配置 
 
 
- 如果使用wifi连接网关(我的wlan0的ip地址是192.168.1.X),由于wifi网络接口无法加入桥接网口,所以网络配置方法也和有线网连的不同。
 
 -  nmcli connection add type bridge con-name br0 ifname br0 ipv4.method manual ipv4.addresses "192.168.100.1/24" # 创建一个桥接网络接口br0
 
 -  nmcli connection up br0 # 使br0处于up状态。
 
 -  修改/etc/sysctl.conf文件,使net.ipv4.ip_forward=1 然后执行sysctl -p立即生效。我的树莓派是Debian12,如果是Debian 13则默认不再读取 /etc/sysctl.conf,需将配置分散到 /etc/sysctl.d/ 目录下的独立文件。
 
 -  增加nftables的NAT:让容器中的花生壳、小米球和能经由wlan0访问公网(masqurade),也让外部网络经由wlan0访问容器中vlmcsd提供的KMS服务(DNAT)。下面是nftables.conf内容:cat /etc/nftables.conf
 
 -  flush ruleset table ip tb_pi { chain ch_fwd { type filter hook forward priority filter; policy accept; iifname "br0" ip saddr 192.168.100.0/24 accept ct state established,related accept } chain ch_snat { type nat hook postrouting priority srcnat; policy accept; oifname "wlan0" ip saddr 192.168.100.0/24 masquerade } chain ch_dnat { type nat hook prerouting priority 100; policy accept; tcp dport 1688 dnat to 192.168.100.13 } } 
 
  复制代码- 上面的nftables配置文件要配合nftables服务生效,确保nftables服务是开机启动的,确保nftables.service文件读取的配置文件路径是/etc/nftables.conf。没问题后,执行systemctl restart nftables 重新载入配置文件。
 
 -  花生壳的.service文件和有线网连接的也不同,比如ip地址,默认路由、测试ping的地址等。服务配置文件内容如下,cat /lib/systemd/system/hsk.service
 
 -  [Unit] Description=hsk(phtunnel) service After=network-online.target Wants=network-online.target [Service] # 使用while ping检查宿主机br0网口,通了之后再启动服务。 ExecStart=systemd-nspawn --bind=/opt/unsafe/hsk:/app --read-only --private-users=2048:65535\ --directory /var/lib/machines/hsk --network-bridge=br0 \ /bin/sh -c 'ip link set host0 up; ip addr add 192.168.100.11/24 dev host0;\ ip route add default via 192.168.100.1 dev host0; echo "等待网络..."; \ while :; do ping -c1 -w1 -W1 192.168.100.1 >/dev/null 2>&1 && break; done;\ /app/phtunnel -c /app/phtunnel.json;' Restart=always RestartSec=30s ProtectSystem=yes [Install] WantedBy=multi-user.target 
 
  复制代码
  
 
其他说明 
 
 
- 由于我是精简镜像,所以很多非常便利的功能不能实现。比如宿主机如果使用的是systemd-networkd服务管理网络,则systemd-networkd.service默认包含 /usr/lib/systemd/network/80-container-ve.network , 此文件匹配所有通过该选项创建的虚拟以太网连接的宿主端接口, 此文件不但为这些接口启用了DHCP功能,而且还为这些接口设置了通向宿主机外部网络的路由(从而可以连通外网)。 该服务还默认包含 /usr/lib/systemd/network/80-container-host0.network , 此文件匹配所有通过该选项创建的虚拟以太网连接的容器端接口,并且为这些接口启用了DHCP功能。 如果在宿主与容器内同时运行了systemd-networkd服务, 那么无须额外的配置,即可自动实现在容器与宿主之间进行IP通信,并且可以连接到外部网络。
 
 - DHCP功能。如果宿主机开启dnsmasq服务,并且把dhcp服务提供给指定网口(比如br0等),容器使用-b启动,并且网口配置成dhcp自动获取ip,则容器启动后,即可获取一个宿主机DHCP服务提供的IP地址
 
 - 容器的其他网络选项
 
 -  --network-zone XX # 则宿主机产生一个叫"vz-XX"的网口,容器启动后,生成一个host0网口。--network-zone XX 的形式穿件的网络会自动在宿主机创建网络接口(第一个容器启动),自动销毁(最后一个容器退出)。可以方便的将一组相关的本地容器,添加到基于虚拟以太网的同一个广播域(也就是同一子网)之中。 这样的广播域就被称为"区域"(zone)。如果结合前面的systemd-networkd的自动分配ip地址、联通外网功能,会很方便。
 
 -  --network-veth-extra XX:YY # 容器启动后,宿主机生产一个名字类似于XX@if2的虚拟网络接口,这里的@if2表示宿主机的XX虚拟接口关联的物理接口是序号为2的接口(ip link show命令查看发现序号2的接口是eth0)。真正修改ip地址的时候,使用的接口名是XX,@if2是不需要带的。容器中的接口是YY@if39,这里的@if39表示容器的YY接口与宿主机的序号第39的接口关联,在宿主机执行ip link show发现39号网络接口的名字是XX。 当然参数也可以不用冒号和冒号后面的名字,表示宿主机和容器产生的网络接口都是XX。
 
 -  --network-veth # 该选项不用带参数,容器启动后,宿主机会产生一个叫"ve-容器名"的虚拟网络接口,虚拟机的网络接口是host0。--network-veth 是使用 systemd-nspawn@容器名.service 模版配置文件是的默认选项。
 
 -  不管是哪种虚拟网络,容器和宿主机的网口都要处于up状态,想要通信就要分配ip地址。
 
 
  
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除 
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |