![使用 cgroupv2(--cgroupns=private)时 Systemd 无法在 docker 容器中运行](https://linux22.com/image/763399/%E4%BD%BF%E7%94%A8%20cgroupv2%EF%BC%88--cgroupns%3Dprivate%EF%BC%89%E6%97%B6%20Systemd%20%E6%97%A0%E6%B3%95%E5%9C%A8%20docker%20%E5%AE%B9%E5%99%A8%E4%B8%AD%E8%BF%90%E8%A1%8C.png)
我将在下面附上最小化的测试用例。但是,它是一个简单的 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-parent
。cgroupdriver
默认情况下应该已经切换到“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