使用 cgroupv2(--cgroupns=private)时 Systemd 无法在 docker 容器中运行

使用 cgroupv2(--cgroupns=private)时 Systemd 无法在 docker 容器中运行

我将在下面附上最小化的测试用例。但是,它是一个简单的 Dockerfile,其中包含以下几行:

VOLUME ["/sys/fs/cgroup"]
CMD ["/lib/systemd/systemd"]

它是基于 Debian:buster-slim 的镜像,并在容器内运行 systemd。实际上,我曾经像这样运行容器:

$ docker run  --name any --tmpfs /run \
    --tmpfs /run/lock --tmpfs /tmp \
    -v /sys/fs/cgroup:/sys/fs/cgroup:ro -it image_name

在我升级一堆主机 Linux 软件包之前,它曾经运行良好。主机内核/systemd 现在似乎默认使用 cgroup v2。之前,它是 cgroup。它停止工作了。但是,如果我提供内核选项以便主机使用 cgroup,那么它又可以正常工作了。

在没有提供内核选项的情况下,修复方法是除了以读写方式挂载之外还--cgroupns=host添加(代替)。docker run/sys/fs/cgroup:rw:ro

我想避免强迫用户提供内核选项。虽然我远非专家,但强制使用 docker 容器的主机命名空间对我来说听起来不太合适。

我正在尝试了解为什么会发生这种情况,并弄清楚应该怎么做。我的目标是在 docker 中运行 systemd,其中主机遵循 cgroup v2。

这是我看到的错误:

$ docker run --name any --tmpfs /run --tmpfs /run/lock --tmpfs /tmp \
    -v /sys/fs/cgroup:/sys/fs/cgroup:rw -it image_name
systemd 241 running in system mode. (+PAM +AUDIT +SELINUX +IMA +APPARMOR +SMACK +SYSVINIT +UTMP +LIBCRYPTSETUP +GCRYPT +GNUTLS +ACL +XZ +LZ4 +SECCOMP +BLKID +ELFUTILS +KMOD -IDN2 +IDN -PCRE2 default-hierarchy=hybrid)
Detected virtualization docker.
Detected architecture x86-64.

Welcome to Debian GNU/Linux 10 (buster)!

Set hostname to <5e089ab33b12>.
Failed to create /init.scope control group: Read-only file system
Failed to allocate manager object: Read-only file system
[!!!!!!] Failed to allocate manager object.
Exiting PID 1...

这看上去不对劲,尤其是这一行看起来很可疑:

Failed to create /init.scope control group: Read-only file system

似乎在 之前应该有一些内容/init.scope。这就是为什么我查看了docker run选项并尝试了--cgroupsns选项。如果我添加--cgroupns=host,它就会起作用。如果我/sys/fs/cgroup以只读方式挂载,则它会失败并出现不同的错误,相应的行如下所示:

Failed to create /system.slice/docker-0be34b8ec5806b0760093e39dea35f4305262d276ecc5047a5f0ff43871ed6d0.scope/init.scope control group: Read-only file system

对我来说,这就像 docker 守护进程/引擎无法为容器配置 XXX.slice 或类似的东西。我认为 docker 可能在某种程度上负责提供命名空间,但有些事情进展不顺利。然而,我一点也不确定。问题/修复是什么?

我本次实验使用的Dockerfile如下:

FROM debian:buster-slim

ENV container docker
ENV LC_ALL C
ENV DEBIAN_FRONTEND noninteractive

USER root
WORKDIR /root

RUN set -x

RUN apt-get update -y \
    && apt-get install --no-install-recommends -y systemd \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* \
    && rm -f /var/run/nologin

RUN rm -f /lib/systemd/system/multi-user.target.wants/* \
    /etc/systemd/system/*.wants/* \
    /lib/systemd/system/local-fs.target.wants/* \
    /lib/systemd/system/sockets.target.wants/*udev* \
    /lib/systemd/system/sockets.target.wants/*initctl* \
    /lib/systemd/system/sysinit.target.wants/systemd-tmpfiles-setup* \
    /lib/systemd/system/systemd-update-utmp*

VOLUME [ "/sys/fs/cgroup" ]

CMD ["/lib/systemd/systemd"]

我使用的是 Debian。docker 版本是 20.10.3 左右。Google 搜索告诉我,docker 从 20.10 开始支持 cgroup v2,但我实际上不明白“支持”是什么意思。

答案1

总结

在我看来用例尚未明确支持。您几乎可以让它工作,但还不够。

根本原因

当 systemd 看到统一的 cgroupfs/sys/fs/cgroup假设它应该能够写入这通常是可能的,但这里并非如此。

基础

首先,你需要创建一个systemd 切片对于docker容器并告诉docker使用它-我目前的docker/daemon.json

{
  "exec-opts": ["native.cgroupdriver=systemd"],
  "features": { "buildkit": true },
  "experimental": true,
  "cgroup-parent": "docker.slice"
}

笔记:并非所有这些选项都是必需的。最重要的是cgroup-parentcgroupdriver默认情况下应该已经切换到“systemd”。

每个切片都有自己的嵌套 cgroup。但有一个警告:每个组可能只是一个“叶子”或“中间”。一旦某个进程拥有了某个 cgroup 的所有权,其他进程就无法管理它。这意味着实际的容器进程需要并且将拥有自己的私人的systemd 范围

参考:请参阅更多有关systemd 资源控制,处理cgroup 命名空间代表团

笔记:这一点 docker daemon 应该--cgroupns private默认使用,但是你仍然可以强制使用它。

现在新启动的容器将拥有自己的组它应该在类似于以下的路径中可用(取决于您的设置):

/sys/fs/cgroup/your_docker_parent.slice/your_container.scope

以下是重点部分:你不能将卷装入容器的/sys/fs/cgroup。上面提到的其私有组的路径应该会自动安装在那里。

目标

现在,理论上,容器应该能够几乎完全自行管理这个委托的私有组。这将允许其自己的 init 进程创建子组。

问题

问题是/sys/fs/cgroup容器中的路径被挂载了只读。我检查了 apparmor 规则并将 seccomp 切换为 unconfined 但无济于事。

假设

我还不完全确定——我目前的假设是,这是 docker/moby/containerd 的安全功能。如果没有私人群组,挂载此路径就完全合理了ro

潜在解决方案

我还发现,用户命名空间重新映射导致私人按照预期/sys/fs/cgroup安装!rw

但这远非完美 -cgroup(以及其他)挂载具有错误的所有权:它归真实系统根 (UID0) 所有,而容器已重新映射到完全不同的用户。一旦我手动调整所有者 - 容器就能够成功启动 systemd init。

怀疑这是 docker 用户重新映射功能的缺陷,可能迟早会被修复。请记住,我可能错了——我没有证实。

讨论

Userns 重新映射有很多缺点,对我来说最好的方案是不用rw它来挂载 cgroupfs。我仍然不知道这是故意为之还是 cgroup/userns 实现的某种限制。

笔记

您的内核启用了 cgroupv2 还不够。根据捆绑的 Linux 发行版,systemd 可能默认使用 v1。

您可以通过内核命令行参数告诉 systemd 使用 cgroupv2:
systemd.unified_cgroup_hierarchy=1

可能还需要明确禁用杂交种cgroupv1 支持以避免使用以下问题: systemd.legacy_systemd_cgroup_controller=0

或者使用以下命令完全禁用内核中的 cgroupv1: cgroup_no_v1=all

答案2

感谢@pinkeen的回答,这是我的Dockerfile和命令行,它运行良好。希望这对您有所帮助:

FROM debian:bullseye
# Using systemd in docker: https://systemd.io/CONTAINER_INTERFACE/
# Make sure cgroupv2 is enabled. To check this: cat /sys/fs/cgroup/cgroup.controllers
ENV container docker
STOPSIGNAL SIGRTMIN+3
VOLUME [ "/tmp", "/run", "/run/lock" ]
WORKDIR /
# Remove unnecessary units
RUN rm -f /lib/systemd/system/multi-user.target.wants/* \
  /etc/systemd/system/*.wants/* \
  /lib/systemd/system/local-fs.target.wants/* \
  /lib/systemd/system/sockets.target.wants/*udev* \
  /lib/systemd/system/sockets.target.wants/*initctl* \
  /lib/systemd/system/sysinit.target.wants/systemd-tmpfiles-setup* \
  /lib/systemd/system/systemd-update-utmp*
CMD [ "/lib/systemd/systemd", "log-level=info", "unit=sysinit.target" ]
docker build -t systemd_test .
docker run -t --rm --name systemd_test \
  --privileged --cap-add SYS_ADMIN --security-opt seccomp=unconfined \
  --cgroup-parent=docker.slice --cgroupns private \
  --tmpfs /tmp --tmpfs /run --tmpfs /run/lock \
  systemd_test

注意:您必须使用 Docker 20.10 或更高版本,并且您的系统启用了 cgroupv2(检查是否/sys/fs/cgroup/cgroup.controllers存在)。

答案3

对于那些想知道如何使用内核命令行解决这个问题的人来说:

# echo 'GRUB_CMDLINE_LINUX=systemd.unified_cgroup_hierarchy=false' > /etc/default/grub.d/cgroup.cfg
# update-grub

这将创建一个“混合” cgroup 设置,使得主机 cgroup v1 再次可供容器的 systemd 使用。

https://github.com/systemd/systemd/issues/13477#issuecomment-528113009

答案4

有趣的是,使用 Docker Desktop for Mac 4.13.1 时,这个 Dockerfile 可以运行:

FROM debian:bullseye

VOLUME [ "/tmp", "/run", "/run/lock" ]

RUN apt-get update && apt-get install -y systemd bash && apt-get clean && mkdir -p /lib/systemd && ln -s /lib/systemd/system /usr/lib/systemd/system;

WORKDIR /

RUN rm -f /lib/systemd/system/multi-user.target.wants/* \
  /etc/systemd/system/*.wants/* \
  /lib/systemd/system/local-fs.target.wants/* \
  /lib/systemd/system/sockets.target.wants/*udev* \
  /lib/systemd/system/sockets.target.wants/*initctl* \
  /lib/systemd/system/sysinit.target.wants/systemd-tmpfiles-setup* \
  /lib/systemd/system/systemd-update-utmp*

CMD [ "/lib/systemd/systemd" ]

只需:

docker build . -t debiansys
docker run --rm -it --privileged debiansys

但事实并非如此:

FROM amazonlinux:2

VOLUME [ "/tmp", "/run", "/run/lock" ]

RUN yum -y update && yum install -y systemd systemd-sysv bash && mkdir -p /lib/systemd && ln -s /lib/systemd/system /usr/lib/systemd/system

WORKDIR /

RUN cd /lib/systemd/system/sysinit.target.wants/ ; \
    for i in *; do [ $i = systemd-tmpfiles-setup.service ] || rm -f $i ; done ; \
    rm -f /lib/systemd/system/multi-user.target.wants/* ; \
    rm -f /etc/systemd/system/*.wants/* ; \
    rm -f /lib/systemd/system/local-fs.target.wants/* ; \
    rm -f /lib/systemd/system/sockets.target.wants/*udev* ; \
    rm -f /lib/systemd/system/sockets.target.wants/*initctl* ; \
    rm -f /lib/systemd/system/basic.target.wants/* ; \
    rm -f /lib/systemd/system/anaconda.target.wants/*

ENTRYPOINT [ "/lib/systemd/systemd" ]
docker build . -t al2sys
docker run --rm -it --privileged al2sys
[!!!!!!] Failed to mount API filesystems, freezing.

我尝试在 hyperkit 机器内重新安装 /sys/fs/cgroup,但似乎没有任何效果......


Client:
 Context:    default
 Debug Mode: false
 Plugins:
  buildx: Docker Buildx (Docker Inc., v0.9.1)
  compose: Docker Compose (Docker Inc., v2.12.1)
  dev: Docker Dev Environments (Docker Inc., v0.0.3)
  extension: Manages Docker extensions (Docker Inc., v0.2.13)
  sbom: View the packaged-based Software Bill Of Materials (SBOM) for an image (Anchore Inc., 0.6.0)
  scan: Docker Scan (Docker Inc., v0.21.0)

Server:
 Containers: 1
  Running: 1
  Paused: 0
  Stopped: 0
 Images: 1
 Server Version: 20.10.20
 Storage Driver: overlay2
  Backing Filesystem: extfs
  Supports d_type: true
  Native Overlay Diff: true
  userxattr: false
 Logging Driver: json-file
 Cgroup Driver: cgroupfs
 Cgroup Version: 2
 Plugins:
  Volume: local
  Network: bridge host ipvlan macvlan null overlay
  Log: awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog
 Swarm: inactive
 Runtimes: runc io.containerd.runc.v2 io.containerd.runtime.v1.linux
 Default Runtime: runc
 Init Binary: docker-init
 containerd version: 9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6
 runc version: v1.1.4-0-g5fd4c4d
 init version: de40ad0
 Security Options:
  seccomp
   Profile: default
  cgroupns
 Kernel Version: 5.15.49-linuxkit
 Operating System: Docker Desktop
 OSType: linux
 Architecture: x86_64
 CPUs: 6
 Total Memory: 7.675GiB
 Name: docker-desktop
 ID: L3E4:BP7K:5SPF:AVIO:ZXZR:DN3F:VD74:OBVO:OERD:LAOT:KTAV:SBNG
 Docker Root Dir: /var/lib/docker
 Debug Mode: false
 HTTP Proxy: http.docker.internal:3128
 HTTPS Proxy: http.docker.internal:3128
 No Proxy: hubproxy.docker.internal
 Registry: https://index.docker.io/v1/
 Labels:
 Experimental: false
 Insecure Registries:
  hubproxy.docker.internal:5000
  127.0.0.0/8
 Live Restore Enabled: false

相关内容