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 -Htop - 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 MemPID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND1 root 20 0 19744 2336 2080 R 0.0 0.1 0:00.04 top
要進一步檢查結果,您可以使用docker exec:
docker exec -it test ps auxUSER 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 -eif [ "$1" = 'postgres' ]; thenchown -R postgres "$PGDATA"if [ -z "$(ls -A "$PGDATA")" ]; thengosu postgres initdbfiexec gosu postgres "$@"
fiexec "$@"
最后,如果您需要在關閉時進行一些額外的清理(或與其他容器通信),或者協調多個可執行文件,您可能需要確保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 startecho "[hit enter key to exit] or run 'docker stop <container>'"
read# stop service and clean up here
echo "stopping apache"
/usr/sbin/apachectl stopecho "exited $0"
如果您使用docker run -it --rm -p 80:80 --name test apache
運行此映像,則可以使用docker exec
或docker top
檢查容器的進程,然后要求腳本停止Apache:
docker exec -it test ps auxUSER 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 auxdocker top testPID 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 testtest
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 topMem: 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 6PID PPID USER STAT VSZ %VSZ %CPU COMMAND1 0 root R 3164 0% 0% top -b
在docker stop上干凈地退出:
/usr/bin/time docker stop testtest
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-param2top - 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 MemPID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND1 root 20 0 2612 604 536 S 0.0 0.0 0:00.02 sh6 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 wauxUSER 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 testtest
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
才能具有值。