通常,您不会这样做。这是一种反模式,因为:
- 您通常对这两个进程有不同的更新周期
- 您可能想要更改每个进程的基本文件系统
- 您希望对这些相互独立的每个进程进行日志记录和错误处理
- 在共享网络或卷之外,这两个进程可能没有其他硬依赖关系
因此,最好的选择是创建两个单独的图像,并使用处理共享专用网络的 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
部分很重要,它用 shell 或进程替换了当前的 shell /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
最后我执行。您可以在我的基础映像存储库中看到一个示例,特别是包含以下部分的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 "$@"
然而,以上更多是为了扩展初始化步骤,而不是为了在容器内运行多个守护进程。考虑到他们每个人都有不好的选择和问题,我希望很清楚为什么在你的场景中运行两个容器是首选。