什麼都要管的systemd居然還可以生成容器!systemd-nspawn是Linux系統內建的一個功能,透過這個工具,能夠生成多個Linux容器,在隔離的環境中執行程式。
使用範例:在Ubuntu系統跑Debian容器
1. systemd-nspawn特色#
systemd-nspawn意思名為systemd namespace spawn。
講到容器化,Linux系統最古早的技術就是使用chroot指令進入一個隔離的環境了。從設計上來說,systemd-nspawn比chroot更好,透過隔離namespace與IPC提供更高安全性。所以systemd-nspawn可以說是chroot的威力加強版(Arch Wiki說systemd-nspawn是「打了類固醇的chroot」XD),它讓容器能夠作為一個系統服務控制,隨時被使用者啟動與停止。
在容器內部,依然能夠使用systemctl指令控制服務,這是Docker難以做到的技術。這表示一旦將容器設定為跟著Linux啟動,它就會跟著Systemd的規則執行。
既然提到容器,就會需要rootfs。雖然systemd-nspawn相容OCI標準,但是它不能直接拿Docker映像檔部署系統,且幾乎沒有人在維護systemd-nspawn映像檔,只能靠debootstrap這類工具產生。
systemd-nspawn沒有Docker生態系的工具鏈完整,很難用來分發應用程式。它的功能就侷限於產生一個暫時性的容器而已。
論將容器與Systemd服務整合,Podman Quadlets的方案也比systemd-nspawn靈活吧。
還有systemd-nspawn最大缺點就是依賴Systemd,沒有chroot的移植性好。chroot能在傳統Sysvinit的Linux發行版正常使用,甚至能搭配BusyBox在root過的Android執行。
因此systemd-nspawn可以看作是Docker以外的輕量替代品。與systemd-nspawn定位最類似的技術應該是LXC。
2. 前置條件#
雖然systemd-nspawn是Systemd內建功能,不過有些條件需要滿足。儘量使用搭載最新版Systemd的發行版,我在Ubuntu 24.04與Fedora 42測試過,都能正常使用,惟部分細節行為不太一樣。
要使用systemd-nspawn功能,部份發行版可能需要額外安裝套件
sudo apt install systemd-container
- 接著,確保宿主機已經啟用
systemd-networkd
與systemd-resolved
服務,爾後容器才能連上網。
sudo systemctl enable --now systemd-networkd
sudo systemctl enable --now systemd-resolved
3. 準備rootfs,以Debian容器為例#
要使用Linux發行版容器,就需要用工具產生rootfs,例如Debian使用debootstrap,Arch Linux配pacbootstrap,Fedora配DNF等等。還有人分享過用docker export解開映像檔當作rootfs的玩法,暫且不提。
假設我們要建立一個Debian的容器,就得先在宿主機安裝debootstrap:
sudo apt install debootstrap
- 建立Debian Stable的rootfs,選取目前的Stable分支(撰文當下是Debian 13),容器建議放在
/var/lib/machines/
sudo debootstrap --include=systemd,dbus stable /var/lib/machines/debian
4. 以systemd-nspawn指令進入容器#
- 第一次登入容器內部,以root身分登入。systemd-nspawn預設是以privileged容器執行的。
sudo systemd-nspawn -D /var/lib/machines/debian -U --machine debian
- 必須先修改Root密碼
passwd root
- 確保容器內有網路
systemctl enable --now systemd-networkd.service
- 然後登出。
exit
- 進入容器後快速按三下
Ctrl + ]
退出登入介面。
5. 使用machinectl控制容器#
幾種啟動systemd-nspawn容器方式:
- sudo systemctl start systemd-nspawn@debian
- sudo machinectl start debian
- sudo systemd-nspawn –boot -U -D /var/lib/machines/debian
- 撰寫.nspawn設定檔,再透過machinectl啟動容器
systemd-nspawn
可看作是一次性登入的指令。使用--boot
參數非必要,但只有使用這個參數的時候,讓Systemd成為PID 1,才能夠在容器裡面使用systemctl指令。
其中machinectl
是在容器開機之後才能使用的指令,根據官方文件,推薦使用這個來控制容器。
可以用machinectl
或systemctl
停止容器
用systemctl enable
設定開機自動啟動容器。
容器啟動之後要用machinectl login
才能重新進入終端機
6. 撰寫.nspawn容器設定檔#
針對個別容器,能夠以.nspawn作結尾的檔案定義其行為。這個檔案語法與Systemd Service Unit一樣。
例如新增/etc/systemd/nspawn/debian.nspawn
檔案。在這個檔案裡面能夠定義Debian容器掛載的目錄以及網路設定,就不需要打落落長的systemd-nspawn指令登入了。
在寫好設定檔之後,重新載入設定:sudo systemctl daemon-reload
可以直接用sudo machinectl start debian
啟動容器,並用sudo machinectl login debian
登入。
7. 容器對外網路設定#
systemd-nspawn容器預設應該是使用host mode,會吃到宿主機的防火牆規則,能看到宿主機所有網路界面。
/etc/resolv.conf
會直接沿用,所以宿主機有開啟systemd-resolved
的話,容器內的這個檔案就會被覆蓋。
我們可以在設定檔/etc/systemd/nspawn/debian.nspawn
裡面停用此選項,確保容器能直接使用宿主機網路:
[Network]
VirtualEthernet=no
如果要隔離容器網路,需要設定Private Network Mode。這個模式下,容器內部的網路會被隔離,/etc/resolv.conf
將不受宿主機影響。要存取容器內部的服務,需要做通訊埠映射。
執行參數是--network-veth
,也就是隔離網路模式--private-network
。它會在容器啟動之後建立一個ve開頭的虛擬網路介面。
在設定檔/etc/systemd/nspawn/debian.nspawn
裡面啟用此選項,確保容器會建立虛擬網路介面:
[Network]
VirtualEthernet=yes
註解:正常來說systemd-nspawn應該是host mode優先才對?我在Fedora 42建立容器能夠直接連上網路。但我在Ubuntu 24.04測試,發現它預設會在容器啟動後建立一個虛擬網路介面。
systemd-nspawn尚支援使用橋接網路或macvlan。
8. 執行圖形程式#
讓容器內的X11程式能顯示在宿主機桌面上。至於純Wayland程式…?啊哈哈不清楚。
- 先用Xephyr生成一個巢狀X11桌面的視窗,DISPLAY環境變數設定為:10
sudo apt install xserver-xephyr
Xephyr -br -ac -noreset -screen 1280x720 :10
- 宿主機需要使用xhost指令允許其他X客戶端在目前的桌面開啟視窗。即使宿主機是Wayland桌面,也能用XWayland相容。
xhost +local:
- 編輯設定檔
/etc/systemd/nspawn/debian.nspawn
,設定啟動容器的時候指定DISPLAY的環境變數,並且掛載/dev/dri
到容器內部(僅限Intel與AMD顯示卡,Nvidia需要掛載更多)啟用圖形加速。再掛載PulseAudio,讓它能夠發出音效。
[Exec]
Boot=yes
Environment=DISPLAY=:10
[Files]
BindReadOnly=/tmp/.X11-unix/
BindReadOnly=/home/宿主機使用者名稱/.Xauthority
Bind=/dev/dri
Bind=/run/user/1000/pulse:/run/user/1000/pulse
- 執行圖形程式前需要在Debian容器內部新增一個使用者,不能使用root。
useradd -m -g users -G wheel,audio,video,storage -s /bin/bash user
passwd user
visudo
su user
- 在容器裡面啟動程式
export DISPLAY=:10
export PULSE_SERVER=unix:/run/user/1000/pulse/
firefox-esr