Dockerfile里的ENTRYPOINT和CMD
文章目录
- 环境
- 总结
- 讲解
- (一)不指定ENTRYPOINT和CMD
- (二)CMD
- (三)ENTRYPOINT
- (四)ENTRYPOINT和CMD的组合
- 参考
环境
- RHEL 9.3
- Docker Community 24.0.7
总结
如果懒得看详细介绍,可以直接看总结:
ENTRYPOINT
和CMD
都可以单独使用,指定启动容器时所运行的命令以及参数。- 更常见的用法是把
ENTRYPOINT
和CMD
组合使用:ENTRYPOINT
指定启动容器时所运行的命令和不变的参数。在启动容器时可以显式覆盖,但一般不这么做。CMD
指定运行参数。在启动容器时可以显式覆盖。
ENTRYPOINT
和CMD
都强烈推荐使用“exec形式”。
例如:
ENTRYPOINT ["ping", "-c", "20"]
CMD ["localhost"]
讲解
ENTRYPOINT
和 CMD
指定启动容器时所运行的命令以及参数。二者都可以单独使用,也可以把二者组合使用。接下来通过示例来看一下二者的用法。
(一)不指定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
可以看到,在没有指定 ENTRYPOINT
和 CMD
时,实际运行的命令是 /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 <container ID> 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 <container ID> 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 <container ID> 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 <container ID> 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 .
启动容器,可以发现, ENTRYPOINT
和 CMD
的表现几乎一模一样。
因此,应尽量使用“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的组合
前面说了这么多, ENTRYPOINT
和 CMD
貌似也没什么本质的区别,那为什么Dockerfile里要有两个相似的指令呢?
实际上,二者的设计理念不一样,典型的用法是把它们组合起来使用:
ENTRYPOINT
:指定默认的启动程序CMD
:指定默认的运行参数
创建 Dockerfile
文件如下:
FROM ubuntu:trusty
ENTRYPOINT ["ping", "-c", "20"]
CMD ["localhost"]
注: -c
是 ping
命令的选项,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 <container ID> 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 <container ID> 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 <container ID> 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形式”时,才能组合出期望的结果。
总结: ENTRYPOINT
和 CMD
组合使用:把运行命令和不变的参数放到 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