Dockerfile 中的 CMD 与 ENTRYPOINT
1、概述
CMD 和 ENTRYPOINT 指令都用于定义容器启动时执行的命令,单从功能上来看,这两个命令几乎是重复的,单独使用其中的一个就可以实现绝大多数的用例。尽管如此,它们在某些情况下具有不同的用途和优势。这篇文章旨在澄清它们的用法,以帮助你在实际应用中做出明智的选择,避免混淆。
2、介绍
2.1 CMD 指令
CMD 用于指定容器启动时默认执行的命令及其参数。如果 Dockerfile 中包含多个 CMD 指令,则只有最后一个会生效。如果在运行容器时提供了命令参数,它们将覆盖 CMD 中定义的默认命令。
CMD 可以有三种形式:- CMD ["executable","param1","param2"](exec 形式,推荐使用,不会通过 shell 执行,更容易处理信号和参数)。
- CMD command param1 param2(shell 形式,会通过 shell 执行)。
- CMD ["param1","param2"](作为 ENTRYPOINT 的默认参数)。
2.2 ENTRYPOINT 指令
ENTRYPOINT 用于配置容器启动时执行的命令。如果 Dockerfile 中包含多个 ENTRYPOINT 指令,则只有最后一个会生效。ENTRYPOINT 与 CMD 一起使用时,CMD 的内容会作为参数传递给 ENTRYPOINT 指定的命令。ENTRYPOINT 可以有两种形式:
- ENTRYPOINT ["executable", "param1", "param2"](exec 形式,可使用CMD的参数和可使用
docker run
后面追加的参数)。 - ENTRYPOINT command param1 param2(shell 形式,不会使用 CMD参数,不可使用
docker run
后面追加的参数)。
2.3 CMD 与 ENTRYPOINT 的Shell格式和Exec格式
Shell格式:指令 <command>,以 CMD 指令为例。
CMD java -jar test.jar
Exec格式:指令 ["executable", "param1", "param2", ...], 以 CMD 指令为例。
CMD ["nginx", "-g", "daemon off;"]
Shell格式中的命令会直接被Shell解析,Exec格式不会直接解析,需要加参数。
2.4 CMD 与 ENTRYPOINT主要区别
CMD 与 ENTRYPOINT都是用于指定启动容器执行的命令,区别在于:
- 当docker run 命令中有参数时,守护进程会忽略CMD命令。
- 使用ENTRYPOINT指令不会忽略,并且会接收docker run 参数附加到命令行中。
为了使构建的容器可以正常启动,我们编写的dockerfile文件必须包含一个CMD或ENTRYPOINT指令(指令在父镜像中的存在也可以)。
3、CMD 与 ENTRYPOINT的结合使用
dockerfile文件中包含多个CMD时,只有最后一个被加载使用。我们在dockerhub中搜索centos官方镜像,看一下的官方dockerfile文件。我们查看latest标签的dockerfile。
FROM scratch ADD centos-8-x86_64.tar.xz / LABEL org.label-schema.schema-version="1.0" org.label-schema.name="CentOS Base Image" org.label-schema.vendor="CentOS" org.label-schema.license="GPLv2" org.label-schema.build-date="20201204" CMD ["/bin/bash"]
只有四行,这就是构建一个latest版本centos8.3.2011镜像的dockerfile全部内容。指定基镜像(这里从scratch这个空镜像开始构建),添加rootfs内容,打标签,通过CMD指定启动命令。
不止centos,其他debian、ubuntu、busybox等镜像都只需通过CMD指定启动命令,比如busybox更为简约。
FROM scratch ADD busybox.tar.xz / CMD ["sh"]
这种基础类、工具类镜像的构建我们只需要指定一个必要CMD来启动容器即可。但是我们编写一个dockerfile并不是为了启动容器而编写,大多数时候我们要在容器运行我们的app,运行我们的服务。
当然通过CMD也可以启动,可是如此一来有一个缺陷,我们上面说到的CMD的启动命令会被docker run 参数代替,我们有下面Dockerfile。
FROM centos CMD ["/bin/top","-b"]
构建后,使用参数ps启动容器。
[root@localhost dockerfiles]# docker run -it centos_top:v1 ps PID TTY TIME CMD 1 pts/0 00:00:00 ps
可看看到启动容器后top -b 已经被替换为ps,并非实现参数的替换。显然这不是我们想要的。有没有什么办法既可以默认启动应用,又可以加载到docker run 参数? 这就是接下来ENTRYPOINT与CMD的妙用。
在2.1章节我们提到CMD ["param1","param2"]形式可以作为ENTRYPOINT参数,同时ENTRYPOINT 指定的命令无法被docker run 参数取代。假如我们把CMD和ENTRYPOINT两个指令相结合,这样我们就可以通过CMD来接收docker run 参数,然后把参数传递给ENTRYPOINT执行。
我们以nginx官方dockerfile latest版本1.21为例,首先我们查看Dockerfile,这里我们只关注启动命令,如下:
... COPY docker-entrypoint.sh / COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d COPY 20-envsubst-on-templates.sh /docker-entrypoint.d COPY 30-tune-worker-processes.sh /docker-entrypoint.d ENTRYPOINT ["/docker-entrypoint.sh"] EXPOSE 80 STOPSIGNAL SIGQUIT CMD ["nginx", "-g", "daemon off;"]
从上面我们可以看到,在启动nginx容器时首先运行docker-entrypoint.sh脚本并把CMD命令中的参数nginx -g "daemon off;"传递进来。即docker run不添加参数时启动容器相当于执行如下脚本与默认参数。
#docker-entrypoint.sh nginx -g "daemon off;"
当我们使用docker run 传入参数会怎样?传入nginx-debug
#docker run -dt nginx nginx-debug -g "daemon off;"
此时启动容器相当于执行如下脚本与参数。
#docker-entrypoint.sh nginx-debug -g "daemon off;"
我们通过ps来看一下我们启动的容器。
# docker run不添加参数 root 6327 6306 0 Aug12 pts/0 00:00:00 nginx: master process nginx -g daemon off; 101 6384 6327 0 Aug12 pts/0 00:00:00 nginx: worker process 101 6385 6327 0 Aug12 pts/0 00:00:00 nginx: worker process # docker run添加参数 root 16800 16780 3 12:51 pts/0 00:00:00 nginx: master process nginx-debug -g daemon off; 101 16857 16800 0 12:51 pts/0 00:00:00 nginx: worker process 101 16858 16800 0 12:51 pts/0 00:00:00 nginx: worker process
显然我们两种参数nginx、nginx-debug的容器都启动成功!
也就是说我们通过ENTRYPOINT ["/docker-entrypoint.sh"]指定的命令在启动时无论如何都会执行,并且可以接收到了docker run 的参数。
4、总结
通过ENTRYPOINT+CMD实现命令默认参数或接收docker run 参数是一种非常流行并且有用的dockerfile编写方式。
5、其他
参考:本文大部分内容都是参考《dockerfile中ENTRYPOINT与CMD的结合》这篇博文。