RUN、CMD 和 ENTRYPOINT 這三個 Dockerfile 指令看上去很類似,很容易混淆。本節將通過實踐詳細討論它們的區別。
簡單的說:
-
RUN 執行命令並創建新的鏡像層,RUN 經常用於安裝軟件包。
-
CMD 設置容器啟動后默認執行的命令及其參數,但 CMD 能夠被
docker run
后面跟的命令行參數替換。 -
ENTRYPOINT 配置容器啟動時運行的命令。
下面我們詳細分析。
Shell 和 Exec 格式
我們可用兩種方式指定 RUN、CMD 和 ENTRYPOINT 要運行的命令:Shell 格式和 Exec 格式,二者在使用上有細微的區別。
Shell 格式
<instruction> <command>
例如:
RUN apt-get install python3
CMD echo "Hello world"
ENTRYPOINT echo "Hello world"
當指令執行時,shell 格式底層會調用 /bin/sh -c <command> 。
例如下面的 Dockerfile 片段:
ENV name Cloud Man
ENTRYPOINT echo "Hello, $name"
執行 docker run <image> 將輸出:
Hello, Cloud Man
注意環境變量 name
已經被值 Cloud Man
替換。
下面來看 Exec 格式。
Exec 格式
<instruction> ["executable", "param1", "param2", ...]
例如:
RUN ["apt-get", "install", "python3"]
CMD ["/bin/echo", "Hello world"]
ENTRYPOINT ["/bin/echo", "Hello world"]
當指令執行時,會直接調用 <command>,不會被 shell 解析。
例如下面的 Dockerfile 片段:
ENV name Cloud Man
ENTRYPOINT ["/bin/echo", "Hello, $name"]
運行容器將輸出:
Hello, $name
注意環境變量“name”沒有被替換。
如果希望使用環境變量,照如下修改
ENV name Cloud Man
ENTRYPOINT ["/bin/sh", "-c", "echo Hello, $name"]
運行容器將輸出:
Hello, Cloud Man
CMD 和 ENTRYPOINT 推薦使用 Exec 格式,因為指令可讀性更強,更容易理解。RUN 則兩種格式都可以。
RUN
RUN 指令通常用於安裝應用和軟件包。
RUN 在當前鏡像的頂部執行命令,並通過創建新的鏡像層。Dockerfile 中常常包含多個 RUN 指令。
RUN 有兩種格式:
-
Shell 格式:RUN
-
Exec 格式:RUN ["executable", "param1", "param2"]
下面是使用 RUN 安裝多個包的例子:
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
注意:apt-get update 和 apt-get install 被放在一個 RUN 指令中執行,這樣能夠保證每次安裝的是最新的包。如果 apt-get install 在單獨的 RUN 中執行,則會使用 apt-get update 創建的鏡像層,而這一層可能是很久以前緩存的。
CMD
CMD 指令允許用戶指定容器的默認執行的命令。
此命令會在容器啟動且 docker run 沒有指定其他命令時運行。
-
如果 docker run 指定了其他命令,CMD 指定的默認命令將被忽略。
-
如果 Dockerfile 中有多個 CMD 指令,只有最后一個 CMD 有效。
CMD 有三種格式:
-
Exec 格式:CMD ["executable","param1","param2"]
這是 CMD 的推薦格式。 -
CMD ["param1","param2"] 為 ENTRYPOINT 提供額外的參數,此時 ENTRYPOINT 必須使用 Exec 格式。
-
Shell 格式:CMD command param1 param2
Exec 和 Shell 格式前面已經介紹過了。
第二種格式 CMD ["param1","param2"] 要與 Exec 格式 的 ENTRYPOINT 指令配合使用,其用途是為 ENTRYPOINT 設置默認的參數。我們將在后面討論 ENTRYPOINT 時舉例說明。
下面看看 CMD 是如何工作的。Dockerfile 片段如下:
CMD echo "Hello world"
運行容器 docker run -it [image] 將輸出:
Hello world
但當后面加上一個命令,比如 docker run -it [image] /bin/bash,CMD 會被忽略掉,命令 bash 將被執行:
root@10a32dc7d3d3:/#
ENTRYPOINT
ENTRYPOINT 指令可讓容器以應用程序或者服務的形式運行。
ENTRYPOINT 看上去與 CMD 很像,它們都可以指定要執行的命令及其參數。不同的地方在於 ENTRYPOINT 不會被忽略,一定會被執行,即使運行 docker run 時指定了其他命令。
ENTRYPOINT 有兩種格式:
-
Exec 格式:ENTRYPOINT ["executable", "param1", "param2"] 這是 ENTRYPOINT 的推薦格式。
-
Shell 格式:ENTRYPOINT command param1 param2
在為 ENTRYPOINT 選擇格式時必須小心,因為這兩種格式的效果差別很大。
Exec 格式
ENTRYPOINT 的 Exec 格式用於設置要執行的命令及其參數,同時可通過 CMD 提供額外的參數。
ENTRYPOINT 中的參數始終會被使用,而 CMD 的額外參數可以在容器啟動時動態替換掉。
比如下面的 Dockerfile 片段:
ENTRYPOINT ["/bin/echo", "Hello"]
CMD ["world"]
當容器通過 docker run -it [image] 啟動時,輸出為:
Hello world
而如果通過 docker run -it [image] CloudMan 啟動,則輸出為:
Hello CloudMan
Shell 格式
ENTRYPOINT 的 Shell 格式會忽略任何 CMD 或 docker run 提供的參數。
最佳實踐
-
使用 RUN 指令安裝應用和軟件包,構建鏡像。
-
如果 Docker 鏡像的用途是運行應用程序或服務,比如運行一個 MySQL,應該優先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可為 ENTRYPOINT 提供額外的默認參數,同時可利用 docker run 命令行替換默認參數。
-
如果想為容器設置默認的啟動命令,可使用 CMD 指令。用戶可在 docker run 命令行中替換此默認命令。
到這里,我們已經具備編寫 Dockerfile 的能力了。如果大家還覺得沒把握,推薦一個快速掌握 Dockerfile 的方法:去 Docker Hub 上參考那些官方鏡像的 Dockerfile。
好了,我們已經學習完如何創建自己的 image,下一節討論如何分發 image。