優(yōu)化docker鏡像體積的方法詳解
關(guān)注鏡像體積有關(guān)的關(guān)鍵點(diǎn)如下:
RUN
、COPY
和ADD
指令會在已有鏡像層的基礎(chǔ)上創(chuàng)建一個新的鏡像層,執(zhí)行指令產(chǎn)生的所有文件系統(tǒng)變更會在指令結(jié)束后作為一個鏡像層整體提交。- 鏡像層具有
copy-on-write
的特性,如果去更新其他鏡像層中已存在的文件,會先將其復(fù)制到新的鏡像層中再修改,造成雙倍的文件空間占用。 - 如果去刪除其他鏡像層的一個文件,只會在當(dāng)前鏡像層生成一個該文件的刪除標(biāo)記,并不會減少整個鏡像的實(shí)際體積。
上述理論可以通過如下 Dockerfile 來驗(yàn)證:
FROM alpine:latest COPY resource.tar / RUN touch /resource.tar RUN rm -f /resource.tar ENTRYPOINT ["/bin/ash"]
我們在 Dockerfile 中簡單地添加、修改和刪除某個資源文件,然后構(gòu)建鏡像查看其鏡像層信息:
$ docker build -t test-image -f Dockerfile . $ docker history test-image:latest IMAGE CREATED CREATED BY SIZE COMMENT 95f1695b2904 About a minute ago /bin/sh -c #(nop) ENTRYPOINT ["/bin/ash"] 0B 1780448c656f About a minute ago /bin/sh -c rm -f /resource.tar 0B a85d29bf7738 About a minute ago /bin/sh -c touch /resource.tar 135MB 6dac335fa653 4 minutes ago /bin/sh -c #(nop) COPY file:66065d6e23e0bc52… 135MB e66264b98777 7 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 7 weeks ago /bin/sh -c #(nop) ADD file:8e81116368669ed3d… 5.53MB
在 docker history
的輸出結(jié)果中可以看到:
RUN touch /resource.tar
指令只是修改了文件的元信息,但依然將整個文件拷貝到了新的鏡像層中。RUN rm -f /resource.tar
指令雖然刪除了文件,并且該文件在運(yùn)行容器時不可見,但依然在前兩個鏡像層中以及最終的鏡像中存在。
分析工具
給代碼做性能調(diào)優(yōu)時,首先要借助 Profiling 工具找到代碼的性能瓶頸,對于優(yōu)化鏡像體積也是如此。下面介紹兩個可以分析鏡像體積的工具:
docker history
docker 自帶的 docker history
命令,該命令可以展示所有鏡像層的創(chuàng)建時間、指令以及體積等較為基礎(chǔ)的信息,但對于復(fù)雜的鏡像則有些乏力。使用方式見上方的示例。
dive
第三方的 dive
工具,該工具可以分析鏡像層組成,并列出每個鏡像層所包含的文件列表,可以很方便地定位到影響鏡像體積的構(gòu)建指令以及具體文件。
以 golang:1.16
鏡像為例,首先安裝 dive,然后執(zhí)行 dive golang:1.16
,輸出如下:
如上圖所示,在左側(cè)選中鏡像層后,在右側(cè)的文件樹視圖中可以清晰地看到該層的具體文件,并能夠篩選相比上一層新增、更新或刪除的文件。在選中的鏡像層中,由于執(zhí)行了 apt-get
安裝編譯依賴,因此在 /usr/lib
目錄下新增了 150MB 依賴庫文件。
優(yōu)化技巧
下面介紹一些優(yōu)化效果比較顯著的優(yōu)化技巧。
分階段構(gòu)建與從零構(gòu)建
分階段構(gòu)建(multi-stage builds)和從零構(gòu)建(build from scratch)是優(yōu)化鏡像體積的基本手段和必備技巧。該技巧將鏡像構(gòu)建過程區(qū)分為構(gòu)建和運(yùn)行環(huán)境,在構(gòu)建環(huán)境安裝編譯器等依賴并編譯所需的二進(jìn)制包,然后將其復(fù)制到僅包含必要運(yùn)行依賴的運(yùn)行環(huán)境中。
對 golang 這類能夠編譯靜態(tài)二進(jìn)制文件的語言來說分階段構(gòu)建的效果尤為明顯,我們可以將編譯產(chǎn)生的二進(jìn)制文件放到 scratch 鏡像中運(yùn)行(scratch 是一個特殊的空鏡像):
FROM golang COPY hell0.go . ENV CGO_ENABLED=0 RUN go build hello.go FROM scratch COPY --from=0 /go/hello . CMD ["./hello"]
如果直接使用 golang 鏡像作為運(yùn)行環(huán)境,其鏡像體積通常接近 1 個 G,其中大部分文件都不是在運(yùn)行容器時所必要的。 將編譯結(jié)果拷貝到運(yùn)行環(huán)境后,體積只有幾十 kb~mb 不等,如果需要在運(yùn)行容器中保留基本的系統(tǒng)工具,可以考慮使用 alpine 鏡像作為運(yùn)行環(huán)境。
關(guān)于分階段構(gòu)建和從零構(gòu)建的更多細(xì)節(jié)可參考 Docker 官方文檔中的 Use multi-stage builds 和 Create a simple parent image using scratch。
避免產(chǎn)生無用的文檔或緩存
docker 鏡像不應(yīng)該包含文檔、緩存等對運(yùn)行容器沒有作用的內(nèi)容。
docker 鏡像不應(yīng)該包含文檔、緩存等對運(yùn)行容器沒有作用的內(nèi)容。
避免在本地保留安裝緩存。
大部分包管理器會在安裝時緩存下載的資源以備之后使用,以 pip 為例,會將下載的響應(yīng)和構(gòu)建的中間文件保存在
~/.cache/pip
目錄,應(yīng)使用--no-cache-dir
選項禁用默認(rèn)的緩存行為。避免安裝文檔。
部分包管理器提供了選項可以不安裝附帶的文檔,如 dnf 可使用
--nodocs
選項。避免緩存包索引。
部分包管理器在執(zhí)行安裝之前,會嘗試查詢所有已啟用倉庫的包列表、版本等元信息緩存在本地作為索引。個別倉庫的索引緩存可達(dá)到 150 M 以上。 我們應(yīng)該僅在安裝包時查詢索引,并在安裝完成后清理,不應(yīng)該在單獨(dú)的指令中執(zhí)行
yum makecache
這類緩存索引的命令。
及時清理不需要的文件
運(yùn)行容器時不需要的文件,一定要在創(chuàng)建的同一層清理,否則依然會保留在最終的鏡像中。
通過包管理安裝包,通常會產(chǎn)生大量的緩存文件,一定要在同一 RUN
指令的結(jié)尾處立刻清理。在安裝依賴數(shù)量較多時,可以節(jié)省大量的緩存空間。
以 dnf
為例:
RUN dnf install -y --nodocs <PACKAGES> \ && dnf clean all \ && rm -rf /var/cache/dnf
以 apt
為例:
RUN apt-get update \ && apt-get install -y <PACKAGES> \ && rm -rf /var/lib/apt/lists/* # 官方的 ubuntu/debian 鏡像 apt-get 會在安裝后自動執(zhí)行 clean 命令
合并多個鏡像層
上文解釋過,應(yīng)該避免在不同鏡像層中更新文件而造成額外的體積占用。當(dāng)構(gòu)建的層數(shù)很多且執(zhí)行指令較復(fù)雜時,很難避免在不同的鏡像層中更新文件,可通過以下手段精簡這部分額外體積:
在最終生成鏡像時將所有鏡像層合并成一層,在 docker build
命令中使用 —squash
即可實(shí)現(xiàn)(需要開啟 docker daemon 的實(shí)驗(yàn)性功能)。以本文開頭的 Dockerfile 為例:
$ docker build -t squash-image --squash -f Dockerfile . $ docker history squash-image IMAGE CREATED CREATED BY SIZE COMMENT 55ded8881d63 9 hours ago 0B merge sha256:95f1695b29044522250de1b0c1904aaf8670b991ec1064d086c0c15865051d5d to sha256:e66264b98777e12192600bf9b4d663655c98a090072e1bab49e233d7531d1294 <missing> 11 hours ago /bin/sh -c #(nop) ENTRYPOINT ["/bin/ash"] 0B <missing> 11 hours ago /bin/sh -c rm -f /resource.tar 0B <missing> 11 hours ago /bin/sh -c touch /resource.tar 0B <missing> 11 hours ago /bin/sh -c #(nop) COPY file:66065d6e23e0bc52… 0B <missing> 7 weeks ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B <missing> 7 weeks ago /bin/sh -c #(nop) ADD file:8e81116368669ed3d… 5.53MB
最終生成的鏡像只有一個鏡像層,包含最后實(shí)際存在的文件系統(tǒng),在合并所有鏡像層的過程中,相當(dāng)于禁用了
copy-on-write
特性。這種做法的壞處在于,鏡像在保存和分發(fā)時是可以復(fù)用鏡像層的,推送鏡像時會跳過鏡像倉庫已存在的鏡像層,拉取鏡像時會跳過本地已拉取過的鏡像層,而合并成一層后則失去了這種優(yōu)勢。
對于可能和其他共用鏡像層的場景,可以采取下面一種方式。
分階段構(gòu)建,將部分中間鏡像層壓縮成一層作為基礎(chǔ)鏡像。 在開發(fā)團(tuán)隊內(nèi)部,我們往往會在官方鏡像的基礎(chǔ)上添加或更新部分依賴,然后作為團(tuán)隊內(nèi)部統(tǒng)一使用的基礎(chǔ)鏡像,這種復(fù)用方式可以大大減少實(shí)際占用的鏡像體積。 更進(jìn)一步,我們可以將這類基礎(chǔ)鏡像壓縮成一層。下面以 golang 官方鏡像為例:
FROM golang:1.16 as base FROM scratch COPY --from=base / / ENTRYPOINT ["/bin/bash"]
壓縮成一層后,
golang:1.16
的鏡像體積從 919MB 變成 913MB,官方鏡像已經(jīng)做了很多優(yōu)化所以節(jié)省空間十分有限,但對于開發(fā)團(tuán)隊內(nèi)部制作的基礎(chǔ)鏡像,這種優(yōu)化往往會帶來意外驚喜。
復(fù)制文件的同時修改元信息
先將文件添加到鏡像內(nèi),然后再修改文件的執(zhí)行權(quán)限和所屬用戶,這類 COPY-RUN 指令在 Dockerfile 中十分常見:
COPY output/hello /usr/bin/hello RUN chmod +x /usr/bin/hello && chown normal:normal /usr/bin/hello
但修改文件元信息也會將文件復(fù)制到新的鏡像層,以上指令會產(chǎn)生兩份相同的文件。在文件體積較大時,會顯著增加整個鏡像的體積。 事實(shí)上,我們可以在復(fù)制文件的同時完成對文件元信息的修改,COPY
和 ADD
指令都提供了修改元信息的 --chmod
和 --chown
選項:
COPY --chmod=755 --chown=normal:normal output/hello /usr/bin/hello
--chmod
特性目前還未添加到官方文檔,使用前需要開啟 docker 的 buildkit 特性(在 docker build
命令前添加 DOCKER_BUILDKIT=1
即可),目前只支持 --chmod=755
和 --chmod=0755
這種設(shè)置方法,不支持 --chmod=+x
。
注:經(jīng)測試,當(dāng)使用 ADD
指令且源文件為下載鏈接時 --chmod
選項不起作用,不清楚這是 docker 的 bug 還是 feature。解決方案是直接使用 RUN
指令 wget + chmod
來替代 ADD
。
以上就是優(yōu)化docker鏡像體積的方法詳解的詳細(xì)內(nèi)容,更多關(guān)于docker鏡像體積優(yōu)化的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Linux服務(wù)器安裝Docker,啟動失敗問題的解決
這篇文章主要介紹了Linux服務(wù)器安裝Docker,啟動失敗問題的解決方案,具有很好的參考價值,希望對大家有所幫助。2023-04-04Docker容器遷移Oracle到MySQL的實(shí)現(xiàn)方法
本文主要介紹了Docker容器遷移Oracle到MySQL的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07docker沒有錯誤日志,鏡像服務(wù)卻啟動不成功的問題以及排查方式
這篇文章主要介紹了docker沒有錯誤日志,鏡像服務(wù)卻啟動不成功的問題以及排查方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-05-05在docker容器中調(diào)用和執(zhí)行宿主機(jī)的docker操作
這篇文章主要介紹了在docker容器中調(diào)用和執(zhí)行宿主機(jī)的docker操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-11-11Docker+Jenkins+Gitee自動化部署maven項目的實(shí)現(xiàn)
本文主要介紹了Docker+Jenkins+Gitee自動化部署maven項目的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-06-06