我正在构建一个用于运行 Gitlab CI 作业的 docker 映像。其中一个组件需要 systemd 启动并在容器内运行,这并不是一件小事,但网上有几个指南,所以我设法做到了。该过程的一部分需要在 Dockerfile 中定义此入口点:
ENTRYPOINT ["/usr/sbin/init"]
以便 systemd 根据需要在容器中作为 PID 1 运行。这似乎与 Gitlab CI 要求相冲突:据我了解,gitlab-runner 会覆盖 Dockerfile 的 CMD 以生成一个 shell,然后执行 CI 脚本。但是 /usr/sbin/init 入口点无法理解 Gitlab 的 CMD,因此不会生成 shell 并且执行会停止。
我不知道如何解决这个问题:
我想不出任何其他可能的解决方案,因此非常感谢任何帮助。
您始终可以分叉 bash shell 来完成工作,然后使用 pid 1 执行 systemd,如下所示:
ENTRYPOINT [ "/bin/bash", "-c", "nohup bash -c \"${@} ; /sbin/init 0\" & exec /sbin/init", "${@}" ]
我终于能够提出一个可行的解决方案。 ENTRYPOINT 执行脚本:
ENTRYPOINT ["/entrypoint.sh"]
它检索 PID 1 的 stdin/stdout fd(即 Gitlab CI 将用于作业 I/O 的那些),通过将它们附加到长时间运行的进程来固定它们,然后将 systemd 生成为 PID 1:
#!/bin/bash
# Start a long-running process to keep the container pipes open
sleep infinity < /proc/1/fd/0 > /proc/1/fd/1 2>&1 &
# Wait a bit before retrieving the PID
sleep 1
# Save the long-running PID on file
echo $! > /container-pipes-pid
# Start systemd as PID 1
exec /usr/lib/systemd/systemd
需要固定 stdin/stdout,因为某些 systemd 版本会在启动时关闭 stdin/stdout,但之所以需要它们,是因为 CI 基础设施使用它们将 CI 命令发送到 shell 并接收控制台输出。因此,将它们附加到
sleep infinity
会使它们即使在 exec /usr/lib/systemd/systemd
之后仍然存在。
然后由 systemd 单元生成 shell(之前已在 Dockerfile 中启用):
[Unit]
Description=Start bash shell attached to container STDIN/STDOUT
[Service]
Type=simple
PassEnvironment=PATH LD_LIBRARY_PATH
ExecStart=/bin/bash -c "echo Attaching to pipes of PID `cat container-pipes-pid` && exec /bin/bash < /proc/`cat container-pipes-pid`/fd/0 > /proc/`cat container-pipes-pid`/fd/1 2>/proc/`cat container-pipes-pid`/fd/2"
ExecStopPost=/usr/bin/systemctl exit $EXIT_STATUS
[Install]
WantedBy=multi-user.target rescue.target
固定的 fd 附加到 shell(这不会产生冲突,因为 systemd 和 sleep 都不在这些 fd 上执行 I/O),因此 shell 正确接收 CI 命令并将控制台输出定向到 CI 日志。然后,在作业结束时,当 CI 基础设施关闭 stdin 时,shell 终止并且该单元关闭容器,返回作业执行结果,以便 CI 正确检索作业结果。
这是一个相当复杂的实现,肯定可以改进和改进,但对于我的目的来说效果很好。
我已经复制了 Nicola Mori 上面发布的解决方案,对于 Debian Bookworm、Trixie 和 Ubuntu Jammy,它工作得很好,未经修改 - 干得好!
对于 Debian Bullseye,
systemd
的路径是 /lib/systemd/systemd
而不是 /usr/lib/systemd/systemd
,因此需要更改。
我将上面发布的 systemd 单元保存为
bash.service
,并在 Docker 文件中添加它和 entrypoint.sh
,如下所示:
COPY bash.service /etc/systemd/system/bash.service
RUN chown root:root /entrypoint.sh \
&& chmod 755 /entrypoint.sh \
&& chown root:root /etc/systemd/system/bash.service \
&& chmod 644 /etc/systemd/system/bash.service \
&& systemctl enable bash.service
ENTRYPOINT ["/entrypoint.sh"]
请注意,如果这些容器在非特权模式下运行,那么它们会失败并显示:
ERROR: Job failed: exit code 255
我不知道是否可以将检查容器是否在特权模式下运行添加到
entrypoint.sh
脚本中,或者检查是否可能会干扰PID分配?
我还在 GitLab Discourse 论坛上发布了相关内容。