Dockerfile中CMD和ENTRYPOINT指令的区别与使用

以下内容翻译自docker官方文档,谨此作为学习记录,方便以后查阅,分享给大家。

CMD

CMD指令设置从一个镜像启动容器的时候被执行的命令,你可以通过shell形式或者exec形式设置CMD 指令,如下:

  1. CMD ["executable","param1","param2"] (exec 形式)
  2. CMD ["param1","param2"] (exec 形式, 作为 ENTRYPOINT 指令的默认参数)
  3. CMD command param1 param2 (shell 形式)

在一个Dockerfile文件中只能有一个CMD 指令,如果有多个存在,那么只有最后一个会生效。

CMD 指令的目的是给执行中的容器提供默认值,这些默认值可以是包含一个可执行的程序,也可以不包含,但是在不包含可执行程序的时候必须设置ENTRYPOINT 指令。

如果你希望容器每次启动的时候都运行同样的可执行程序,那么应该考虑联合使用ENTRYPOIN 和CMD 指令. 如果你在执行 docker run的时候指定了command(如: docker run -it --rm --name=imageT imageT:v1.0 /bin/ls -al /home将会执行ls -al /home 而忽略CMD 指令的设置),那么这个command将会覆盖CMD (无论exec形式还是shell形式),但是ENTRYPOINT 指令不会受影响。

如果CMD 指令的作用是被用来给ENTRYPOINT 指令提供默认参数的话,那么 CMD 和ENTRYPOINT 指令都必须写成exec 形式

注意:不要混淆RUN 指令和CMD 指令。在build镜像的时候,RUN 指令会真正的执行命令并产生对应的结果,然而CMD 指令并不会执行,它只是指定了预设的命令。

ENTRYPOINT

ENTRYPOINT 指令用来配置一个容器,其将作为可执行文件运行。

ENTRYPOINT 指令有两种形式:

exec形式,也是被优先推荐的形式:

css 复制代码
ENTRYPOINT ["executable", "param1", "param2"]

shell形式:

bash 复制代码
ENTRYPOINT command param1 param2

下面这个例子,命令将会运行一个nginx镜像的容器,监听在80端口

css 复制代码
$ docker run -i -t --rm -p 80:80 nginx

docker run 后面的所有参数会被添加到exec形式的ENTRYPOINT指令末尾,这样的话,我们就可以通过这种形式给入口点传递参数,比如 docker run -d 将会把 -d 参数传递给入口点。通过设置--entrypoint 标志 :docker run --entrypoint 可以重写ENTRYPOINT指令。

shell形式将忽略任何CMD指令。shell形式会将ENTRYPOINT作为/bin/sh -c的子命令启动,并且不传递信号。这意味着ENTRYPOINT指令中的可执行程序不会是容器中的PID 1进程,也不会接收Unix信号。在这种情况下,可执行程序不会从docker stop 接收SIGTERM信号。

Dockerfile中只有最后一个ENTRYPOINT指令才会生效。

Exec形式的例子

可以使用ENTRYPOINT的exec形式来设置固定的默认命令和参数,使用CMD的任意一种形式来设置会变化的其他默认值。

css 复制代码
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]

运行上面这个镜像(Dockerfile)的容器,结果如下:

shell 复制代码
$ docker run -it --rm --name=test test:v1.0 -H

top - 08:25:00 up  7:27,  0 users,  load average: 0.00, 0.01, 0.05
Threads:   1 total,   1 running,   0 sleeping,   0 stopped,   0 zombie
%Cpu(s):  0.1 us,  0.1 sy,  0.0 ni, 99.7 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem:   2056668 total,  1616832 used,   439836 free,    99352 buffers
KiB Swap:  1441840 total,        0 used,  1441840 free.  1324440 cached Mem

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
    1 root      20   0   19744   2336   2080 R  0.0  0.1   0:00.04 top

通过docker exec来查看运行的具体进程,如下:

sql 复制代码
$ docker exec -it test ps aux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  2.6  0.1  19752  2352 ?        Ss+  08:24   0:00 top -b -H
root         7  0.0  0.1  15572  2164 ?        R+   08:25   0:00 ps aux

你能使用docker stop test优雅的发送请求给top命令让其退出

下面这个Dockerfile通过ENTRYPOINT 来运行Apache在前台,即Apache进程是容器里的PID 1进程

sql 复制代码
FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

当你需要编写一个启动脚本来启动某个可执行程序的时候,为了确保最后的可执行程序能够接收到Unix信号,你可以使用linux的exec和gosu命令,如下:

bash 复制代码
#!/usr/bin/env bash
set -e

if [ "$1" = 'postgres' ]; then
    chown -R postgres "$PGDATA"

    if [ -z "$(ls -A "$PGDATA")" ]; then
        gosu postgres initdb
    fi

    exec gosu postgres "$@"
fi

exec "$@"

最后,如果你需要在退出的时候做一些额外的清理工作(或者是与其它容器通信),或者需要协调不少于一个的可执行程序,那么你需要确保ENTRYPOINT 的脚本接收Unix信号,传递信号,然后做一些处理,如下:

bash 复制代码
#!/bin/sh
# Note: I've written this using sh so it works in the busybox container too

# USE the trap if you need to also do manual cleanup after the service is stopped,
#     or need to start multiple services in the one container
trap "echo TRAPed signal" HUP INT QUIT TERM

# start service in background here
/usr/sbin/apachectl start

echo "[hit enter key to exit] or run 'docker stop <container>'"
read

# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stop

echo "exited $0"
bash 复制代码
$ docker exec -it test ps aux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.0   4448   692 ?        Ss+  00:42   0:00 /bin/sh /run.sh 123 cmd cmd2
root        19  0.0  0.2  71304  4440 ?        Ss   00:42   0:00 /usr/sbin/apache2 -k start
www-data    20  0.2  0.2 360468  6004 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
www-data    21  0.2  0.2 360468  6000 ?        Sl   00:42   0:00 /usr/sbin/apache2 -k start
root        81  0.0  0.1  15572  2140 ?        R+   00:44   0:00 ps aux

$ docker top test

PID                 USER                COMMAND
10035               root                {run.sh} /bin/sh /run.sh 123 cmd cmd2
10054               root                /usr/sbin/apache2 -k start
10055               33                  /usr/sbin/apache2 -k start
10056               33                  /usr/sbin/apache2 -k start

$ /usr/bin/time docker stop test

test
real	0m 0.27s
user	0m 0.03s
sys	0m 0.03s

Shell形式的例子

shell形式就是为ENTRYPOINT指定一个普通字符串,它将在/bin/sh -c中执行,具体表现就是会在字符串最前面加上/bin/sh -c。此形式将使用shell运算来替换shell环境变量,并将忽略任何CMD或docker run命令行参数。为了确保docker stop会正确地向任何长时间运行的ENTRYPOINT可执行程序发出信号,你需要用exec启动它:

css 复制代码
FROM ubuntu
ENTRYPOINT exec top -b

当运行这个镜像时,你将会看到 PID 1 进程是 top -b , 如下:

yaml 复制代码
$ docker run -it --rm --name test top

Mem: 1704520K used, 352148K free, 0K shrd, 0K buff, 140368121167873K cached
CPU:   5% usr   0% sys   0% nic  94% idle   0% io   0% irq   0% sirq
Load average: 0.08 0.03 0.05 2/98 6
  PID  PPID USER     STAT   VSZ %VSZ %CPU COMMAND
    1     0 root     R     3164   0%   0% top -b

这种情况下,使用 docker stop 可以让容器干净的退出。

bash 复制代码
$ /usr/bin/time docker stop test

test
real	0m 0.20s
user	0m 0.02s
sys	0m 0.04s

如果你忘记在 ENTRYPOINT指令的前面添加 exec ,如下:

css 复制代码
FROM ubuntu
ENTRYPOINT top -b
CMD -- --ignored-param1

运行上面这个镜像时,你会发现ENTRYPOINT 后面的指令并不是PID 1 进程:

yaml 复制代码
$ docker run -it --name test top --ignored-param2

top - 13:58:24 up 17 min,  0 users,  load average: 0.00, 0.00, 0.00
Tasks:   2 total,   1 running,   1 sleeping,   0 stopped,   0 zombie
%Cpu(s): 16.7 us, 33.3 sy,  0.0 ni, 50.0 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
MiB Mem :   1990.8 total,   1354.6 free,    231.4 used,    404.7 buff/cache
MiB Swap:   1024.0 total,   1024.0 free,      0.0 used.   1639.8 avail Mem

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND
    1 root      20   0    2612    604    536 S   0.0   0.0   0:00.02 sh
    6 root      20   0    5956   3188   2768 R   0.0   0.2   0:00.00 top

如果运行docker stop test ,容器将不会干净的退出,stop 命令将会在超时后强制发送SIGKILL 信号给容器。

sql 复制代码
$ docker exec -it test ps waux

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.4  0.0   2612   604 pts/0    Ss+  13:58   0:00 /bin/sh -c top -b --ignored-param2
root         6  0.0  0.1   5956  3188 pts/0    S+   13:58   0:00 top -b
root         7  0.0  0.1   5884  2816 pts/1    Rs+  13:58   0:00 ps waux

$ /usr/bin/time docker stop test

test
real	0m 10.19s
user	0m 0.04s
sys	0m 0.03s

理解 CMD 和 ENTRYPOINT 如何相互影响

CMD 和ENTRYPOINT 指令都定义了在运行容器时执行的命令,下面是几条规则:

  1. Dockerfile中至少要有一个CMD或者ENTRYPOINT指令
  2. 当把容器作为可执行程序运行时,应该设置ENTRYPOINT
  3. CMD应该用作定义ENTRYPOINT命令的默认参数或在容器中执行特定命令的一种方式。
  4. 当使用可选参数运行容器时,CMD 将被覆盖。

下面的表格总结了 ENTRYPOINT / 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 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

注意:如果CMD是在基本镜像定义的,设置ENTRYPOINT会将CMD重置为空值。在这种情况下,CMD必须在当前镜像中重新设置定义才会有一个值。

相关推荐
m0_748254881 小时前
docker离线安装及部署各类中间件(x86系统架构)
docker·中间件·系统架构
栀栀栀栀栀栀3 小时前
Docker 2025/2/24
运维·docker·容器
高hongyuan4 小时前
创建私人阿里云docker镜像仓库
阿里云·docker·云计算
wssswsss16 小时前
docker容器网络配置及常用操作
网络·docker·容器
青年vs阳光18 小时前
win10把c盘docker虚拟硬盘映射迁移到别的磁盘
运维·docker·容器
Abdullah al-Sa1 天前
Docker教程(喂饭级!)
c++·人工智能·docker·容器
web2u1 天前
Docker入门及基本概念
java·运维·服务器·spring·docker·容器
笑虾1 天前
Docker 自制镜像:Ubuntu 安装 samba+Webmin
ubuntu·docker·samba
元气满满的热码式1 天前
Docker实战-使用docker compose搭建博客
运维·docker·容器
哥是黑大帅1 天前
Docker基于Ollama本地部署大语言模型
python·docker·语言模型