CMD 命令和 ENTRYPOINT 命令的区别

标签:Docker首次发布:2024-01-20最近修改:2026-08-19

CMD 命令

CMD-shell 形式

1. 创建 Dockerfile1

dockerfile
# vim Dockerfile1# 写入以下两行内容FROM centos:7CMD cal

2. 构建和运行新镜像

bash
# 说明:-f 用于指定本次构建所要使用的Dockerfile的文件名。docker build -f Dockerfile1 -t my-cal:1.0 .

构建成功运行新镜像。运行后可以查看到当前月份的日历。

bash
[root@centos cal]# docker run -it my-cal:1.0    September 2023Su Mo Tu We Th Fr Sa                1  2 3  4  5  6  7  8  910 11 12 13 14 15 1617 18 19 20 21 22 2324 25 26 27 28 29 30

3. 覆盖 CMD

bash
[root@centos cal]# date2023年 09月 11日 星期一 10:00:45 CST[root@centos cal]# docker run -it my-cal dateMon Sep 11 02:00:53 UTC 2023

在 docker run 命令中指定要执行的命令,Dockerfile 中通过 CMD 指定的默认的命令就不会在执行,也就是说 Dockerfile 中的 CMD 命令是可以被覆盖的。

4. 添加命令选项

bash
[root@centos cal]# docker run -it my-cal:1.0 -ydocker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "-y": executable file not found in $PATH: unknown.

cal 命令添加 -y 选项可以显示当前年的所有月份。但这种方式无法为 CMD 中指定的默认的命令指定选项。

小总结

cmd-shell 的形式可以被覆盖,但不能添加命令选项。

CMD-exec 形式

1. 创建 Dockerfile2、构建和运行新镜像

此步骤与前面类似,只是把 Dockerfile1 里面的 CMD cal 替换为 CMD ["cal"] 或者 CMD ["/bin/bash", "-c", "cal"]

CMD-exec 语法:CMD ["executable","param1","param2"]
  • executable 是你想要运行的命令或程序。
  • param1 和 param2 是传递给这个命令或程序的参数。
bash
[root@centos cal]# vim Dockerfile2[root@centos cal]# docker build -f Dockerfile2 -t my-cal:2.0 .......[root@centos cal]# docker run -it my-cal:2.0    September 2023Su Mo Tu We Th Fr Sa                1  2 3  4  5  6  7  8  910 11 12 13 14 15 1617 18 19 20 21 22 2324 25 26 27 28 29 30

最后运行新镜像的结果和 CMD-shell 形式的结果一样。

2.覆盖 CMD 和添加命令选项

bash
[root@centos cal]# docker run -it my-cal:2.0 dateMon Sep 11 02:26:45 UTC 2023[root@centos cal]# docker run -it my-cal:2.0 -ydocker: Error response from daemon: failed to create task for container: failed to create shim task: OCI runtime create failed: runc create failed: unable to start container process: exec: "-y": executable file not found in $PATH: unknown.
小总结

cmd-exec 的形式可以被覆盖,但不能添加命令选项。

ENTRYPOINT 命令

ENTRYPOINT-shell

1. 创建 Dockerfile3、构建和运行新镜像

ENTRYPOINT-shell 形式的语法:ENTRYPOINT command param1 param2

dockerfile
# vim Dockerfile3# 写入以下两行内容:FROM centos:7ENTRYPOINT cal# 构建镜像: docker build -f Dockerfile3 -t my-cal:3.0 .# 运行镜像: docker run -it my-cal:3.0

结果如下:

bash
[root@centos cal]# docker run -it my-cal:3.0    September 2023Su Mo Tu We Th Fr Sa                1  2 3  4  5  6  7  8  910 11 12 13 14 15 1617 18 19 20 21 22 2324 25 26 27 28 29 30

2. 覆盖 ENTRYPOINT 和 添加命令选项

bash
[root@centos cal]# docker run -it my-cal:3.0 date    September 2023Su Mo Tu We Th Fr Sa                1  2 3  4  5  6  7  8  910 11 12 13 14 15 1617 18 19 20 21 22 2324 25 26 27 28 29 30[root@centos cal]# docker run -it my-cal:3.0 -y    September 2023Su Mo Tu We Th Fr Sa                1  2 3  4  5  6  7  8  910 11 12 13 14 15 1617 18 19 20 21 22 2324 25 26 27 28 29 30

可以看到 ENTRYPOINT-shell 形式的指令是不会被 docker run 中指定的命令给覆盖掉的。在 docker run 中添加的命令选项,对于 ENTRYPOINT 中指定的命令是无效的。在这点上不像 CMD 指令一样报错。

ENTRYPOINT-exec

为了更好的理解 ENTRYPOINT 命令,ENTRYPOINT-exec 又分了两种情况进行讨论。分别是 ENTRYPOINT [“cal”] 和 ENTRYPOINT [“/bin/bash”,“-c”,“cal”] 。看看它们之间的区别有哪些以及为什么。

1. 创建 Dockerfile4、构建和运行新镜像

dockerfile
# vim Dockerfile4# 写入以下两行内容:FROM centos:7ENTRYPOINT ["cal"]# 构建镜像: docker build -f Dockerfile4 -t my-cal:4.0 .# 运行镜像: docker run -it my-cal:4.0

运行结果如下:

bash
[root@centos cal]# docker run -it my-cal:4.0    September 2023Su Mo Tu We Th Fr Sa                1  2 3  4  5  6  7  8  910 11 12 13 14 15 1617 18 19 20 21 22 2324 25 26 27 28 29 30

2. 覆盖 ENTRYPOINT 和 添加命令选项

bash
[root@centos cal]# docker run -it my-cal:4.0 datecal: illegal year value: use 1-9999: 'date'[root@centos cal]# docker run -it my-cal:4.0 -y                               2023       January               February                 MarchSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa 1  2  3  4  5  6  7             1  2  3  4             1  2  3  4 8  9 10 11 12 13 14    5  6  7  8  9 10 11    5  6  7  8  9 10 1115 16 17 18 19 20 21   12 13 14 15 16 17 18   12 13 14 15 16 17 1822 23 24 25 26 27 28   19 20 21 22 23 24 25   19 20 21 22 23 24 2529 30 31               26 27 28               26 27 28 29 30 31        April                   May                   JuneSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa                   1       1  2  3  4  5  6                1  2  3 2  3  4  5  6  7  8    7  8  9 10 11 12 13    4  5  6  7  8  9 10 9 10 11 12 13 14 15   14 15 16 17 18 19 20   11 12 13 14 15 16 1716 17 18 19 20 21 22   21 22 23 24 25 26 27   18 19 20 21 22 23 2423 24 25 26 27 28 29   28 29 30 31            25 26 27 28 29 3030        July                  August                SeptemberSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa                   1          1  2  3  4  5                   1  2 2  3  4  5  6  7  8    6  7  8  9 10 11 12    3  4  5  6  7  8  9 9 10 11 12 13 14 15   13 14 15 16 17 18 19   10 11 12 13 14 15 1616 17 18 19 20 21 22   20 21 22 23 24 25 26   17 18 19 20 21 22 2323 24 25 26 27 28 29   27 28 29 30 31         24 25 26 27 28 29 3030 31       October               November               DecemberSu Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa   Su Mo Tu We Th Fr Sa 1  2  3  4  5  6  7             1  2  3  4                   1  2 8  9 10 11 12 13 14    5  6  7  8  9 10 11    3  4  5  6  7  8  915 16 17 18 19 20 21   12 13 14 15 16 17 18   10 11 12 13 14 15 1622 23 24 25 26 27 28   19 20 21 22 23 24 25   17 18 19 20 21 22 2329 30 31               26 27 28 29 30         24 25 26 27 28 29 30                                              31

可以看到这种的 ENTRYPOINT-exec 形式的指令也是不会被 docker run 中指定的命令给覆盖掉的,并且会报出错误信息。而在 docker run 中添加的命令选项,对于 ENTRYPOINT 中指定的命令却是是有效的,能够显示出整年的日历。

ENTRYPOINT-exec 的另一种形式

为了节省篇幅,我简要写出关键步骤后直接看运行结果。

dockerfile
# 建立Dockerfile5# vim Dockerfile5# 写入以下两行内容:FROM centos:7ENTRYPOINT ["/bin/bash","-c","cal"]# 构建镜像: docker build -f Dockerfile5 -t my-cal:5.0 .# 运行镜像: docker run -it my-cal:5.0

运行结果如下:

bash
[root@centos cal]# docker run -it my-cal:5.0    September 2023Su Mo Tu We Th Fr Sa                1  2 3  4  5  6  7  8  910 11 12 13 14 15 1617 18 19 20 21 22 2324 25 26 27 28 29 30

覆盖 ENTRYPOINT 和 添加命令选项的结果如下:

bash
[root@centos cal]# docker run -it my-cal:5.0 date    September 2023Su Mo Tu We Th Fr Sa                1  2 3  4  5  6  7  8  910 11 12 13 14 15 1617 18 19 20 21 22 2324 25 26 27 28 29 30[root@centos cal]# docker run -it my-cal:5.0 -y    September 2023Su Mo Tu We Th Fr Sa                1  2 3  4  5  6  7  8  910 11 12 13 14 15 1617 18 19 20 21 22 2324 25 26 27 28 29 30

发现最后的结果与 ENTRYPOINT-shell 的形式一样。也就是不会被覆盖并且添加选项无效。

基于前面五个实验的总结

Dockerfile 的命令 命令是否可以被覆盖 是否可以添加选项
CMD cal 可以被覆盖 不能添加选项,添加选项后会报错
CMD [“cal”] 可以被覆盖 不能添加选项,添加选项后会报错
ENTRYPOINT cal 不可以被覆盖,添加的命令会被忽略 不能添加选项,添加的选项被忽略
ENTRYPOINT [“cal”] 不可以被覆盖,添加命令后会报错 可以添加选项
ENTRYPOINT [“/bin/bash”,“-c”,“cal”] 不可以被覆盖,添加的命令会被忽略 不能添加选项,添加的选项被忽略

在总结之前先来看看 docker run 命令的语法:

bash
[root@centos cal]# docker run --helpUsage:  docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

可以看到:IMAGE 的后面是可以接命令和参数的,但命令和参数并不是必须的,所以用 [ ] 括起来了。所以结论如下:

  1. shell 命令格式和 exec 命令格式的区别:

    对于 shell 形式的 ENTRYPOINT 或者 CMD,docker daemon 会创建一个新的 /bin/bash shell 进程,并在这个新进程中运行你的命令。这意味着你的命令会在一个新的 shell 环境中运行,这个环境有它自己的环境变量,且和启动容器的环境是隔离的。

    这与 exec 形式不同,exec 形式直接在 Docker 容器的主进程中运行命令,没有额外创建 shell 进程。这就是为什么 exec 形式可以接收 docker run 附加的参数。

  2. CMD 指令:

    Dockerfile 中的 CMD 指令是可以被 docker run 命令中的[COMMAND]替代的。也就是说,如果 docker run 命令中的 IMAGE 后仍有内容,此时 docker daemon 首先会将 IMAGE 后面的[COMMAND]替换掉 CMD 指令中的[COMMAND],如果有两个或两个以上的内容,docker daemon 会认为是从第二个内容开始后面的都是[ARG]。所以,添加的-y 会报错,这是因为没有-y 这样的[COMMAND]。

  3. ENTRYPOINT 指令:

    如果 Dockerfile 中的[EXECUTABLE]是通过 ENTRYPOINT 指定的,则该镜像的启动命令 docker run 中是可以添加参数[ARG]的。也就是说,ENTRYPOINT 指定的命令是不可被替代的,并且如果 IMAGE 后面还有内容,这些内容会被作为参数传递给 ENTRYPOINT 指定的命令。但加上参数后如果有语法错误,那么 docker daemon 就会忽略该参数。

最后的总结:基于以上三个结论,下面我就写一下每种命令执行的底层原理。

  1. 对于 CMD-shell 来说,首先 docker daemon 会 fork 一个新进程,该进程就是一个 shell 环境。如果 docker run 命令后面的 IMAGE没有命令和参数,那么 docker daemon 会将Dockerfile 里面的 CMD-shell 命令放在这个新 shell 里面执行;如果 docker run 命令后面的 IMAGE有命令和参数,那么会将 IMAGE 后面的命令和参数放在新 shell 里面执行。在这个新的 shell 环境有它自己的环境变量,且和启动容器的环境是隔离的。
  2. 对于 CMD-exec 来说,命令的执行环境就是运行容器的主进程的环境。如果 docker run 命令后面的 IMAGE没有命令和参数,那么 docker daemon 会将 Dockerfile 里面的 CMD-shell 命令放在运行容器的主进程的环境执行;如果 docker run 命令后面的 IMAGE有命令和参数,那么 IMAGE 后面的命令和参数将会替换掉 Dockerfile 里面的 CMD-exec 命令,也就是只执行 IMAGE 后面的命令和参数
  3. 对于 ENTRYPOINT-shell 来说,首先 docker daemon 会 fork 一个新进程,该进程就是一个 shell 环境。如果 docker run 命令后面的 IMAGE没有命令和参数,那么 docker daemon 会将Dockerfile 里面的 ENTRYPOINT-shell 命令放在这个新 shell 里面执行;如果 docker run 命令后面的 IMAGE有命令和参数,那对不起,IMAGE 后面所有的命令和参数都会被丢弃
  4. 对于 ENTRYPOINT-exec 来说,命令的执行环境就是运行容器的主进程的环境。如果 docker run 命令后面的 IMAGE没有命令和参数,那么 docker daemon 会将 Dockerfile 里面的 ENTRYPOINT-exec 命令放在运行容器的主进程的环境执行;如果 docker run 命令后面的 IMAGE有命令和参数,那么 docker daemon 会将 IMAGE 后面所有的命令和参数追加到 Dockerfile 里面的 ENTRYPOINT-exec 命令的后面,但是如果追加后有语法错误,那么就不会追加了。最后在运行容器的主进程的环境执行命令
  5. 最后就是 ENTRYPOINT [“/bin/bash”,“-c”,“cal”]的执行原理了,其实这有点类似于 shell 和 exec 两种形式的结合版,对于最后这个命令的底层执行原理请读者自行理解。不过需要注意的是 cal -y 的执行结果和/bin/bash -c cal -y 的执行结果是不一样的;而 cal -y 和 /bin/bash -c “cal -y” 这两条命令的执行结果是一样的。

CMD 与 ENTRYPOINT 组合使用

这两个指令都用于指定容器启动时要执行的命令,无论哪个指令,每个 Dockerfile 中都只能有一个 CMD/ENTERYPOINT 指令,多个 CMD/ENTERYPOINT 指令只会执行最后一个。如果同时存在多个 CMD 和 ENTRYPOINT,Docker 只会执行最后一个。此外,CMD 和 ENTRYPOINT 可以配合使用。当 ENTRYPOINT 和 CMD 同时存在时,CMD 中的内容会被作为 ENTRYPOINT 的参数。在这种模式下,docker run 命令后面的参数会覆盖 CMD 的内容。

验证如下:

dockerfile
# 新建Dockerfile6# vim Dockerfile6# 写入以下三行内容FROM centos:7CMD ["hello world"]ENTRYPOINT ["echo"]# 构建镜像: docker build -f Dockerfile6 -t my-cal:6.0 .# 运行镜像: docker run -it my-cal:6.0

运行结果如下:

bash
[root@centos cal]# docker run -it my-cal:6.0hello world[root@centos cal]# docker run -it my-cal:6.0 "hello docker world"hello docker world

Comments

评论区将在滚动到这里时加载。