玩轉dockerfile

鏡像的緩存特性

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

包括 Dockerfile 在內的任何腳本和程序都會出錯。有錯並不可怕,但必須有辦法排查,因此本節討論如何 debug Dockerfile。

先回顧一下經過 Dockerfile 構建鏡像的過程:

  1. 從 base 鏡像運行一個容器。
  2. 執行一條指令,對容器作修改。
  3. 執行相似 docker commit 的操做,生成一個新的鏡像層。
  4. Docker 再基於剛剛提交的鏡像運行一個新容器。
  5. 重複 2-4 步,直到 Dockerfile 中的全部指令執行完畢。

從這個過程能夠看出,若是 Dockerfile 因爲某種緣由執行到某個指令失敗了,咱們也將可以獲得前一個指令成功執行構建出的鏡像,這對調試 Dockerfile 很是有幫助。咱們能夠運行最新的這個鏡像定位指令失敗的緣由。

咱們來看一個調試的例子。Dockerfile 內容以下:

執行 docker build

Dockerfile 在執行第三步 RUN 指令時失敗。咱們能夠利用第二步建立的鏡像 22d31cc52b3e 進行調試,方式是經過 docker run -it 啓動鏡像的一個容器。

手工執行 RUN 指令很容易定位失敗的緣由是 busybox 鏡像中沒有 bash。雖然這是個極其簡單的例子,但它很好地展現了調試 Dockerfile 的方法。

到這裏相信你們對 Dockerfile 的功能和使用流程有了比較完整的印象,但尚未系統學習 Dockerfile 的各類指令和實際用法,下節會開始這個主題。

Dockerfile 經常使用指令

是時候系統學習 Dockerfile 了。

下面列出了 Dockerfile 中最經常使用的指令,完整列表和說明可參看官方文檔。

FROM

指定 base 鏡像。

MAINTAINER

設置鏡像的做者,能夠是任意字符串。

COPY

將文件從 build context 複製到鏡像。

COPY 支持兩種形式:

  1. COPY src dest
  2. COPY ["src", "dest"]

注意: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 vs CMD vs ENTRYPOINT

RUN、CMD 和 ENTRYPOINT 這三個 Dockerfile 指令看上去很相似很容易混淆。本節將經過實踐詳細討論它們的區別。

簡單的說

  1. RUN 執行命令並建立新的鏡像層RUN 常常用於安裝軟件包。
  2. CMD 設置容器啓動後默認執行的命令及其參數但 CMD 可以被 docker run 後面跟的命令行參數替換。
  3. ENTRYPOINT 配置容器啓動時運行的命令。

下面咱們詳細分析。

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 有兩種格式

  1. Shell 格式RUN
  2. Exec 格式RUN ["executable", "param1", "param2"]

下面是使用 RUN 安裝多個包的例子

注意apt-get update 和 apt-get install 被放在一個 RUN 指令中執行這樣可以保證每次安裝的是最新的包。若是 apt-get install 在單獨的 RUN 中執行則會使用 apt-get update 建立的鏡像層而這一層多是好久之前緩存的。

CMD

CMD 指令容許用戶指定容器的默認執行的命令。

此命令會在容器啓動且 docker run 沒有指定其餘命令時運行。

  1. 若是 docker run 指定了其餘命令CMD 指定的默認命令將被忽略。
  2. 若是 Dockerfile 中有多個 CMD 指令只有最後一個 CMD 有效。

CMD 有三種格式

  1. Exec 格式CMD ["executable","param1","param2"]
    這是 CMD 的推薦格式。
  2. CMD ["param1","param2"] 爲 ENTRYPOINT 提供額外的參數此時 ENTRYPOINT 必須使用 Exec 格式。
  3. 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/bashCMD 會被忽略掉命令 bash 將被執行

root@10a32dc7d3d3:/#

ENTRYPOINT

ENTRYPOINT 指令可以讓容器以應用程序或者服務的形式運行。

ENTRYPOINT 看上去與 CMD 很像它們均可以指定要執行的命令及其參數。不一樣的地方在於 ENTRYPOINT 不會被忽略必定會被執行即便運行 docker run 時指定了其餘命令。

ENTRYPOINT 有兩種格式

  1. Exec 格式ENTRYPOINT ["executable", "param1", "param2"] 這是 ENTRYPOINT 的推薦格式。
  2. Shell 格式ENTRYPOINT command param1 param2

在爲 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 提供的參數。

最佳實踐

  1. 使用 RUN 指令安裝應用和軟件包構建鏡像。
  2. 若是 Docker 鏡像的用途是運行應用程序或服務好比運行一個 MySQL應該優先使用 Exec 格式的 ENTRYPOINT 指令。CMD 可爲 ENTRYPOINT 提供額外的默認參數同時可利用 docker run 命令行替換默認參數。
  3. 若是想爲容器設置默認的啓動命令可以使用 CMD 指令。用戶可在 docker run 命令行中替換此默認命令。

到這裏咱們已經具有編寫 Dockerfile 的能力了。若是你們還以爲沒把握推薦一個快速掌握 Dockerfile 的方法去 dockerhub.com 上參考那些官方鏡像的 Dockerfile。

 

調試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
相關文章
相關標籤/搜索