天天看點

自我總結,編寫Dockerfile的9大最佳實踐

雖然 Dockerfile 簡化了鏡像建構的過程,并且把這個過程可以進行版本控制,但是很多人建構鏡像的時候,都有一種沖動——把可能用到的東西都打包到鏡像中。這種不正當的 Dockerfile 使用也會導緻很多問題:

  • docker 鏡像太大。如果你經常使用鏡像或者建構鏡像,一定會遇到那種很大的鏡像,甚至有些能達到 2G 以上
  • docker 鏡像的建構時間過長。每個 build 都會耗費很長時間,對于需要經常建構鏡像(比如單元測試)的地方這可能是個大問題
  • 重複勞動。多次鏡像建構之間大部分内容都是完全一樣而且重複的,但是每次都要做一遍,浪費時間和資源

希望讀者能夠對 docker 鏡像有一定的了解,閱讀這篇文章至少需要一下前提知識:

  • 了解 docker 的基礎概念,運作過容器
  • 熟悉 docker 鏡像的基礎知識,知道鏡像的分層結構
  • 最好是負責過某個 docker 鏡像的建構(使用 docker build 指令建立過自己的鏡像)

Dockerfile 和鏡像建構

Dockerfile 是由一個個指令組成的,每個指令都對應着最終鏡像的一層。每行的第一個單詞就是指令,後面所有的字元串是這個指令的參數,關于 Dockerfile 支援的指令以及它們的用法,可以參考官方文檔,這裡不再贅述。

當運作 docker build 指令的時候,整個的建構過程是這樣的:

  • 讀取 Dockerfile 檔案發送到 docker daemon
  • 讀取目前目錄的所有檔案(context),發送到 docker daemon
  • 對 Dockerfile 進行解析,處理成指令加上對應參數的結構
  • 按照順序循環周遊所有的指令,對每個指令調用對應的處理函數進行處理
  • 每個指令(除了 FROM)都會在一個容器執行,執行的結果會生成一個新的鏡像
  • 為最後生成的鏡像打上标簽

編寫 Dockerfile 的一些最佳實踐

1. 使用統一的 base 鏡像

有些文章講優化鏡像會提倡使用盡量小的基礎鏡像,比如 busybox 或者 alpine 等。我更推薦使用統一的大家比較熟悉的基礎鏡像,比如 ubuntu,centos 等,因為基礎鏡像隻需要下載下傳一次可以共享,并不會造成太多的存儲空間浪費。它的好處是這些鏡像的生态比較完整,友善我們安裝軟體,除了問題進行調試。

2. 動靜分離

經常變化的内容和基本不會變化的内容要分開,把不怎麼變化的内容放在下層,建立出來不同基礎鏡像供上層使用。比如可以建立各種語言的基礎鏡像,python2.7、python3.4、go1.7、java7等等,這些鏡像包含了最基本的語言庫,每個組可以在上面繼續建構應用級别的鏡像。

3. 最小原則:隻安裝必需的東西

很多人建構鏡像的時候,都有一種沖動——把可能用到的東西都打包到鏡像中。要遏制這種想法,鏡像中應該隻包含必需的東西,任何可以有也可以沒有的東西都不要放到裡面。因為鏡像的擴充很容易,而且運作容器的時候也很友善地對其進行修改。這樣可以保證鏡像盡可能小,建構的時候盡可能快,也保證未來的更快傳輸、更省網絡資源。

4. 一個原則:每個鏡像隻有一個功能

不要在容器裡運作多個不同功能的程序,每個鏡像中隻安裝一個應用的軟體包和檔案,需要互動的程式通過 pod(kubernetes 提供的特性) 或者容器之間的網絡進行交流。這樣可以保證子產品化,不同的應用可以分開維護和更新,也能減小單個鏡像的大小。

5. 使用更少的層

雖然看起來把不同的指令盡量分開來,寫在多個指令中容易閱讀和了解。但是這樣會導緻出現太多的鏡像層,而不好管理和分析鏡像,而且鏡像的層是有限的。盡量把相關的内容放到同一個層,使用換行符進行分割,這樣可以進一步減小鏡像大小,并且友善檢視鏡像曆史。

RUN apt-get update\  && apt-get install -y --no-install-recommends \  bzr \  cvs \  git \  mercurial \  subversion \  && apt get clean      

6. 減少每層的内容

盡管隻安裝必須的内容,在這個過程中也可能會産生額外的内容或者臨時檔案,我們要盡量讓每層安裝的東西保持最小。

  • 比如使用 --no-install-recommends 參數告訴 apt-get 不要安裝推薦的軟體包
  • 安裝完軟體包,清楚 /var/lib/apt/list/ 緩存
  • 删除中間檔案:比如下載下傳的壓縮包
  • 删除臨時檔案:如果指令産生了臨時檔案,也要及時删除

7. 不要在 Dockerfile 中單獨修改檔案的權限

因為 docker 鏡像是分層的,任何修改都會新增一個層,修改檔案或者目錄權限也是如此。如果有一個指令單獨修改大檔案或者目錄的權限,會把這些檔案複制一份,這樣很容易導緻鏡像很大。

解決方案也很簡單,要麼在添加到 Dockerfile 之前就把檔案的權限和使用者設定好,要麼在容器啟動腳本(entrypoint)做這些修改,或者拷貝檔案和修改權限放在一起做(這樣最終也隻是增加一層)。

8. 利用 cache 來加快建構速度

如果 Docker 發現某個層已經存在了,它會直接使用已經存在的層,而不會重新運作一次。如果你連續運作 docker build 多次,會發現第二次運作很快就結束了。

不過從 1.10 版本開始,Content Addressable Storage 的引入導緻緩存功能的實效,目前引入了 --cache-from 參數可以手動指定一個鏡像來使用它的緩存。

9. 版本控制和自動建構

最好把 Dockerfile 和對應的應用代碼一起放到版本控制中,然後能夠自動建構鏡像。這樣的好處是可以追蹤各個版本鏡像的内容,友善了解不同鏡像有什麼差別,對于調試和復原都有好處。

另外,如果運作鏡像的參數或者環境變量很多,也要有對應的文檔給予說明,并且文檔要随着 Dockerfile 變化而更新,這樣任何人都能參考着文檔很容易地使用鏡像,而不是下載下傳了鏡像不知道怎麼用。

作者:kevinyan