找回密码
 立即注册
首页 业界区 安全 systemd-nspawn容器体积精简和桥接网络实战 ...

systemd-nspawn容器体积精简和桥接网络实战

赵淳美 2025-9-26 10:48:36
目录

  • 前言
  • 需求
  • 精简容器体积
  • 创建目录结构
  • 测试容器是否正常启动
  • 创建并测试容器的独立网络
  • 形成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/stable/main/installer-arm64/current/images/cdrom/initrd.gz  #下载适用于arm64架构的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)依赖的动态库如下
  1.        linux-vdso.so.1
  2.        libpthread.so.0
  3.        libc.so.6 => /lib/aarch64-linux-gnu/libc.so.6
  4.        /lib/ld-linux-aarch64.so.1
  5.        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|os-release)" -delete # 删除etc/下除了passwd、group、fstab、os-release之外的所有文件或目录
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 # 查看容器的根,发现占用空间是3.1M。这个体积已经满意了。但是精简后也要缺点,就是容器启动的时候不能使用-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
  1.     [Unit]
  2.     Description=hsk(phtunnel) service
  3.     After=network-online.target
  4.     Wants=network-online.target
  5.    
  6.     [Service]
  7.     # 如果想要服务启动过程及运行日志,请执行journalctl -feu hsk ,下面启动过程使用循环ping检查网关连通性,通了之后再启动服务。
  8.     ExecStart=systemd-nspawn --bind=/opt/unsafe/hsk:/app --read-only --private-users=2048:65535\
  9.                              --machine hsk --network-bridge=br1 /bin/sh -c 'ip link set host0 up; ip addr add 192.168.1.111/24 dev host0;\
  10.                              ip route add default via 192.168.1.1 dev host0; echo "等待网络..."; \
  11.                              while :; do ping -c1 -w1 -W1 192.168.1.1 >/dev/null 2>&1 && break; done;\
  12.                              /app/phtunnel -c /app/phtunnel.json;'
  13.    
  14.     Restart=always
  15.     RestartSec=30s
  16.     ProtectSystem=yes
  17.    
  18.     [Install]
  19.     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网络接口无法加入桥接网口,所以网络配置方法也和有线网连的不同,需要nftables做NAT。
    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访问外部网络,也让外部网络经由wlan0访问容器网络。下面是nftables.conf内容:cat /etc/nftables.conf
    1.   flush ruleset
    2.   
    3.   table ip tb_pi {
    4.           chain ch_fwd {
    5.                   type filter hook forward priority filter; policy accept;
    6.                   iifname "br0" ip saddr 192.168.100.0/24 accept
    7.                   ct state established,related accept
    8.           }
    9.   
    10.           chain ch_snat {
    11.                   type nat hook postrouting priority srcnat; policy accept;
    12.                   oifname {wlan0,eth0} ip saddr 192.168.100.0/24  masquerade
    13.           }
    14.   
    15.           chain ch_dnat {
    16.                   type nat hook prerouting priority 100; policy accept;
    17.                   tcp dport 1688 dnat to 192.168.100.13
    18.           }
    19.   }
    复制代码
    上面的nftables配置文件要配合nftables服务生效,确保nftables服务是开机启动的,确保nftables.service文件读取的配置文件路径是/etc/nftables.conf。没问题后,执行systemctl restart nftables 重新载入配置文件。
    花生壳的.service文件和有线网连接的也不同,比如ip地址,默认路由、测试ping的地址等。服务配置文件内容如下,cat /lib/systemd/system/hsk.service
    1.   [Unit]
    2.   Description=hsk(phtunnel) service
    3.   After=network-online.target
    4.   Wants=network-online.target
    5.   
    6.   [Service]
    7.   # 使用while ping检查宿主机br0网口,通了之后再启动服务。
    8.   ExecStart=systemd-nspawn --bind=/opt/unsafe/hsk:/app --read-only --private-users=2048:65535\
    9.                            --directory /var/lib/machines/hsk --network-bridge=br0 \
    10.                            /bin/sh -c 'ip link set host0 up; ip addr add 192.168.100.11/24 dev host0;\
    11.                            ip route add default via 192.168.100.1 dev host0; echo "等待网络..."; \
    12.                            while :; do ping -c1 -w1 -W1 192.168.100.1 >/dev/null 2>&1 && break; done;\
    13.                            /app/phtunnel -c /app/phtunnel.json;'
    14.   
    15.   Restart=always
    16.   RestartSec=30s
    17.   ProtectSystem=yes
    18.   
    19.   [Install]
    20.   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 模版配置文件是的默认选项。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册