【docker】Dockerfile文件、run命令中的cmd、ENTRYPOINT命令(两种不同的进程执行方式 shell 和 exec)

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 的关系

  1. 仅定义CMD或仅定义ENTRYPOINT,各自生效
  2. 同时定义CMD和ENTRYPOINT,CMD 只为 ENTRYPOINT 提供参数,追加到ENTRYPOINT 尾部
  3. CMD 可由 docker run <image> 后的命令覆盖,同时覆盖参数
  4. 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

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值