Docker 會緩存已有鏡像的鏡像層,構建新鏡像時,若是某鏡像層已經存在,就直接使用,無需從新建立。docker
舉例說明。
在前面的 Dockerfile 中添加一點新內容,往鏡像中複製一個文件:shell
① 確保 testfile 已存在。ubuntu
② 重點在這裏:以前已經運行過相同的 RUN 指令,此次直接使用緩存中的鏡像層 35ca89798937。centos
③ 執行 COPY 指令。
其過程是啓動臨時容器,複製 testfile,提交新的鏡像層 8d02784a78f4,刪除臨時容器。緩存
在 ubuntu-with-vi-dockerfile 鏡像上直接添加一層就獲得了新的鏡像 ubuntu-with-vi-dockerfile-2。bash
若是咱們但願在構建鏡像時不使用緩存,能夠在 docker build
命令中加上 --no-cache
參數。網絡
Dockerfile 中每個指令都會建立一個鏡像層,上層是依賴於下層的。不管何時,只要某一層發生變化,其上面全部層的緩存都會失效。學習
也就是說,若是咱們改變 Dockerfile 指令的執行順序,或者修改或添加指令,都會使緩存失效。ui
舉例說明,好比交換前面 RUN 和 COPY 的順序:spa
雖然在邏輯上這種改動對鏡像的內容沒有影響,但因爲分層的結構特性,Docker 必須重建受影響的鏡像層。
從上面的輸出能夠看到生成了新的鏡像層 bc87c9710f40,緩存已經失效。
除了構建時使用緩存,Docker 在下載鏡像時也會使用。例如咱們下載 httpd 鏡像。
docker pull 命令輸出顯示第一層(base 鏡像)已經存在,不須要下載。
由 Dockerfile 可知 httpd 的 base 鏡像爲 debian,正好以前已經下載過 debian 鏡像,因此有緩存可用。經過 docker history 能夠進一步驗證。
包括 Dockerfile 在內的任何腳本和程序都會出錯。有錯並不可怕,但必須有辦法排查,因此本節討論如何 debug Dockerfile。
先回顧一下經過 Dockerfile 構建鏡像的過程:
從這個過程能夠看出,若是 Dockerfile 因爲某種緣由執行到某個指令失敗了,咱們也將可以獲得前一個指令成功執行構建出的鏡像,這對調試 Dockerfile 很是有幫助。咱們能夠運行最新的這個鏡像定位指令失敗的緣由。
咱們來看一個調試的例子。Dockerfile 內容以下:
執行 docker build
:
Dockerfile 在執行第三步 RUN 指令時失敗。咱們能夠利用第二步建立的鏡像 22d31cc52b3e 進行調試,方式是經過 docker run -it
啓動鏡像的一個容器。
手工執行 RUN 指令很容易定位失敗的緣由是 busybox 鏡像中沒有 bash。雖然這是個極其簡單的例子,但它很好地展現了調試 Dockerfile 的方法。
到這裏相信你們對 Dockerfile 的功能和使用流程有了比較完整的印象,但尚未系統學習 Dockerfile 的各類指令和實際用法,下節會開始這個主題。
是時候系統學習 Dockerfile 了。
下面列出了 Dockerfile 中最經常使用的指令,完整列表和說明可參看官方文檔。
FROM
指定 base 鏡像。
MAINTAINER
設置鏡像的做者,能夠是任意字符串。
COPY
將文件從 build context 複製到鏡像。
COPY 支持兩種形式:
注意:src 只能指定 build context 中的文件或目錄。
ADD
與 COPY 相似,從 build context 複製文件到鏡像。不一樣的是,若是 src 是歸檔文件(tar, zip, tgz, xz 等),文件會被自動解壓到 dest。
ENV
設置環境變量,環境變量可被後面的指令使用。例如:
...
ENV MY_VERSION 1.3
RUN apt-get install -y mypackage=$MY_VERSION
...
EXPOSE
指定容器中的進程會監聽某個端口,Docker 能夠將該端口暴露出來。咱們會在容器網絡部分詳細討論。
VOLUME
將文件或目錄聲明爲 volume。咱們會在容器存儲部分詳細討論。
WORKDIR
爲後面的 RUN, CMD, ENTRYPOINT, ADD 或 COPY 指令設置鏡像中的當前工做目錄。
RUN
在容器中運行指定的命令。
CMD
容器啓動時運行指定的命令。
Dockerfile 中能夠有多個 CMD 指令,但只有最後一個生效。CMD 能夠被 docker run 以後的參數替換。
ENTRYPOINT
設置容器啓動時運行的命令。
Dockerfile 中能夠有多個 ENTRYPOINT 指令,但只有最後一個生效。CMD 或 docker run 以後的參數會被當作參數傳遞給 ENTRYPOINT。
下面咱們來看一個較爲全面的 Dockerfile:
注:Dockerfile 支持以「#」開頭的註釋。
構建鏡像:
① 構建前確保 build context 中存在須要的文件。
② 依次執行 Dockerfile 指令,完成構建。
運行容器,驗證鏡像內容:
① 進入容器,當前目錄即爲 WORKDIR。
若是 WORKDIR 不存在,Docker 會自動爲咱們建立。
② WORKDIR 中保存了咱們但願的文件和目錄:
目錄 bunch:由 ADD 指令從 build context 複製的歸檔文件 bunch.tar.gz,已經自動解壓。
文件 tmpfile1:由 RUN 指令建立。
文件 tmpfile2:由 COPY 指令從 build context 複製。
③ ENV 指令定義的環境變量已經生效。
在上面這些指令中,RUN、CMD、ENTRYPOINT 很重要且容易混淆,下節專門討論。
RUN、CMD 和 ENTRYPOINT 這三個 Dockerfile 指令看上去很相似很容易混淆。本節將經過實踐詳細討論它們的區別。
簡單的說
docker run
後面跟的命令行參數替換。下面咱們詳細分析。
Shell 和 Exec 格式
咱們可用兩種方式指定 RUN、CMD 和 ENTRYPOINT 要運行的命令Shell 格式和 Exec 格式兩者在使用上有細微的區別。
例如
當指令執行時shell 格式底層會調用 /bin/sh -c <command> 。
例以下面的 Dockerfile 片斷
執行 docker run <image> 將輸出
Hello, Cloud Man
注意環境變量 name
已經被值 Cloud Man
替換。
下面來看 Exec 格式。
例如
當指令執行時會直接調用 <command>不會被 shell 解析。
例以下面的 Dockerfile 片斷
運行容器將輸出
Hello, $name
注意環境變量「name」沒有被替換。
若是但願使用環境變量照以下修改
運行容器將輸出
Hello, Cloud Man
CMD 和 ENTRYPOINT 推薦使用 Exec 格式由於指令可讀性更強更容易理解。RUN 則兩種格式均可以。
RUN
RUN 指令一般用於安裝應用和軟件包。
RUN 在當前鏡像的頂部執行命令並經過建立新的鏡像層。Dockerfile 中經常包含多個 RUN 指令。
RUN 有兩種格式
下面是使用 RUN 安裝多個包的例子
注意apt-get update 和 apt-get install 被放在一個 RUN 指令中執行這樣可以保證每次安裝的是最新的包。若是 apt-get install 在單獨的 RUN 中執行則會使用 apt-get update 建立的鏡像層而這一層多是好久之前緩存的。
CMD
CMD 指令容許用戶指定容器的默認執行的命令。
此命令會在容器啓動且 docker run 沒有指定其餘命令時運行。
CMD 有三種格式
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/bashCMD 會被忽略掉命令 bash 將被執行
root@10a32dc7d3d3:/#
ENTRYPOINT
ENTRYPOINT 指令可以讓容器以應用程序或者服務的形式運行。
ENTRYPOINT 看上去與 CMD 很像它們均可以指定要執行的命令及其參數。不一樣的地方在於 ENTRYPOINT 不會被忽略必定會被執行即便運行 docker run 時指定了其餘命令。
ENTRYPOINT 有兩種格式
在爲 ENTRYPOINT 選擇格式時必須當心由於這兩種格式的效果差異很大。
Exec 格式
ENTRYPOINT 的 Exec 格式用於設置要執行的命令及其參數同時可經過 CMD 提供額外的參數。
ENTRYPOINT 中的參數始終會被使用而 CMD 的額外參數能夠在容器啓動時動態替換掉。
好比下面的 Dockerfile 片斷
當容器經過 docker run -it [image] 啓動時輸出爲
Hello world
而若是經過 docker run -it [image] CloudMan 啓動則輸出爲
Hello CloudMan
Shell 格式
ENTRYPOINT 的 Shell 格式會忽略任何 CMD 或 docker run 提供的參數。
最佳實踐
到這裏咱們已經具有編寫 Dockerfile 的能力了。若是你們還以爲沒把握推薦一個快速掌握 Dockerfile 的方法去 dockerhub.com 上參考那些官方鏡像的 Dockerfile。
dockerfile編寫的過程當中,不可避免會遇到運行構建新鏡像錯誤的問題,那麼咱們應該怎樣調試dockerfile呢。其實,當咱們遇到某個指令失敗時,咱們也可以獲得前一個指令構建的鏡像。所以,咱們能夠進入到前一個臨時鏡像,調試下一個指令。
好比運行Dockerfile後,報錯信息以下,在step3,即 RUN cp tmpfile tmpdir/ 時出現了錯誤。
[root@localhost debug-dockerfile]# docker build -t debug-dockerfile .
Sending build context to Docker daemon 2.56kB
Step 1/3 : FROM centos:7.4.1708
---> 295a0b2bd8ea Step 2/3 : RUN touch tmpfile ---> Running in 7530981ccd45 Removing intermediate container 7530981ccd45 ---> 8408a48380c2 Step 3/3 : RUN cp tmpfile tmpdir/ ---> Running in a50d0a45ce94 cp: cannot create regular file 'tmpdir/': Not a directory The command '/bin/sh -c cp tmpfile tmpdir/' returned a non-zero code: 1
這時,咱們能夠進入到前面一個指令中獲取到的臨時鏡像8408a48380c2,調試下一個指令。
[root@localhost debug-dockerfile]# docker run -it 8408a48380c2
經過ll命令,咱們能夠看到上一個命令建立的文件tmpfile
1. COPY/ADD文件夾時默認複製文件來中的文件
ADD go /usr/local/
將您的本地目錄的內容複製到docker鏡像go
的/usr/local/
目錄中。
要複製go
正在/usr/local/
使用的目錄:
ADD go /usr/local/go
要麼
COPY go /usr/local/go