天天看點

玩轉dockerfile

鏡像的緩存特性

Docker 會緩存已有鏡像的鏡像層,建構新鏡像時,如果某鏡像層已經存在,就直接使用,無需重新建立。

舉例說明。

在前面的 Dockerfile 中添加一點新内容,往鏡像中複制一個檔案:

玩轉dockerfile
玩轉dockerfile

① 確定 testfile 已存在。

② 重點在這裡:之前已經運作過相同的 RUN 指令,這次直接使用緩存中的鏡像層 35ca89798937。

③ 執行 COPY 指令。

其過程是啟動臨時容器,複制 testfile,送出新的鏡像層 8d02784a78f4,删除臨時容器。

在 ubuntu-with-vi-dockerfile 鏡像上直接添加一層就得到了新的鏡像 ubuntu-with-vi-dockerfile-2。

玩轉dockerfile

如果我們希望在建構鏡像時不使用緩存,可以在

docker build

指令中加上

--no-cache

參數。

Dockerfile 中每一個指令都會建立一個鏡像層,上層是依賴于下層的。無論什麼時候,隻要某一層發生變化,其上面所有層的緩存都會失效。

也就是說,如果我們改變 Dockerfile 指令的執行順序,或者修改或添加指令,都會使緩存失效。

舉例說明,比如交換前面 RUN 和 COPY 的順序:

玩轉dockerfile

雖然在邏輯上這種改動對鏡像的内容沒有影響,但由于分層的結構特性,Docker 必須重建受影響的鏡像層。

玩轉dockerfile

從上面的輸出可以看到生成了新的鏡像層 bc87c9710f40,緩存已經失效。

除了建構時使用緩存,Docker 在下載下傳鏡像時也會使用。例如我們下載下傳 httpd 鏡像。

玩轉dockerfile

docker pull 指令輸出顯示第一層(base 鏡像)已經存在,不需要下載下傳。

由 Dockerfile 可知 httpd 的 base 鏡像為 debian,正好之前已經下載下傳過 debian 鏡像,是以有緩存可用。通過 docker history 可以進一步驗證。

玩轉dockerfile

調試 Dockerfile

包括 Dockerfile 在内的任何腳本和程式都會出錯。有錯并不可怕,但必須有辦法排查,是以本節讨論如何 debug Dockerfile。

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

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

從這個過程可以看出,如果 Dockerfile 由于某種原因執行到某個指令失敗了,我們也将能夠得到前一個指令成功執行建構出的鏡像,這對調試 Dockerfile 非常有幫助。我們可以運作最新的這個鏡像定位指令失敗的原因。

我們來看一個調試的例子。Dockerfile 内容如下:

玩轉dockerfile

執行

docker build

玩轉dockerfile

Dockerfile 在執行第三步 RUN 指令時失敗。我們可以利用第二步建立的鏡像 22d31cc52b3e 進行調試,方式是通過

docker run -it

啟動鏡像的一個容器。

玩轉dockerfile

手工執行 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

注:Dockerfile 支援以“#”開頭的注釋。

建構鏡像:

玩轉dockerfile

① 建構前確定 build context 中存在需要的檔案。

② 依次執行 Dockerfile 指令,完成建構。

運作容器,驗證鏡像内容:

玩轉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 格式二者在使用上有細微的差別。

玩轉dockerfile

例如

玩轉dockerfile

當指令執行時shell 格式底層會調用 /bin/sh -c <command> 。

例如下面的 Dockerfile 片段

玩轉dockerfile

執行 docker run <image> 将輸出

Hello, Cloud Man           

複制

注意環境變量

name

已經被值

Cloud Man

替換。

下面來看 Exec 格式。

玩轉dockerfile

例如

玩轉dockerfile

當指令執行時會直接調用 <command>不會被 shell 解析。

例如下面的 Dockerfile 片段

玩轉dockerfile

運作容器将輸出

Hello, $name           

複制

注意環境變量“name”沒有被替換。

如果希望使用環境變量照如下修改

玩轉dockerfile

運作容器将輸出

Hello, Cloud Man           

複制

CMD 和 ENTRYPOINT 推薦使用 Exec 格式因為指令可讀性更強更容易了解。RUN 則兩種格式都可以。

RUN

RUN 指令通常用于安裝應用和軟體包。

RUN 在目前鏡像的頂部執行指令并通過建立新的鏡像層。Dockerfile 中常常包含多個 RUN 指令。

RUN 有兩種格式

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

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

玩轉dockerfile

注意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 片段

玩轉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           

複制