Dockerfile里的ENTRYPOINT和CMD

文章目录

  • 环境
  • 总结
  • 讲解
    • (一)不指定ENTRYPOINT和CMD
    • (二)CMD
    • (三)ENTRYPOINT
    • (四)ENTRYPOINT和CMD的组合
  • 参考

环境

  • RHEL 9.3
  • Docker Community 24.0.7

总结

如果懒得看详细介绍,可以直接看总结:

  • ENTRYPOINTCMD 都可以单独使用,指定启动容器时所运行的命令以及参数。
  • 更常见的用法是把 ENTRYPOINTCMD 组合使用:
    • ENTRYPOINT 指定启动容器时所运行的命令和不变的参数。在启动容器时可以显式覆盖,但一般不这么做。
    • CMD 指定运行参数。在启动容器时可以显式覆盖。
  • ENTRYPOINTCMD 都强烈推荐使用“exec形式”。

例如:

ENTRYPOINT ["ping", "-c", "20"]
CMD ["localhost"]

讲解

ENTRYPOINTCMD 指定启动容器时所运行的命令以及参数。二者都可以单独使用,也可以把二者组合使用。接下来通过示例来看一下二者的用法。

(一)不指定ENTRYPOINT和CMD

创建 Dockerfile 文件如下:

FROM ubuntu:trusty

构建:

docker build -t kaidemo0 .

启动容器:

docker run kaidemo0

结果什么也没有发生:既没有报错,也没有任何输出。

通过 docker ps -a 查看容器:

➜  ~ docker ps -a
CONTAINER ID   IMAGE                    COMMAND                  CREATED          STATUS                      PORTS                      NAMES
f3aeb967fdd2   kaidemo0                 "/bin/bash"              49 seconds ago   Exited (0) 48 seconds ago                              affectionate_noyce

可以看到,在没有指定 ENTRYPOINTCMD 时,实际运行的命令是 /bin/bash

要想与容器交互,可以加上 -it 选项:

➜  ~ docker run -it kaidemo0
root@1389cac2f57e:/# ls
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
root@1389cac2f57e:/# ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 09:07 pts/0    00:00:00 /bin/bash
root          18       1  0 09:07 pts/0    00:00:00 ps -ef
root@1389cac2f57e:/# exit
exit

注:

  • -i :interactive,交互式的,接收用户输入
  • -t :tty,分配一个伪终端

也可在 docker run 时指定运行的命令,比如:

➜  ~ docker run kaidemo0 ping localhost
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.069 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.032 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.024 ms

在另一个命令行窗口,查看容器:

➜  test1 docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
ab8ed71eae08   kaidemo0         "ping localhost"         3 seconds ago   Up 3 seconds                              interesting_dijkstra

最后,在第一个窗口,按下“Ctrl + C”停止ping命令,同时退出容器。

总结:若不指定ENTRYPOINT和CMD,启动容器时,默认运行的命令是 /bin/bash 。要显式加上 -it 选项才能进入容器做事。也可以在启动容器时显式指定运行的命令。

(二)CMD

创建 Dockerfile 文件如下:

FROM ubuntu:trusty
CMD ping localhost

构建:

docker build -t kaidemo1 .

启动容器:

docker run kaidemo1

由于 CMD 指令指定了 ping localhost 命令,容器启动时会自动运行该命令,如下:

➜  ~ docker run kaidemo1      
PING localhost (127.0.0.1) 56(84) bytes of data.
64 bytes from localhost (127.0.0.1): icmp_seq=1 ttl=64 time=0.145 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.199 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.065 ms
^C64 bytes from localhost (127.0.0.1): icmp_seq=4 ttl=64 time=0.084 ms
64 bytes from localhost (127.0.0.1): icmp_seq=5 ttl=64 time=0.030 ms
......

注意:按下 “Ctrl + C” 无法停止ping命令,原因稍后解释。

在另一个命令行窗口,查看docker容器:

➜  ~ docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
25e553984c8a   kaidemo1         "/bin/sh -c 'ping lo…"   7 seconds ago   Up 7 seconds                              sleepy_lalande

同样,尝试停止容器也无效:

➜  ~ docker stop 25e553984c8a
^C

docker stop 命令hang住了,只能按“Ctrl + C”中止。

最后,运行 docker rm -f 强制删除容器:

➜  ~ docker rm -f 25e553984c8a
25e553984c8a

原因解释:通过刚才的 docker ps 可以看到,实际运行的命令是 /bin/sh -c 'ping localhost'

  • docker run kaidemo0 ping localhost 时,在容器里查看进程:
➜  docker docker exec  ps -ef       
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 02:08 ?        00:00:00 ping localhost
root           7       0  0 02:09 ?        00:00:00 ps -ef
  • docker run kaidemo1 时,在容器里查看进程:
➜  docker docker exec  ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 02:12 ?        00:00:00 /bin/sh -c ping localhost
root           7       1  0 02:12 ?        00:00:00 ping localhost
root           8       0  0 02:12 ?        00:00:00 ps -ef
  • docker run -it kaidemo1 时,在容器里查看进程:
➜  docker docker exec  ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 02:16 pts/0    00:00:00 /bin/sh -c ping localhost
root           8       1  0 02:16 pts/0    00:00:00 ping localhost
root           9       0  0 02:16 ?        00:00:00 ps -ef

注意PID为1的进程,分别为:

  • ping localhost
  • /bin/sh -c ping localhost (没有TTY)
  • /bin/sh -c ping localhost (有TTY)

当我们在容器外部发送POSIX信号(比如“Ctrl + C”)到容器里,对于 docker run kaidemo1/bin/sh 命令不会转发消息给实际运行的ping命令,所以无法停止ping。

之所以出现这样的问题,是因为我们在Dockerfile里使用了“shell形式”,即:

CMD ping localhost

Docker会把该命令作为shell的子命令(即 /bin/sh -c xxxxx ),这就带来了问题。

为了避免这个问题,可以使用“exec形式”。

创建 Dockerfile 文件如下:

FROM ubuntu:trusty
CMD ["ping", "localhost"]

构建:

docker build -t kaidemo2 .

启动容器:

docker run kaidemo2

在另一个命令行窗口,查看容器:

➜  docker docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
a6e67096ca3a   kaidemo2         "ping localhost"         6 seconds ago   Up 4 seconds                              stupefied_euler

查看容器里的进程:

➜  ~ docker exec  ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 02:33 ?        00:00:00 ping localhost
root           7       0  0 02:33 ?        00:00:00 ps -ef

可见,应尽量使用“exec形式”,以避免子shell的问题。

可以在启动容器时,显式指定要运行的命令,覆盖 CMD 的命令。例如:

➜  ~ docker run kaidemo2 ls -l 
total 8
drwxr-xr-x.   2 root root 4096 Dec 17  2019 bin
drwxr-xr-x.   2 root root    6 Apr 10  2014 boot
drwxr-xr-x.   5 root root  340 Jan  6 02:39 dev
......

总结:假设没有指定 ENTRYPOINT 指令,则 CMD 指令可以指定启动启动时要运行的命令。强烈推荐使用“exec形式”,以避免子shell的问题。可以在启动容器时,显式指定要运行的命令,覆盖 CMD 指定的命令。

(三)ENTRYPOINT

创建 Dockerfile 文件如下:

FROM ubuntu:trusty
ENTRYPOINT ping localhost

构建:

docker build -t kaidemo3 .

启动容器,可以发现, ENTRYPOINTCMD 的表现几乎一模一样。

因此,应尽量使用“exec形式”:

ENTRYPOINT ["ping", "localhost"]

另外,在启动容器时,覆盖 ENTRYPOINT 的方法和 CMD 不同,例如:

➜  ~ docker run kaidemo4 ls       
ping: unknown host ls

可见,运行的还是ping命令,只不过参数变成了 ls 。这是因为覆盖的是 CMD 指令,而不是 ENTRYPOINT 指令。关于二者的组合,稍后会有介绍。

要想覆盖 ENTRYPOINT 指令,可以使用 --entrypoint 选项:

➜  ~ docker run --entrypoint ls kaidemo4 
bin
boot
dev
......

但是,不推荐使用这种做法,原因稍后会有介绍。

总结: ENTRYPOINT 的表现几乎和 CMD 完全一致。假设没有指定 CMD 指令,则 ENTRYPOINT 指令可以指定启动启动时要运行的命令。强烈推荐使用“exec形式”,以避免子shell的问题。可以在启动容器时,通过 --entrypoint 选项显式指定要运行的命令,覆盖 ENTRYPOINT 指定的命令,但一般不这么做。

(四)ENTRYPOINT和CMD的组合

前面说了这么多, ENTRYPOINTCMD 貌似也没什么本质的区别,那为什么Dockerfile里要有两个相似的指令呢?

实际上,二者的设计理念不一样,典型的用法是把它们组合起来使用:

  • ENTRYPOINT :指定默认的启动程序
  • CMD :指定默认的运行参数

创建 Dockerfile 文件如下:

FROM ubuntu:trusty
ENTRYPOINT ["ping", "-c", "20"]
CMD ["localhost"]

注: -cping 命令的选项,c表示count, -c 20 就是ping 20次。

构建:

docker build -t kaidemo5 .

启动容器:

docker run kaidemo5

在另一个命令行窗口,查看容器:

➜  ~ docker ps                      
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
135c6466f888   kaidemo5         "ping -c 20 localhost"   3 seconds ago   Up 2 seconds                              upbeat_vaughan

查看容器里的进程:

➜  ~ docker exec  ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 03:16 ?        00:00:00 ping -c 20 localhost
root           7       0  0 03:17 ?        00:00:00 ps -ef

可见,实际运行的命令,是把 ENTRYPOINT 的内容和 CMD 的内容组合起来了

  • ENTRYPOINT["ping", "-c", "20"]
  • CMD["localhost"]

最终命令是: ping -c 20 localhost

如果我们想ping另外一台主机,只需在 docker run 时覆盖 CMD 的值:

docker run kaidemo5 127.0.0.1

在另一个命令行窗口,查看容器:

➜  ~ docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
b438242b4e84   kaidemo5         "ping -c 20 127.0.0.1"   3 seconds ago   Up 2 seconds                              intelligent_torvalds

查看容器里的进程:

➜  ~ docker exec  ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 03:25 ?        00:00:00 ping -c 20 127.0.0.1
root           8       0  0 03:25 ?        00:00:00 ps -ef

可见,通过覆盖 CMD 的值,就可以改变运行的参数。

在上面的例子里, -c 20 是写死在 ENTRYPOINT 里的,如果想要用户可以配置ping的次数,则应放在 CMD 里,以便用户在 docker run 时覆盖。

创建 Dockerfile 文件如下:

FROM ubuntu:trusty
ENTRYPOINT ["ping"]
CMD ["-c", "20", "localhost"]

构建:

docker build -t kaidemo6 .

启动容器:

docker run kaidemo6

效果和 docker run kaidemo5 是一样的,运行的都是 ping -c 20 localhost

启动容器时替换 CMD 参数:

docker run kaidemo6 -c 30 127.0.0.1

在另一个命令行窗口查看容器:

➜  ~ docker ps                      
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS         PORTS                      NAMES
7885692cb508   kaidemo6         "ping -c 30 127.0.0.1"   4 seconds ago   Up 3 seconds                              wizardly_shtern

查看容器里的进程:

➜  ~ docker exec  ps -ef
UID          PID    PPID  C STIME TTY          TIME CMD
root           1       0  0 03:38 ?        00:00:00 ping -c 30 127.0.0.1
root           7       0  0 03:38 ?        00:00:00 ps -ef

可见,实际运行的是 ping -c 30 127.0.0.1

另外要注意的是,二者一定都要使用“exec形式”。如果使用“shell形式”,则会转为 /bin/sh xxx ,二者组合后,会造成混乱。

CMD localhost CMD [“localhost”]
ENTRYPOINT ping -c 10 /bin/sh -c ‘ping -c 10’ /bin/sh -c localhost /bin/sh -c ‘ping -c 3’ localhost
ENTRYPOINT [“ping”,“-c”,“10”] ping -c 10 /bin/sh -c localhost ping -c 3 localhost

可见,只有 二者都是“exec形式”时,才能组合出期望的结果。

总结: ENTRYPOINTCMD 组合使用:把运行命令和不变的参数放到 ENTRYPOINT 里,把可变的参数放到 CMD 里,以便在 docker run 时替换。二者都要使用“exec形式”。可用 --entrypoint 覆盖命令,但一般不这么做,因为 ENTRYPOINT 代表的是容器用途,一般不会改变,可变的是运行参数。

参考

  • https://spacelift.io/blog/docker-entrypoint-vs-cmd (里面有些内容和我实际测试结果不同,可能是Docker版本不同?)
  • https://zhuanlan.zhihu.com/p/30555962 (里面有些内容和我实际测试结果不同,可能是Docker版本不同?)
  • https://docs.docker.com/engine/reference/builder

你可能感兴趣的:(docker,docker)