Linux 容器 (LXC) 是一種在單個宿主機(LXC host)運行多個隔離的 Linux 系統(容器)的作業系統級虛擬化方法。它並不提供虛擬機,而是提供了一個具有獨立 CPU、內存、塊 I/O、網絡等空間和資源控制環境的虛擬環境。這是通過 LXC 宿主機上 Linux 內核的 namespaces 和 cgroups 特性實現的。它類似於 chroot ,但是具有更好的隔離性。
LXD 可以被用做 LXC 的管理器,本頁面則涉及直接使用 LXC 。
使用容器的替代方法包括 systemd-nspawn 和 Docker 。
特權容器或非特權容器
LXC 支持兩種類型的容器:特權和非特權。
一般來說,特權容器被認為是不安全的[1]。
運行非特權容器比運行特權容器更安全,因為非特權容器在設計上具有更高程度的隔離性。其中的關鍵在於容器內的 root UID 被映射為宿主機上的非 root UID ,這使得容器內部的攻擊更難以對宿主機系統施加影響。換句話說,如果攻擊者設法逃離容器,它們會發現自己在宿主機上被限制或沒有權限。
Arch 的 linux包 、 linux-lts包 和 linux-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
安裝
需要的軟體
安裝 lxc包 和 arch-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 命令。
或者,您可以通過創建一個 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 用戶的身份建立非特權容器。同樣為 sysctl 的
user.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"
。 - 啟用 sysctl 的
kernel.unprivileged_userns_clone
配置來允許普通用戶運行非特權容器。可以以 root 身份運行sysctl kernel.unprivileged_userns_clone=1
來為當前會話生效或閱讀 sysctl.d(5) 使其永久生效。
宿主機網絡配置
LXC 支持多種不同的虛擬網絡類型和設備(見 lxc.container.conf(5) § NETWORK)。本節所介紹的虛擬網絡類型中,大部分都需要一個宿主機上的網橋設備。
這裡主要有幾個可供參考的配置:
- 主機網橋
- NAT 網橋
主機網橋需要宿主機上的網絡設置工具來配置一個共享網橋接口。宿主機和所有 LXC 容器將在同一個網絡中被指派 IP 地址(如 192.168.1.x)。當你的目標是將一些暴露在網絡上的服務如 web 伺服器或 VPN 伺服器容器化時,這可能更加便捷。用戶可以將 LXC 當作物理 LAN 上的其他 PC,並在路由器上為其配置相應的埠轉發。增加的便捷也可以認為是增加的威脅向量,同樣的,如果 WAN 流量被轉發給 LXC,將其運行在不同的網絡範圍上將顯現更小的威脅面。
NAT 網橋不需要宿主機的網絡設置工具來配置網橋。lxc包 自帶的 lxc-net
將建立一個叫 lxcbr0
的 NAT 網橋。這個 NAT 網橋是一個獨立的網橋,具有不與乙太網設備或物理網絡橋接的私有網絡。它以宿主機的一個子網的形式存在。
使用主機網橋
使用 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"
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 埠上。
為了從 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
配置容器
下面的例子同時適用於特權和非特權容器。主要對於非特權容器,默認會出現例子中沒有出現的額外配置,包括 lxc.idmap = u 0 100000 65536
和 lxc.idmap = g 0 100000 65536
這些在啟用非特權容器支持(可選)中作為可選配置添加的值。
具有網絡支持的基本配置
當一個進程使用 /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
如果 LXC 客戶機依然出現 permission denied 的報錯,在宿主機調用 xhost +
來允許客戶機連接宿主機的顯示伺服器。注意這種完全放開顯示適配器權限可能帶來的安全風險。
另外,在上面的掛載點之前添加下面的配置。
lxc.mount.entry = tmpfs tmp tmpfs defaults
VPN 相關
運行容器化的 OpenVPN 或 WireGuard, 見 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,由基本容器衍生的其他容器可以進行修改。
例如,設置一個基礎容器,並通過以下命令以基礎容器為基礎創建兩個快照(snapshot)容器,稱之為「snap1」和「snap2」.
# lxc-copy -n base -N snap1 -B overlayfs -s # lxc-copy -n base -N snap2 -B overlayfs -s
這些快照可以像其他容器一樣被啟動和關閉。用戶可以通過以下命令選擇性的刪除某些快照和其中的數據。該刪除指令不會影響基礎容器。
# lxc-destroy -n snap1 -f
在pi-hole和OpenVPN管理快照的腳本及系統組件可在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.conf
的NoExtract中以阻止它們被重新下載,見 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容器)執行基本命令(如ls、cat等)時,可能會出現此錯誤。在附加容器時,請使用參數--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
命令重啟即可。