如何入口点Docker 容器具体是如何工作的?我有一个包含 ENTRYPOINT 脚本的基本 docker 镜像,我正在其上构建另一个包含另一个 ENTRYPOINT 脚本的镜像。
当我运行图像时,两个 ENTRYPOINT 脚本都会运行吗?
答案1
如果父图像包含一行:
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
# ...
exec /entrypoint-parent.sh "$@"
这exec
部分很重要,它用 shell 或进程替换当前 shell /entrypoint-parent.sh
,从而消除信号处理问题。结果是您在子入口点中运行初始化的第一部分,然后委托给原始父入口点。这确实要求您跟踪父入口点的名称,这可能会在基础映像的不同版本之间发生变化。
选项 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 /child-process.bin
请注意,"$@"
我一直使用的符号是将的值CMD
作为参数传递到父入口点。
选项 C:切换到类似主管。我不太喜欢这个,因为它意味着在你的容器内运行多个守护进程,而你通常最好将其分成多个容器。当单个子进程不断失败时,你需要决定正确的响应是什么。
选项 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 "$@"
答案2
不,只有一个入口点,因此您的脚本将替换父映像中定义的入口点。但您的脚本可以调用(*)或借用父映像使用的脚本中的代码。
(*) 当然,只有当这是最后一步时才会发生这种情况。请注意,在设计良好的容器中,您应该将exec
脚本的最后一个命令设置为容器的主进程。如果您未能这样做,则发送的用于正常终止容器的信号 (SIGINT/SIGTERM) 将发送到您的脚本(不会对它们执行任何操作),而不是发送到容器“真实”进程。这将 1) 使容器缓慢停止,2) 将阻止正常退出:docker stop
将发送 SIGTERM,如果容器未及时终止(10 秒),它将使用 SIGKILL。如果您的exec
脚本本身exec
就是一个命令,那么该命令将成为主进程,一切都会很好。
答案3
正如前面的答案所提到的:
- 您的入口点规范将取代之前的入口点。
- 如果您编写了一个入口点并调用原始入口点,则您应该使用“exec”来确保原始入口点作为单独的进程运行,而不是作为入口点内的函数运行。
- 将原始参数传递给原始入口点:“#@”
更好的解决方案可能是,如果镜像规范托管在 git 平台上,则分叉 git 存储库,根据需要修改入口点,然后从这个新分叉中使用 docker build 中的从 git 构建功能构建镜像。