1. 程式人生 > >Docker鏡像構建的優化總結

Docker鏡像構建的優化總結

因此 行為 tex from 過程 file 要求 語義 一個

Docker鏡像構建的優化總結
隨著我們對docker鏡像的持續使用,在此過程中如果不加以註意並且優化,鏡像的體積會越來越多。很多時候我們在使用docker部署應用時,會發現鏡像的體積至少有1G以上。鏡像體積的增大,不單單會增加磁盤資源與網絡資源的開銷,也會影響應用的部署效率,使得應用的部署時間會越來越長。因此我們需要減少部署鏡像的體積以加快部署效率,降低資源的開銷。而對於鏡像的優化,可以通過對dockerfile的優化來實現。

一、鏡像最小化

1、選擇最精簡的基礎鏡像

選擇體積最小的基礎鏡像可有效降低鏡像體積。如:alpine、busybox等

2、清理鏡像構建的中間產物。

構建鏡像的過程中,當dockerfile的指令執行完成後,刪除鏡像不需要用的的文件。如使用yum安裝組件,最後可使用yum clean all鏡像清理不需要的文件或者使用系統rm命令刪除不需要的源文件等。

3、減少鏡像的層數

鏡像是一個分層存儲的文件,並且鏡像對層數也是有一定數量的限制,當前鏡像的層數最高是127層,如果不多加註意,將會導致鏡像越來越臃腫。在使用dockerfile構建鏡像時,dockerfile中的每一條指令都會生成一個層,因此可以通過合並dockerfile中可合並的指令,減少最終生成鏡像的層數。例如:在dockerfile中使用RUN執行shell命令是,可以用"&&"將多條命令連接起來。

二、構建速度最快化

1、充分利用鏡像構建緩存

我們可以利用構建的緩存來加快鏡像構建速度,Docker構建默認會開啟緩存,緩存生效有三個關鍵點,鏡像父層沒有發生變化,構建指令不變,添加文件校驗和一致。只要一個構建指令滿足這三個條件,這一層鏡像構建就不會再執行,它會直接利用之前構建的結果。某一層的鏡像緩存失效之後,它之後的鏡像層緩存都會失效。我們應該把變化最少的部分放在Dockerfile的前面,這樣可以充分利用鏡像緩存。dockerfile中有可能導致緩存失效的命令WORKDIR、CMD、ENV、ADD等,像這些命令最好放到dockerfile底部,以便在構建鏡像過程中最大限度使用緩存。

2、刪除構建目錄中(默認:Dockerfile所在目錄)不需要用的的文件。

編寫.dockerignore文件過濾構建過程中不必要的文件或者創建單獨的目錄,並且目錄中僅存在鏡像構建過程中需要使用的文件。
Docker 在運行時分為 Docker 引擎(也就是服務端守護進程)和客戶端工具。Docker 的引擎提供了一組 REST API,被稱為 Docker Remote API,而如 docker 命令這樣的客戶端工具,則是通過這組 API 與 Docker 引擎交互,從而完成各種功能。因此,雖然表面上我們好像是在本機執行各種 docker 功能,但實際上,一切都是使用的遠程調用形式在服務端(Docker 引擎)完成。docker build 命令構建鏡像,其實並非在本地構建,而是在服務端,也就是 Docker 引擎中構建的。
構建鏡像時,Docker需要先準備context ,將所有需要的文件收集到進程中。默認的context包含Dockerfile目錄中的所有文件。如果目錄中的存在大量不相關的文件,不僅會導致構建緩慢,而且還會導致鏡像體積增大。
.dockerignore示例如下:
在一個git項目中,我們並不需要.git目錄等內容。可以在.dockerignore文件中加入以下內容:
.git/

.dockerignore 的作用和語法類似於 .gitignore,可以忽略一些不需要的文件,這樣可以有效加快鏡像構建時間,同時減少Docker鏡像的大小。

3、註意優化網絡請求

我們使用一些鏡像源或者在dockerfile中使用互聯網上的url時,去用一些網絡比較好的開源站點,這樣可以節約時間、減少失敗率

三、dockerfile指令優化

1、COPY指令和ADD指令的區別

COPY 復制文件
格式:
COPY <源路徑>... <目標路徑>
COPY ["<源路徑1>",... "<目標路徑>"]
COPY 指令將從構建上下文目錄中 <源路徑> 的文件/目錄復制到新的一層的鏡像內的 <目標路徑> 位置。比如:
COPY package.json /usr/src/app/
<源路徑> 可以是多個,甚至可以是通配符,其通配符規則要滿足 Go 的 filepath.Match 規則,如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目標路徑> 可以是容器內的絕對路徑,也可以是相對於工作目錄的相對路徑(工作目錄可以用 WORKDIR 指令來指定)。目標路徑不需要事先創建,如果目錄不存在會在復制文件前先行創建缺失目錄。
此外,還需要註意一點,使用 COPY 指令,源文件的各種元數據都會保留。比如讀、寫、執行權限、文件變更時間等。這個特性對於鏡像定制很有用。特別是構建相關文件都在使用 Git 進行管理的時候。

ADD 更高級的復制文件

ADD 指令和 COPY 的格式和性質基本一致。但是在 COPY 基礎上增加了一些功能。

比如 <源路徑> 可以是一個 URL,這種情況下,Docker 引擎會試圖去下載這個鏈接的文件放到 <目標路徑> 去。下載後的文件權限自動設置為 600,如果這並不是想要的權限,那麽還需要增加額外的一層 RUN進行權限調整,另外,如果下載的是個壓縮包,需要解壓縮,也一樣還需要額外的一層 RUN 指令進行解壓縮。所以不如直接使用 RUN 指令,然後使用 wget 或者 curl 工具下載,處理權限、解壓縮、然後清理無用文件更合理。因此,這個功能其實並不實用,而且不推薦使用。

如果 <源路徑> 為一個 tar 壓縮文件的話,壓縮格式為 gzip, bzip2 以及 xz 的情況下,ADD 指令將會自動解壓縮這個壓縮文件到 <目標路徑> 去。

在某些情況下,這個自動解壓縮的功能非常有用,比如官方鏡像 ubuntu 中:

FROM scratch
ADD ubuntu-xenial-core-cloudimg-amd64-root.tar.gz /
...
但在某些情況下,如果我們真的是希望復制個壓縮文件進去,而不解壓縮,這時就不可以使用 ADD 命令了。

在 Docker 官方的最佳實踐文檔中要求,盡可能的使用 COPY,因為 COPY 的語義很明確,就是復制文件而已,而 ADD 則包含了更復雜的功能,其行為也不一定很清晰。最適合使用 ADD 的場合,就是所提及的需要自動解壓縮的場合。

另外需要註意的是,ADD 指令會令鏡像構建緩存失效,從而可能會令鏡像構建變得比較緩慢。

因此在 COPY 和 ADD 指令中選擇的時候,可以遵循這樣的原則,所有的文件復制均使用 COPY 指令,僅在需要自動解壓縮的場合使用 ADD。

2、CMD 與 ENTRYPOINT的區別

CMD
CMD 指令設置鏡像中的默認啟動命令和參數. 容器啟動之後, 如果沒有加入任何啟動命令(也就是在鏡像參數之後沒有添加任何內容) 則默認執行鏡像中 CMD 設置的默認的啟動命令

設置啟動命令時, 應該盡量使用 JSON 格式 CMD ["command", "arg1", "arg2"]
例如 nginx 的啟動方式: CMD ["nginx", "-D"]

如果開發者和使用者都不是很熟悉 CMD 和 ENTRYPOINT 的工作原理的情況下, 盡量避免這兩個指令配合使用
例如 Django 的啟動方式: CMD ["python", "manage.py", "runserver", "0.0.0.0:8989"]

相反, 如果開發者和使用者都很熟悉 CMD 和 ENTRYPOINT 的工作原理, 推薦 CMD 作為 ENTRYPOINT 的參數來配套使用

ENTRYPOINT
當需要把容器當做一個命令行工具使用時, 推薦通過 ENTRYPOINT 指令設置鏡像的入口程序

當啟動主程序之前還需要執行大量的前置操作時, 可以將 ENTRYPOINT 的入口指令設置為一個腳本 start.sh

當 dockerfile 中指定了 ENTRYPOINT 的時候, docker run 如果在鏡像之後添加的指令, 那麽這些指令將被當做 ENTRYPOINT 的參數執行
如果 dockerfile 中同時有 CMD 和 ENTRYPOINT 指令, 當 CMD 指令可執行時, 它將在 ENTRYPOINT 之前運行; 如果 CMD 不是可執行的命令, 則將作為 ENTRYPOINT 的命令參數追加

3、WORKDIR

盡量使用絕對路徑
切換目錄的時候盡量使用 WORKDIR, 而不是使用 RUN cd /data

4、USER

如果容器中的應用程序運行時不需要特殊的權限, 可以通過 USER 指令把應用程序的所有者設置為非 root 用戶. 如果該用戶不存在, 首先需要使用 RUN 命令在鏡像中創建用戶.

如果在每次編譯鏡像時, 對用戶的 UID/GID 有要求需要保持一致, 應該在新建用戶和組的時候指定 UID和 GID
在鏡像中避免使用sudo 命令. 應為該命令使用的 TTY 不確定, 對接收信號量也會造成影響. 如果確實需要使用 sudo 功能, 則可是使用 gosu 命令替代
可以用 root 用戶初始化一個 daemon, 然後用非 root 用戶啟動這個 daemon
為了減少鏡像體積, 應該避免不必要的用戶切換

5、EXPOSE

EXPOSE 用來聲明未來容器內需要監聽的端口, 在 bridge 模式下, 這些容器內部的端口會映射到宿主機的端口上, 建議在容器內部不要更改應用原生的端口號

EXPOSE 中只能指定未來容器內部需要暴露的端口, 不能指定未來容器外部與內部端口之間的映射關系, 比如設置 EXPOSE 80:80 是沒有任何意義的

Docker鏡像構建的優化總結