「Allen 談 Docker 系列」docker build 的 cache 機制

jopen 9年前發布 | 46K 次閱讀 Docker
 

「Allen 談 Docker 系列」DaoCloud 正在啟動 Docker 技術系列文章,每周都會為大家推送一期真材實料的精選 Docker 文章。主講人為 DaoCloud 核心開發團隊成員 Allen 孫宏亮,他是 InfoQ《Docker 源碼分析》專欄作者,已出版《Docker 源碼分析》一書。Allen 接觸 Docker 近兩年,愛鉆研系統實現原理,及 Linux 操作系統。

「Allen 談 Docker 系列」docker build 的 cache 機制

Docker 的鏡像技術一直是重中之重,《Allen 談 Docker 系列》也已經有不少篇幅深入剖析 Docker 的鏡像原理。從一開始,我們認識到 Docker 鏡像的層級管理;而后我們開始窺探 Docker 鏡像體積的含義;隨后我們又深入解析了 Docker 鏡像的包含的內容:鏡像文件系統內容以及鏡像 json 文件。隨著循序漸進的深入分析,我們會發現對于 Docker 鏡像的研究將無可避免地遇見一條 Docker 命令,那就是 “docker build”。

反復審視該命令,相信有兩部分內容是 Docker 愛好者絕對不容錯過的,那就是鏡像 cache 機制和docker commit 原理,本文首先帶大家了解并深入 Docker 鏡像的 cache 機制。

docker build 簡介

眾所周知,一個 Dockerfile 唯一的定義了一個 Docker 鏡像。如此依賴,Docker 必須提供一種方式,將 Dockerfile 轉換為 Docker 鏡像,采用的方式就是docker build 命令。以如下的 Dockerfile 為例:

FROM ubuntu:14.04
RUN apt-get update
ADD run.sh /
VOLUME /data
CMD ["./run.sh"]

一般此 Dockerfile 的當前目錄下,必須包含文件 run.sh。通過執行以下命令

docker build -t="my_new_image"

即可將當前目錄下的 Dockerfile 構建成一個名為my_new_image的鏡像,鏡像的默認 tag 為 latest。對于以上的docker build請求,Docker Daemon 新創建了 4 層鏡像,除了 FROM 命令,其余的 RUN、ADD、VOLUME 以及 CMD 命令都會創建一層新的鏡像。

鏡像 cache 機制介紹

Dockerfile 可以通過docker build命令構建為一個新的鏡像,Dockerfile 中每一條命令都會構建出一個新的鏡像層。既然如此,構建成功后宿主機上的鏡像層是否會不斷增多,導致磁盤空間資源逐漸縮小?另外,一個 Dockerfile 如果構建多次,對于 Dockerfile 中的某一指定命令,是否會出現產生多個對應鏡像層的情況呢?

鏡像層的增多自然是毋庸置疑,然而并非每一次構建的每一條 Dockerfile 命令都會產生一個全新的鏡像層。談及原因,那我們必須談談 docker build 的 cache 機制。

docker build的 cache 機制 : Docker Daemnon 通過 Dockerfile 構建鏡像時,當發現即將新構建出的鏡像與已有的某鏡像重復時,可以選擇放棄構建新的鏡像,而是選用已有的鏡像作為構建結果,也就是采取本地已經 cache 的鏡像作為結果。

反復閱讀以上解釋,細心的朋友肯定會有兩點疑惑:

1. “即將構建出的鏡像”屬于仍未構建完成的鏡像,通過何種方式來標識此鏡像?

2. 涉及到鏡像比較,重復時選擇放棄構建,那鏡像比較時重復的標準是什么?

cache 機制實現原理

Docker 鏡像,由鏡像層文件系統內容和鏡像 json文件組成,而這兩者都含有一個相同的鏡像 ID 。還記得我們之前談及的父鏡像和子鏡像的概念嗎?此處也會大量運用鏡像間的父子關系。

還是以上文中的 Dockerfile 為例,我們結合下圖,著重分析命令FROM ubuntu:14.04和RUN apt-get update。

「Allen 談 Docker 系列」docker build 的 cache 機制

FROM ubuntu:14.04: FROM 命令是 Dockerfile 中唯一不可缺少的命令,它為最終構建出的鏡像設定了一個基礎鏡像(base image)。docker build命令解析 Dockerfile 的 FROM 命令時,可以立即獲悉在哪一個鏡像基礎上完成下一條命令RUN apt-get update的鏡像構建。此時,Docker Daemon 獲取 ubuntu:14.04 鏡像的鏡像 ID,并提取該鏡像 json 文件中的內容,以備下一條命令構建時使用。

RUN apt-get update:RUN 命令是在上一層鏡像(即 ubuntu:14.04 鏡像)之上運行 apt-get update,所有對文件系統內容有更新的文件,都會保留于新構建的鏡像層中,同時更新上一層鏡像的 json 文件,更新鏡像 json 文件的 Cmd 屬性為"/bin/sh -c apt-get update"。注意:鏡像 json 文件的 Cmd 屬性與鏡像 json 文件中 config 屬性的 Cmd 屬性,詳見下圖 RUN 命令所對應鏡像(鏡像 ID 為:0aaab7ef57ee)中兩個 Cmd 的區別:

「Allen 談 Docker 系列」docker build 的 cache 機制

完成一條非 FROM 命令的構建,即產生一個新的鏡像,新的鏡像為其上一條命令產生鏡像的子鏡像。基于此以及以上的知識,我們可以提出這樣的一個猜想:

“是否可以在構建 Dockerfile 某一命令前,就預知即將構建出新一層鏡像的形態?”

圍繞此問題,我們繼續分析。未構建命令RUN apt-get update前,我們可以肯定的事實有以下幾點:

  • 鏡像關系 :對于命令RUN apt-get update的構建,一定將會產生一個新鏡像,新鏡像的父鏡像 ID 為 ubuntu:14.04 的鏡像 ID,即 8251da35e7a7。
  • 鏡像 json 文件更新 :運行命令 apt-get update 后產生新鏡像,新鏡像 json 文件僅僅更新 ubuntu:14.04 鏡像 json 文件的 Cmd 屬性,其它如 config 屬性均不會進行修改。
  • 鏡像層文件系統內容更新 :運行 apt-get update 后,對于容器可讀寫層的內容更新,全部將被打包進新鏡像的鏡像層文件系統內容。

基于這 3 個事實,我們再提出一個假設:如果在構建命令RUN apt-get update前,Docker Daemon 已經存在一個鏡像滿足以下兩點:

  • 此鏡像的父鏡像為 ubuntu:14.04
  • 此鏡像的 json 文件僅僅將 ubuntu:14.04 鏡像 json 文件的 Cmd 屬性更新為 apt-get update

那么是否可以認為:即將新構建的鏡像與此鏡像完全一致,不需要另行構建,只需復用此鏡像即可?

如果你認可以上假設,那么 cache 機制的核心就接近浮出水面了: 遍歷本地所有鏡像,發現鏡像與即將構建出的鏡像一致時,將找到的鏡像作為 cache 鏡像,復用 cache 鏡像作為構建結果。

cache 機制注意事項

可以說,cache 機制很大程度上做到了鏡像的復用,降低存儲空間的同時,還大大縮短了構建時間。然而,不得不說的是,想要用好 cache 機制,那就必須了解利用 cache 機制時的一些注意事項。

1. ADD 命令與 COPY 命令:Dockerfile 沒有發生任何改變,但是命令ADD run.sh /中 Dockerfile 當前目錄下的 run.sh 卻發生了變化,從而將直接導致鏡像層文件系統內容的更新,原則上不應該再使用 cache。那么,判斷 ADD 命令或者 COPY 命令后緊接的文件是否發生變化,則成為是否延用 cache 的重要依據。Docker 采取的策略是:獲取 Dockerfile 下內容(包括文件的部分 inode 信息),計算出一個唯一的 hash 值,若 hash 值未發生變化,則可以認為文件內容沒有發生變化,可以使用 cache 機制;反之亦然。

2. RUN 命令存在外部依賴 :一旦 RUN 命令存在外部依賴,如RUN apt-get update,那么隨著時間的推移,基于同一個基礎鏡像,一年的 apt-get update 和一年后的 apt-get update, 由于軟件源軟件的更新,從而導致產生的鏡像理論上應該不同。如果繼續使用 cache 機制,將存在不滿足用戶需求的情況。Docker 一開始的設計既考慮了外部依賴的問題,用戶可以使用參數 --no-cache 確保獲取最新的外部依賴,命令為docker build --no-cache -t="my_new_image" .

3. 樹狀的鏡像關系決定了,一次新鏡像的成功構建將導致后續的 cache 機制全部失效:這一點很好理解,一旦產生一個新的鏡像,同時意味著產生一個新的鏡像 ID,而當前宿主機環境中肯定不會存在一個鏡像,此鏡像 ID 的父鏡像 ID 是新產生鏡像的ID。這也是為什么,書寫 Dockerfile 時,應該將更多靜態的安裝、配置命令盡可能地放在 Dockerfile 的較前位置。

總結

docker build 的 cache 機制實現了鏡像的復用,不僅節省了鏡像的存儲空間,也為鏡像構建節省了大量的時間。 同時,如何命中 cache 鏡像,也是衡量 Dockerfile 書寫是否合理的重要標準之一。

欲知 Docker 鏡像更精彩的內容,且聽下回分解。下回內容預告:docker commit 的來龍去脈。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!