跳至內容
出自 Arch Linux 中文维基


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

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

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

特權容器或非特權容器

LXC 支持兩種類型的容器:特權非特權

一般來說,特權容器被認為是不安全[1]

運行非特權容器比運行特權容器更安全,因為非特權容器在設計上具有更高程度的隔離性。其中的關鍵在於容器內的 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) § NETWORK)。本節所介紹的虛擬網絡類型中,大部分都需要一個宿主機上的網橋設備。

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

  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 網段不會和本地網絡衝突。一種選擇可用 IP 地址的方法是使用已經動態分配給容器的地址之一。可以使用 lxc-ls -f 命令進行檢查。

然後我們需要修改 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
防火牆相關

基於宿主機運行的防火牆類型,可能需要允許 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(1) 命令創建容器。在 lxc-3.0.0-1 版本中,上游移除了本地存儲的容器模板。

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

# lxc-create --name playtime --template download -- --dist archlinux --release current --arch amd64

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

# lxc-create --name playtime --template download

要查看 download 模板的選項列表:

# lxc-create -t download --help
提示:
  • 用戶可以選擇安裝 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

管理容器

基本使用方法

列出所有已安裝的LXC容器:

# lxc-ls -f

可以使用Systemd的lxc@CONTAINER_NAME.service啟動停止LXC。啟用 lxc@CONTAINER_NAME.service可以在宿主系統啟動時連帶啟動LXC。

也可以不使用Systemd來啟動或停止LXC。啟動一個LXC容器:

# lxc-start -n CONTAINER_NAME

停止容器:

# lxc-stop -n CONTAINER_NAME

登錄容器:

# lxc-console -n CONTAINER_NAME

登錄容器後,它就像普通的Linux系統一樣,可以進行設置root帳戶密碼、創建用戶、下載軟體包等操作。

執行容器內命令:

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

它和lxc-console相似,用於以root權限在容器內執行一條命令。若不使用 --clear-env選項,容器將攜帶宿主機的環境變量值(例如$PATH,容器使用其他發行版的環境變量可能無法執行某些指令)。

高級使用方法

LXC容器克隆

通過快照運行多個容器可以減少管理容器權限的負擔(如用戶管理和系統更新等)。該策略是以一個最新版本的容器為基礎,需要使用容器時,克隆一個基礎容器並在這個克隆的基礎容器上構建應用。容器使用overlayfs掛載,overlayfs只會存儲和基本容器有差異的數據,故這種策略能儘可能減少對系統和磁碟的占用。基本容器是只讀的,但是通過overlayfs,由基本容器衍生的其他容器可以進行修改。

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

原因:The note needs a reference. (在 Talk:Linux 容器 中討論)
注意:因為安全原因,目前的主線Arch Linux內核不支持非特權容器使用overlayfs

例如,設置一個基礎容器,並通過以下命令以基礎容器為基礎創建兩個快照(snapshot)容器,稱之為「snap1」和「snap2」.

# lxc-copy -n base -N snap1 -B overlayfs -s
# lxc-copy -n base -N snap2 -B overlayfs -s
注意:如果在基礎容器上設定了靜態IP,在啟動快照容器前應為它手動設定IP。如果涉及到自動化流程,可以在自動化腳本內使用sed對快照容器進行IP設置。

這些快照可以像其他容器一樣被啟動和關閉。用戶可以通過以下命令選擇性的刪除某些快照和其中的數據。該刪除指令不會影響基礎容器。

# lxc-destroy -n snap1 -f

pi-holeOpenVPN管理快照的腳本及系統組件可在lxc-service-snapshots獲得。

將一個特權容器轉換為非特權容器

當一個系統被設置為使用非特權容器時(見 #Enable support to run unprivileged containers (optional)[損壞的連結:無效的章節]),nsexec-bzrAUR包含一個被稱為uidmapshift的工具將某個已經存在的特權容器轉換為非特權容器從而避免重新構建新容器。

警告:
  • 強烈建議在使用這個工具前備份容器!!!
  • 這個工具不會更改ACL內的的UID和GID,請手動變更。

可以使用以下命令來進行轉換:

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

直接運行uidmapshift可查看使用幫助。

運行Xorg程序

使用lxc-attach或SSH調用目標容器,並在需要使用的程序前加上X程序的DISPLAY ID。對於大多數簡單的設置,顯示編號通常為0。

一個簡單的例子,在容器內運行Firefox並在主機屏幕上顯示:

$ DISPLAY=:0 firefox

或者為避免直接連接容器,以下命令可以在宿主容器使這個過程自動化:

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

提示與技巧

在非特權容器中 ping 無法工作

在非特權容器中,如果沒有額外的配置步驟,ping 很可能無法工作。示例錯誤:

% ping www.google.com
ping: socktype: SOCK_RAW
ping: socket: Operation not permitted
ping: -> missing cap_net_raw+p capability or setuid?

要在主機上修復容器 foo 中的此問題:

# lxc-attach -n foo -- chmod u+s /usr/bin/ping

故障排除

Root用戶登錄失敗

如果在使用lxc-console登錄時顯示以下錯誤:

login: root
Login incorrect

以及容器的 journal 顯示:

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

刪除容器內文件系統的 /etc/securetty[2]/usr/share/factory/etc/securetty。或者將它們加入/etc/pacman.confNoExtract中以阻止它們被重新下載,見 FS#45903

或者使用lxc-attach創建一個新用戶並用它來登陸系統,在登錄後切換為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

容器配置中使用 veth 時無網絡連接

如果你通過/etc/lxc/containername/config將網絡接口設定為veth後無法訪問區域網或廣域網,請檢查這個虛擬網口是否獲得ip地址並正確連接到網絡。

ip addr show veth0 
inet 192.168.1.111/24

你可以關閉所有有關的靜態ip集,並像平常一樣通過已啟動的容器設置這些ip

例如 container/config

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

然後通過容器的首選方法分配 IP,見網絡配置#Network management.

無法找到命令

當在相對於宿主機系統使用不同Linux發行版的容器中(例如在Arch Linux宿主機系統中運行Debian容器)執行基本命令(如lscat等)時,可能會出現此錯誤。在附加容器時,請使用參數--clear-env

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

Failed at step KEYRING spawning...

在非特權容器內執行的服務可能會出現以下錯誤:

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

在容器內添加文件/etc/lxc/unpriv.seccomp,並寫入以下配置:

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

並在容器配置文件的lxc.idmap屬性後添加:

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

已知的問題

缺失lxc.init.static會導致lxc-execute執行失敗

lxc-execute 指令運行失敗並顯示以下錯誤:Unable to open lxc.init.static。參見 FS#63814 了解詳情。

使用 lxc-start 命令重啟即可。

參見