systemd-nspawn容器實戰【鏡像精簡到3MB,容器使用獨立網絡】
前言
以前我的樹莓派服務是放docker容器中的,但是后來docker訪問受限,于是就用systemd-nspawn容器替代。systemd-nspawn容器的功能相對docker而言沒那么豐富,但是勝在簡單、輕量、便利。
本博文是作者“進取有樂”原創,僅發布于博客園,轉載請注明出處。
需求
容器大小:我不喜歡太大的容器,所以容器要精簡,經過我精簡后的鏡像只有幾MB大小。我的服務有花生殼、小米球、vlmcsd(KMS服務器)
容器網絡:我希望不使用宿主機網絡空間,使用容器獨立的網絡。可以使用橋接、macvlan、ipvlan等,橋接網絡麻煩點,所以這里詳細介紹橋接網絡。其他如macvlan、ipvlan、network-zone、veth等操作比較簡單,在文末介紹。
精簡容器體積
為了精簡容器,開始時,我想要用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.confflush 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 address命令修改ip地址的時候,使用的接口名是XX,XX@if2后面的@if2要去掉。容器中的接口是YY@if39,這里的@if39表示容器的YY接口與宿主機的序號第39的接口關聯,在宿主機執行ip link show發現39號網絡接口的名字是XX。 當然參數也可以不用冒號和冒號后面的名字,表示宿主機和容器產生的網絡接口都是XX。
--network-veth# 該選項不用帶參數,容器啟動后,宿主機會產生一個叫"ve-容器名"的虛擬網絡接口,虛擬機的網絡接口是host0。--network-veth 是使用 systemd-nspawn@容器名.service 模版配置文件是的默認選項。--network-macvlan 宿主機網口# 該選項隱含--private-network。容器與宿主機的mac地址不同。例如--network-macvlan=eth0進入容器后,我們會發現容器中mv-eth0的mac地址和宿主機eth0的mac地址不同。容器的mv-eth0需要設置up,并分配ip地址才可以使用(與宿主機的eth0同一個網段)。我測試發現不可以ping通宿主機ip,但是可以ping通同個局域網的其他主機。
--network-ipvlan 宿主機網口# 該選項隱含--private-network。容器與宿主機的mac地址相同。例如--network-ipvlan=eth0進入容器后,我們會發現容器中iv-eth0的mac地址和宿主機eth0的mac地址相同。容器的iv-eth0需要設置up,并分配ip地址才可以使用(與宿主機的eth0同一個網段)。我測試發現不可以ping通宿主機ip,但是可以ping通同個局域網的其他主機。 -
不管是哪種虛擬網絡,容器和宿主機的網口都要處于up狀態,想要通信就要分配ip地址。
浙公網安備 33010602011771號