ENTRYPOINT 的格式和 RUN 指令格式一樣,分為 exec 格式和 shell 格式。
ENTRYPOINT 的目的和 CMD 一樣,都是在指定容器啟動程序及參數。
ENTRYPOINT 在運行時也可以替代,不過比 CMD 要略顯繁瑣,需要通過docker run 的參數 --entrypoint 來指定。
當指定了 ENTRYPOINT 后, CMD 的含義就發生了改變,不再是直接的運行其命令,而是將 CMD 的內容作為參數傳給 ENTRYPOINT 指令,換句話說實際執行時,將變為:
<ENTRYPOINT> "<CMD>"
那么有了 CMD 后,為什么還要有 ENTRYPOINT 呢?這種
<ENTRYPOINT> "<CMD>"
有什么好處么?讓我們來看幾個場景。
場景一:讓鏡像變成像命令一樣使用
假設我們需要一個得知自己當前公網 IP 的鏡像,那么可以先用 CMD 來實現:
FROM ubuntu:16.04 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* CMD [ "curl", "-s", "http://ip.cn" ]
假如我們使用 docker build -t myip . 來構建鏡像的話,如果我們需要查詢當前公網 IP,只需要執行:
$ docker run myip
當前 IP:61.148.226.66 來自:北京市 聯通
嗯,這么看起來好像可以直接把鏡像當做命令使用了,不過命令總有參數,如果我們希望加參數呢?
比如從上面的 CMD 中可以看到實質的命令是 curl ,那么如果我們希望顯示 HTTP 頭信息,就需要加上 -i 參數。那么我們可以直接加 -i
參數給 docker run myip 么?
$ docker run myip -i docker: Error response from daemon: invalid header field value " oci runtime error: container_linux.go:247: starting container pr ocess caused \"exec: \\\"-i\\\": executable file not found in $P ATH\"\n".
我們可以看到可執行文件找不到的報錯, executable file not found 。之前我們說過,跟在鏡像名后面的是 command ,運行時會替換 CMD 的默認值。因此
這里的 -i 替換了原來的 CMD ,而不是添加在原來的 curl -s http://ip.cn 后面。而 -i 根本不是命令,所以自然找不到。
那么如果我們希望加入 -i 這參數,我們就必須重新完整的輸入這個命令:
$ docker run myip curl -s http://ip.cn -i
這顯然不是很好的解決方案,而使用 ENTRYPOINT 就可以解決這個問題。現在我們重新用 ENTRYPOINT 來實現這個鏡像:
FROM ubuntu:16.04 RUN apt-get update \ && apt-get install -y curl \ && rm -rf /var/lib/apt/lists/* ENTRYPOINT [ "curl", "-s", "http://ip.cn" ]
這次我們再來嘗試直接使用 docker run myip -i :
$ docker run myip
當前 IP:61.148.226.66 來自:北京市 聯通
$ docker run myip -i
HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Tue, 22 Nov 2016 05:12:40 GMT
Content-Type: text/html; charset=UTF-8
Vary: Accept-Encoding
X-Powered-By: PHP/5.6.24-1~dotdeb+7.1
X-Cache: MISS from cache-2
X-Cache-Lookup: MISS from cache-2:80
X-Cache: MISS from proxy-2_6
Transfer-Encoding: chunked
Via: 1.1 cache-2:80, 1.1 proxy-2_6:8006
Connection: keep-alive
當前 IP:61.148.226.66 來自:北京市 聯通
可以看到,這次成功了。這是因為當存在 ENTRYPOINT 后, CMD 的內容將會作為參數傳給 ENTRYPOINT ,而這里 -i 就是新的 CMD ,因此會作為參數傳給curl ,從而達到了我們預期的效果。
場景二:應用運行前的准備工作
啟動容器就是啟動主進程,但有些時候,啟動主進程前,需要一些准備工作。
比如 mysql 類的數據庫,可能需要一些數據庫配置、初始化的工作,這些工作要在最終的 mysql 服務器運行之前解決。
此外,可能希望避免使用 root 用戶去啟動服務,從而提高安全性,而在啟動服務前還需要以 root 身份執行一些必要的准備工作,最后切換到服務用戶身份啟動服務。或者除了服務外,其它命令依舊可以使用 root 身份執行,方便調試等。
這些准備工作是和容器 CMD 無關的,無論 CMD 為什么,都需要事先進行一個預處理的工作。這種情況下,可以寫一個腳本,然后放入 ENTRYPOINT 中去執行,而這個腳本會將接到的參數(也就是 <CMD> )作為命令,在腳本最后執行。
比如官方鏡像 redis 中就是這么做的:
FROM alpine:3.4 ... RUN addgroup -S redis && adduser -S -G redis redis ... ENTRYPOINT ["docker-entrypoint.sh"] EXPOSE 6379 CMD [ "redis-server" ]
可以看到其中為了 redis 服務創建了 redis 用戶,並在最后指定了 ENTRYPOINT 為 docker-entrypoint.sh 腳本。
#!/bin/sh ... # allow the container to be started with `--user` if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then chown -R redis . exec su-exec redis "$0" "$@" fi exec "$@"
該腳本的內容就是根據 CMD 的內容來判斷,如果是 redis-server 的話,則切
換到 redis 用戶身份啟動服務器,否則依舊使用 root 身份執行。比如:
$ docker run -it redis id uid=0(root) gid=0(root) groups=0(root)