Linux 容器
Linux 容器 (LXC) 是 Linux 内核容器化功能的用户空间接口,提供了一种使用 操作系统级虚拟化 的方法,它使用 命名空间、cgroups 和 LXC 主机上的其他 Linux 内核 capabilities(7)。lxc(7) 被认为是介于 chroot 和成熟虚拟机之间的东西。
Incus 或 LXD 可以用作 LXC 的管理器。本页面讨论直接使用 LXC。
容器的替代方案包括 systemd-nspawn、Docker 和 Podman。
特权或非特权容器
LXC 支持两种类型的容器:特权 和 非特权。
通常,特权 容器被认为是 不安全的[1]。
在非特权容器上,容器内的 root UID 映射到主机上的非特权 UID,这使得容器内部的黑客攻击更难导致主机系统上的后果。换句话说,如果攻击者设法逃脱容器,他们应该发现自己在主机上只有有限的或没有权限。
Arch linux、linux-lts 和 linux-zen 内核软件包目前提供对非特权容器的开箱即用支持。 同样,对于 linux-hardened 软件包,非特权容器仅适用于系统管理员;由于用户命名空间默认情况下对普通用户禁用,因此需要额外的内核配置更改。
本文包含用户运行任何类型容器的信息,但为了使用非特权容器,可能需要额外的步骤。
用于说明非特权容器的示例
为了说明 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
换句话说,映射 65536 个连续 uids 的范围,从容器端 uid 0 开始,这应该是主机视角的 uid 100000,直到并包括容器端 uid 65535,主机将知道为 uid 165535。将相同的映射应用于 gids。
创建或编辑 /etc/subuid
的 subuid(5) 和 /etc/subgid
的 subgid(5),以包含每个能够运行容器的用户容器化 uid/gid 对的映射。下面的示例仅适用于 root 用户(和 systemd 系统单元)
/etc/subuid
root:100000:65536
/etc/subgid
root:100000:65536
此外,仅当您预先委派 cgroup 时,以非特权用户身份运行非特权容器才有效(cgroup2 委派模型强制执行此限制,而不是 liblxc)。使用以下 systemd 命令委派 cgroup(根据 LXC - 入门:以用户身份创建非特权容器)
$ systemd-run --unit=myshell --user --scope -p "Delegate=yes" lxc-start container_name
这对于其他 lxc 命令也类似。
或者,通过创建 systemd 单元(根据 Rootless Containers: Enabling CPU, CPUSET, and I/O delegation)委派非特权 cgroups
/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 桥接
默认情况下,lxc-net
服务配置为创建和使用桥接接口和虚拟以太网对设备,一侧分配给容器,另一侧在主机上连接到桥接。这是使用 dnsmasq 自动完成的。
要使用此设置,首先,安装 dnsmasq,它是 lxc-net
的依赖项,然后启动 和 启用 lxc-net.service
。
可以通过创建 /etc/default/lxc-net
文件或使用以下模板编辑 /etc/default/lxc
文件来覆盖某些 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
命令检查这一点。可选地,创建一个配置文件来手动定义任何容器的 IP 地址
/etc/lxc/dnsmasq.conf
dhcp-host=playtime,10.0.3.100
防火墙注意事项
根据主机运行的防火墙,可能需要允许来自 lxcbr0
的入站数据包到主机,以及来自 lxcbr0
的出站数据包通过主机传输到其他网络。要测试这一点,请尝试启动一个配置为使用 DHCP 进行 IP 分配的容器,并查看 lxc-net
是否能够为容器分配 IP 地址(使用 lxc-ls -f
检查)。如果未分配 IP,则需要调整主机的策略。
# 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" } }
此外,由于容器在 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 流量转发到端口 22 上 lxc 的 IP 地址。
要从 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 用户身份创建和启动容器,必须应用额外的配置。
在 /etc/lxc/lxc-usernet
下创建 usernet 文件。根据 lxc-usernet(5),每行的条目是
user type bridge number
使用需要创建容器的用户配置该文件。桥接将与 /etc/default/lxc-net
中定义的桥接相同。
/etc/lxc/default.conf
的副本在非 root 用户的主目录中是必需的,例如 ~/.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 -n playtime -t download
要查看下载模板选项的列表
# lxc-create -t download --help
-B btrfs
来创建 Btrfs 子卷以存储容器化的 rootfs。如果借助 lxc-clone
命令克隆容器,这将非常方便。ZFS 用户可以相应地使用 -B zfs
。容器配置
以下示例可以用于特权和非特权容器。请注意,对于非特权容器,默认情况下会存在其他行,这些行未在示例中显示,包括 #启用运行非特权容器的支持(可选) 部分中可选定义的 lxc.idmap = u 0 100000 65536
和 lxc.idmap = g 0 100000 65536
值。
带网络的基本配置
容器特定的配置,包括进程使用容器时要虚拟化/隔离的系统资源,在 /var/lib/lxc/CONTAINER_NAME/config
中定义。阅读 lxc.container.conf(5) 以了解配置文件的语法和可能的选项。
使用模板创建容器时生成的基本配置文件。阅读 lxc.conf(5) 以获取更多信息。
默认情况下,创建过程将进行最少设置,而没有网络支持。以下是由 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
容器内的挂载
可以在容器的 rootfs
外部创建一个主机卷,然后将该卷挂载到容器内部。这可能是有利的,例如,如果容器化相同的架构,并且想要在主机和容器之间共享 pacman 软件包。另一个示例可能是共享目录。阅读 lxc.container.conf(5) § MOUNT POINTS 以获取更多信息。一般语法是
lxc.mount.entry = /var/cache/pacman/pkg var/cache/pacman/pkg none bind 0 0
Xorg 程序注意事项(可选)
为了在主机的显示器上运行程序,需要定义一些绑定挂载,以便容器化程序可以访问主机的资源。
/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 客户机中遇到权限被拒绝错误,请在主机中调用 xhost +
以允许客户机连接到主机的显示服务器。请注意通过这样做打开显示服务器的安全隐患。此外,在之前添加以下行以上绑定挂载行。
/var/lib/lxc/playtime/config
lxc.mount.entry = tmpfs tmp tmpfs defaults
VPN 注意事项
要运行容器化的 OpenVPN 或 WireGuard,请参阅 Linux Containers/Using VPNs。
管理容器
基本用法
列出所有已安装的 LXC 容器
# lxc-ls -f
Systemd 可用于通过 lxc@CONTAINER_NAME.service
启动 和 停止 LXC。启用 lxc@CONTAINER_NAME.service
以使其在主机系统启动时启动。
用户也可以在没有 systemd 的情况下启动/停止 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 对快照进行更改。
例如,按照上述步骤设置一个容器。在本指南中,我们将其称为“base”。现在创建 “base” 的 2 个快照,我们将其称为 “snap1” 和 “snap2”,使用以下命令
# lxc-copy -n base -N snap1 -B overlayfs -s # lxc-copy -n base -N snap2 -B overlayfs -s
快照可以像任何其他容器一样启动/停止。用户可以选择使用以下命令销毁快照以及其中的所有新数据。请注意,底层的 “base” lxc 不受影响
# lxc-destroy -n snap1 -f
用于管理 pi-hole 和 OpenVPN 快照的 Systemd 单元和包装器脚本可在 lxc-service-snapshots 中找到,以自动化该过程。
将特权容器转换为非特权容器
一旦系统配置为使用非特权容器(参见#启用运行非特权容器的支持(可选)),nsexec-bzrAUR 包含一个名为 uidmapshift
的实用程序,该实用程序能够将现有的特权容器转换为非特权容器,以避免完全重建镜像。
- 建议在使用此实用程序之前备份现有镜像!
- 此实用程序不会在 ACL 中移动 UID 和 GID,用户需要手动移动它们!
调用该实用程序以进行转换,如下所示
# uidmapshift -b /var/lib/lxc/foo 0 100000 65536
只需调用不带任何参数的 uidmapshift
即可获得其他选项。
运行 Xorg 程序
附加到或 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
并且容器的 日志 显示
pam_securetty(login:auth): access denied: tty 'pts/0' is not secure !
删除 容器 文件系统上的 /etc/securetty
[3] 和 /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 时没有网络连接
如果您无法使用配置为 veth 并在 /etc/lxc/containername/config
中设置的网络接口访问您的 LAN 或 WAN。如果虚拟接口获得了分配的 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,另请参见 网络配置#网络管理。
错误:未知命令
当在附加的容器上键入基本命令(ls、cat 等)时,如果容器化的 Linux 发行版相对于主机系统不同(例如,Arch Linux 主机系统中的 Debian 容器),则可能会发生错误。附加时,使用参数 --clear-env
# lxc-attach -n container_name --clear-env
错误:在 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-execute 因缺少 lxc.init.static 而失败
lxc-execute
失败,并显示错误消息 Unable to open lxc.init.static
。有关详细信息,请参见 FS#63814。
使用 lxc-start
启动容器工作正常。