CMD和ENTRYPOINT的配置比对详解
概述
Dockerfile的语法包括CMD和Entrypoint两种格式都可以配置用于容器内主进程的启动,两者的语义并不是很合适。新上手很容易搞不清楚两者之间的区别和关系,在不同的配置搭配下会产生怎样的运行效果也没有一个条理清晰的思路。
同样的,k8s也有一套类似的配置语法:command和args。那么k8s的语法中两者又是怎样的搭配关系,它们跟docker语法又是怎样的映射关系呢?
本文将进行详情分析。
Docker中CMD和ENTRYPOINT的区别
两种运行模式
不管是CMD还是ENTRYPOINT配置,实际都是有两种模式:SHELL模式,EXEC模式。
SHELL模式
命令是运行在命令解析器中的,比如linux的/bin/sh -c
, windows的cmd /S /C
。
此时容器中的init进程(PID为1)是/bin/sh -c <process>
,而不是容器的可执行程序,<process>
只做为/bin/sh -c
的子进程存在。
Linux内核机制下,PID为1的进程(通常是init)有区别与其他进程的地方:
- PID为1的进程死掉后,其他所有进程都会被KILL信号杀死(也就是强制退出)
- 当某父进程死掉后,PID为1的进程会自动继承为其子进程的父进程。
- 内核不会为PID为1的进程自动注册信号处理程序。PID为1的进程无法接受SIGTERM和SIGINT这类信号,只能SIGKILL强制退出。
因此,我们无法通过docker stop <container>
进行优雅退出,因为kubernetes和docker只能发送SIGKILL信号给PID为1的进程,而此时PID为1的/bin/sh -c
没办法传递信号给<process>
,只会通过SIGKILL
强制退出。而强制退出带来的后果可能是写入中断,数据异常等。
当然也有解决办法,比如共享进程的namespace,使用专门的init程序,比如tini,supervisor等。最方便的是使用exec命令从shell脚本启动进程,进程会继承PID 1:
# shell模式下,通过exec配置可以接受signal
ENTRYPOINT exec cmd1 param1
语法如下
#单独用CMD
CMD command param1 param2
#单独用ENTRYPOINT
ENTROYPOINT command param1 param2
#搭配使用只会执行ENTRYPOINT,因此CMD此时配置没什么意义
以上的运行效果是一样的。
EXEC模式
主进程就是<process>
,所以不经过shell默认的环境变量解释替换过程。由此带来的问题是,类似cd ~
和cd $HOME
是无效的,因为此时是docker负责进行变量解析。
这种模式下配置是被解析成json array的,因此必须全部使用双引号!
语法如下
# 单独用CMD
CMD ["executable", "param1", "param2"]
# 单独用ENTRYPOINT
ENTRYPOINT ["executable", "param1", "param2"]
# 搭配使用1
ENTRYPOINT ["executable"]
CMD ["param1", "param2"]
# 搭配使用2
ENTRYPOINT ["executable", "param1"]
CMD ["param2"]
以上语法的运行效果也是一样的。
配置
简单来说,CMD和ENTRYPOINT的作用都一样,都是用于容器的主进程启动。唯一不同的是,当配置了ENTRYPOINT时,CMD只作为ENTRYPOINT的命令参数存在。两者也可以单独存在。
因此,配置其实有三种方式
# exec模式
1. CMD ["exec_cmd", "param1"]
2. ENTRYPOINT ["exec_cmd", "param1"]
3. ENTRYPOINT ["exec_cmd"]
CMD ["param1"]
当然3也有两种配置方式,比如ENTRYPOINT自带参数和不带参数。
最佳实践应该是3的方式,即ENTRYPOINT配置可执行命令,CMD搭配默认参数。
不同配置的运行结果
简单来说,ENTRYPOINT优先级最高,当Dockerfile中配置了ENTRYPOINT并使用EXEC模式时,CMD作为额外参数搭配。
No EntryPoint | EntryPoint exec_entry p1_entry | EntryPoint [“exec_entry”, “p1_entry”] | |
---|---|---|---|
No CMD | error, not allowed | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry |
CMD [“exec_cmd”, “p1_cmd”] | exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry exec_cmd p1_cmd |
CMD [“p1_cmd”, “p2_cmd”] | p1_cmd p2_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry p1_cmd p2_cmd |
CMD exec_cmd p1_cmd | /bin/sh -c exec_cmd p1_cmd | /bin/sh -c exec_entry p1_entry | exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd |
优先级
整体来说,ENTRYPOINT优先级大于CMD。而同样配置项,通过命令行指定的参数 优先级大于镜像Dockerfile配置,前者可以覆盖掉镜像的配置。
也就是说: docker run –entrypoint是最高优先级,ENTRYPOINT是次优先级。
Kubernetes
k8s与docker的映射关系
kubernetes中,命令和参数的语义会更合适一些,即command和args, 分别对应Dockerfile中的ENTRYPOINT和CMD。
详情如下表:
Description | Docker field name | Kubernetes field name |
---|---|---|
The command run by the container | Entrypoint | command |
The arguments passed to the command | Cmd | args |
配置
与Dockerfile一样,Pod的定义中可以指定容器运行的命令及参数。
apiVersion: v1
kind: Pod
metadata:
name: command-demo
labels:
purpose: demonstrate-command
spec:
containers:
- name: command-demo-container
image: debian
command: ["printenv"]
args: ["HOSTNAME", "KUBERNETES_PORT"]
restartPolicy: OnFailure
不同配置的运行结果
简单来说,k8s的优先级高于Dockefile。因此,不同配置的运行结果如下表:
Image Entrypoint | Image Cmd | Container command | Container args | Command run |
---|---|---|---|---|
[/ep-1] |
[foo bar] |
[ep-1 foo bar] |
||
[/ep-1] |
[foo bar] |
[/ep-2] |
[ep-2] |
|
[/ep-1] |
[foo bar] |
[zoo boo] |
[ep-1 zoo boo] |
|
[/ep-1] |
[foo bar] |
[/ep-2] |
[zoo boo] |
[ep-2 zoo boo] |