systemd-nspawn - Arch Linux 中文维基

跳至內容

# systemd-nspawn

出自 Arch Linux 中文维基

翻譯狀態：

- 本文（或部分內容）譯自 Systemd-nspawn，最近一次同步於 2024-12-18，若英文版本有所更改，則您可以幫助同步與翻譯更改的內容。
- 您可以在 ArchWiki 的對應頁面找到本文翻譯的原始修訂歷史。
- 本文可能與英文原文存在出入。

相關文章

- Linux Containers
- systemd
- systemd-networkd
- Docker

systemd-nspawn 跟 chroot 命令類似，是個終極版的 chroot。

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

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

相比 LXC 或 Libvirt， systemd-nspawn 更容易配置。

## 安裝

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

## 用例

### 創建並啟動一個最小的 Arch Linux 容器

首先創建一個目錄來保存容器。在這個示例中我們將使用 `~/MyContainer`.

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

    # pacstrap -K -c ~/MyContainer base [additional packages/groups]

提示：base包 已經不再依賴內核包 linux包，這已為容器化做好準備。
注意：如果是從一個 pacstrap 不可用的作業系統中創建，可以使用如下方法替代容器鏡像 從現有_Linux_發行版安裝_Arch_Linux#方法一：使用_Bootstrap_鏡像（推薦）。pacman 密鑰環需要在容器中進行初始化，參閱 從現有_Linux_發行版安裝_Arch_Linux#初始化_pacman_密匙環。

一旦安裝完成後，進入容器中並設置root密碼：

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

提示：設置根密碼是可選的。你可以運行 `machinectl shell root@MyContainer` 直接獲取一個 root shell ，並不需要登錄。

最後, 啟動容器:

    # 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-keyring包 或 ubuntu-keyring包 中的一個或兩個（當然要安裝你想要的發行版的keyring）。

然後通過以下結構式調用 deboostrap：

    # debootstrap codename container-name repository-url

- codename：對於 Debian，有效的代碼名稱要不然是穩定性別名如 `stable`、`testing` 或 `unstable`，要不然就是版本名稱如 `bookworm` 或 `sid`。你可以從這裡獲取名稱列表：[1]。對於 Ubuntu：只有像是 `jammy` 或 `noble` 這樣的名稱應該被使用而不是版本號。你可以從 [2] 或者 [3] 獲取一份代碼名稱與版本號的對應表格。

- container-name 是會包含作業系統文件樹的目錄，如果它不存在的話將會被創建。

- repository-url：下載作業系統文件樹的鏡像地址。目前 Debian 發布在任何有效鏡像站，比如說 CDN 支持的 https://deb.debian.org/debian 。 Ubuntu 的鏡像站在 [4]，比如說 https://archive.ubuntu.com/ubuntu 。

注意：

- 如果你需要一個以歸檔的 Debian 版本（指的是任何在2024年8月，Debian 10/Buster 之前的版本），請使用一個特殊的 debian-archive 鏡像地址：https://archive.debian.org/debian/ 。Ubuntu 應該使用 https://old-releases.ubuntu.com/ubuntu/ 。
- systemd-nspawn 要求容器中的作業系統使用 systemd 作為其 init 程序（以 PID 1 運行）。從 Debian 8 （「jessie」） [5] 以及 Ubuntu 15.04 （「vivid」）[6] 之後，這是兩者的默認 init 程序。但請注意，對於不包含在上述密鑰包中的版本，會出現與 「未知簽名密鑰（unknown signing key）」相關的問題，比如任何早於 Debian 9 （「Stretch」）[7] 的發行版本。這使得後者成為成為無需額外操作即可安裝的最老版本。

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

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

提示：如果計劃使用 #machinectl 管理容器，請確保使用合適的包名在目標作業系統容器中安裝 dbus 包，否則容器將不會工作。可以安裝 `systemd-container` 時使用 `--include=` 選項，這始終是 dbus 和 systemd 的依賴項：

    # debootstrap --include=systemd-container codename /var/lib/machines/container-name repository-url

### Create a Fedora or AlmaLinux environment

Install dnf包, and edit the `/etc/dnf/dnf.conf` file to add the required Fedora repositories.

    /etc/dnf/dnf.conf

    [fedora]
    name=Fedora $releasever - $basearch
    metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-$releasever&arch=$basearch
    gpgkey=https://getfedora.org/static/fedora.gpg

    [updates]
    name=Fedora $releasever - $basearch - Updates
    metalink=https://mirrors.fedoraproject.org/metalink?repo=updates-released-f$releasever&arch=$basearch
    gpgkey=https://getfedora.org/static/fedora.gpg

The fedora.gpg file contain the gpg keys for the latest Fedora releases https://getfedora.org/security/. To set up a minimal Fedora 37 container:

    # mkdir /var/lib/machines/container-name
    # dnf --releasever=37 --best --setopt=install_weak_deps=False --repo=fedora --repo=updates --installroot=/var/lib/machines/container-name install dhcp-client dnf fedora-release glibc glibc-langpack-en iputils less ncurses passwd systemd systemd-networkd systemd-resolved util-linux vim-default-editor

注意： If you want to install a different Fedora release, keep in mind that different releases will have distinct package requirements.

If you are using btrfs filesystem create a subvolume instead of creating a directory.

An Enterprise Linux derivative like AlmaLinux has three repositories enabled by default, BaseOS wich contains a core set that provides the basis for all installations, AppStream that includes additional applications, language packages, etc and Extras that contains packages not included in RHEL. So for a minimal container we only need to add the BaseOS repository to `/etc/dnf/dnf.conf`

    /etc/dnf/dnf.conf

    [baseos]
    name=AlmaLinux $releasever - BaseOS
    mirrorlist=https://mirrors.almalinux.org/mirrorlist/$releasever/baseos
    gpgkey=https://repo.almalinux.org/almalinux/RPM-GPG-KEY-AlmaLinux-$releasever

To create an AlmaLinux 9 minimal container:

    # dnf --repo=baseos --releasever=9 --best --installroot=/var/lib/machines/container-name --setopt=install_weak_deps=False install almalinux-release dhcp-client dnf glibc-langpack-en iproute iputils less passwd systemd vim-minimal

This will install the latest minor version of AlmaLinux 9, you can choose to install a specific point release, but you will need to change the gpgpkey entry to manually point to RPM-GPG-KEY-AlmaLinux-9

Just like Arch, Fedora or AlmaLinux will not let you log in as root without a password. To set up the root password, run systemd-nspawn without the `-b` option:

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

### 編譯與測試包

請參閱 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 選項

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

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

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

### machinectl

注意：machinectl 工具要求在容器中安裝 systemd 和 dbus包 。有關詳細討論，請參閱 [8]。

容器可以被 `machinectl subcommand container-name` 命令管理， 比如說，啟動容器：

    $ machinectl start container-name

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

相似的， 還有其他的子命令 `poweroff`， `reboot`， `status` 和 `show`. 詳細解釋請看 machinectl(1) § Machine Commands 。

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

其他常見命令：

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

machinectl 也有管理容器（或虛擬機）鏡像和鏡像傳輸的子命令。 詳情見 machinectl(1) § Image Commands 與 machinectl(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/` 被意外移除。 [11]
- 當有`--settings=override`（在`systemd-nspawn@.service`文件內被指定）時，.nspawn內與命令行中所指定的為網絡選項的交互不能正常工作。[12] 作為一個變通方法，你需要引入`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時，主機聯網是默認模式。

另一方面，私有網絡模式適用於應與主機系統隔離的 "系統容器"。創建虛擬以太網鏈路是一個非常靈活的工具，可以創建複雜的虛擬網絡。這是由machinectl或`systemd-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-容器名@if2`和`host0@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 DHCP和systemd-networkd#Network bridge with static IP addresses，了解使用systemd-networkd進行配置的例子。

還有一個`--network-zone=zone-name`選項，它與`--network-bridge`類似，但網橋由systemd-nspawn和systemd-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=interface`將`interface`分配給容器（並使它在容器啟動時對主機不可用）。請注意，`--network-interface`意味著`--private-network`。

提示：自 v256 版本起，systemd-nspawn 容器支持傳遞無線網絡接口。

### 埠映射

當啟用私有網絡時，可以使用`-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` 連接到主機而不是容器。只有到其他接口的連接才會受到埠映射的影響。詳情請參見 [13]。
- 埠映射只對IPv4連接有效。[14]。

### 域名解析

容器中的域名解析 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身份啟動。​

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

原因：Very little of Linux Containers#Enable support to run unprivileged containers (optional) applies to systemd-nspawn.（在Talk:Systemd-nspawn討論）
注意：此功能需要 user_namespaces(7), 更多信息請參見 Linux Containers#Enable support to run unprivileged containers (optional)。

最簡單的方法是通過`-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? 中討論）

詳情見 Xhost 和 Change 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 environment 或 window 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

你可以通過運行 `glxinfo` 或 `glxgears` 來確認它已經被啟用。

##### 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-ro` 於 systemd-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_key 、keyctl 和 bpf 暴露給容器，而這些調用是沒有命名間隔的。這仍然可能是一個安全風險，儘管它比完全禁用用戶命名空間要低得多，就像人們在 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包 包。你需要重新啟動容器以使所有配置生效。

### 映射本地用戶並將其主目錄綁定掛載到容器中

按照#創建並啟動一個最小的 Arch Linux 容器中的說明，在 `/var/lib/machines/MyContainer/` 中創建一個容器。

創建一個包含 `BindUser=` 的配置文件，以將選定的本地用戶名映射到容器中。請注意，這需要 `PrivateUsers=`，詳見 systemd-nspawn(1) 手冊頁。在容器內外綁定掛載的主目錄中創建的文件將具有相同的 UID 和 GID。

    /etc/systemd/nspawn/MyContainer.nspawn

    [Exec]
    User=username
    PrivateUsers=pick

    [Files]
    BindUser=username

在配置中同時使用 `User=` 指定了用於在容器內運行命令（例如交互式 shell）的默認用戶：

    # systemd-nspawn -M MyContainer bash

這對於從另一個 Linux 發行版測試 Arch Linux 軟體包很有幫助。

## 疑難解答

### 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`[15]和`/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是通過主機映射過去的，詳情請查看：[16]， 解決這個問題的方法有很多種，如下面幾種：

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

## See also

- Automatic console login
- Creating containers with systemd-nspawn
- Presentation by Lennart Poettering on systemd-nspawn
- Running Firefox in a systemd-nspawn container
- Graphical applications in systemd-nspawn

分類：​

- 虛擬化
- 沙箱
- 使用「模板:風格」標記的頁面或章節

隱藏分類：​

- 使用「模板:準確性」標記的頁面或章節

systemd-nspawn
新增話題