跳转至内容

Linux Containers

来自 ArchWiki
(重定向自 Linux Container)

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

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

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

特权容器或非特权容器

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

特权容器中,容器内的 root UID(UID 0)映射到主机上的 root UID。

注意 LXC 开发人员认为特权容器“不安全”,请参阅上述链接了解更多信息。

非特权容器中,容器内的 root UID 映射到主机上的一个非特权 UID,这使得容器内的黑客攻击更难对宿主系统造成后果。换句话说,如果攻击者设法逃离容器,他们应该发现自己在主机上拥有有限或无权。

Arch linuxlinux-ltslinux-zen 内核软件包目前支持非特权容器的开箱即用。对于 linux-hardened 软件包,非特权容器仅供系统管理员使用;因此,需要额外的内核配置更改才能为普通用户启用用户命名空间。

本文档包含有关用户运行这两种容器类型的信息,但为了使用非特权容器,可能需要额外步骤

为了说明 UID 映射的强大功能,请考虑下面来自运行中的非特权容器的输出。在其中,我们看到容器内的进程由容器内的 root 用户拥有,来自ps的输出。

[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 将允许主机系统运行特权 lxcs。

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

修改 /etc/lxc/default.conf 文件,使其包含以下行:

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

换句话说,映射从容器侧 UID 0 开始的 65536 个连续 UID,从主机的角度来看是 UID 100000,直到包含容器侧 UID 65535,主机将称之为 UID 165535。对 GID 应用相同的映射。

注意 systemd 会为自身目的动态分配特定范围内的 UID/GID(systemd-homed、systemd 单元文件中的DynamicUser=选项、systemd-nspawn 等)。请参阅 [1] 获取 systemd 使用的 UID/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 - 入门:以用户身份创建非特权容器)。

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

这对于其他 lxc 命令也同样有效。

此文章或章节是合并到 cgroups#User delegation 的候选。

注意: 不特定于 Linux Containers,避免重复。 (讨论于 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身份启动非特权容器。如果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)。大多数虚拟网络类型都需要主机上的桥接设备,本节将对此进行说明。

有几种主要的设置需要考虑:

  1. 主机桥接
  2. NAT 桥接

主机桥接需要主机网络管理器来管理一个共享的桥接接口。主机和任何 lxc 都将被分配同一网络中的 IP 地址(例如 192.168.1.x)。在容器化一些暴露网络服务的场景(如 Web 服务器或 VPN 服务器)时,这可能更简单。用户可以认为 lxc 就像物理局域网上的另一台 PC,并在路由器中相应地转发所需端口。这种额外简单性也可以被视为一个额外的威胁向量,同样,如果 WAN 流量被转发到 lxc,将其运行在单独的范围内会呈现更小的攻击面。

NAT 桥接不需要主机网络管理器来管理桥接。 lxc 附带 lxc-net,它创建了一个名为 lxcbr0 的 NAT 桥接。NAT 桥接是一个独立的桥接,具有一个私有网络,该网络不桥接到主机的以太网设备或物理网络。它存在于主机中的一个私有子网。

使用 lxc-net "虚拟 NAT 路由器"

默认情况下,lxc 附带 lxc-net shell 脚本,该脚本需要可选的依赖项 dnsmasq 才能安装并运行。lxc-net 像 NAT 路由器一样运行,类似于 OpenWRT,并默认创建一个名为 lxcbr0网络桥接。它还管理 nftablesiptables 进行流量转发。此工具的大多数配置都可以在其配置文件 /etc/default/lxc-net 中修改。

启用 lxc-net.service
/etc/default/lxc-net
USE_LXC_BRIDGE="true"

现在您可以启动 lxc-net.service,并且除了其他事项外,默认的桥接 lxcbr0 应该出现在

$ ip a
高级设置

可以通过创建 /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,则需要调整主机的策略。

提示 多个程序可能会影响单个系统上的nftables,包括 VPN 和其他虚拟化软件。运行 nft list ruleset 来显示所有当前活动的规则。

使用 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 规则示例

此规则的目的是允许到 lxc 的 ssh 流量。

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

此规则将源自 2221 端口的 TCP 流量转发到容器的 22 端口的 IP 地址。

注意 确保允许主机上的 2221/tcp 流量,并允许容器上的 22/tcp 流量。

要从局域网中的另一台 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
提示 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 客户机中仍然遇到“权限被拒绝”错误,请在主机上调用 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-holeOpenVPN 快照的 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

并且容器的 journal 显示:

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 没有网络连接

如果您通过 /etc/lxc/containername/config 配置的网络接口设置为 veth,但无法访问您的 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 产生失败...

非特权容器中的服务可能会出现以下消息而失败:

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 启动容器工作正常。

参见