systemd-nspawn

出自 Arch Linux 中文维基

systemd-nspawnchroot 命令類似,是個終極版的 chroot。

systemd-nspawn 可以在輕量的命名空間容器中運行命令或系統。它比 chroot 強大的地方是,它完全虛擬了文件系統層次結構、進程樹、各種 IPC 子系統以及主機名。

systemd-nspawn 將容器中各種內核接口的訪問限制為只讀,像是 /sys, /proc/sys/sys/fs/selinux。網絡接口和系統時鐘不能從容器內更改,不能創建設備節點。不能從容器中重啟宿主機,也不能加載內核模塊。

相比 LXCLibvirt, systemd-nspawn 更容易配置。

安裝[編輯 | 編輯原始碼]

systemd-nspawn 是 systemd 的一部分並被打進 systemd 軟體包中。

用例[編輯 | 編輯原始碼]

創建並啟動最小 Arch Linux 容器[編輯 | 編輯原始碼]

首先安裝 arch-install-scripts

然後,創建一個目錄來保存容器。在這個用例中我們將使用 ~/MyContainer

接下來,我們使用 pacstrap 在容器中安裝一個基本的Arch系統。其中至少需要安裝包組 base

# pacstrap -K -c ~/MyContainer base [additional packages/groups]
提示:base 已經不再依賴內核包 linux,這已為容器化做好準備。

一旦安裝完成後,chroot至容器中並設置root密碼:

# systemd-nspawn -D ~/MyContainer
# passwd
# logout

最後, 啟動容器:

# systemd-nspawn -b -D ~/MyContainer

參數 -b 將會啟動這個容器(比如:以 PID=1 運行 systemd), 而不是僅僅啟動一個 shell, 而參數 -D 指定成為容器根目錄的目錄。

容器啟動後,輸入密碼以"root"身份登錄。

注意: 如果登錄失敗顯示 "Login incorrect", 問題可能是 securetty TTY 設備白名單導致的。 請查看#Root登錄失敗

可以在容器內運行 poweroff 來關閉容器。在主機端,容器可以通過 machinectl 工具進行控制。

注意: 要從容器內終止 session,請按住 Ctrl 並快速地按 ] 三下。

創建 Debian 或 Ubuntu 環境[編輯 | 編輯原始碼]

安裝 debootstrap 以及 debian-archive-keyringubuntu-keyring 中的一個或兩個(當然要安裝你想要的發行版的keyring)。

注意: systemd-nspawn要求容器中的作業系統以 PID 1 運行systemd init,並且systemd-nspawn要安裝在容器中。為了完善依賴,需確保在容器中安裝debian軟體包systemd-container,debian包systemd-container依賴dbus,但是dbus不會被默認安裝。所以還要確保dbusdbus-broker這些debian包被安裝在容器內系統中。

在這裡很容易設置 Debian 或 Ubuntu 環境:

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

對於 Debian,有效的codename要麼是滾動式名稱像是 "stable" 或 "testing",要麼是發行名稱如 "stretch" 或 "sid"。對於 Ubuntu,應使用"xenial"或者"zesty"等codename。 代碼名稱的完整列表位於 [1] 中。對於 Debian 鏡像,"repository-url" 可以是 https://deb.debian.org/debian/ 。 對於 Ubuntu 鏡像, "repository-url" 可以是 http://archive.ubuntu.com/ubuntu/ 。"repository-url" 應該包含尾隨斜線。

與 Arch 相同,Debian 和 Ubuntu 不會讓您在首次登錄時無密碼登錄。為了設置root密碼登錄,要不使用"-b"參數並設置密碼:

# cd /var/lib/machines
# systemd-nspawn -D ./container-name
# passwd
# logout

編譯與測試包[編輯 | 編輯原始碼]

請參閱 Creating packages for other distributions 尋找更多用例.

管理[編輯 | 編輯原始碼]

位於 /var/lib/machines/ 的容器可以被 machinectl 命令所控制,它可以內部控制 systemd-nspawn@.service 單元的實例。 /var/lib/machines/ 下的子目錄對應著容器的名字, 比如 /var/lib/machines/container-name/

注意: 如果因為某些原因容器不能移進 /var/lib/machines/ , 可以使用符號連結。 詳情看 machinectl(1) § FILES AND DIRECTORIES

默認 systemd-nspawn 選項[編輯 | 編輯原始碼]

要明白非常重要的一點是通過 machinectlsystemd-nspawn@.service 啟動的容器所使用的默認選項與通過 systemd-nspawn 命令手動啟動的有所不同。 通過服務啟動所使用的額外選項有:

  • -b/--boot – 管理的容器會自動搜索一個init程序,並以PID 1的形式調用它。
  • --network-veth 關聯於 --private-network – 管理的容器獲得一個虛擬網絡接口,並與主機網絡斷開連接。 詳情看 #網絡
  • -U – 如果內核支持,管理的容器默認使用 user_namespaces(7) 特性。 解釋請看 #無特權容器
  • --link-journal=try-guest

這些行為可以在每個容器配置文件中被覆蓋, 詳情看 #配置

machinectl[編輯 | 編輯原始碼]

注意: machinectl 工具要求在容器中安裝 systemddbus 。有關詳細討論,請參閱 [2]

容器可以被 machinectl subcommand container-name 命令管理, 比如說,啟動容器:

$ machinectl start container-name
注意: machinectl 要求 container-name 只包含 ASCII 字符,數字與連字符。這樣才能保證 hostname 的合法性。比如,如果 container-name 包含了下劃線,這將不會被識別並且當你運行 machinectl start container_name 時將會報錯 Invalid machine name container_name。詳情請看 [3][4]

相似的, 還有其他的子命令 poweroffrebootstatusshow. 詳細解釋請看 machinectl(1) § Machine Commands

提示:關機與重啟操作可以在容器內通過 poweroff and reboot 命令執行。

其他常見命令:

  • machinectl list – 顯示現在正在運行的容器
  • machinectl login container-name – 在一個容器中打開一個交互式登錄會話
  • machinectl shell [username@]container-name – 在容器中打開一個交互式shell會話(這將立即調用一個用戶進程,而不需要通過容器中的登錄進程)
  • machinectl enable container-namemachinectl disable container-name – 啟用或禁用容器的隨開機啟動,詳見 #啟用容器的隨開機啟動

machinectl 也有管理容器(或虛擬機)鏡像和鏡像傳輸的子命令。 詳情見 machinectl(1) § Image Commandsmachinectl(1) § Image Transfer Commands

這篇文章的某些內容需要擴充。

原因: 增加一些明確的例子,如何使用鏡像傳輸命令。最重要的是,在哪裡可以找到合適的鏡像。 (在 Talk:Systemd-nspawn 中討論)

systemd 工具鏈[編輯 | 編輯原始碼]

大部分核心的 systemd 工具鏈已更新為對容器有效。工具們通常提供了 -M, --machine= 選項並把容器名稱作為參數。 用例:

查看特定容器的 journal 日誌:

# journalctl -M MyContainer

顯示控制組的內容:

$ systemd-cgls -M MyContainer

查看容器的啟動時間:

$ systemd-analyze -M MyContainer

查看資源使用情況的概況:

$ systemd-cgtop

配置[編輯 | 編輯原始碼]

容器前設置[編輯 | 編輯原始碼]

要指定容器前設置而不是全局覆蓋,可以使用.nspawn文件。 詳情見 systemd.nspawn(5)

注意:
  • 當執行machinectl remove 的時候,.nspawn 文件可能會從 /etc/systemd/nspawn/ 被意外移除。 [5]
  • 當有--settings=override(在systemd-nspawn@.service文件內被指定)時,.nspawn內與命令行中所指定的為網絡選項的交互不能正常工作。[6] 作為一個變通方法,你需要引入VirtualEthernet=on選項,即使服務指定了--network-veth

啟用容器的隨開機啟動[編輯 | 編輯原始碼]

當頻繁使用一個容器時,你可能希望在系統啟動時啟動它。

首先確保 machines.target 已經 enabled

容器被 machinectl 的可發現可以 enabled 或者 disabled。

$ machinectl enable container-name
注意:
  • 這樣做的效果是啟用 systemd-nspawn@container-name.service systemd單元。
  • #默認 systemd-nspawn 選項中提到的,由machinectl啟動的容器會獲得一個虛擬的乙太網接口。要禁用私有網絡,請參見#使用主機網絡

資源控制[編輯 | 編輯原始碼]

您可以使用控制組來使用 systemctl set-property 實現對容器的限制和資源管理,請參閱 systemd.resource-control(5)。例如,您可能希望限制內存量或 CPU 使用率。要將容器的內存消耗限制為 2 GiB:

# systemctl set-property systemd-nspawn@myContainer.service MemoryMax=2G

或者將 CPU 時間使用率限制為大約相當於 2 個內核:

# systemctl set-property systemd-nspawn@myContainer.service CPUQuota=200%

這將在 /etc/systemd/system.control/systemd-nspawn@myContainer.service.d/ 中創建常駐文件}。

根據文檔,MemoryHigh 是控制內存消耗的首選方法,但也不會像 MemoryMax 那樣進行強制限制。您可以使用這兩個選項將 MemoryMax 作為您最後的一道防線。還要考慮到如果您不會限制容器可以「看到」的 CPU 數量,但通過限制容器相對於總 CPU 時間的最大時間,也可以獲得類似的結果。

提示:如果不希望在重新啟動後保留此更改,則可以傳遞選項 --runtime 使更改成為臨時性的。您可以使用 systemd-cgtop 檢查其結果。

網絡[編輯 | 編輯原始碼]

systemd-nspawn 容器可以使用 主機網絡 或者 私有網絡:

  • 在主機網絡模式下,容器可以完全訪問主機網絡。這意味著容器將能夠訪問主機上的所有網絡服務,來自容器的數據包將在外部網絡中顯示為來自主機(即共享同一IP位址)。
  • 在私有網絡模式下,容器與主機的網絡斷開連接,這使得容器無法使用所有網絡接口,但環回設備和明確分配給容器的接口除外。為容器設置網絡接口有多種不同的方法:
    • 可以將現有接口分配給容器(例如,如果您有多個乙太網設備)。
    • 可以創建一個與現有接口(即VLAN接口)相關聯的虛擬網絡接口,並將其分配給容器。
    • 可以創建主機和容器之間的虛擬乙太網連結。
在後一種情況下,容器的網絡是完全隔離的(與外部網絡以及其他容器),由管理員來配置主機和容器之間的網絡。這通常涉及創建一個網橋network bridge來連接多個(物理或虛擬)接口,或者在多個接口之間設置一個NATNetwork Address Translation

主機網絡模式適用於應用程式容器,它不運行任何網絡軟體來配置分配給容器的接口。當你從shell運行systemd-nspawn時,主機聯網是默認模式。

另一方面,私有網絡模式適用於應與主機系統隔離的 "系統容器"。創建虛擬乙太網鏈路是一個非常靈活的工具,可以創建複雜的虛擬網絡。這是由machinectlsystemd-nspawn@.service啟動的容器的默認模式。

下面的小節描述了常見的情況。關於可用的systemd-nspawn選項,請參見systemd-nspawn(1) § Networking Options

使用主機網絡[編輯 | 編輯原始碼]

要禁用私有網絡和創建由machinectl啟動的容器使用的虛擬乙太網連結,請添加.nspawn文件,其中包含以下選項:

/etc/systemd/nspawn/container-name.nspawn
[Network]
VirtualEthernet=no

這將覆蓋在systemd-nspawn@.service中使用的-n/--network-veth選項,新啟動的容器將使用主機網絡模式。

使用虛擬乙太網連結[編輯 | 編輯原始碼]

如果使用-n/--network-veth選項啟動容器,systemd-nspawn將在主機和容器之間創建一個虛擬的乙太網連結。連結的主機側將作為名為ve-容器名稱的網絡接口提供。鏈路的容器側將被命名為host0。請注意,這個選項意味著 --private-network

注意:
  • 如果容器名稱太長,接口名稱將被縮短(例如ve-long-conKQGh而不是ve-long-container-name),以適應15個字符的限制。全名將被設置為接口的altname屬性(見ip-link(8)),並且仍然可以用來引用接口。
  • 當檢查帶有ip link的接口時,接口名會顯示一個後綴,例如ve-容器名@if2host0@if9@ifN實際上並不是接口名稱的一部分,相反,ip link附加了這個信息,以指示虛擬乙太網電纜連接到另一端的哪個 "槽"。
例如,顯示為ve-foo@if2的主機虛擬乙太網接口與容器foo相連,在容器內部與第二個網絡接口--在容器內部運行ip link時,顯示索引為2的接口。同理,容器內名為host0@if9的接口連接到主機上的第9個網絡接口。

當您啟動容器時,必須為兩個接口(主機上和容器中)分配一個 IP 地址。如果您在主機上和容器中都使用systemd-networkd,這就是開箱即用:

  • 主機上的/usr/lib/systemd/network/80-container-ve.network文件與ve-container-name接口相匹配,並啟動一個DHCP伺服器,該伺服器為主機接口和容器分配IP位址,
  • 容器中的/usr/lib/systemd/network/80-container-host0.network文件與host0接口相匹配,並啟動一個DHCP客戶端,該客戶端從主機接收一個IP位址。

如果不使用 systemd-networkd,可以配置靜態 IP 地址或在主機接口上啟動 DHCP 伺服器,在容器中啟動 DHCP 客戶端。詳情請參見網絡配置

要讓容器訪問外部網絡,您可以按照 Internet sharing#Enable NAT 中的描述配置 NAT。如果您使用 systemd-networkd,這將通過 /usr/lib/systemd/network/80-container-ve.network 中的 IPMasquerade=yes 選項自動(部分)完成。然而,這只會發出一個iptables規則,比如說:

-t nat -A POSTROUTING -s 192.168.163.192/28 -j MASQUERADE

filter表必須手動配置,如Internet sharing#Enable NAT所示。您可以使用通配符來匹配所有以ve-開頭的接口:

# iptables -A FORWARD -i ve-+ -o internet0 -j ACCEPT
注意: systemd-networkd使用libiptc庫與iptables交互。如果您使用nftables,請安裝 iptables-nft 轉譯層。參見 systemd issue 13307

並且,為了接收DHCP伺服器的入站連接,你需要在 ve-+ 接口上開放 UDP 67 埠(由 systemd-networkd 操控):

# iptables -A INPUT -i ve-+ -p udp -m udp --dport 67 -j ACCEPT

使用網絡橋接[編輯 | 編輯原始碼]

如果您已在主機系統上配置了網橋network bridge,則可以為容器創建一個虛擬乙太網鏈路,並將其主機側添加到網橋中。這可以通過--network-bridge=bridge-name選項來完成。請注意,--network-bridge意味著--network-veth,即虛擬乙太網鏈路是自動創建的。然而,鏈路的主機端將使用vb-前綴而不是ve-,因此用於啟動DHCP伺服器和IP偽裝的systemd-networkd選項將不會被應用。

網橋的管理由管理員負責。例如,網橋可以用一個物理接口連接虛擬接口,也可以只連接幾個容器的虛擬接口。參見systemd-networkd#Network bridge with DHCPsystemd-networkd#Network bridge with static IP addresses,了解使用systemd-networkd進行配置的例子。

還有一個--network-zone=zone-name選項,它與--network-bridge類似,但網橋由systemd-nspawnsystemd-networkd自動管理。當第一個用vz-zone-name配置的容器啟動時,會自動創建名為--network-zone=zone-name的網橋接口,當最後一個用--network-zone=zone-name配置的容器退出時,會自動刪除。因此,這個選項可以方便地將多個相關的容器放置在一個共同的虛擬網絡上。請注意,vz-*接口由systemd-networkd管理的方式與ve-*接口相同,使用/usr/lib/systemd/network/80-container-vz.network文件中的選項。

使用 "macvlan" 或者 "ipvlan" 接口[編輯 | 編輯原始碼]

您可以在現有的物理接口(即VLAN接口)上創建一個虛擬接口,並將其添加到容器中,而不是創建一個虛擬的乙太網鏈路(其主機端可能被添加到橋接中,也可能沒有)。該虛擬接口將與底層主機接口進行橋接,從而使容器暴露在外部網絡中,從而使其能夠通過DHCP從與主機相連的同一區域網中獲得一個獨特的IP位址。

systemd-nspawn 提供兩個選項:

  • --network-macvlan=interface – 虛擬接口的MAC地址將與底層物理interface不同,並被命名為mv-interface
  • --network-ipvlan=interface – 虛擬接口的MAC地址將與底層物理interface相同,並命名為iv-interface。​

所有選項都意味著 --private-network.

使用現有接口[編輯 | 編輯原始碼]

如果主機系統有多個物理網絡接口,可以使用--network-interface=interfaceinterface分配給容器(並使它在容器啟動時對主機不可用)。請注意,--network-interface意味著--private-network

注意: 目前不支持將無線網絡接口傳遞給systemd-nspawn容器。[7]

埠映射[編輯 | 編輯原始碼]

當啟用私有網絡時,可以使用-p/-port選項或使用.nspawn文件中的Port設置將主機上的各個埠映射到容器上的埠。這可以通過向nat表發出iptables規則來完成,但需要手動配置forward表中的filter鏈,如#使用虛擬乙太網連結所示。

例如,將主機上的TCP埠8000映射到容器中的TCP埠80:

/etc/systemd/nspawn/container-name.nspawn
[Network]
Port=tcp:8000:80
注意:
  • systemd-nspawn在映射埠時明確地排除了loopback接口。因此,在上面的例子中,localhost:8000 連接到主機而不是容器。只有到其他接口的連接才會受到埠映射的影響。詳情請參見 [8]
  • 埠映射只對IPv4連接有效。[9]

域名解析[編輯 | 編輯原始碼]

容器中的域名解析 Domain name resolution 可以以主機系統中相同的方式配置。另外,systemd-nspawn 提供了管理容器中 /etc/resolv.conf 文件的選項:

  • --resolv-conf can be used on command-line
  • ResolvConf= can be used in .nspawn files

這些相應的選項有許多可能的值,在 systemd-nspawn(1) § Integration Options 的描述中會提到。 默認值為 auto,這意味著:

  • 如果啟用了--private-network/etc/resolv.conf就會保持容器中的原樣。
  • 否則,如果systemd-resolved在主機上運行,它的存根文件resolv.conf將被複製或綁定到容器中。
  • 否則,/etc/resolv.conf 文件就會被從主機複製或綁定到容器上。

在後兩種情況下,如果容器根部是可寫的,則複製文件,如果是只讀的,則綁定掛載。

對於第二種 systemd-resolved 在宿主機上運行的情況, systemd-nspawn 希望它也能在容器中運行,所以容器將會從宿主中 /etc/resolv.conf 文件建立樁符號連結。如果不是這樣,那麼默認值 auto 將不會生效,你應該使用任意 replace-* 選項來替換這個連結。

提示和技巧[編輯 | 編輯原始碼]

運行 non-shell/init 命令[編輯 | 編輯原始碼]

來自 systemd-nspawn(1) § Execution_Options:

[選項] --as-pid2 [調用] shell 或以進程ID (PID) 2 而不是 PID 1 (init) 來運行特定程序。 [...] 建議使用這種模式來調用容器中的任意命令,除非它們已經被修改為以PID 1的形式正確運行。或者換句話說:這個改變應該被用於幾乎所有的命令,除非該命令是指一個init或shell的實現。

無特權容器[編輯 | 編輯原始碼]

systemd-nspawn支持無特權的容器,不過容器需要以root身份啟動。​

本文或本章節的語言、語法或風格需要改進。參考:Help:Style

原因:Very little of Linux Containers#Enable support to run unprivileged containers (optional) applies to systemd-nspawn.(在Talk:Systemd-nspawn討論)

最簡單的方法是通過-U選項讓systemd-nspawn自動選擇一個未使用的UID/GID範圍。

# systemd-nspawn -bUD ~/MyContainer

如果內核支持用戶命名空間,-U選項相當於--private-users=pick --private-users-ownership=auto。詳情請參見systemd-nspawn(1) § User Namespacing Options

注意: 您也可以手動指定容器的UID/GID範圍,但是,這很少有用。

如果一個容器曾使用 --private-users-ownership=chown 選項(或者在一個 -U 需要 --private-users-ownership=chown 的文件系統中)在私有UID/GID範圍啟動,就需要一直這樣使用它以避免權限錯誤。另外,也可以在一個ID範圍從0開始的容器文件系統中撤銷--private-users-ownership=chown的效果:

# systemd-nspawn -D ~/MyContainer --private-users=0 --private-users-ownership=chown

使用 X 環境[編輯 | 編輯原始碼]

本文或本章節的事實準確性存在爭議。

原因: 本節末尾有關 systemd 版本的注釋似乎已經過時。對於我(譯者註:原作者)來說(systemd 版本為239) 如果 /tmp/.X11-unix 權限是 rw,X應用程式可以工作運行。(在 Talk:Systemd-nspawn#/tmp/.X11-unix contents have to be bind-mounted as read-only - still relevant? 中討論)


詳情見 XhostChange root#Run graphical applications from chroot.

您需要設置您容器會話中 DISPLAY 以連接到外部 X 伺服器。

X 在 /tmp 文件夾中存儲一些必要的文件。為了使容器能顯示任何內容,它需要訪問這些文件。為此,當啟動容器時,請追加 --bind-ro=/tmp/.X11-unix 選項。

注意: 從 systemd 版本 235 開始, /tmp/.X11-unix 的內容 必須以只讀方式裝入,否則它們將從文件系統中消失。只讀掛載標誌不會阻止在套接字上使用 connect()。如果你還綁定了/run/user/1000 那麼你有可能希望顯式綁定 /run/user/1000/bus 為只讀,以防止 dbus 套接字被刪除。

避免 xhost[編輯 | 編輯原始碼]

xhost 僅提供對 X 伺服器相當粗糙的訪問權限。更細節的訪問可通過$XAUTHORITY 文件控制。遺憾的是, 僅使 $XAUTHORITY 文件在容器中可被訪問並無法正常的執行工作: 這是因為您的 $XAUTHORITY 文件只特定於您的主機,但是容器是另一台主機。 根據 stackoverflow 下面這個技巧可以讓你的X伺服器接受來自於你容器中的X應用的 $XAUTHORITY 文件:

$ XAUTH=/tmp/container_xauth
$ xauth nextract - "$DISPLAY" | sed -e 's/^..../ffff/' | xauth -f "$XAUTH" nmerge -
# systemd-nspawn -D myContainer --bind=/tmp/.X11-unix --bind="$XAUTH" -E DISPLAY="$DISPLAY" -E XAUTHORITY="$XAUTH" --as-pid2 /usr/bin/xeyes

上面第二行將連接的組設定為 "FamilyWild",GID為65535,會使輸入匹配你的每一個顯示。 更多信息請參考 Xsecurity(7)

使用 X 嵌套/Xephyr[編輯 | 編輯原始碼]

另一個運行X應用程式並避免共享X桌面風險的簡單方法是使用X嵌套。 這裡的優點是完全避免了容器內應用程式和非容器內應用程式之間的交互,並且能夠運行不同的 desktop environmentwindow manager,缺點是使用 Xephyr 時性能較差,並且缺乏硬體加速。 在容器外啟動 Xephyr:

# Xephyr :1 -resizeable

然後在使用下面的參數啟動容器:

--setenv=DISPLAY=:1 --bind-ro=/tmp/.X11-unix/X1

不需要其他綁定。 在某些情況下,你可能仍然需要在容器中手動設置 DISPLAY=:1(主要是在與 b 一起使用時)。

運行 Firefox[編輯 | 編輯原始碼]

以PID 1運行​

 # systemd-nspawn --setenv=DISPLAY=:0 \
              --setenv=XAUTHORITY=~/.Xauthority \
              --bind-ro=$HOME/.Xauthority:/root/.Xauthority \
              --bind=/tmp/.X11-unix \
              -D ~/containers/firefox \
              --as-pid2 \
              firefox
              
注意: 因此,如果不使用 #無特權容器,firefox會以根用戶的身份運行,這就帶來了自身的風險。在這種情況下,你可以先選擇在容器內 添加一個用戶,然後在 systemd-nspawn 調用中增加 --user <username> 選項。

或者你可以啟動容器,讓systemd-networkd等設置虛擬網絡接口:

# systemd-nspawn --bind-ro=$HOME/.Xauthority:/root/.Xauthority \
              --bind=/tmp/.X11-unix \
              -D ~/containers/firefox \
              --network-veth -b

一旦你的容器被啟動,就像這樣運行Xorg二進位文件:

# systemd-run -M firefox --setenv=DISPLAY=:0 firefox

3D圖形加速[編輯 | 編輯原始碼]

為了啟用加速的3D圖形,可能有必要在 .nspawn 文件中加入以下一行,將 /dev/dri 掛載到容器中。

Bind=/dev/dri

上述技巧來自patrickskiba.com。這將明顯解決了以下問題

libGL error: MESA-LOADER: failed to retrieve device information
libGL error: Version 4 or later of flush extension not found
libGL error: failed to load driver: i915

你可以通過運行 glxinfoglxgears 來確認它已經被啟用。

Nvidia GPUs[編輯 | 編輯原始碼]

如果你不能在容器上安裝與主機上相同的nvidia驅動版本,你可能需要同時綁定驅動庫文件。你可以在主機上運行 pacman -Ql nvidia-utils 來查看它包含的所有文件。你不需要把所有文件都複製過來。當容器通過 machinectl start container-name 運行時,下面的systemd覆蓋文件將把所有必要的文件綁定過來。

本文或本章節的事實準確性存在爭議。

原因:/usr/lib/ 綁定到 /usr/lib/x86_64-linux-gnu/ 是沒有必要的。(在 Talk:Systemd-nspawn 中討論)


/etc/systemd/system/systemd-nspawn@.service.d/nvidia-gpu.conf
[Service]
ExecStart=
ExecStart=systemd-nspawn --quiet --keep-unit --boot --link-journal=try-guest --machine=%i \
--bind=/dev/dri \
--bind=/dev/shm \
--bind=/dev/nvidia0 \
--bind=/dev/nvidiactl \
--bind=/dev/nvidia-modeset \
--bind=/usr/bin/nvidia-bug-report.sh:/usr/bin/nvidia-bug-report.sh \
--bind=/usr/bin/nvidia-cuda-mps-control:/usr/bin/nvidia-cuda-mps-control \
--bind=/usr/bin/nvidia-cuda-mps-server:/usr/bin/nvidia-cuda-mps-server \
--bind=/usr/bin/nvidia-debugdump:/usr/bin/nvidia-debugdump \
--bind=/usr/bin/nvidia-modprobe:/usr/bin/nvidia-modprobe \
--bind=/usr/bin/nvidia-ngx-updater:/usr/bin/nvidia-ngx-updater \
--bind=/usr/bin/nvidia-persistenced:/usr/bin/nvidia-persistenced \
--bind=/usr/bin/nvidia-powerd:/usr/bin/nvidia-powerd \
--bind=/usr/bin/nvidia-sleep.sh:/usr/bin/nvidia-sleep.sh \
--bind=/usr/bin/nvidia-smi:/usr/bin/nvidia-smi \
--bind=/usr/bin/nvidia-xconfig:/usr/bin/nvidia-xconfig \
--bind=/usr/lib/gbm/nvidia-drm_gbm.so:/usr/lib/x86_64-linux-gnu/gbm/nvidia-drm_gbm.so \
--bind=/usr/lib/libEGL_nvidia.so:/usr/lib/x86_64-linux-gnu/libEGL_nvidia.so \
--bind=/usr/lib/libGLESv1_CM_nvidia.so:/usr/lib/x86_64-linux-gnu/libGLESv1_CM_nvidia.so \
--bind=/usr/lib/libGLESv2_nvidia.so:/usr/lib/x86_64-linux-gnu/libGLESv2_nvidia.so \
--bind=/usr/lib/libGLX_nvidia.so:/usr/lib/x86_64-linux-gnu/libGLX_nvidia.so \
--bind=/usr/lib/libcuda.so:/usr/lib/x86_64-linux-gnu/libcuda.so \
--bind=/usr/lib/libnvcuvid.so:/usr/lib/x86_64-linux-gnu/libnvcuvid.so \
--bind=/usr/lib/libnvidia-allocator.so:/usr/lib/x86_64-linux-gnu/libnvidia-allocator.so \
--bind=/usr/lib/libnvidia-cfg.so:/usr/lib/x86_64-linux-gnu/libnvidia-cfg.so \
--bind=/usr/lib/libnvidia-egl-gbm.so:/usr/lib/x86_64-linux-gnu/libnvidia-egl-gbm.so \
--bind=/usr/lib/libnvidia-eglcore.so:/usr/lib/x86_64-linux-gnu/libnvidia-eglcore.so \
--bind=/usr/lib/libnvidia-encode.so:/usr/lib/x86_64-linux-gnu/libnvidia-encode.so \
--bind=/usr/lib/libnvidia-fbc.so:/usr/lib/x86_64-linux-gnu/libnvidia-fbc.so \
--bind=/usr/lib/libnvidia-glcore.so:/usr/lib/x86_64-linux-gnu/libnvidia-glcore.so \
--bind=/usr/lib/libnvidia-glsi.so:/usr/lib/x86_64-linux-gnu/libnvidia-glsi.so \
--bind=/usr/lib/libnvidia-glvkspirv.so:/usr/lib/x86_64-linux-gnu/libnvidia-glvkspirv.so \
--bind=/usr/lib/libnvidia-ml.so:/usr/lib/x86_64-linux-gnu/libnvidia-ml.so \
--bind=/usr/lib/libnvidia-ngx.so:/usr/lib/x86_64-linux-gnu/libnvidia-ngx.so \
--bind=/usr/lib/libnvidia-opticalflow.so:/usr/lib/x86_64-linux-gnu/libnvidia-opticalflow.so \
--bind=/usr/lib/libnvidia-ptxjitcompiler.so:/usr/lib/x86_64-linux-gnu/libnvidia-ptxjitcompiler.so \
--bind=/usr/lib/libnvidia-rtcore.so:/usr/lib/x86_64-linux-gnu/libnvidia-rtcore.so \
--bind=/usr/lib/libnvidia-tls.so:/usr/lib/x86_64-linux-gnu/libnvidia-tls.so \
--bind=/usr/lib/libnvidia-vulkan-producer.so:/usr/lib/x86_64-linux-gnu/libnvidia-vulkan-producer.so \
--bind=/usr/lib/libnvoptix.so:/usr/lib/x86_64-linux-gnu/libnvoptix.so \
--bind=/usr/lib/modprobe.d/nvidia-utils.conf:/usr/lib/x86_64-linux-gnu/modprobe.d/nvidia-utils.conf \
--bind=/usr/lib/nvidia/wine/_nvngx.dll:/usr/lib/x86_64-linux-gnu/nvidia/wine/_nvngx.dll \
--bind=/usr/lib/nvidia/wine/nvngx.dll:/usr/lib/x86_64-linux-gnu/nvidia/wine/nvngx.dll \
--bind=/usr/lib/nvidia/xorg/libglxserver_nvidia.so:/usr/lib/x86_64-linux-gnu/nvidia/xorg/libglxserver_nvidia.so \
--bind=/usr/lib/vdpau/libvdpau_nvidia.so:/usr/lib/x86_64-linux-gnu/vdpau/libvdpau_nvidia.so \
--bind=/usr/lib/xorg/modules/drivers/nvidia_drv.so:/usr/lib/x86_64-linux-gnu/xorg/modules/drivers/nvidia_drv.so \
--bind=/usr/share/X11/xorg.conf.d/10-nvidia-drm-outputclass.conf:/usr/share/X11/xorg.conf.d/10-nvidia-drm-outputclass.conf \
--bind=/usr/share/dbus-1/system.d/nvidia-dbus.conf:/usr/share/dbus-1/system.d/nvidia-dbus.conf \
--bind=/usr/share/egl/egl_external_platform.d/15_nvidia_gbm.json:/usr/share/egl/egl_external_platform.d/15_nvidia_gbm.json \
--bind=/usr/share/glvnd/egl_vendor.d/10_nvidia.json:/usr/share/glvnd/egl_vendor.d/10_nvidia.json \
--bind=/usr/share/licenses/nvidia-utils/LICENSE:/usr/share/licenses/nvidia-utils/LICENSE \
--bind=/usr/share/vulkan/icd.d/nvidia_icd.json:/usr/share/vulkan/icd.d/nvidia_icd.json \
--bind=/usr/share/vulkan/implicit_layer.d/nvidia_layers.json:/usr/share/vulkan/implicit_layer.d/nvidia_layers.json \
DeviceAllow=/dev/dri rw
DeviceAllow=/dev/shm rw
DeviceAllow=/dev/nvidia0 rw
DeviceAllow=/dev/nvidiactl rw
DeviceAllow=/dev/nvidia-modeset rw
注意: 每當你在主機上升級nvidia驅動時,你將需要重啟容器,並可能需要在容器中運行 ldconfig 以更新庫。

訪問主機文件系統[編輯 | 編輯原始碼]

請見 --bind--bind-rosystemd-nspawn(1).

如果主機和容器都是 Arch Linux,則例如,可以共享 pacman 緩存:

# systemd-nspawn --bind=/var/cache/pacman/pkg

或許你還可以使用文件來進行指定的先於容器的綁定:

/etc/systemd/nspawn/my-container.nspawn
[Files]
Bind=/var/cache/pacman/pkg

詳情見 #容器前設置.

要將該目錄綁定到容器內的不同路徑,請添加路徑,並用冒號分隔。例如:

# systemd-nspawn --bind=/path/to/host_dir:/path/to/container_dir

如果是 #無特權容器,產生的掛載點將由nobody用戶擁有。這可以通過 idmap 掛載選項來修改。

# systemd-nspawn --bind=/path/to/host_dir:/path/to/container_dir:idmap

在非systemd系統上運行[編輯 | 編輯原始碼]

詳情見 Init#systemd-nspawn

使用Btrfs子卷作為容器的根[編輯 | 編輯原始碼]

要使用 Btrfs subvolume 作為容器根目錄的模板,請使用 --template 標誌。這將獲取子卷的快照,並以它填充容器的根目錄。

注意: 如果指定的模板路徑不是子卷的根,則會複製整個樹。這將非常耗時。​

例如,要使用位於/.snapshots/403/snapshot的快照:

# systemd-nspawn --template=/.snapshots/403/snapshots -b -D my-container

其中my-container是將為容器創建的目錄的名稱。關機後,會保留新創建的子卷。​

使用容器的臨時Btrfs快照[編輯 | 編輯原始碼]

可以使用--ephemeral-x標誌來創建容器的臨時btrfs快照,並將其作為容器的根。在容器中啟動時所作的任何更改都將丟失。例如:

# systemd-nspawn -D my-container -xb

其中my-container現有容器或系統的目錄。例如,如果/是一個btrfs子卷,我們可以通過以下操作創建一個當前運行的主機系統的短暫容器:

# systemd-nspawn -D / -xb 

關閉容器電源後,創建的btrfs子卷會立即被刪除。

在 systemd-nspawn 中運行 docker[編輯 | 編輯原始碼]

Docker 20.10以來,在啟用 cgroups v2 (Arch Linux默認)的情況下,可以在無特權的 systemd-nspawn 容器內運行Docker容器,而不會因為禁用cgroups和用戶名字空間而破壞安全措施。要做到這一點,請編輯或創建 /etc/systemd/nspawn/myContainer.nspawn,並添加以下配置。

/etc/systemd/nspawn/myContainer.nspawn
[Exec]
SystemCallFilter=add_key keyctl bpf

然後,Docker 應該在容器內按原樣工作。

注意: 上述配置將系統調用 add_keykeyctlbpf 暴露給容器,而這些調用是沒有命名間隔的。這仍然可能是一個安全風險,儘管它比完全禁用用戶命名空間要低得多,就像人們在 cgroups v2 之前必須做的那樣。

由於 overlayfs 不能用於用戶命名空間,並且在 systemd-nspawn 中不可用,默認情況下,Docker 會退回到使用低效的 vfs 作為其存儲驅動,每次啟動容器都會創建一個鏡像的副本。這一點可以通過使用 fuse-overlayfs 作為其存儲驅動來解決。要做到這一點,我們需要首先將 fuse 暴露給容器。

/etc/systemd/nspawn/myContainer.nspawn
[Files]
Bind=/dev/fuse

然後允許容器讀取和寫入設備節點。

# systemctl set-property systemd-nspawn@myContainer DeviceAllow='/dev/fuse rwm'

最後,在容器中安裝 fuse-overlayfs 包。你需要重新啟動容器以使所有配置生效。

疑難解答[編輯 | 編輯原始碼]

Root登錄失敗[編輯 | 編輯原始碼]

如果您在嘗試登錄時(即使用machinectl login <name>)得到以下錯誤:

arch-nspawn login: root
Login incorrect

並且容器中journal顯示:

pam_securetty(login:auth): access denied: tty 'pts/0' is not secure !


本文或本章節的事實準確性存在爭議。

原因: 用戶不應編輯 /usr/lib 中的文件,/usr/lib/tmpfiles.d/arch.conf 中的改動會在 filesystem 升級後丟失。(在 Talk:Systemd-nspawn 中討論)


只刪除容器文件系統中的/etc/securetty[10]/usr/share/factory/etc/securetty ,或者也可以根據需要只簡單地向容器文件系統中 /etc/securetty 添加所需的pty終端設備(如 pts/0)。

但是任何改變都會在下一次啟動時被覆蓋,因此有必要同時在容器文件系統上的 /usr/lib/tmpfiles.d/arch.conf 文件中刪除 /etc/securetty 條目,見FS#63236。如果你選擇刪除,也可以在 /etc/pacman.conf 中選擇將這些文件列入黑名單(NoExtract),以防止它們被重新安裝。詳見 FS#45903

execv(...) failed: Permission denied[編輯 | 編輯原始碼]

當試圖通過systemd-nspawn -bD /path/to/container來啟動容器時(或在容器中執行一些東西),出現以下錯誤:

execv(/usr/lib/systemd/systemd, /lib/systemd/systemd, /sbin/init) failed: Permission denied

即使有關文件的權限(即 /lib/systemd/systemd)是正確的,這也可能是由於以非root用戶的身份掛載容器所在的文件系統造成的。例如,如果您在fstab中手動掛載了具有noauto,user,...選項的磁碟,systemd-nspawn將不允許執行文件,即使它們是由root用戶擁有的。

TERM中的終端類型不正確(破損的顏色)[編輯 | 編輯原始碼]

當通過machinectl login登錄容器時,容器內的終端中的顏色和按鍵可能會被破壞。這可能是由於TERM環境變量中的終端類型不正確。除非明確配置,否則環境變量不會從主機上的 shell 繼承,而是回到 systemd 中固定的默認值 (vt220)。要配置,在容器內為 container-getty@.service systemd 服務創建一個配置覆蓋,啟動 machinectl login 的登錄 getty,並將 TERM 設置為與您登錄的主機終端匹配的值:

/etc/systemd/system/container-getty@.service.d/term.conf
[Service]
Environment=TERM=xterm-256color

或者使用machinectl shell。它正確地繼承了終端的TERM環境變量。

在容器內掛載NFS共享[編輯 | 編輯原始碼]

目前(2019年6月)還不可能

這篇文章的某些內容需要擴充。

原因: 下述方法未經過驗證,正在討論 (在 Talk:Systemd-nspawn#A_trick_way_to_mount_a_NFS_share_with_the_container 中討論)

曲線救國的方式是使用主機掛載NFS後通過目錄映射到容器目錄,NFS掛載方式請查看 NFS#Client。掛載後會遇到權限錯誤,這是因為容器的用戶ID是通過主機映射過去的,詳情請查看:[11], 解決這個問題的方法有很多種,如下面幾種:

  1. 在主機上使用管理員權限將NFS掛載目錄所屬用戶ID修改為容器所對應的實際用戶ID。
  2. 在容器啟動時使用--private-users參數指定使用用戶作為特權用戶啟動。
  3. 在掛載目錄時使用--bind-user參數指定NFS目錄所屬用戶映射到容器中。

See also[編輯 | 編輯原始碼]