Linux 容器

出自 ArchWiki
(重定向自 Linux Container

Linux 容器 (LXC) 是 Linux 内核容器化功能的用户空间接口,它提供了一种操作系统级虚拟化的方法,使用命名空间cgroups 和其他 Linux 内核 capabilities(7) 在 LXC 主机上。lxc(7) 被认为是介于 chroot 和功能完善的虚拟机之间的东西。

IncusLXD 可以用作 LXC 的管理器。本页面介绍直接使用 LXC。

使用容器的替代方案包括 systemd-nspawnDockerPodman

特权或非特权容器

LXC 支持两种类型的容器:特权容器和非特权容器。

一般来说,特权容器被认为是不安全的[1]

非特权容器上,容器内的 root UID 映射到主机上的非特权 UID,这使得容器内的攻击更难对主机系统造成影响。换句话说,如果攻击者设法逃脱容器,他们应该发现自己在主机上只有有限或没有权限。

Arch linuxlinux-ltslinux-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

设置

所需软件

安装 lxcarch-install-scripts 将允许主机系统运行特权 lxc。

启用运行非特权容器的支持(可选)

修改 /etc/lxc/default.conf 以包含以下行

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

换句话说,映射一个范围为 65536 的连续 uid,从容器侧 uid 0 开始,从主机的角度来看,这将是 uid 100000,直到并包括容器侧 uid 65535,主机将知道为 uid 165535。将相同的映射应用于 gid。

创建或编辑 /etc/subuid 中的 subuid(5) 和 /etc/subgid 中的 subgid(5),以包含每个能够运行容器的用户的容器化 uid/gid 对的映射。 下面的示例仅适用于 root 用户(和 systemd 系统单元)

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

本文或本节内容的准确性存在争议。

原因:以下说明与提供的参考[2]不符(在 Talk:Linux Containers#Unprivileged_containers_for_unprivileged_users 中讨论)

此外,只有预先委派 cgroup 时,才能以非特权用户身份运行非特权容器(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 Containers 中讨论)

或者,通过创建 systemd 单元来委派非特权 cgroups(根据 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-hardenedlxd 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)。 主机上的桥接设备是大多数类型的虚拟网络所必需的,本节将对此进行说明。

有几个主要的设置需要考虑

  1. 主机桥接
  2. 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"
提示:确保桥接的 IP 范围不干扰本地网络。 选择可用 IP 地址的一种方法是使用已动态分配给容器的地址之一。 这可以使用 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 的用户只需运行以下两行即可启用此功能

# 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 流量转发到 lxc 在端口 22 上的 IP 地址。

注意:确保允许主机上的 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 用户身份创建和启动容器,必须应用额外的配置。

/etc/lxc/lxc-usernet 下创建 usernet 文件。 根据 lxc-usernet(5),每行的条目是

user type bridge number

使用需要创建容器的用户配置文件。 桥接将与 /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 -n playtime -t download

查看下载模板选项列表

# lxc-create -t download --help
提示:Btrfs 用户可以附加 -B btrfs 以创建 Btrfs 子卷来存储容器化的 rootfs。 如果借助 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.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

本文或本节内容的准确性存在争议。

原因:设置 xhost + 非常不安全,请改用基于 Cookie 的身份验证。(在 Talk:Linux Containers 中讨论)

如果仍然在 LXC 客户机中遇到permission denied 错误,请在主机中调用 xhost + 以允许客户机连接到主机的显示服务器。 请注意通过这样做打开显示服务器的安全隐患。 此外,在上述绑定挂载行之前添加以下行。

/var/lib/lxc/playtime/config
lxc.mount.entry = tmpfs tmp tmpfs defaults

VPN 注意事项

要运行容器化的 OpenVPNWireGuard,请参阅 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 允许对快照进行更改。

本文或本节需要扩充。

原因:该注释需要参考。(在 Talk:Linux Containers 中讨论)
注意:由于安全考虑,当前主线 Arch Linux 内核不支持非特权容器的 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 定义了静态 IP,则需要在启动 “snap1” 和 “snap2” 之前在配置中手动更改它。 如果该过程要自动化,则可以使用 sed 的脚本自动执行此操作,但这超出了本 Wiki 节的范围。

快照可以像任何其他容器一样启动/停止。 用户可以选择使用以下命令销毁快照以及其中的所有新数据。 请注意,底层的 “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 地址,另请参阅 网络配置#网络管理

错误:未知命令

当在连接的容器上键入基本命令(lscat 等)时,如果容器化的 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 启动容器工作正常。

参见