Linux 容器

出自 Arch Linux 中文维基

本文或本節需要翻譯。要貢獻翻譯,請訪問簡體中文翻譯團隊

附註: Partial translation.(在 Talk:Linux 容器# 中討論)

Linux 容器 (LXC) 是一種在單個宿主機(LXC host)運行多個隔離的 Linux 系統(容器)的作業系統級虛擬化方法。它並不提供虛擬機,而是提供了一個具有獨立 CPU、內存、塊 I/O、網絡等空間和資源控制環境的虛擬環境。這是通過 LXC 宿主機上 Linux 內核的 namespacescgroups 特性實現的。它類似於 chroot ,但是具有更好的隔離性。

LXD 可以被用做 LXC 的管理器,本頁面則涉及直接使用 LXC 。

使用容器的替代方法包括 systemd-nspawnDocker

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

LXC 容器可以被設置為以 特權非特權 配置運行。

一般來說,運行 非特權 容器比運行 特權 容器 更安全 ,因為 非特權 容器在設計上具有更高程度的隔離性。其中的關鍵在於容器內的 root UID 被映射為宿主機上的非 root UID ,這使得容器內部的攻擊更難以對宿主機系統施加影響。換句話說,如果攻擊者設法逃離容器,它們會發現自己在宿主機上被限制或沒有權限。

Arch 的 linuxlinux-ltslinux-zen 內核軟體包現在提供了 非特權 容器的開箱即用支持。 類似的,對於 linux-hardened 軟體包, 非特權 容器僅適用於系統管理員;對於普通用戶,默認情況下禁用了 namespace ,因此需要進行額外的內核配置更改。

本文包含了用戶運行任何類型容器所需的信息,但是為了運行 非特權 容器可能需要 額外的操作

一個例子說明非特權容器[編輯 | 編輯原始碼]

為了說明 UID 映射的作用,考慮以下來自運行中的非特權容器的輸出。在 ps 命令的輸出中,我們可以看到容器化的進程由容器化的 root 用戶所有:

[root@unprivileged_container /]# ps -ef | head -n 5
UID        PID  PPID  C STIME TTY          TIME CMD
root         1     0  0 17:49 ?        00:00:00 /sbin/init
root        14     1  0 17:49 ?        00:00:00 /usr/lib/systemd/systemd-journald
dbus        25     1  0 17:49 ?        00:00:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
systemd+    26     1  0 17:49 ?        00:00:00 /usr/lib/systemd/systemd-networkd

然而在宿主機中,可以看到這些容器化的 root 進程事實上顯示為以映射用戶(ID>99999)的身份運行,而不是宿主機上真實的 root 用戶:

[root@host /]# lxc-info -Ssip --name sandbox
State:          RUNNING
PID:            26204
CPU use:        10.51 seconds
BlkIO use:      244.00 KiB
Memory use:     13.09 MiB
KMem use:       7.21 MiB
[root@host /]# ps -ef | grep 26204 | head -n 5
UID        PID  PPID  C STIME TTY          TIME CMD
100000   26204 26200  0 12:49 ?        00:00:00 /sbin/init
100000   26256 26204  0 12:49 ?        00:00:00 /usr/lib/systemd/systemd-journald
100081   26282 26204  0 12:49 ?        00:00:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
100000   26284 26204  0 12:49 ?        00:00:00 /usr/lib/systemd/systemd-logind

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

需要的軟體[編輯 | 編輯原始碼]

安裝 lxcarch-install-scripts 使宿主機系統能夠運行特權 lxc 容器。

啟用非特權容器支持(可選)[編輯 | 編輯原始碼]

更改 /etc/lxc/default.conf 使其包含下面的配置行:

lxc.idmap = u 0 100000 65536
lxc.idmap = g 0 100000 65536

創建 /etc/subuid/etc/subgid 為每個可以運行容器的用戶配置容器化 UID/GID 對的映射。下面的示例僅針對 root 用戶(和 systemd 系統單元):

/etc/subuid
root:100000:65536
/etc/subgid
root:100000:65536

此外,只有在提前委派一個 cgroup 時,以非 root 用戶運行非特權容器才有效( cgroup2 委派模型強制執行此限制,而不是 liblxc )。使用以下 systemd 命令來委派 cgroup (根據 LXC - Getting started: Creating unprivileged containers as a user):

$ systemd-run --unit=myshell --user --scope -p "Delegate=yes" lxc-start container_name

同樣的方式也適用於其他 lxc 命令。

本文或本章節可能需要合併到cgroups#User delegation

附註: 這不是 Linux 容器特有的問題,避免重複。(在 Talk:Linux 容器 中討論)

或者,您可以通過創建一個 systemd 單元來委派非特權 cgroup (根據 Rootless Containers: Enabling CPU, CPUSET, and I/O delegation ):

/etc/systemd/system/user@.service.d/delegate.conf
[Service]
Delegate=cpu cpuset io memory pids
linux-hardened 和自定義內核上的非特權容器[編輯 | 編輯原始碼]

希望在 linux-hardened 或自定義內核上運行 非特權 容器的用戶需要完成幾個額外的配置步驟。

首先,內核需要支持用戶命名空間(具有 CONFIG_USER_NS 配置)。所有 Arch Linux 內核都有 CONFIG_USER_NS 的支持。然而,基於更一般的安全考慮, linux-hardened 內核僅為 root 用戶啟用了用戶命名空間。這裡有兩個建立 非特權 容器的選項:

  • 只以 root 用戶的身份建立非特權容器。同樣為 sysctluser.max_user_namespaces 配置設置一個正值來滿足你的環境要求,如果當前值為 0 (這將解決運行 lxc info --show-log container_name 時產生的 Failed to clone process in new user namespace 錯誤)。
  • linux-hardened & lxd 5.0.0 下,你可能需要配置 /etc/subuid & /etc/subgid 為使用 root:1000000:65536 。你還可能需要以特權模式啟用 第一個 容器。這會解決錯誤 newuidmap failed to write mapping "newuidmap: uid range [0-1000000000) -> [1000000-1001000000) not allowed"
  • 啟用 sysctlkernel.unprivileged_userns_clone 配置來允許普通用戶運行非特權容器。可以以 root 身份運行 sysctl kernel.unprivileged_userns_clone=1 來為當前會話生效或閱讀 sysctl.d(5) 使其永久生效。

宿主機網絡配置[編輯 | 編輯原始碼]

LXC 支持多種不同的虛擬網絡類型和設備(見 lxc.container.conf(5) )。本節所介紹的虛擬網絡類型中,大部分都需要一個宿主機上的網橋設備。

這裡主要有幾個可供參考的配置:

  1. 主機網橋
  2. NAT 網橋

主機網橋需要宿主機上的網絡設置工具來配置一個共享網橋接口。宿主機和所有 lxc 容器將在同一個網絡中被指派 IP 地址(如 192.168.1.x )。當你的目標是將一些暴露在網絡上的服務如 web 伺服器或 VPN 伺服器容器化時,這可能更加便捷。用戶可以將 lxc 當作物理 LAN 上的其他 PC ,並在路由器上為其配置相應的埠轉發。增加的便捷也可以認為是增加的威脅向量,同樣的,如果 WAN 流量被轉發給 lxc ,將其運行在不同的網絡範圍上將顯現更小的威脅面。

NAT 網橋不需要宿主機的網絡設置工具來配置網橋。 lxc 自帶的 lxc-net 將建立一個叫 lxcbr0 的 NAT 網橋。這個 NAT 網橋是一個獨立的網橋,具有不與乙太網設備或物理網絡橋接的私有網絡。它以宿主機的一個子網的形式存在。

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

Network bridge

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

安裝 dnsmasq ,它是 lxc-net 的依賴,並在網橋啟動前,先為其建立配置文件:

/etc/default/lxc-net
# Leave USE_LXC_BRIDGE as "true" if you want to use lxcbr0 for your
# containers.  Set to "false" if you'll use virbr0 or another existing
# bridge, or mavlan to your host's NIC.
USE_LXC_BRIDGE="true"

# If you change the LXC_BRIDGE to something other than lxcbr0, then
# you will also need to update your /etc/lxc/default.conf as well as the
# configuration (/var/lib/lxc/<container>/config) for any containers
# already created using the default config to reflect the new bridge
# name.
# If you have the dnsmasq daemon installed, you'll also have to update
# /etc/dnsmasq.d/lxc and restart the system wide dnsmasq daemon.
LXC_BRIDGE="lxcbr0"
LXC_ADDR="10.0.3.1"
LXC_NETMASK="255.255.255.0"
LXC_NETWORK="10.0.3.0/24"
LXC_DHCP_RANGE="10.0.3.2,10.0.3.254"
LXC_DHCP_MAX="253"
# Uncomment the next line if you'd like to use a conf-file for the lxcbr0
# dnsmasq.  For instance, you can use 'dhcp-host=mail1,10.0.3.100' to have
# container 'mail1' always get ip address 10.0.3.100.
#LXC_DHCP_CONFILE=/etc/lxc/dnsmasq.conf

# Uncomment the next line if you want lxcbr0's dnsmasq to resolve the .lxc
# domain.  You can then add "server=/lxc/10.0.3.1' (or your actual $LXC_ADDR)
# to your system dnsmasq configuration file (normally /etc/dnsmasq.conf,
# or /etc/NetworkManager/dnsmasq.d/lxc.conf on systems that use NetworkManager).
# Once these changes are made, restart the lxc-net and network-manager services.
# 'container1.lxc' will then resolve on your host.
#LXC_DOMAIN="lxc"
提示: 確保網橋的 IP 網段不會和本地網絡衝突。

然後我們需要修改 LXC 容器模板使我們的容器使用我們的網橋:

/etc/lxc/default.conf
lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.hwaddr = 00:16:3e:xx:xx:xx

作為一個可選配置,創建一個配置文件來手動為任意一個容器指定 IP 地址:

/etc/lxc/dnsmasq.conf
dhcp-host=playtime,10.0.3.100

現在 startenable lxc-net.service 來建立網橋接口。

防火牆相關[編輯 | 編輯原始碼]

基於宿主機運行的防火牆類型,可能需要允許 lxcbr0 的入口流量進入宿主機,以及 lxcbr0 的出口流量穿過宿主機進入其他網絡。為了測試這是否可以實現,嘗試啟動一個容器並使用 DHCP 自動獲取 IP 地址,檢查 lxc-net 是否能夠為容器註冊一個 IP 地址。(如果 IP 地址並沒有被成功分配,可以用 lxc-ls -f 檢查,宿主機有必要更改的策略。

ufw 用戶可以簡單運行 下面兩行命令 來放行入口和出口流量:

# ufw allow in on lxcbr0
# ufw route allow in on lxcbr0

或者, nftables 用戶可以編輯 /etc/nftables.conf (並運行 nft -f /etc/nftables.conf 重載該配置;運行 nft -cf /etc/nftables.conf 來檢查格式是否正確)使容器能夠具有網際網路訪問權限(將 "eth0" 替換成系統中具有網際網路連接的設備;運行 ip link 列出所有設備:

/etc/nftables.conf
table inet filter {
  chain input {
    ...
    iifname "lxcbr0" accept comment "Allow lxc containers"
    
    pkttype host limit rate 5/second counter reject with icmpx type admin-prohibited
    counter
  }
  chain forward {
    ...
    iifname "lxcbr0" oifname "eth0" accept comment "Allow forwarding from lxcbr0 to eth0"
    iifname "eth0" oifname "lxcbr0" accept comment "Allow forwarding from eth0 to lxcbr0"
  }
}

另外,由於 lxc 運行在 10.0.3.x 子網上,需要將對諸如 ssh 、 httpd 等服務的訪問主動轉發到 lxc 。原則上,主機上的防火牆需要對容器上預期埠的入口流量進行轉發。

iptables 規則示例[編輯 | 編輯原始碼]

這個示例規則的功能是允許 ssh 流量轉發到 lxc :

# iptables -t nat -A PREROUTING -i eth0 -p tcp --dport 2221 -j DNAT --to-destination 10.0.3.100:22

這個規則將 2221 埠的 tcp 流量轉發到 lxc 的 22 埠上。

注意: 確保允許宿主機上 2221/tcp 的流量和 lxc 上 22/tcp 的流量。

為了從 LAN 上的另一台 PC 通過 ssh 連接到容器,需要 ssh 到宿主機的 2221 埠。宿主機將會把流量轉發給容器。

$ ssh -p 2221 host.lan
ufw 規則示例[編輯 | 編輯原始碼]

如果使用 ufw,將下面的內容添加到 /etc/ufw/before.rules 中:

/etc/ufw/before.rules


*nat
:PREROUTING ACCEPT [0:0]
-A PREROUTING -i eth0 -p tcp --dport 2221 -j DNAT --to-destination 10.0.3.100:22
COMMIT
以非 root 用戶運行容器[編輯 | 編輯原始碼]

為了使用非 root 用戶創建並啟動容器,需要進行額外的配置。

建立 usernet 文件名為 /etc/lxc/lxc-usernet 。根據 lxc-usernet(5),每一行配置格式為:

user type bridge number

為每個需要建立容器的用戶添加配置。 bridge 需要和 /etc/default/lxc-net 中定義的一樣。

在非 root 用戶的家目錄還需要一份 /etc/lxc/default.conf 配置文件的拷貝,如 ~/.config/lxc/default.conf (如果有必要的話請建立該目錄)。

以非 root 用戶運行容器需要 ~/.local/share/ 具有 +x 權限。在啟動容器前使用 chmod 命令應用該更改。

創建容器[編輯 | 編輯原始碼]

使用 lxc-create 命令創建容器。在 lxc-3.0.0-1 版本中,上游移除了本地存儲的容器模板。

使用像下面這樣的調用,來建立一個 Arch 容器:

# lxc-create -n playtime -t download -- --dist archlinux --release current --arch amd64

使用像下面這樣的調用並從支持的列表中選擇,來建立其他發行版的容器:

# lxc-create -n playtime -t download
提示:
  • 用戶可以選擇安裝 haveged 包並 start haveged.service ,來避免在設置過程中,等待系統熵種子生成時出現類似掛起的情況。否則 private/GPG 密鑰的生成過程可能使整個過程變得更長。
  • Btrfs 用戶可以應用 -B btrfs 來建立一個 Btrfs 子卷以存儲容器化的根目錄。這在使用 lxc-clone 命令進行容器克隆時十分有用。 ZFS 用戶可以相應地應用 -B zfs
注意: 想要使用傳統模板的用戶可以在 lxc-templatesAUR 找到它們,或者用戶也可以用 distrobuilder 建立自己的模板。

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

下面的例子同時適用於 特權非特權 容器。主要對於非特權容器,默認會出現例子中沒有出現的額外配置,包括 lxc.idmap = u 0 100000 65536lxc.idmap = g 0 100000 65536 這些在 啟用非特權容器支持(可選) 中作為可選配置添加的值。

具有網絡支持的基本配置[編輯 | 編輯原始碼]

注意: 在 lxc-1:2.1.0-1 版本中,許多配置選項改變了。已有的容器需要被更新;用戶需要參考 v2.1 發布註記 中包含這些變化的表格。

當一個進程使用 /var/lib/lxc/CONTAINER_NAME/config 中定義的容器時,系統資源將被虛擬化/隔離。在默認情況下,創建容器的進程將應用一個沒有網絡支持的最小化配置。下面是一個具有 lxc-net.service 提供網絡支持的示例:

/var/lib/lxc/playtime/config
# Template used to create this container: /usr/share/lxc/templates/lxc-archlinux
# Parameters passed to the template:
# For additional config options, please look at lxc.container.conf(5)

# Distribution configuration
lxc.include = /usr/share/lxc/config/common.conf
lxc.arch = x86_64

# Container specific configuration
lxc.rootfs.path = dir:/var/lib/lxc/playtime/rootfs
lxc.uts.name = playtime

# Network configuration
lxc.net.0.type = veth
lxc.net.0.link = lxcbr0
lxc.net.0.flags = up
lxc.net.0.hwaddr = ee:ec:fa:e9:56:7d

容器中的掛載點[編輯 | 編輯原始碼]

對於 特權 容器,用戶可以選擇宿主機上的目錄並以 bind 方式掛載在容器中。這在容器化相同架構並希望在容器和宿主機之間分享 pacman 軟體包時非常有用。另一個例子是共享文件夾。配置的格式是:

lxc.mount.entry = /var/cache/pacman/pkg var/cache/pacman/pkg none bind 0 0
注意: 若不對文件系統權限進行更改,這在 非特權 容器上將無法使用。

圖形化程序相關(可選)[編輯 | 編輯原始碼]

為了在宿主機上顯示容器中的程序窗口,需要定義一些掛載點,使容器化的程序可以獲取宿主機上的資源。向 /var/lib/lxc/playtime/config 添加下面的段落:

## for xorg
lxc.mount.entry = /dev/dri dev/dri none bind,optional,create=dir
lxc.mount.entry = /dev/snd dev/snd none bind,optional,create=dir
lxc.mount.entry = /tmp/.X11-unix tmp/.X11-unix none bind,optional,create=dir,ro
lxc.mount.entry = /dev/video0 dev/video0 none bind,optional,create=file

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

原因: 設置 xhost + 是非常不安全的,參考 cookie-based authentication 作為替代。(在 Talk:Linux 容器 中討論)


如果 LXC 客戶機依然出現 permission denied 的報錯,在宿主機調用 xhost + 來允許客戶機連接宿主機的顯示伺服器。注意這種完全放開顯示適配器權限可能帶來的安全風險。 另外,在上面的掛載點 之前 添加下面的配置。

lxc.mount.entry = tmpfs tmp tmpfs defaults

VPN 相關[編輯 | 編輯原始碼]

運行容器化的 OpenVPNWireGuard, 見 Linux 容器/使用 VPN

Managing containers[編輯 | 編輯原始碼]

Basic usage[編輯 | 編輯原始碼]

To list all installed LXC containers:

# lxc-ls -f

Systemd can be used to start and to stop LXCs via lxc@CONTAINER_NAME.service. Enable lxc@CONTAINER_NAME.service to have it start when the host system boots.

Users can also start/stop LXCs without systemd. Start a container:

# lxc-start -n CONTAINER_NAME

Stop a container:

# lxc-stop -n CONTAINER_NAME

To login into a container:

# lxc-console -n CONTAINER_NAME

Once logged, treat the container like any other linux system, set the root password, create users, install packages, etc.

To attach to a container:

# lxc-attach -n CONTAINER_NAME --clear-env

That works nearly the same as lxc-console, but it causes starts with a root prompt inside the container, bypassing login. Without the --clear-env flag, the host will pass its own environment variables into the container (including $PATH, so some commands will not work when the containers are based on another distribution).

Advanced usage[編輯 | 編輯原始碼]

LXC clones[編輯 | 編輯原始碼]

Users with a need to run multiple containers can simplify administrative overhead (user management, system updates, etc.) by using snapshots. The strategy is to setup and keep up-to-date a single base container, then, as needed, clone (snapshot) it. The power in this strategy is that the disk space and system overhead are truly minimized since the snapshots use an overlayfs mount to only write out to disk, only the differences in data. The base system is read-only but changes to it in the snapshots are allowed via the overlayfs.

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

原因: The note needs a reference. (在 Talk:Linux 容器 中討論)
注意: overlayfs for unprivileged containers is not supported in the current mainline Arch Linux kernel due to security considerations.

For example, setup a container as outlined above. We will call it "base" for the purposes of this guide. Now create 2 snapshots of "base" which we will call "snap1" and "snap2" with these commands:

# lxc-copy -n base -N snap1 -B overlayfs -s
# lxc-copy -n base -N snap2 -B overlayfs -s
注意: If a static IP was defined for the "base" lxc, that will need to manually changed in the configuration for "snap1" and for "snap2" before starting them. If the process is to be automated, a script using sed can do this automatically although this is beyond the scope of this wiki section.

The snapshots can be started/stopped like any other container. Users can optionally destroy the snapshots and all new data therein with the following command. Note that the underlying "base" lxc is untouched:

# lxc-destroy -n snap1 -f

Systemd units and wrapper scripts to manage snapshots for pi-hole and OpenVPN are available to automate the process in lxc-service-snapshots.

Converting a privileged container to an unprivileged container[編輯 | 編輯原始碼]

Once the system has been configured to use unprivileged containers (see, #Enable support to run unprivileged containers (optional)[損壞的連結:無效的章節]), nsexec-bzrAUR contains a utility called uidmapshift which is able to convert an existing privileged container to an unprivileged container to avoid a total rebuild of the image.

警告:
  • It is recommended to backup the existing image before using this utility!
  • This utility will not shift UIDs and GIDs in ACL, users will need to shift them manually!

Invoke the utility to convert over like so:

# uidmapshift -b /var/lib/lxc/foo 0 100000 65536

Additional options are available simply by calling uidmapshift without any arguments.

Running Xorg programs[編輯 | 編輯原始碼]

Either attach to or SSH into the target container and prefix the call to the program with the DISPLAY ID of the host's X session. For most simple setups, the display is always 0.

An example of running Firefox from the container in the host's display:

$ DISPLAY=:0 firefox

Alternatively, to avoid directly attaching to or connecting to the container, the following can be used on the host to automate the process:

# lxc-attach -n playtime --clear-env -- sudo -u YOURUSER env DISPLAY=:0 firefox

Troubleshooting[編輯 | 編輯原始碼]

Root login fails[編輯 | 編輯原始碼]

If presented with following error upon trying to login using lxc-console:

login: root
Login incorrect

And the container's journal shows:

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

Delete /etc/securetty[1] and /usr/share/factory/etc/securetty on the container file system. Optionally add them to NoExtract in /etc/pacman.conf to prevent them from getting reinstalled. See FS#45903 for details.

Alternatively, create a new user in lxc-attach and use it for logging in to the system, then switch to root.

# lxc-attach -n playtime
[root@playtime]# useradd -m -Gwheel newuser
[root@playtime]# passwd newuser
[root@playtime]# passwd root
[root@playtime]# exit
# lxc-console -n playtime
[newuser@playtime]$ su

No network-connection with veth in container config[編輯 | 編輯原始碼]

If you cannot access your LAN or WAN with a networking interface configured as veth and setup through /etc/lxc/containername/config. If the virtual interface gets the ip assigned and should be connected to the network correctly.

ip addr show veth0 
inet 192.168.1.111/24

You may disable all the relevant static ip formulas and try setting the ip through the booted container-os like you would normaly do.

Example container/config

...
lxc.net.0.type = veth
lxc.net.0.name = veth0
lxc.net.0.flags = up
lxc.net.0.link = bridge
...

And then assign the IP through a preferred method inside the container, see also 網絡配置#Network management.

Error: unknown command[編輯 | 編輯原始碼]

The error may happen when a basic command (ls, cat, etc.) on an attached container is typed hen a different Linux distribution is containerized relative to the host system (e.g. Debian container in Arch Linux host system). Upon attaching, use the argument --clear-env:

# lxc-attach -n container_name --clear-env

Error: Failed at step KEYRING spawning...[編輯 | 編輯原始碼]

Services in an unprivileged container may fail with the following message

some.service: Failed to change ownership of session keyring: Permission denied
some.service: Failed to set up kernel keyring: Permission denied
some.service: Failed at step KEYRING spawning ....: Permission denied

Create a file /etc/lxc/unpriv.seccomp containing

/etc/lxc/unpriv.seccomp
2
blacklist
[all]
keyctl errno 38

Then add the following line to the container configuration after lxc.idmap

lxc.seccomp.profile = /etc/lxc/unpriv.seccomp

Known issues[編輯 | 編輯原始碼]

lxc-execute fails due to missing lxc.init.static[編輯 | 編輯原始碼]

lxc-execute fails with the error message Unable to open lxc.init.static. See FS#63814 for details.

Starting containers using lxc-start works fine.

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