Linux 容器

来自 Arch Linux 中文维基

本文或本节需要翻译。要贡献翻译,请访问简体中文翻译团队

附注: Partial translation.(在 Talk:Linux 容器# 中讨论)

Linux 容器 (LXC) 是一种在单个宿主机(LXC host)运行多个隔离的 Linux 系统(容器)的操作系统级虚拟化方法。它并不提供虚拟机,而是提供了一个具有独立 CPU、内存、块 I/O、网络等空间和资源控制环境的虚拟环境。这是通过 LXC 宿主机上 Linux 内核的 namespacescgroups 特性实现的。它类似于 chroot ,但是具有更好的隔离性。

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

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

特权容器或非特权容器[编辑 | 编辑源代码]

LXC 容器可以被设置为以 特权非特权 配置运行。

一般来说,运行 非特权 容器比运行 特权 容器 更安全 ,因为 非特权 容器在设计上具有更高程度的隔离性。其中的关键在于容器内的 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) )。本节所介绍的虚拟网络类型中,大部分都需要一个宿主机上的网桥设备。

这里主要有几个可供参考的配置:

  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 网段不会和本地网络冲突。

然后我们需要修改 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

现在 startenable lxc-net.service 来建立网桥接口。

防火墙相关[编辑 | 编辑源代码]

基于宿主机运行的防火墙类型,可能需要允许 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 命令创建容器。在 lxc-3.0.0-1 版本中,上游移除了本地存储的容器模板。

使用像下面这样的调用,来建立一个 Arch 容器:

# lxc-create -n playtime -t download -- --dist archlinux --release current --arch amd64

使用像下面这样的调用并从支持的列表中选择,来建立其他发行版的容器:

# lxc-create -n playtime -t download
提示:
  • 用户可以选择安装 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

Managing containers[编辑 | 编辑源代码]

Basic usage[编辑 | 编辑源代码]

To list all installed LXC containers:

# lxc-ls -f

Systemd can be used to start and to stop LXCs via lxc@CONTAINER_NAME.service. Enable lxc@CONTAINER_NAME.service to have it start when the host system boots.

Users can also start/stop LXCs without systemd. Start a container:

# lxc-start -n CONTAINER_NAME

Stop a container:

# lxc-stop -n CONTAINER_NAME

To login into a container:

# lxc-console -n CONTAINER_NAME

Once logged, treat the container like any other linux system, set the root password, create users, install packages, etc.

To attach to a container:

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

That works nearly the same as lxc-console, but it causes starts with a root prompt inside the container, bypassing login. Without the --clear-env flag, the host will pass its own environment variables into the container (including $PATH, so some commands will not work when the containers are based on another distribution).

Advanced usage[编辑 | 编辑源代码]

LXC clones[编辑 | 编辑源代码]

Users with a need to run multiple containers can simplify administrative overhead (user management, system updates, etc.) by using snapshots. The strategy is to setup and keep up-to-date a single base container, then, as needed, clone (snapshot) it. The power in this strategy is that the disk space and system overhead are truly minimized since the snapshots use an overlayfs mount to only write out to disk, only the differences in data. The base system is read-only but changes to it in the snapshots are allowed via the overlayfs.

这篇文章的某些内容需要扩充。

原因: The note needs a reference. (在 Talk:Linux 容器 中讨论)
注意: overlayfs for unprivileged containers is not supported in the current mainline Arch Linux kernel due to security considerations.

For example, setup a container as outlined above. We will call it "base" for the purposes of this guide. Now create 2 snapshots of "base" which we will call "snap1" and "snap2" with these commands:

# lxc-copy -n base -N snap1 -B overlayfs -s
# lxc-copy -n base -N snap2 -B overlayfs -s
注意: If a static IP was defined for the "base" lxc, that will need to manually changed in the configuration for "snap1" and for "snap2" before starting them. If the process is to be automated, a script using sed can do this automatically although this is beyond the scope of this wiki section.

The snapshots can be started/stopped like any other container. Users can optionally destroy the snapshots and all new data therein with the following command. Note that the underlying "base" lxc is untouched:

# lxc-destroy -n snap1 -f

Systemd units and wrapper scripts to manage snapshots for pi-hole and OpenVPN are available to automate the process in lxc-service-snapshots.

Converting a privileged container to an unprivileged container[编辑 | 编辑源代码]

Once the system has been configured to use unprivileged containers (see, #Enable support to run unprivileged containers (optional)[损坏的链接:无效的章节]), nsexec-bzrAUR contains a utility called uidmapshift which is able to convert an existing privileged container to an unprivileged container to avoid a total rebuild of the image.

警告:
  • It is recommended to backup the existing image before using this utility!
  • This utility will not shift UIDs and GIDs in ACL, users will need to shift them manually!

Invoke the utility to convert over like so:

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

Additional options are available simply by calling uidmapshift without any arguments.

Running Xorg programs[编辑 | 编辑源代码]

Either attach to or SSH into the target container and prefix the call to the program with the DISPLAY ID of the host's X session. For most simple setups, the display is always 0.

An example of running Firefox from the container in the host's display:

$ DISPLAY=:0 firefox

Alternatively, to avoid directly attaching to or connecting to the container, the following can be used on the host to automate the process:

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

Troubleshooting[编辑 | 编辑源代码]

Root login fails[编辑 | 编辑源代码]

If presented with following error upon trying to login using lxc-console:

login: root
Login incorrect

And the container's journal shows:

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

Delete /etc/securetty[1] and /usr/share/factory/etc/securetty on the container file system. Optionally add them to NoExtract in /etc/pacman.conf to prevent them from getting reinstalled. See FS#45903 for details.

Alternatively, create a new user in lxc-attach and use it for logging in to the system, then switch to 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

No network-connection with veth in container config[编辑 | 编辑源代码]

If you cannot access your LAN or WAN with a networking interface configured as veth and setup through /etc/lxc/containername/config. If the virtual interface gets the ip assigned and should be connected to the network correctly.

ip addr show veth0 
inet 192.168.1.111/24

You may disable all the relevant static ip formulas and try setting the ip through the booted container-os like you would normaly do.

Example container/config

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

And then assign the IP through a preferred method inside the container, see also 网络配置#Network management.

Error: unknown command[编辑 | 编辑源代码]

The error may happen when a basic command (ls, cat, etc.) on an attached container is typed hen a different Linux distribution is containerized relative to the host system (e.g. Debian container in Arch Linux host system). Upon attaching, use the argument --clear-env:

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

Error: Failed at step KEYRING spawning...[编辑 | 编辑源代码]

Services in an unprivileged container may fail with the following message

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

Create a file /etc/lxc/unpriv.seccomp containing

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

Then add the following line to the container configuration after lxc.idmap

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

Known issues[编辑 | 编辑源代码]

lxc-execute fails due to missing lxc.init.static[编辑 | 编辑源代码]

lxc-execute fails with the error message Unable to open lxc.init.static. See FS#63814 for details.

Starting containers using lxc-start works fine.

See also[编辑 | 编辑源代码]