你肯定你會寫 Dockerfile 嗎?

原文連接:你肯定你會寫 Dockerfile 嗎?docker

現在 GitHub 倉庫中已經包含了成千上萬的 Dockerfile,但並非全部的 Dockerfile 都是高效的。本文將從五個方面來介紹 Dockerfile 的最佳實踐,以此來幫助你們編寫更優雅的 Dockerfile。若是你是 Docker 的初學者,恭喜你,這篇文章就是爲你準備的。後面的系列將會更加深刻,敬請期待!緩存

本文使用一個基於 Maven 的 Java 項目做爲示例,而後不斷改進 Dockerfile 的寫法,直到最後寫出一個最優雅的 Dockerfile。中間的全部步驟都是爲了說明某一方面的最佳實踐。安全

1. 減小構建時間

一個開發週期包括構建 Docker 鏡像,更改代碼,而後從新構建 Docker 鏡像。在構建鏡像的過程當中,若是可以利用緩存,能夠減小沒必要要的重複構建步驟。maven

構建順序影響緩存的利用率

鏡像的構建順序很重要,當你向 Dockerfile 中添加文件,或者修改其中的某一行時,那一部分的緩存就會失效,該緩存的後續步驟都會中斷,須要從新構建。因此優化緩存的最佳方法是把不須要常常更改的行放到最前面,更改最頻繁的行放到最後面。ide

只拷貝須要的文件,防止緩存溢出

當拷貝文件到鏡像中時,儘可能只拷貝須要的文件,切忌使用 COPY . 指令拷貝整個目錄。若是被拷貝的文件內容發生了更改,緩存就會被破壞。在上面的示例中,鏡像中只須要構建好的 jar 包,所以只須要拷貝這個文件就好了,這樣即便其餘不相關的文件發生了更改也不會影響緩存。工具

最小化可緩存的執行層

每個 RUN 指令都會被看做是可緩存的執行單元。太多的 RUN 指令會增長鏡像的層數,增大鏡像體積,而將全部的命令都放到同一個 RUN 指令中又會破壞緩存,從而延緩開發週期。當使用包管理器安裝軟件時,通常都會先更新軟件索引信息,而後再安裝軟件。推薦將更新索引和安裝軟件放在同一個 RUN 指令中,這樣能夠造成一個可緩存的執行單元,不然你可能會安裝舊的軟件包。post

2. 減少鏡像體積

鏡像的體積很重要,由於鏡像越小,部署的速度更快,攻擊範圍越小。優化

刪除沒必要要依賴

刪除沒必要要的依賴,不要安裝調試工具。若是實在須要調試工具,能夠在容器運行以後再安裝。某些包管理工具(如 apt)除了安裝用戶指定的包以外,還會安裝推薦的包,這會平白無故增長鏡像的體積。apt 能夠經過添加參數 -–no-install-recommends 來確保不會安裝不須要的依賴項。若是確實須要某些依賴項,請在後面手動添加。ui

刪除包管理工具的緩存

包管理工具會維護本身的緩存,這些緩存會保留在鏡像文件中,推薦的處理方法是在每個 RUN 指令的末尾刪除緩存。若是你在下一條指令中刪除緩存,不會減少鏡像的體積。操作系統

固然了,還有其餘更高級的方法能夠用來減少鏡像體積,以下文將會介紹的多階段構建。接下來咱們將探討如何優化 Dockerfile 的可維護性、安全性和可重複性。

3. 可維護性

儘可能使用官方鏡像

使用官方鏡像能夠節省大量的維護時間,由於官方鏡像的全部安裝步驟都使用了最佳實踐。若是你有多個項目,能夠共享這些鏡像層,由於他們均可以使用相同的基礎鏡像。

使用更具體的標籤

基礎鏡像儘可能不要使用 latest 標籤。雖然這很方便,但隨着時間的推移,latest 鏡像可能會發生重大變化。所以在 Dockerfile 中最好指定基礎鏡像的具體標籤。咱們使用 openjdk 做爲示例,指定標籤爲 8。其餘更多標籤請查看官方倉庫

使用體積最小的基礎鏡像

基礎鏡像的標籤風格不一樣,鏡像體積就會不一樣。slim 風格的鏡像是基於 Debian 發行版製做的,而 alpine 風格的鏡像是基於體積更小的 Alpine Linux 發行版製做的。其中一個明顯的區別是:Debian 使用的是 GNU 項目所實現的 C 語言標準庫,而 Alpine 使用的是 Musl C 標準庫,它被設計用來替代 GNU C 標準庫(glibc)的替代品,用於嵌入式操做系統和移動設備。所以使用 Alpine 在某些狀況下會遇到兼容性問題。 以 openjdk 爲例,jre 風格的鏡像只包含 Java 運行時,不包含 SDK,這麼作也能夠大大減小鏡像體積。

4. 重複利用

到目前爲止,咱們一直都在假設你的 jar 包是在主機上構建的,這還不是理想方案,由於沒有充分利用容器提供的一致性環境。例如,若是你的 Java 應用依賴於某一個特定的操做系統的庫,就可能會出現問題,由於環境不一致(具體取決於構建 jar 包的機器)。

在一致的環境中從源代碼構建

源代碼是你構建 Docker 鏡像的最終來源,Dockerfile 裏面只提供了構建步驟。

首先應該肯定構建應用所需的全部依賴,本文的示例 Java 應用很簡單,只須要 MavenJDK,因此基礎鏡像應該選擇官方的體積最小的 maven 鏡像,該鏡像也包含了 JDK。若是你須要安裝更多依賴,能夠在 RUN 指令中添加。pom.xml 文件和 src 文件夾須要被複制到鏡像中,由於最後執行 mvn package 命令(-e 參數用來顯示錯誤,-B 參數表示以非交互式的「批處理」模式運行)打包的時候會用到這些依賴文件。

雖然如今咱們解決了環境不一致的問題,但還有另一個問題:**每次代碼更改以後,都要從新獲取一遍 pom.xml 中描述的全部依賴項。**下面咱們來解決這個問題。

在單獨的步驟中獲取依賴項

結合前面提到的緩存機制,咱們可讓獲取依賴項這一步變成可緩存單元,只要 pom.xml 文件的內容沒有變化,不管代碼如何更改,都不會破壞這一層的緩存。上圖中兩個 COPY 指令中間的 RUN 指令用來告訴 Maven 只獲取依賴項。

如今又遇到了一個新問題:跟以前直接拷貝 jar 包相比,鏡像體積變得更大了,由於它包含了不少運行應用時不須要的構建依賴項。

使用多階段構建來刪除構建時的依賴項

多階段構建能夠由多個 FROM 指令識別,每個 FROM 語句表示一個新的構建階段,階段名稱能夠用 AS 參數指定。本例中指定第一階段的名稱爲 builder,它能夠被第二階段直接引用。兩個階段環境一致,而且第一階段包含全部構建依賴項。

第二階段是構建最終鏡像的最後階段,它將包括應用運行時的全部必要條件,本例是基於 Alpine 的最小 JRE 鏡像。上一個構建階段雖然會有大量的緩存,但不會出如今第二階段中。爲了將構建好的 jar 包添加到最終的鏡像中,可使用 COPY --from=STAGE_NAME 指令,其中 STAGE_NAME 是上一構建階段的名稱。

多階段構建是刪除構建依賴的首選方案。

本文從在非一致性環境中構建體積較大的鏡像開始優化,一直優化到在一致性環境中構建最小鏡像,同時充分利用了緩存機制。下一篇文章將會介紹多階段構建的更多其餘用途。


相關文章
相關標籤/搜索