返回新闻概览

LXC 3.1 已发布

2018 年 12 月 14 日

简介

LXC 团队很高兴地宣布发布 LXC 3.1.0!

这是一个中间功能版本,而不是我们的主要 LTS 版本或 LTS 修补程序版本。我们计划在未来发布更多此类版本,但请注意,这些版本的支持将受到限制,因为我们主要关注生产环境中的 LTS。

新功能

使用 AppArmor 启用各种重新挂载选项

为了避免 MAC 限制绕过,读写绑定挂载需要对某些路径进行限制,但只读绑定挂载不应该存在此问题。此外,nosuidnodevnoexec 标志的组合也不应该有问题,并且在较新的 systemd 版本中需要这些标志,因此只要与 ro,remount,bind 结合使用,就允许这些标志。

使用新的套接字选项 NETLINK_DUMP_STRICT_CHK,用户空间可以通过 setsockopt() 使用该选项来请求对转储请求中的标头和属性进行严格检查。

要获得转储功能,例如基于标头中的数据或附加到转储请求的属性的内核端筛选,用户空间必须为 NETLINK_DUMP_STRICT_CHK 和非零值调用 setsockopt()。这对于使用 IFA_TARGET_NETNSID 属性(用于通过 LXC 从网络命名空间有效检索信息)是必需的。

在启动时分配新的密钥环

为了隔离和保护主机密钥环,每个 LXC 容器将在启动时尝试为自己分配一个新的密钥环。

完全支持 cgroup2

LXC 一直支持 cgroup2,但没有遵守其严格的委托模型。现在,LXC 已准备好完全支持它。

实现从容器检索网络设备和地址的有效方法

基于 LXD 团队完成的内核工作,现在可以查询网络命名空间,而无需执行代价高昂的 fork()setns() 系统调用。相反,网络命名空间由网络命名空间标识符识别。因此,引入了新的网络命名空间感知版本和极大地改进且安全的 getifaddrs() 版本(称为 netns_getifaddrs()),LXC 使用该版本。它是 getifaddrs() 的严格超集。

lxc_has_api_extension() 引入 API

从现在开始,每个新的 API 添加都将获得一个唯一的名称,该名称可以传递给 lxc_has_api_extension()。这模仿了 LXD 的 API 扩展检查。这允许 API 用户查询给定的 LXC 实例是否支持给定的 API 扩展。

添加 lxc.cgroup.relative 配置键

这添加了新的 lxc.cgroup.relative 配置键。该键可用于指示 LXC 从不转义到根 cgroup。这使用户可以轻松遵守 cgroup2systemd 强制的限制。具体来说,这使得将 LXC 容器作为 systemd 服务运行成为可能。

在启动时分配新的网络命名空间标识符

每个容器现在将在启动时分配一个唯一的网络命名空间标识符。LXC 可以使用它来显着加快对网络命名空间执行的操作(例如网络设备配置和检索)。

添加 lxc.rootfs.managed 配置键

这引入了新的配置键,可用于指示此 LXC 实例是否管理容器存储。如果 LXC 不管理存储,则 LXC 不会修改容器存储。例如,对 c->destroy(c) 的 API 调用将运行任何销毁挂钩,但不会销毁实际的 rootfs(当然,除非挂钩在 LXC 背后这样做)。

删除所有 VLA

LXC 现在默认使用 -Wvla 编译。

AppArmor 配置文件生成

这复制了 lxd 的 AppArmor 配置文件生成。这尝试检测诸如 cgroup 命名空间、AppArmor 命名空间和堆叠支持之类的功能,并且具有针对非特权容器的条件配置文件部分。

这引入了以下对配置的更改
- lxc.apparmor.profile = generated
固定值“generated”将导致使用此功能,否则,除非使用下一个键明确请求,否则不应该发生任何功能更改。
- lxc.apparmor.allow_nesting
这是一个布尔值,如果启用,会导致以下更改:当使用生成的 AppArmor 配置文件时,它们将包含允许创建嵌套容器的必要更改。除了通常的挂载点之外,/dev/.lxc/proc/dev/.lxc/sys 将包含 procfssysfs 挂载点,没有 lxcfs 覆盖,如果使用生成的 AppArmor 配置文件,这些覆盖将无法直接读写。
- lxc.apparmor.raw
要附加到配置文件的原始 AppArmor 配置文件行列表。仅在使用生成配置文件时有效。

为了使 apparmor_parser 的缓存有用,这添加了
--with-apparmor-cache-dir ./configure 选项。

添加挂载注入 API

这项工作是在 LizaTretyakova 的学士论文中完成的。团队对这项出色的工作感到非常高兴和感谢!

能够在容器运行时动态地与挂载进行交互是用户长期以来的要求,也是我们在 LXD 中长期支持的功能。此功能使以下主要用例成为可能
- 将挂载注入正在运行的容器
这允许用户动态地向容器添加挂载。一个例子是在容器耗尽磁盘空间之前向容器添加一个新的专用存储设备。
- 启用设备热插拔
在容器运行时与挂载进行交互对于向容器添加新设备也是必需的。具体来说,任何已放弃能够创建设备节点(例如 CAP_MKNOD) 的功能的特权容器或任何非特权容器都将无法创建设备。这需要在主机命名空间内的主机上,由具有足够特权的进程创建此类设备,然后将其作为挂载注入到容器中。

为此,添加了两个新的 API 调用

int (*mount)(struct lxc_container *c, const char *source, const char *target,
                    const char *filesystemtype, unsigned long mountflags, const void *data,
                    struct lxc_mount *mnt);

int (*umount)(struct lxc_container *c, const char *target, unsigned long mountflags,
                      struct lxc_mount *mnt);

添加 lxc.monitor.signal.pdeath 配置键

设置发送到容器 init 的信号,当 lxc 监控退出时。默认情况下,它设置为 SIGKILL,这会导致 lxc 监控进程死后所有容器进程被杀死。为了确保容器即使在 lxc 监控进程死后也能保持活动状态,请将其设置为 0

构建共享和静态 liblxc

LXC 现在默认情况下将构建共享库和静态库。

适应 Linux 内核 4.18 中的 mknod() 更改

从提交开始

55956b59df33 ("vfs: Allow userns root to call mknod on owned filesystems.")

Linux 将允许 mknod() 在用户命名空间中为用户ns root 使用,如果 CAP_MKNOD 可用。
但是,这些设备节点是无用的,因为

static struct super_block *alloc_super(struct file_system_type *type, int flags,
                                       struct user_namespace *user_ns)
{
        /* <snip> */

        if (s->s_user_ns != &init_user_ns)
                s->s_iflags |= SB_I_NODEV;

        /* <snip> */
}

将在文件系统上设置 SB_I_NODEV 标志。当在非 init 用户ns 中创建的设备节点被 open() 时,调用链将命中

bool may_open_dev(const struct path *path)
{
        return !(path->mnt->mnt_flags & MNT_NODEV) &&
                !(path->mnt->mnt_sb->s_iflags & SB_I_NODEV);
}

这将导致 EPERM,因为设备节点位于非 init-用户ns 拥有的 fs 上,因此由于 SB_I_NODEV 而不会授予对设备节点的访问权限。LXC 已学会正确处理这种情况。

使用 execveat() 执行应用程序容器

应用程序容器依赖于最小的 init 系统来运行其工作负载。不要通过打开绑定挂载到容器中的文件来执行它,只需将文件描述符传递给 execveat()。这使得应用程序容器启动更安全、更简单。

在记录时启用每线程容器名称前缀

现在,每个运行不同容器但共享单个日志文件的线程都可以通过将容器的名称打印到日志中来识别。

重构 cgroup 处理

这将 cgroup 处理的构造函数实现替换为更简单、线程安全的按需 cgroup 驱动程序初始化模型。
使 cgroup 初始化代码在构造函数中运行意味着每次共享库被映射时都会运行 cgroup 解析代码。这是不必要的开销。更干净的实现是在需要时按需分配 cgroup 驱动程序。

在运行挂钩时提升环境能力

在非常受限制的容器中(例如,仅使用单个非 root 用户映射运行的非特权容器),在启动期间无法执行需要特权的操作。
通过在运行挂钩时提升环境能力,可以在 exec 中保留特权。

允许在非特权容器中以 rw 方式挂载 /sys

通过 LXD 团队完成的新的内核工作,现在可以在用户命名空间内发送 uevents。这意味着是时候让 udev 在容器内运行了。此前的先决条件是 /sysrw 方式挂载。如果没有,udev 将拒绝启动。

添加 strlcpy()strlcat(),并弃用 strncpy()strncat()

这使得字符串处理更安全,因为 strlcat()strlcpy() 始终返回有效的字符串,并允许正确检查截断。

基于编译器的强化

默认情况下,LXC 将开启各种编译器强化选项,例如

-Wimplicit-fallthrough
-Wcast-align
-Wstrict-prototypes
-fstack-clash-protection
-fstack-protector-strong
--mcet -fcf-protection
-Werror=implicit-function-declaration

线程安全改进

代码库已进一步强化,可在多线程环境中使用

seccomp: 支持架构堆叠

这允许支持以下用例,以及更多用例
- 64 位内核和 64 位用户空间运行 32 位容器
- 64 位内核和 32 位用户空间运行 64 位容器
- 64 位内核和 64 位用户空间运行 32 位容器,再运行 64 位容器
- ...

支持在容器中没有 `uid 0` 的应用程序容器

这允许启动在容器内部没有 `uid 0` 映射的容器。
以下是一个示例配置

lxc.include = /usr/share/lxc/config/common.conf
lxc.include = /usr/share/lxc/config/userns.conf
lxc.arch = linux64
lxc.rootfs.path = dir:/home/brauner/.local/share/lxc/c1/rootfs
lxc.uts.name = c1
lxc.net.0.type = empty
lxc.hook.mount =

# Only map uid and gid 1000 on the host to uid and gid 1001 inside the container.
lxc.idmap = u 1001 1000 1
lxc.idmap = g 1001 1000 1

# Switch to uid and gid 1001 in the container.
lxc.init.uid = 1001
lxc.init.gid = 1001

在没有 `gid` 挂载选项的内核上支持 `devpts` 挂载

旧版本的内核不支持 `gid` 挂载选项来授予特定组访问权限。LXC 现在会自动处理这种情况,并且仅在内核支持 `gid` 挂载选项时才添加 `gid = 5`。

错误修复

LXC 3.1.0 包含与 LXC 3.0.1、3.0.2 和 3.0.3 相同的所有错误修复。

支持和升级

LXC 3.1 不是 LTS 版本,因此只会被支持到 LXC 3.2 发布为止。我们建议需要更强支持承诺的用户保持在我们的 LTS 版本之一上。

下载