Dockerfile 中命令的两种书写方式的区别
最早的初衷是要研究一下运行 Docker 容器时如何向其传递参数,却冷不防掉入了另一个深渊,不得不关心起 Dockerfile 中命令(包括 RUN, CMD 和 ENTRYPOINT) 的两种不同写法上的区别。
所以呢,先要稍稍了解一下 Dockerfile 中 RUN, CMD, ENTRYPOINT 这三个指令
关于以上三个命令的区别,这儿有篇文章讲得很清楚 RUN vs CMD vs ENTRYPOINT - 每天5分钟玩转 Docker 容器技术(17),此处也照搬了些文字。
RUN, CMD 和 ENTRYPOINT 都支持两种写法,即 exec 和 shell 格式,见 Dockerfile reference #ENTRYPOINT 对这两种方式的解释。RUN 只影响如何构建镜像,所以镜像中不保留 RUN 命令。CMD 和 ENTRYPOINT 都可以在运行容器时执行命令,这里不讲述它们间的区别,而要说的是它们所支持的 exec 和 shell 两种格式的写法。此篇以 ENTRYPOINT 为例说明两种格式的区别,CMD 类似。
注:exec 格式的 ENTRYPOINT 或 CMD 就是它们实际在 docker 镜像中的样子,可用
有了这个 shell 到 exec 格式的映射关系之后,我们就不难理解为什么 shell 格式的 ENTRYPOINT 不能接收 CMD 或
对于 shell 格式
假如我们换成 exec 格式的写法
shell 格式由于命令总是由 "/bin/sh -e" 启动的子进程,它不是 PID 1 超级进程,从而无法收到 Unix 的信号,自然不能收到从
简述一下
shell 的内建命令 exec 将并不启动新的shell,而是用要被执行命令替换当前的 shell 进程,并且将老进程的环境清理掉,exec 后的命令不再是 shell 的子进程序,而且 exec 命令后的其它命令将不再执行。从执行效果上可以看到 exec 会把当前的 shell 关闭掉,直接启动它后面的命令。
虽然它与之后的命令(如上
另外,由于通过 "/bin/sh" 的搭桥,命令中的变量(如 $JAVA_OPTS) 也会被正确解析,因此
注意:exec 只会启动后面的第一个命令,
2023-12-18, 再次对比 java ... 与 exec java ... 的区别,发现又没有区别了
Dockerfile ENTRYPOINT:
Dockerfile ENTRYPOINT:
链接:
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。
所以呢,先要稍稍了解一下 Dockerfile 中 RUN, CMD, ENTRYPOINT 这三个指令
- RUN 执行命令并创建新的镜像层,常用于安装软件包。可以多个,为避免创建过多的镜像层,我们尽量把命令合在一起,用分号或 &&。它与容器运行期无关。
- CMD 设置容器启动后默认执行的命令及其参数,但 CMD 能够在启动容器时被覆盖。多个 CMD 只有最后一个是有效的
- ENTRYPOINT 配置容器启动时运行的命令。多个 ENTRYPOINT 也是只有最后一个有效
关于以上三个命令的区别,这儿有篇文章讲得很清楚 RUN vs CMD vs ENTRYPOINT - 每天5分钟玩转 Docker 容器技术(17),此处也照搬了些文字。
RUN, CMD 和 ENTRYPOINT 都支持两种写法,即 exec 和 shell 格式,见 Dockerfile reference #ENTRYPOINT 对这两种方式的解释。RUN 只影响如何构建镜像,所以镜像中不保留 RUN 命令。CMD 和 ENTRYPOINT 都可以在运行容器时执行命令,这里不讲述它们间的区别,而要说的是它们所支持的 exec 和 shell 两种格式的写法。此篇以 ENTRYPOINT 为例说明两种格式的区别,CMD 类似。
exec 格式
ENTRYPOINT ["executable", "param1", "param2"]必须清楚了解命令 "executable" 的每一个参数,一个萝卜一个坑,不能随便乱拆与合并。例如执行 jar 文件的命令
java -Xmx256M -jar /app.jar写成 exec 格式就是
ENTRYPOINT ["java", "-Xmx256M", "-jar", "/app.jar"]而不能写成
ENTRYPOINT ["java", "-Xmx256M", "-jar /app.jar"]否则
docker run 运行它时出错Unrecognized option: -jar /app.jar"-jar" 和 "/app.jar" 分别是两个参数。
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
exec 格式是一种数组形式,该格式的 ENTRYPOINT 能接收 CMD 或 dock run <image> 后的参数作为附加参数,相当于是往这个数组中附加元素。例如 Dockerfile 中写成ENTRYPOINT ["echo", "Hello"]假设置构建出的镜像名(repository) 是 test(以下都以 test 作为镜像名称), 那么执行下面 docker 命令
$ docker run test World and China输出是
Hello World and China使用 exec 格式的 ENTRYPOINT 与 CMD 同在时还能接收 CMD 送过来的参数,如 Dockerfile
ENTRYPOINT ["echo", "Hello"]执行 docker run 命令
CMD ["World"]
$ docker run test输出
Hello World另外,如果执行如下 docker 命令
docker run test China Haha输出就是
Hello China Haha原因是 CMD 在运行容器时由
docker run <image> 后的命令覆盖的了,所以 World 不见了。注:exec 格式的 ENTRYPOINT 或 CMD 就是它们实际在 docker 镜像中的样子,可用
docker inspect <image> 查看。shell 格式
ENTRYPOINT command param1 param2官方也虽然定义了这么一种格式,其实它的确没什么特别之处。格式上其实就是平时怎么写完整命令 ENTRYPOINT 后就怎么写,没有中括号让你划分一个个参数,这是便利之处。同样的例子
ENTRYPOINT java -Xmx256M -jar /app.jar为什么说 shell 格式没什么特别之处呢?因为只要对构建出的镜像用
docker inspect <image> 看下就知道怎么一回事$ docker inspect test观察到该 test 镜像的 ENTRYPOINT 实际上是
"Entrypoint": [因此也就是所谓 ENTRYPOINT 后那个完整 shell 命令最终是作为 "/bin/sh" 的第二个参数。同样的 CMD 的 shell 格式
"/bin/sh",
"-c",
"java -Xmx256M -jar /app.jar"
],
CMD echo helloinspect 看到该镜像中实际的 CMD 是
"Cmd": [CMD 后完整命令也是作为 "/bin/sh" 的第二个参数。
"/bin/sh",
"-c",
"echo hello"
],
有了这个 shell 到 exec 格式的映射关系之后,我们就不难理解为什么 shell 格式的 ENTRYPOINT 不能接收 CMD 或
docker run 传过来的参数。因为参数将作为 "/bin/sh" 的参数而非 shell 的参数,举例说明:对于 shell 格式
ENTRYPOINT java -Xmx256M -jar /app.jar从 CMD 或
docker run 而来的参数( 比如 Hello World) 最终将会组成下面完整的 ENTRYPOINTENTRYPOINT ["/bin/sh", "-c", "java -Xmx256M -jar /app.jar", "Hello", "World"]"Hello", "World" 并不是 java 命令的参数,而 "/bin/sh" 也会忽略掉它们。
为何 shell 格式可用变量而 exec 格式不一定行
最后一个问题,当我们使用 shell 格式时,总是可以使用内联的环境变量。例如在启动 java 程序时希望通过 JAVA_OPTS 来控制 JVM 参数,所以 ENTRYPOINT 这么写ENTRYPOINT java $JAVA_OPTS -jar /app.jar启动该镜像时用 -e 指定过小的堆内存大小报错
$ docker run -e JAVA_OPTS="-Xms2G" test出错信息告诉了我们 $JAVA_OPTS 被替换成了 "-Xms2G"。
Error occurred during initialization of VM
Too small initial heap
假如我们换成 exec 格式的写法
ENTRYPOINT ["java", "$JAVA_OPTS", "-jar", "/app.jar"]用上面同样的
docker run 命令$ docker run -e JAVA_OPTS="-Xms2G" test从出错消息可以看出 exec 格式中的 "$JAVA_OPTS" 根本无法通过环境变量进行替换,原因是变量替换操作实际是由 "/bin/sh" 能完成的,shell 格式总是由 "/bin/sh -c" 启动的。如果 exec 格式的 ENTRYPOINT 也希望能解析变量,就得依样写成
Error: Could not find or load main class $JAVA_OPTS
ENTRYPOINT ["/bin/sh", "-c", "java $JAVA_OPTS -jar /app.jar"]
其他各自特点(缺点)
exec 格式要求一个坑一个参数,所以像上面见到的那样无法在中间动态插入参数,比如不能在中间某一个位置上写上 "-Xmx5G -Xms2G", 这分明是两个参数,只能在后面附加参数shell 格式由于命令总是由 "/bin/sh -e" 启动的子进程,它不是 PID 1 超级进程,从而无法收到 Unix 的信号,自然不能收到从
docker stop <container> 发来的 SIGTERM 信号。简述一下
docker stop <container> 工作原理,它向容器中的 PID 为 1 进程发送 SIGTERM 信号,并给予 10 秒钟(可用参数 --time) 清理,超时才 -9 强杀,这样可以比较优雅的关闭容器。"/bin/sh -e" 是一个 PID 1 进程,它收到了 SIGTERM 却不会转发给它的子命令,这样就造成了 "/bin/sh -e" 收到 SIGTERM 未作响应被强杀,同时把它的子进程毫无征兆的干掉了。像在 Java 中用 Runtime.addShutdownHook() 是捕获不到该信号的。增强型 shell 格式
这里补充一种 ENTRYPOINT 的声明格式,它实质是 shell 格式,为而把它单独列出来关键就在于 shell 的exec 命令。此 exec 非前面 exec 格式中的 exec, 而是一个结结实实的 shell 命令。ENTRYPOINT exec command param1 param2 ...比如:
ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar它仍然是 shell 格式,所以 inspect 镜像后看到的 ENTRYPOINT 是
ENTRYPOINT ["/bin/sh", "-c" "exec java $JAVA_OPTS -jar /app.jar"]然而加了
exec 的绝妙之处在于:shell 的内建命令 exec 将并不启动新的shell,而是用要被执行命令替换当前的 shell 进程,并且将老进程的环境清理掉,exec 后的命令不再是 shell 的子进程序,而且 exec 命令后的其它命令将不再执行。从执行效果上可以看到 exec 会把当前的 shell 关闭掉,直接启动它后面的命令。
虽然它与之后的命令(如上
exec java $JAVA_OPTS -jar /app.jar)还是作为 "/bin/sh" 的第二个参数,但 exec 来了个金蝉脱壳,让这里的 java 进程得已作为一个 PID 1 的超级进程,进行使得这个 java 进程可以收到 SIGTERM 信号。或者理解 exec 为 "/bin/sh" 的子进程,但是借助于 exec 让它后面的进程启动在最顶端。另外,由于通过 "/bin/sh" 的搭桥,命令中的变量(如 $JAVA_OPTS) 也会被正确解析,因此
ENTRYPOINT exec command param1 param2 ... 是被推荐的格式。注意:exec 只会启动后面的第一个命令,
exec ls; top 或 exec ls && top 只会执行 ls 命令。2023-12-18, 再次对比 java ... 与 exec java ... 的区别,发现又没有区别了
Dockerfile ENTRYPOINT:
ENTRYPOINT java $JAVA_OPTS -jar /app.jar "$0" "$@"
1sh-4.2# ps -ef
2UID PID PPID C STIME TTY TIME CMD
3root 1 0 0 00:01 ? 00:00:00 /dev/init -- /bin/sh -c java $JAVA_OPTS -jar /app.jar "$0" "$@"
4root 7 1 5 00:01 ? 00:00:38 java -Xms2g -Xmx2g /app.jar /bin/shDockerfile ENTRYPOINT:
ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar "$0" "$@"
1sh-4.2# ps -ef
2UID PID PPID C STIME TTY TIME CMD
3root 1 0 0 00:26 ? 00:00:00 /dev/init -- /bin/sh -c exec java $JAVA_OPTS -jar /app.jar "$0" "$@"
4root 7 1 99 00:26 ? 00:00:46 java -Xms2g -Xmx2g -jar /app.jar /bin/shENTRYPOINT ["java", "-Xms2g", "-Xmx2g", "-jar", "/app.jar", "$0", "$@"]
1sh-4.2# ps -ef
2UID PID PPID C STIME TTY TIME CMD
3root 1 0 0 00:36 ? 00:00:00 /dev/init -- java -Xms2g -Xmx2g -jar /app.jar $0 $@
4root 7 1 31 00:36 ? 00:00:40 java -Xms2g -Xmx2g -jar /app.jar $0 $@链接:
- RUN vs CMD vs ENTRYPOINT - 每天5分钟玩转 Docker 容器技术(17)
- Dockerfile reference #ENTRYPOINT
- 优雅的终止docker容器
- Shell form ENTRYPOINT example
[版权声明]
本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。