Shell格式和Exec格式
在Dockerfile中,RUN、CMD和ENTRYPOINT指令都可以使用两种格式:Shell格式和Exec格式。
-
exec 格式:
INSTRUCTION ["executable","param1","param2"]
-
shell 格式:
INSTRUCTION command param1 param2
exec格式使得避免使用shell字符串处理成为可能,并且可以使用特定的命令shell或任何其他可执行文件来调用命令。它使用JSON数组语法,数组中的每个元素都是一个命令、标志或参数。
shell格式更加灵活,强调易用性、灵活性和可读性。
在使用shell格式时系统会自动选择一个命令shell来执行指令,而在使用exec格式时,需要明确指定使用哪个命令shell或其他可执行文件来执行命令。换句话说,shell格式会自动选择执行环境,而exec格式需要手动指定执行环境。
Exec 格式
Exec格式被解析为一个JSON数组,这意味着你必须使用双引号(")而不是单引号(')来包围单词。
ENTRYPOINT ["/bin/bash", "-c", "echo hello"]
Exec格式最适合用于指定ENTRYPOINT
指令,并结合CMD
来设置可以在运行时覆盖的默认参数。
变量替换
使用Exec格式不会自动调用命令shell。这意味着不会发生常规的shell处理,例如变量替换。例如,RUN [ "echo", "$HOME" ]
不会处理$HOME
的变量替换。
如果你想进行shell处理,可以使用Shell格式,或者直接在Exec格式中执行shell,例如:RUN [ "sh", "-c", "echo $HOME" ]
。当使用Exec格式并直接执行shell时,就像Shell格式一样,是由shell进行环境变量替换,而不是构建器。
反斜杠
在Exec格式中,你必须对反斜杠进行转义。这在Windows上特别重要,因为在Windows中反斜杠是路径分隔符。以下行将因未被视为有效的JSON而被视为Shell格式,并以意外的方式失败:
RUN ["c:\windows\system32\tasklist.exe"]
正确的语法示例是:
RUN ["c:\\windows\\system32\\tasklist.exe"]
Shell 格式
与Exec格式不同,使用Shell格式的指令总是使用命令shell。Shell格式不使用JSON数组格式,而是一个常规的字符串。Shell格式字符串允许你使用转义字符(默认是反斜杠)来换行,将单个指令延续到下一行。这使得它更容易用于更长的命令,因为它允许你将它们分割成多行。例如,考虑以下两行:
RUN source $HOME/.bashrc && \
echo $HOME
它们等同于以下单行:
RUN source $HOME/.bashrc && echo $HOME
你可以使用heredocs与Shell格式来拆分命令:
RUN <<EOF
source $HOME/.bashrc && \
echo $HOME
EOF
使用不同的shell
你可以使用SHELL
命令更改默认shell。例如:
SHELL ["/bin/bash", "-c"]
RUN echo hello
CMD
CMD
指令设置了在从镜像运行容器时要执行的命令。
你可以使用shell格式或exec格式来指定CMD指令:
-
CMD ["executable","param1","param2"]
(exec格式) -
CMD ["param1","param2"]
(exec格式,作为ENTRYPOINT的默认参数) -
CMD command param1 param2
(shell格式)
Dockerfile中只能有一个CMD
指令。如果列出多个CMD,只有最后一个会生效。
CMD
的目的是为正在执行的容器提供默认值。这些默认值可以包括一个可执行文件,也可以省略可执行文件,此时你必须同时指定ENTRYPOINT
指令。
如果你希望容器每次运行时都执行相同的可执行文件,那么你应该考虑结合使用ENTRYPOINT
和CMD
。如果用户在docker run
中指定了参数,它们将覆盖在CMD
中指定的默认值,但仍会使用默认的ENTRYPOINT
。
如果CMD
用于为ENTRYPOINT
指令提供默认参数,则应该以exec格式
指定CMD
和ENTRYPOINT
指令。
注意
不要将RUN与CMD混淆。RUN实际上运行一个命令并提交结果;CMD
不会在构建时执行任何操作,而是为镜像指定预期的命令。
ENTRYPOINT
ENTRYPOINT
允许您配置一个将作为可执行文件运行的容器。
ENTRYPOINT
有两种可能的格式:
- 首选的 exec 格式:
ENTRYPOINT ["executable", "param1", "param2"]
- shell 格式:
ENTRYPOINT command param1 param2
以下命令从带有默认内容、监听端口 80 的 nginx 启动容器:
docker run -i -t --rm -p 80:80 nginx
对于 docker run <image>
的命令行参数将附加在 exec 格式的 ENTRYPOINT
中的所有元素之后,并将覆盖使用 CMD
指定的所有元素。这允许将参数传递给入口点,即 docker run <image> -d
将向入口点传递 -d
参数。您可以使用 docker run --entrypoint
标志覆盖 ENTRYPOINT
指令。
ENTRYPOINT
的 shell 格式阻止使用任何 CMD
命令行参数。它还将您的 ENTRYPOINT
作为 /bin/sh -c
的子命令启动,不会传递信号。这意味着可执行文件不会成为容器的 PID 1
,并且不会接收 Unix 信号。在这种情况下,您的可执行文件不会从 docker stop <container>
接收 SIGTERM
信号。
Dockerfile 中的最后一个 ENTRYPOINT 指令将生效。
Exec 格式的 ENTRYPOINT 示例
您可以使用 exec 格式的 ENTRYPOINT
来设置相对稳定的默认命令和参数,然后使用 CMD
的任一格式来设置更有可能被更改的其他默认值。
FROM ubuntu
ENTRYPOINT ["top", "-b"]
CMD ["-c"]
当您运行容器时,您可以看到top是唯一的进程:
docker run -it --rm --name test top -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:
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(即作为PID 1
):
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"]
如果您需要为单个可执行文件编写启动脚本,您可以使用exec
和gosu
命令确保最终可执行文件接收Unix信号:
#!/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信号,传递它们,然后执行更多工作:
#!/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"
如果您使用docker run -it --rm -p 80:80 --name test apache
运行此映像,则可以使用docker exec
或docker top
检查容器的进程,然后要求脚本停止Apache:
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 格式 ENTRYPOINT 示例
您可以为ENTRYPOINT
指定一个纯字符串,它将在 /bin/sh-c
中执行。此表单将使用shell处理来替换shell环境变量,并将忽略任何CMD
或docker run
命令行参数。为了确保docker stop
将正确地发出任何长时间运行的ENTRYPOINT
可执行文件的信号,您需要记住以exec开头:
FROM ubuntu
ENTRYPOINT exec top -b
当您运行此映像时,您将看到单个PID 1
进程:
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上干净地退出:
/usr/bin/time docker stop test
test
real 0m 0.20s
user 0m 0.02s
sys 0m 0.04s
如果您忘记将exec
添加到ENTRYPOINT
的开头:
FROM ubuntu
ENTRYPOINT top -b
CMD -- --ignored-param1
然后,您可以运行它(为下一步命名):
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
您可以从top
的输出中看到指定的ENTRYPOINT
不是PID 1
。
如果您随后运行docker stop test
,容器将不会干净地退出-stop
命令将在超时后强制发送SIGKILL
:
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
命令或在容器中执行 ad-hoc 命令的默认参数的一种方式。 - 使用替代参数运行容器时,
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 |
Note
: 如果从基本图像定义了CMD
,则设置ENTRYPOINT
会将CMD
重置为空值。在这种情况下,必须在当前图像中定义CMD
才能具有值。