以下内容翻译自docker官方文档,谨此作为学习记录,方便以后查阅,分享给大家。
CMD
CMD指令设置从一个镜像启动容器的时候被执行的命令,你可以通过shell形式或者exec形式设置CMD 指令,如下:
- CMD ["executable","param1","param2"] (exec 形式)
- CMD ["param1","param2"] (exec 形式, 作为 ENTRYPOINT 指令的默认参数)
- 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 指令都定义了在运行容器时执行的命令,下面是几条规则:
- Dockerfile中至少要有一个CMD或者ENTRYPOINT指令
- 当把容器作为可执行程序运行时,应该设置ENTRYPOINT
- CMD应该用作定义ENTRYPOINT命令的默认参数或在容器中执行特定命令的一种方式。
- 当使用可选参数运行容器时,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必须在当前镜像中重新设置定义才会有一个值。