簡介
在查看Dockerfile可用指令(instructions)時楼熄,會發(fā)看起來有一些“重復(fù)”指令(即不同指令實現(xiàn)的功能幾乎相同)腺占。之前我們講解了COPY和ADD的區(qū)別橡娄,本章會分析CMD與ENTRTYPOINT的不同。
ENTRYPOINT與CMD都可以對iamge配置啟動命令稠鼻。但兩者之間還是有一些細微的區(qū)別煤蚌。多數(shù)情況下需要用戶在二者中選擇其一使用,但也可以共同使用兩者鸿捧。下面將具體分析二者不同的使用場景:
ENTRYPOINT or CMD
最終,ENTRYPOINT與CMD都提供了一個方法疙渣,讓用戶指定容器默認啟動命令。事實上堆巧,如果你希望你的image是可執(zhí)行的(啟動docker run時不額外指定啟動命令就可以運行)妄荔,那么你必須在Dockerfile中使用ENTRYPOINT或CMD
嘗試運行一個沒有使用ENTRYPOINT或CMD指令的image,啟動時會報錯:
$ docker run alpine
FATA[0000] Error response from daemon: No command specified
你能在Docker Hub上找到的大多數(shù)linux版本基礎(chǔ)鏡像都使用了/bin/sh
或/bin/bash
這樣的shell命令來作為CMD啟動命令谍肤。這意味著啦租,任何人運行這些image時,都會默認進入到交互式shell界面中(假設(shè)運行時指定了-t/-i參數(shù))
這對通用的基礎(chǔ)鏡像是十分方便的荒揣,但是當(dāng)你希望運行自己的image時(即非通用基礎(chǔ)iamge時)篷角,更多時候需要指定一個更具體的可執(zhí)行文件或命令來作為CMD或ENTRYPOINT參數(shù)。
Overrides
在Dockerfile中指定的ENTRYPOINT或CMD為你的image指定默認啟動命令系任。并且恳蹲,用戶可以選擇在容器運行時重寫(overrides)這些值中的任何一個虐块。
例如,假設(shè)我們有以下Dockerfile:
FROM ubuntu:trusty
CMD ping localhost
如果我們構(gòu)建該image 嘉蕾,在運行是會看到如下輸出:
$ docker run -t demo
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.051 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.038 ms
^C
--- localhost ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.026/0.032/0.039/0.008 ms
你可以看到贺奠,在容器啟動時,自動運行了ping命令错忱。然而儡率,在啟動容器時,我們可以在image名稱后面添加一個參數(shù)來重寫默認CMD指令:
$ docker run demo hostname
6c1573c0d4c0
在上述例子中以清,hostname命令代替了ping命令運行
默認ENTRYPOINT指令也可以被類似的方式重寫儿普,不過需要使用--entrypoint參數(shù)
$ docker run --entrypoint hostname demo
CMD的使用場景
考慮到重寫CMD指令是非常簡單的,所以在希望用戶使用該image創(chuàng)建容器時擁有更高的靈活性掷倔,可以更方便的指定任何自己想要的啟動命令時箕肃,更推薦CMD指令。例如今魔,你有一個通用的Ruby Image勺像,默認情況下將啟動一個交互式的irb會話(CMD irb),但你也想給用戶一個選項來運行任意的Ruby腳本(docker運行Ruby Ruby -e 'puts "Hello"')错森。
ENTRYPOINT的使用場景
相反吟宦,ENTRYPOINT指令適合用于:希望容器最終運行時所執(zhí)行的命令與Dockerfile內(nèi)配置的命令相同的場景下。也就是說涩维,不希望用戶重寫image啟動命令
通常使用Docker作為指定可執(zhí)行文件的容器是很方便的殃姓。假設(shè)您有一個Python腳本的實用程序,您需要發(fā)布它瓦阐,但又不想讓安裝正確的解釋器版本和依賴項給最終用戶帶來負擔(dān)蜗侈。你可以配置好解釋器版本與依賴后,通過ENTRYPOINT指定可執(zhí)行文件睡蟋。用戶便可以使用docker運行你的image踏幻,它的行為就像直接運行你的腳本,但又不用考慮依賴項戳杀、啟動命令參數(shù)等信息该面,直接運行即可。
當(dāng)然信卡,使用CMD指令可以實現(xiàn)同樣的功能隔缀,但使用了ENTRYPOINT相當(dāng)于給用戶傳遞了一個強烈的信息:這個容器只為運行這一個程序而存在,盡量不要修改容器啟動命令來另作他用傍菇。
將ENTRYPOINT與CMD組合使用時猾瘸,ENTRYPOINT的效用將會更清楚,但我們在后文討論這種用法。
Shell vs. Exec
ENTRYPOINT與CMD指令都支持兩種不同的參數(shù)格式:Shell格式與Exec格式牵触,在上面的例子中淮悼,我們使用了shell格式:
CMD executable param1 param2
Shell
當(dāng)使用Shell格式時,容器啟動時會使用/bin/sh -c
來執(zhí)行指定的可執(zhí)行/二進制/文件荒吏。容器啟動后敛惊,運行docker ps
就可以清楚看到:
$ docker run -d demo
15bfcddb11b5cde0e230246f45ba6eeb1e6f56edb38a91626ab9c478408cb615
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED
15bfcddb4312 demo:latest "/bin/sh -c 'ping localhost'" 2 seconds ago
我們再次運行了“demo”容器,可以看到容器啟動命令為:/bin/sh -c 'ping localhost
這看起來沒什么問題绰更,命令也正常被運行瞧挤。但是當(dāng)我們使用shell格式來傳遞ENTRYPOINT或CMD參數(shù)時還是會有一些微妙的小問題。現(xiàn)在我們進入到容器內(nèi)儡湾,查看容器內(nèi)的進程就會看到如下信息:
$ docker exec 15bfcddb ps -f
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 20:14 ? 00:00:00 /bin/sh -c ping localhost
root 9 1 0 20:14 ? 00:00:00 ping localhost
root 49 0 0 20:15 ? 00:00:00 ps -f
請注意特恬,PID為1的進程并不是我們所期望的ping
命令,而是/bin/sh
徐钠。這會導(dǎo)致當(dāng)我們需要向容器發(fā)送任何類型的POSIX信號癌刽,/bin/sh
不會將信號轉(zhuǎn)發(fā)給子進程(詳細原理可參考: Gracefully Stopping Docker Containers)。
除了PID1的問題外尝丐,可能很多輕量化的image并不會包含任何shell程序显拜。當(dāng)容器啟動時,并不會檢查容器內(nèi)是否有shell程序可用爹袁。如果你的鏡像并不包含/bin/sh
命令远荠,那么顯然容器會啟動失敗。
Exec
所以更好的選擇是使用Exec格式來傳遞ENTRYPOINT與CMD參數(shù)失息,例如:
CMD ["executable","param1","param2"]
注意譬淳,CMD指令后的參數(shù)被格式化成了JSON數(shù)組
當(dāng)使用Exec格式的CMD指令后,容器啟動時該命令將不會通過Shell的方式運行盹兢,而是直接執(zhí)行邻梆。
讓我們將上述的Dockerfile改為Exec格式看看實際效果:
FROM ubuntu:trusty
CMD ["/bin/ping","localhost"]
重新構(gòu)建image,查看容器啟動命令:
$ docker build -t demo .
[truncated]
$ docker run -d demo
90cd472887807467d699b55efaf2ee5c4c79eb74ed7849fc4d2dbfea31dce441
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED
90cd47288780 demo:latest "/bin/ping localhost" 4 seconds ago
可以看到绎秒,ping
命令在沒有shell介入的情況下直接運行浦妄。并且是容器內(nèi)的PID1進程
所以無論使用ENTRYPOINT或CMD指令,都推薦使用Exec格式替裆。因為它可以使你的應(yīng)用清晰的運行在容器的PID1進程上校辩。
ENTRYPOINT and CMD
到目前為止,我們討論了如何使用ENTRYPOINT或CMD指令來指定image默認啟動命令辆童。然而,在一些情況下惠赫,我們可以同時使用ENTRYPOINT與CMD把鉴。
將ENTRYPOINT與CMD組合使用依舊可以指定image默認啟動命令,同時也了指定image啟動命令的默認參數(shù)。同時該參數(shù)可以被方便的重寫庭砍。讓我們看下述例子:
FROM ubuntu:trusty
ENTRYPOINT ["/bin/ping","-c","3"]
CMD ["localhost"]
重新構(gòu)建image并不附加任何參數(shù)啟動容器:
$ docker build -t ping .
[truncated]
$ docker run ping
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.025 ms
64 bytes from localhost (127.0.0.1): icmp_seq=2 ttl=64 time=0.038 ms
64 bytes from localhost (127.0.0.1): icmp_seq=3 ttl=64 time=0.051 ms
--- localhost ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.025/0.038/0.051/0.010 ms
$ docker ps -l
CONTAINER ID IMAGE COMMAND CREATED
82df66a2a9f1 ping:latest "/bin/ping -c 3 localhost" 6 seconds ago
請注意场晶,啟動命令為Dockerfile中ENTRYPOINT與CMD值的組合。當(dāng)同時使用ENTRYPOINT與CMD指令時怠缸,CMD指令的值會被追加到ENTRYPOINT值的后面诗轻,組合成為一條啟動命令。且CMD指令仍然保留容易被重寫的特性揭北,用戶可以很方便的通過在docker run
后添加參數(shù)來重寫CMD值(即啟動命令的參數(shù))扳炬。下例展示了如何修改ping
命令的默認參數(shù):
$ docker run ping docker.io
PING docker.io (162.242.195.84) 56(84) bytes of data.
64 bytes from 162.242.195.84: icmp_seq=1 ttl=61 time=76.7 ms
64 bytes from 162.242.195.84: icmp_seq=2 ttl=61 time=81.5 ms
64 bytes from 162.242.195.84: icmp_seq=3 ttl=61 time=77.8 ms
--- docker.io ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2003ms
rtt min/avg/max/mdev = 76.722/78.695/81.533/2.057 ms
$ docker ps -l --no-trunc
CONTAINER ID IMAGE COMMAND CREATED
0d739d5ea4e5 ping:latest "/bin/ping -c 3 docker.io" 51 seconds ago
現(xiàn)在,運行image就像運行一個普通可執(zhí)行文件(命令)一樣搔体,指定要執(zhí)行的可執(zhí)行文件(image)恨樟,并在其后指定相應(yīng)的參數(shù)。
請注意疚俱,作為ENTRYPOINT的一部分包含的-c 3參數(shù)實際上成為了ping命令的“硬編碼”參數(shù)劝术。它包含在image的每次調(diào)用中,重寫CMD參數(shù)并不會影響ENTRYPOINT中的參數(shù)呆奕。
Always Exec
當(dāng)同時使用ENTRYPOINT與CMD养晋,請注意必須使用Exec格式來傳遞參數(shù)。你會發(fā)現(xiàn)梁钾,使用Shell格式或混合使用绳泉,將永遠不會得到你想要的效果:
Dockerfile Command
ENTRYPOINT /bin/ping -c 3
CMD localhost /bin/sh -c '/bin/ping -c 3' /bin/sh -c localhost
ENTRYPOINT ["/bin/ping","-c","3"]
CMD localhost /bin/ping -c 3 /bin/sh -c localhost
ENTRYPOINT /bin/ping -c 3
CMD ["localhost"]" /bin/sh -c '/bin/ping -c 3' localhost
ENTRYPOINT ["/bin/ping","-c","3"]
CMD ["localhost"] /bin/ping -c 3 localhost
上述例子中,只有當(dāng)同時使用Exec格式的ENTRYPOINT與CMD時陈轿,才能實現(xiàn)我們想要的功能圈纺。
總結(jié)
ENTRYPOINT與CMD指令都可以幫助你在Dockerfile中配置容器啟動命令。但二者各對應(yīng)了不同的應(yīng)用場景麦射,實際使用中還需挑選一個合適的指令蛾娶。并且二者并不是互斥了,在某些場景下同時使用兩個指令也是必須的潜秋。但無論如何使用蛔琅,請忘記Shell格式,在任何情況下Exec格式都一定是正確的選擇峻呛。
參考:https://www.ctl.io/developers/blog/post/dockerfile-entrypoint-vs-cmd/