文章目录
1. CMD概述
CMD 指令就是用于指定默认的容器主进程的启动命令的。
cmd 支持三种格式
-
CMD ["executable","param1","param2"]
简称exec格式
,使用 exec 执行,推荐方式;
exec命令 在linux中用于调用并执行指令的命令。exec命令通常用在shell脚本程序中,可以调用其他的命令。如果在当前终端中使用命令,则当指定的命令执行完毕后会立即退出终端。exec格式,cmd命令启动的就是PID1进程
-
CMD command param1 param2
简称shell格式
,在 /bin/sh 中执行,提供给需要交互的应用;默认以/bin/sh -c来运行它,这意味着此进程在容器的的PID不为1,不能接受unix信号,因此使用
docker stop <container>
命令停止容器时,此进程接受不到SIGTERM信号。exec格式或shell格式影响PID1进程,详情参见【k8s】理解Docker容器的进程管理(PID1进程、进程信号处理、僵尸进程)中的“3. 进程信号处理”和“4. 孤儿进程与僵尸进程管理”章节
-
CMD ["param1","param2"]
提供给 ENTRYPOINT 的默认参数;
也就是说当定义了 ENTRYPOINT 的话,CMD 只为 ENTRYPOINT 提供参数,自身已经不是命令的主体了,下面我们会用例子解释。
注意:
1.指定启动容器时执行的命令, 每个Dockerfile 只能有一条 CMD 命令
。如果指定了多条命令,只有最后一条会被执行。
2.如果用户启动容器时候指定
了运行的命令,则会覆盖
掉 CMD 指定的命令。
了解CMD命令还是不够的
,因为另一个ENTRYPOINT
属性作用和CMD是相似的,那么二者是什么关系呢
1.1 演示
在dockerfile中定义:
CMD echo "Hello world"
运行容器 docker run -it [image] ,不带cmd命令,将输出:
Hello world
但当后面加上一个命令,比如 docker run -it [image] echo newvalue,CMD 会被覆盖,命令echo newvalue 将被执行:
newvalue
2. ENTRYPOINT
两种格式:
- ENTRYPOINT [“executable”, “param1”, “param2”]
- ENTRYPOINT command param1 param2(shell中执行)。
配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖
。
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效
2.1 演示
在dockerfile中定义:
ENTRYPOINT ["/bin/echo", "Hello"]
当容器通过 docker run -it [image] 启动时,输出为:
Hello
而如果通过 docker run -it [image] echo world启动,则输出为:
Hello echo world
能看出 docker run中的命令和参数,全部作为内部的ENTRYPOINT 的参数,追加到尾部。
docker run -it [image]
--ENTRYPOINT
echo world 强制覆盖内部
3. 二者联系
注意:不管是ENTRYPOINT 还是CMD ,语法格式都分为exec格式和shell格式,参见第五章节
对于一个 docker 镜像,我们可以这么来理解 ENTRYPOINT 与 CMD 的关系
- 仅定义CMD或仅定义ENTRYPOINT,各自生效
- 同时定义CMD和ENTRYPOINT,CMD 只为 ENTRYPOINT 提供参数,追加到ENTRYPOINT 尾部
- CMD 可由
docker run <image>
后的命令覆盖,同时覆盖参数 - ENTRYPOINT必然生效,
docker run <image>
后的命令追加到ENTRYPOINT 尾部
我们分别举例子看下:
一、未定义 ENTRYPOINT, 定义了 CMD
某个镜像的Dockerfile配置如下:
CMD ["echo","param1","param2","param3"]
我们看下输出:
$ docker run -it ubuntu
param1 param2 param3
此时echo是一个executable 命令,后面跟的"param1",“param2”,"param3"都是echo的参数
二、同时定义了 ENTRYPOINT 和 CMD
ENTRYPOINT ["echo", "hello"]
CMD ["echo", "param1","param2","param3"]
我们看下输出:
$ docker run -it ubuntu
hello echo param1 param2 param3
我们发现输出内容和例子一相比的话,多个一个echo,也就是说此时CMD命令中的"echo"不再是一个executable命令
了,而是整体作为ENTRYPOINT 命令的参数了,追加到ENTRYPOINT 命令参数的屁股后面,等价于:
ENTRYPOINT ["echo", "hello","echo", "param1","param2","param3"]
思考:同时定义了 ENTRYPOINT 和 CMD,并在docker run 携带指令呢?
例如 docker run -it ubuntu echo zhangsan
,我猜测 应该是内部CMD被覆盖,而新的CMD命令仍被追加到ENTRYPOINT 尾部:
hello echo zhangsan
4. 怎么查看docker镜像的dockerfile
我们知道了根据dockerfile来制作镜像,如果给你一个现成的镜像,你能逆向查看出dockerfile吗?
详情参见《怎么查看docker镜像的dockerfile》
否则,你怎么知道该镜像使用的是CMD还是ENTRYPOINT ,使用的是shell格式还是CMD格式?
5. 两种不同的进程执行方式 shell 和 exec
关于ENTRYPOINT和CMD指令的不同,我们可以参见官方的Dockerfile说明和最佳实践
- https://docs.docker.com/engine/reference/builder/#entrypoint
- https://docs.docker.com/engine/reference/builder/#cmd
值得注意的一点是:在ENTRYPOINT和CMD指令中,提供两种不同的进程执行方式 shell 和 exec
5.1 shell 格式
在 shell 方式中,CMD/ENTRYPOINT指令以如下方式定义:
CMD executable param1 param2
这种方式中的PID1进程是以/bin/sh -c ”executable param1 param2”方式启动的
5.2 exec格式
而在 exec 方式中,CMD/ENTRYPOINT指令以如下方式定义:
CMD ["executable","param1","param2"]
注意这里的可执行命令和参数是利用JSON字符串数组的格式定义的,这样PID1进程会以 executable param1 param2
方式启动的。另外,在docker run命令中指明的命令行参数也是以 exec 方式启动的。
5.3 两种方式的区别
为了解释两种不同运行方式的区别,我们利用不同的Dockerfile分别创建两个Redis镜像
"Dockerfile_shell"文件内容如下,会利用shell方式启动redis服务:
FROM ubuntu:14.04
RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD "/usr/bin/redis-server"
"Dockerfile_exec"文件内容如下,会利用exec方式启动redis服务:
FROM ubuntu:14.04
RUN apt-get update && apt-get -y install redis-server && rm -rf /var/lib/apt/lists/*
EXPOSE 6379
CMD ["/usr/bin/redis-server"]
然后基于它们构建两个镜像"myredis:shell"和"myredis:exec":
docker build -t myredis:shell -f Dockerfile_shell .
docker build -t myredis:exec -f Dockerfile_exec .
运行"myredis:shell"镜像,我们可以发现它的启动进程(PID1)是/bin/sh -c "/usr/bin/redis-server"
,并且它创建了一个子进程/usr/bin/redis-server *:6379
:
docker@default:~$ docker run -d --name myredis myredis:shell
49f7fc37f4b7cf1ed7f5296537a93b2ad23b1b6686a05e5c7e40e9a2b2d3665e
docker@default:~$ docker exec myredis ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:12 ? 00:00:00 /bin/sh -c "/usr/bin/redis-server"
root 5 1 0 08:12 ? 00:00:00 /usr/bin/redis-server *:6379
root 8 0 0 08:12 ? 00:00:00 ps -ef
下面运行"myredis:exec"镜像,我们可以发现它的启动进程是/usr/bin/redis-server *:6379,并没有其他子进程存在:
docker@default:~$ docker run -d --name myredis2 myredis:exec
d1df0e4f4e3bbe36fca94f08df9ad3306fa1dee86415c853ddc5593fb9fa5673
docker@default:~$ docker exec myredis2 ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 08:13 ? 00:00:00 /usr/bin/redis-server *:6379
root 8 0 0 08:13 ? 00:00:00 ps -ef
由此我们可以清楚的看到,以exec和shell方式执行命令可能会导致容器的PID1进程不同。然而这又有什么问题呢?
原因在于: PID1进程对于操作系统而言具有特殊意义。操作系统的PID1进程是init进程,以守护进程方式运行,是所有其他进程的祖先,具有完整的进程生命周期管理能力。在Docker容器中,PID1进程是启动进程,它也会负责容器内部进程管理的工作。而这也将导致进程管理在Docker容器内部和完整操作系统上的不同。
参考
理解Docker容器的进程管理(PID1进程(容器内kill命令无法杀死)、进程信号处理、僵尸进程) 参考了两种不同的进程执行方式 shell 和 exec