聊一聊Docker構建優化解析
在本文中,我將介紹一些經常被忽視的概念,這些概念將有助于優化Docker鏡像開發和構建過程。
讓我們從Docker構建過程的簡短描述開始。這是通過使用Docker CLI工具運行docker build命令觸發的過程。
docker build命令根據Dockerfile的文件中指定的指令構建Docker鏡像。Dockerfile是一個文本文檔,其中包含用戶在命令行上調用以組裝映像的所有有序命令。
Docker鏡像由只讀層組成。每層代表一個Dockerfile指令。這些層是堆疊在一起的,每個層都是上一層的變化的增量。通常可以認為這些層是緩存的一種形式。僅對更改的層進行更新,而不是對每個更改進行更新。
下面的示例描述了Dockerfile的內容:
- FROM registry.docker.com/baseimg/centos7-jdk8:latest
- MAINTAINER Luga "luga_sx@outofmemory.cn";
- RUN mkdir -p /tools/apps/{microserice}
- RUN mkdir -p /tools/apps/{microserice}/cache
- ADD {microserice}.jar /tools/apps/{microserice}/{microserice}.jar
- EXPOSE 9999
- ENV TZ 'Asia/Shanghai'
- ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom -Djava.awt.headless=true -Denv=DEV -Dapollo.cluster=DEFAULT -Dspring.profiles.active=prm -Dfile.encoding=utf-8","-jar","/tools/apps/{microserice}/{microserice}.jar"]
該文件中的每條指令代表Docker鏡像中的單獨一層。以下是每條指令的簡要說明:
- FROM:從JDK創建一個層Docker鏡像,(此處的鏡像非Docker Hub上面直接拉取,而是基于源碼自定義制作)
- COPY:從Docker客戶端的當前目錄添加文件
- RUN:使用make構建您的應用程序
- CMD:指定在容器中運行什么命令
基于上述命令行,在構建過程中執行上述命令時,將在Docker鏡像中創建層,一個完整的Docker鏡像將由此誕生。然而,在實際的項目活動中,我們需要從性能、穩定性、安全性等等方面對我們所創建的Docker鏡像進行不斷的調整、優化,以滿足業務場景需求。
針對Docker的構建過程,我想分享一些優化建議,以幫助有效地構建鏡像:
臨時容器
Dockerfile定義的鏡像會生成短暫的容器。在這種情況下,臨時容器是指可以停放并銷毀,然后重建的容器,并使用絕對最小的設置和配置替換為新生成的容器。臨時容器可以認為是一次性的。每個實例都是新的,并且與以前的容器實例無關。在開發Docker鏡像時,我們應該利用盡可能多的臨時模式。
減少不必要的軟件包
盡量避免安裝不必要的文件和軟件包。Docker鏡像應保持精簡。這有助于提高可移植性,縮短構建時間,降低復雜性并減小文件大小。例如,在大多數情況下,不需要在容器上安裝文本編輯器。不要安裝任何非必需的應用程序或服務。
實現.dockerignore文件
.dockerignore文件排除與在其中聲明的模式匹配的文件和目錄。這有助于避免將不必要的大文件或敏感文件和目錄發送到守護程序,并避免將它們添加到公共鏡像。
要在不重構源存儲庫的情況下排除與構建無關的文件,請使用.dockerignore文件。該文件支持類似于.gitignore文件的排除模式。
排序多行參數
盡可能通過字母數字排序多行參數來簡化以后的更改。這有助于避免軟件包重復,并使列表更易于更新。
解耦應用
依賴于其他應用程序的應用程序被視為“已耦合”。在某些情況下,它們托管在同一主機或計算節點上。這在非容器部署中很常見,但對于微服務,每個應用程序應存在于其自己的單獨容器中。將應用程序解耦到多個容器中,可以更輕松地水平縮放和重用容器。例如,一個解耦的Web應用程序堆棧可能包含三個單獨的容器,每個容器都有自己的唯一鏡像:一個用于管理Web應用程序,一個用于管理數據庫的容器以及一個用于內存中緩存的容器。將每個容器限制為一個進程是一個很好的經驗法則。根據業務規則,使容器保持清潔和模塊化。然后,如果容器相互依賴,則可以使用Docker容器網絡來確保這些容器可以通信。
最小化層數
僅使用RUN、COPY和ADD等指令即可創建圖層。其他指令僅僅是創建臨時的中間鏡像,并且最終不會增加構建的大小。在可能的情況下,我們可以在構建過程中包含其他工具或者調試信息,而無需增加最終鏡像的大小。
利用構建緩存
在構建鏡像時,Docker會逐步執行Dockerfile中的指令,并按順序執行每個指令。在每條指令中,Docker都會在其緩存中搜索要使用的現有鏡像,而不是創建新的重復鏡像。
Docker鏡像通常在構建的過程中遵循以下基本規則:
1、從已在緩存中的父鏡像開始,將下一條指令與從該基本鏡像派生的所有子鏡像進行比較,以查看是否其中一個是使用完全相同的指令構建的。如果不是,則高速緩存無效。在大多數情況下,僅將Dockerfile中的指令與子鏡像之一進行比較就足夠。
2、對于ADD和COPY指令,將檢查鏡像中文件的內容,并為每個文件計算一個校驗標識。在這些校驗標識中通常不考慮文件的最后修改時間和最后訪問時間。在緩存查找期間,將校驗標識與現有鏡像中的進行比較。如果文件中的任何內容(例如內容和元數據)發生了更改,則緩存將無效。
3、除了ADD和COPY命令外,緩存檢查不會查看容器中的文件來確定緩存是否匹配。例如,在處理RUN apt-get -y update命令時,不會檢查容器中更新的文件以確定是否存在緩存命中。在這種情況下,命令字符串用于查找匹配項。
4、緩存無效后,所有后續Dockerfile命令都會生成新鏡像,并且不使用緩存。
在CI管道中優化Docker鏡像構建
前面幾節中提到的所有優化概念對于在CI管道中實施都是有效的。特別是緩存。如果Dockerfile發生了變化,那么利用緩存仍然是減少構建時間的最佳方法。作為CI管道的一部分,這是如何工作的?當使用Docker執行器作為構建作業的運行時,可以利用稱為Docker層緩存(DLC)的功能來加快構建速度。
當構建Docker鏡像是CI流程的常規部分時,DLC是一項很不錯的功能。DLC將保存在作業中創建的鏡像層。DLC會緩存在工作期間構建的任何Docker鏡像的各個層,然后在后續的CircleCI運行中重用未更改的鏡像層,而不是每次都重新構建整個鏡像。
Dockerfile提交的次數越少,鏡像構建步驟將運行得越快。DLC可以與機器執行程序和遠程Docker環境(setup_remote_docker)一起使用。重要的是要注意,DLC僅在使用docker build,docker compose或類似的Docker命令創建自己的Docker鏡像時有用,它不會減少所有構建啟動初始環境所花費的時間。