如何在一个 Docker 容器中运行多个服务
已发表: 2022-06-30Docker 是一种将堆栈组件打包为隔离容器的技术。 通常的做法是在自己的容器中运行每个进程,从而在组件之间创建一个清晰的分界线。 这增强了模块化并让您获得容器化的可扩展性优势。
仍然可能存在您希望在单个容器中运行多个服务的情况。 虽然这在 Docker 生态系统中并非自然而然,但我们将展示一些不同的方法,您可以使用这些方法来创建具有多个长生命周期进程的容器。
识别问题
Docker 容器运行单个前台进程。 这是由图像的ENTRYPOINT
和CMD
指令定义的。 ENTRYPOINT
在图像的Dockerfile
中设置,而CMD
在创建容器时可以被覆盖。 容器在其前台进程退出时自动停止。
您可以从CMD
启动其他进程,但容器只会在原始前台进程处于活动状态时保持运行。 使用ENTRYPOINT/CMD
机制无法直接通过两个独立服务的组合生命周期保持容器运行。
在一个入口点包装多个进程
包装脚本是该问题的最简单解决方案。 您可以编写一个脚本来启动所有进程并等待它们完成。 将脚本设置为 Docker ENTRYPOINT
会将其作为容器的前台进程运行,保持容器运行直到其中一个包装脚本退出。
#!/bin/bash /opt/第一个进程 & /opt/第二进程 & 等待-n 退出 $?
此脚本启动容器内的/opt/first-process
和/opt/second-process
二进制文件。 &
的使用允许脚本继续执行,而无需等待每个进程退出。 wait
用于暂停脚本,直到其中一个进程终止。 然后脚本以完成的脚本发出的状态码退出。
该模型导致容器同时运行first-process
和second-process
,直到其中一个退出。 此时,容器将停止,即使其他进程可能仍在运行。
要使用此脚本,请修改 Docker 映像的ENTRYPOINT
和CMD
以使其成为容器的前台进程:
入口点 ["/bin/sh"] CMD ["./path/to/script.sh"]
--init
容器选项
管理容器进程的一个挑战是在它们退出时有效地清理它们。 Docker 将您的CMD
作为进程 ID 1 运行,使其负责处理信号和消除僵尸。 如果您的脚本没有这些功能,您最终可能会在容器中保留孤立的子进程。
docker docker run
命令有一个--init
标志,它修改入口点以使用tini
作为 PID 1。这是一个最小的 init 进程实现,它运行你的CMD
,处理信号转发,并不断地收割僵尸。
如果您希望生成许多进程并且不想手动处理清理,那么使用--init
是值得的。 Tini 是一种轻量级的 init 风格,专为容器设计。 它比systemd
和upstart
等成熟的替代品要小得多。
使用专用流程管理器
当您需要管理大量流程时,手动编写脚本很快就会变得次优。 采用进程管理器是在 Docker 容器中运行多个服务的另一种方式。 流程管理器成为您的ENTRYPOINT
,并负责在您的工作流程之后启动、维护和清理。
实施此方法时有几个选项可用。 supervisord
是一种流行的选择,可以通过/etc/supervisor/conf.d/supervisord.conf
文件轻松配置:
[程序:apache2] 命令=/usr/sbin/apache2 -DFOREGROUND [程序:mysqld] 命令=/usr/sbin/mysqld_safe
此配置文件将supervisord
配置为启动 Apache 和 MySQL。 要在 Docker 容器中使用它,请将所有必需的包添加到您的映像中,然后将您的supervisord
配置文件复制到正确的位置。 将supervisord
设置为镜像的CMD
以在容器启动时自动运行它。
来自 ubuntu:最新 运行 apt-get install -y apache2 mysql-server 主管 复制 supervisord.conf /etc/supervisor/conf.d/supervisord.conf 入口点 ["/bin/sh"] CMD ["/usr/bin/supervisord"]
由于supervisord
持续运行,因此当您的监控进程之一退出时,无法停止容器。 另一种选择是s6-overlay
,它确实具有此功能。 它使用声明式服务模型,您可以将服务脚本直接放入/etc/services.d
:
# 添加 s6-overlay 到你的图片 添加 https://github.com/just-containers/s6-overlay/releases/download/v3.1.0.0/s6-overlay-noarch.tar.xz /tmp 运行 tar -C / -Jxpf /tmp/s6-overlay-noarch.tar.xz 运行 printf "#!/bin/shn/usr/sbin/apache2 -DFOREGROUND" > /etc/services.d/first-service/run 运行 chmod +x /etc/services.d/first-service/run # 使用 s6-overlay 作为图像的入口点 入口点 ["/init"]
你可以在你的服务目录中添加一个可执行的finish
脚本来处理使用docker stop
停止容器。 当s6-overlay
的进程由于stop
命令而收到TERM
信号时,它将自动运行这些脚本。
完成脚本接收其服务的退出代码作为其第一个参数。 当服务由于未捕获的信号而被终止时,代码设置为 256。 该脚本需要将最终退出代码写入/run/s6-linux-init-container-results/exitcode
; s6-overlay 读取此文件并以其中的值退出,导致该代码用作容器的停止代码。
#!/bin/sh echo "$1" > /run/s6-linux-init-container-results/exitcode
什么时候应该在一个容器中运行多个进程?
此技术最适用于无法分离以作为独立容器运行的紧密耦合进程。 您可能有一个依赖于后台帮助实用程序的程序或一个对各个进程执行其自己的管理的单一应用程序。 上面显示的技术可以帮助您将这些类型的软件容器化。
仍应尽可能避免在容器中运行多个进程。 坚持单一的前台进程可以最大限度地隔离,防止组件相互干扰,并提高您调试和测试特定部分的能力。 您可以使用容器编排器单独扩展组件,让您可以灵活地运行更多资源密集型流程的实例。
结论
容器通常有一个前台进程,只要它还活着就一直运行。 该模型与容器化最佳实践相一致,让您从该技术中获得最大收益。
在某些情况下,您可能需要在容器中运行多个进程。 由于所有图像最终都有一个入口点,因此您必须编写一个包装脚本或添加一个负责启动目标二进制文件的进程管理器。
流程管理器为您提供所需的一切,但使用额外的包和配置使您的图像膨胀。 包装脚本更简单,但可能需要与 Docker 的--init
标志配对以防止僵尸进程扩散。