Docker 多个入口点
Posted
技术标签:
【中文标题】Docker 多个入口点【英文标题】:Docker multiple entrypoints 【发布时间】:2013-09-19 05:58:00 【问题描述】:假设我有以下 Dockerfile:
FROM ubuntu
RUN apt-get update
RUN apt-get install -y apache2
RUN apt-get install -y mongod #pretend this exists
EXPOSE 80
ENTRYPOINT ["/usr/sbin/apache2"]
ENTRYPOINT
命令使apache2
在容器启动时启动。当容器以命令service mongod start
启动时,我还希望能够启动mongod
。但是,根据documentation,Dockerfile 中必须只有一个ENTRYPOINT
。那么这样做的正确方法是什么?
【问题讨论】:
【参考方案1】:我可以想到几种方法:
你可以写一个脚本放在容器(ADD
)上,它会执行所有的启动命令,然后把它放在ENTRYPOINT
我认为你可以在ENTRYPOINT
上放任何shell命令,所以你可以在service mongod start && /usr/sbin/apache2
【讨论】:
service mongod start && /usr/sbin/apache2
不会监控这两个进程。【参考方案2】:
我无法使用&&
工作。我能够按照这里的描述解决这个问题:https://***.com/a/19872810/2971199
所以在你的情况下你可以这样做:
RUN echo "/usr/sbin/apache2" >> /etc/bash.bashrc
RUN echo "/path/to/mongodb" >> /etc/bash.bashrc
ENTRYPOINT ["/bin/bash"]
您可能需要/想要编辑您的启动命令。
如果您多次运行 Dockerfile,请小心,您可能不希望将多个命令副本附加到您的 bash.bashrc
文件中。您可以使用grep
和if
语句使您的RUN
命令具有幂等性。
【讨论】:
我尝试访问/连接终端会发生什么。它将运行这些进程。【参考方案3】:我的解决方案是将单个脚本放入 /opt/run/
并执行它们:
#!/bin/bash
LOG=/var/log/all
touch $LOG
for a in /opt/run/*
do
$a >> $LOG &
done
tail -f $LOG
而我的入口点就是这个脚本的位置,比如说它叫做/opt/bin/run_all
:
ADD 00_sshd /opt/run/
ADD 01_nginx /opt/run/
ADD run_all /opt/bin/
ENTRYPOINT ["/opt/bin/run_all"]
【讨论】:
Supervisor 可以说更好,因为它可以在进程死亡时重新启动进程,具有可配置的日志记录选项,可以通过 RPC 远程接收命令等。尽管如此 这个答案更适合没有安装主管或其他类似技术选项的环境。简单明了 如果进程死亡,这将无法正常工作,因为它不会重新启动进程,正如@twneale 指出的那样。我不明白为什么不能在 Docker 映像中安装 Supervisor 或类似的轻量级进程管理器。 你也不需要日志文件。如果你愿意,你可以做 "/my/runnable >> /dev/stdout"【参考方案4】:正如 Jared Markell 所说,如果你想在一个 docker 容器中启动多个进程,你必须使用supervisor。您必须配置主管来告诉他启动您的不同进程。
我在 blog post 中写过这个,但你有一个真正的 nice article here 详细说明如何以及为什么在 Docker 中使用主管。
基本上,您会想要执行以下操作:
FROM ubuntu
RUN apt-get update
RUN apt-get install -y apache2
RUN apt-get install -y mongod #pretend this exists
RUN apt-get install -y supervisor # Installing supervisord
ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf
EXPOSE 80
ENTRYPOINT ["/usr/bin/supervisord"]
并添加配置文件supervisord.conf
[supervisord]
nodaemon=true
[program:mongodb]
command=/etc/mongod/mongo #To adapt, I don't know how to launch your mongodb process
[program:apache2]
command=/usr/sbin/apache2 -DFOREGROUND
编辑:由于这个答案得到了很多人的支持,我想准确地警告一下,使用 Supervisor 不被认为是最佳实践 运行多个作业。相反,您可能有兴趣为您的不同进程创建多个容器并通过docker compose 管理它们。 简而言之,Docker Compose 允许您在一个文件中定义应用所需的所有容器,并在一个命令中启动它们。
【讨论】:
【参考方案5】:简单的答案是你不应该这样做,因为它违反了单一职责原则:一个容器,一个服务。想象一下,由于突然的工作负载,您想要生成额外的 MongoDB 云映像 - 为什么还要以 1:1 的比例增加 Apache2 实例? 相反,您应该链接这些盒子并让它们通过 TCP 说话。请参阅https://docs.docker.com/userguide/dockerlinks/ 了解更多信息。
【讨论】:
单个服务可能需要运行多个守护进程/进程,这是本次讨论的主题。如果我们谈论运行 Web 服务器和数据库,它们应该在不同的容器中,那么您的观点是有道理的,但是您也可能处于需要运行使用消息队列的 Python/Ruby/Java 应用程序的情况观察者。然后您需要在该容器中启动 2 个进程(2 个入口点):您的应用程序和队列观察器。 @JoséL.Patiño 不确定我是否理解 - 队列观察者将如何与网络应用程序对话?通过 TCP/IP?如果是这种情况,您仍然可以在两个不同的队列中运行它们,并相互独立地缩放它们(这是我最初的观点)。如果通过 IPC/UNIX 套接字/else 进行通信,则可能会有所不同。 我认为 Jose 所指出的情况是您有一个服务正在运行并且一个小脚本也需要运行(这并不像我们想象的那么罕见)。我刚刚在 docker 容器中运行服务器时遇到了这个问题,需要执行 'crond' 才能在 alpine 中运行 cron。我敢肯定还有很多其他情况。 对于这些小型脚本/辅助服务器,有一种称为 sidecar 容器 的通用模式 - 仅为该脚本/服务器创建一个新映像,根据需要打开端口以与之通信主容器。【参考方案6】:您不能在 Dockerfile 中指定多个入口点。要在同一个 docker 容器中运行多个服务器,您必须使用能够启动服务器的命令。 Supervisord 已经被引用,但我也可以推荐 multirun,这是我的一个项目,它是一个更轻量级的替代方案。
【讨论】:
【参考方案7】:在 docker docs 中有一个答案: https://docs.docker.com/config/containers/multi-service_container/
总之
如果您需要在一个容器中运行多个服务,您可以通过几种不同的方式来完成。
第一个是运行管理您的进程的脚本。
第二种是使用进程管理器,如supervisord
【讨论】:
【参考方案8】:如果您尝试运行多个并发 npm 脚本,例如监视脚本和构建脚本,请查看:
How can I run multiple npm scripts in parallel?
【讨论】:
【参考方案9】:通常情况下,您不会这样做。这是一种反模式,因为:
-
这两个进程的更新周期通常不同
您可能希望更改每个进程的基本文件系统
您希望对每个相互独立的进程进行日志记录和错误处理
在共享网络或卷之外,这两个进程可能没有其他硬依赖关系
因此,最好的选择是创建两个单独的图像,并使用处理共享专用网络的 compose 文件启动两个容器。
如果您无法遵循该最佳实践,那么您最终会遇到以下情况。父图像包含一行:
ENTRYPOINT ["/entrypoint-parent.sh"]
并且您想将以下内容添加到您的子图像中:
ENTRYPOINT ["/entrypoint-child.sh"]
然后将结果图像中ENTRYPOINT
的值替换为/entrypoint-child.sh
,换句话说,ENTRYPOINT
只有一个值。 Docker 只会调用一个进程来启动您的容器,尽管该进程可以产生子进程。有几种技术可以扩展入口点。
选项 A:调用您的入口点,然后在最后运行父入口点,例如/entrypoint-child.sh
可能看起来像:
#!/bin/sh
echo Running child entrypoint initialization steps here
/usr/bin/mongodb ... &
exec /entrypoint-parent.sh "$@"
exec
部分很重要,它用/entrypoint-parent.sh
外壳或进程替换了当前外壳,从而消除了信号处理问题。结果是您在子入口点运行初始化的第一位,然后委托给原始父入口点。这确实需要您跟踪父入口点的名称,可能会在基础映像的版本之间发生变化。这也意味着您在 mongodb 上失去了错误处理和优雅终止,因为它是在后台运行的。这可能会导致错误的健康容器和数据丢失,我不建议将这两种方法用于生产环境。
选项 B:在后台运行父入口点。这不太理想,因为除非您采取一些额外的步骤,否则您将不再对父进程进行错误处理。最简单的,这在您的/entrypoint-child.sh
中如下所示:
#!/bin/sh
# other initialization steps
/entrypoint-parent.sh "$@" &
# potentially wait for parent to be running by polling
# run something new in the foreground, that may depend on parent processes
exec /usr/bin/mongodb ...
注意,我一直使用的"$@"
表示法是将CMD
的值作为参数传递给父入口点。
选项 C:切换到 supervisord 之类的工具。我不是这个的超级粉丝,因为它仍然意味着在你的容器中运行多个守护进程,你通常最好将它分成多个容器。当单个子进程不断失败时,您需要确定正确的响应是什么。
选项 D:与选项 A 和 B 类似,我经常创建一个入口点脚本目录,可以在映像构建的不同级别进行扩展。入口点本身没有改变,我只是将新文件添加到根据文件名顺序调用的目录中。在我的场景中,这些脚本都在前台运行,最后我执行CMD
。您可以在我的 base image repo 中看到一个示例,特别是 entrypoint.d
目录和 bin/entrypointd.sh
脚本,其中包含以下部分:
# ...
for ep in /etc/entrypoint.d/*; do
ext="$ep##*."
if [ "$ext" = "env" -a -f "$ep" ]; then
# source files ending in ".env"
echo "Sourcing: $ep"
set -a && . "$ep" && set +a
elif [ "$ext" = "sh" -a -x "$ep" ]; then
# run scripts ending in ".sh"
echo "Running: $ep"
"$ep"
fi
done
# ...
# run command with exec to pass control
echo "Running CMD: $@"
exec "$@"
然而,以上更多是为了扩展初始化步骤,而不是为了在容器内运行多个守护进程。鉴于它们各自都有不好的选择和问题,我希望很清楚为什么在您的方案中首选运行两个容器。
【讨论】:
这应该是公认的答案以上是关于Docker 多个入口点的主要内容,如果未能解决你的问题,请参考以下文章
当孩子定义另一个镜像时,Docker 父镜像的入口点会发生啥?
Docker .Net 6 错误程序不包含适用于入口点的静态“主要”方法