Docker鏡像優(yōu)化指南:如何有效地壓縮鏡像體積并縮短構(gòu)建時間?
譯文時至今日,大家已經(jīng)能夠從多種Docker支持的存儲驅(qū)動程序中做出選擇,從而確保其與我們的實際環(huán)境以及用例切實吻合——然而,除非深入理解鏡像層(更不用提鏡像與容器本身),否則一般用戶根本不會考慮這方面問題。很明顯,這些簡單而且缺乏吸引力的技術(shù)元素層雖然是構(gòu)成鏡像的基本條件,但卻往往得不到高度關(guān)注——因為閃亮的新型工具往往比基本信息更能抓人眼球。
在今天的文章中,我們將探討鏡像體積及構(gòu)建時間方面的話題——而這兩項工作也已經(jīng)成為用戶們迫切需要實現(xiàn)的目標(biāo)。
讓我們首先著眼于鏡像與層,對其概念加以闡述:
- Docker鏡像是一套由只讀層外加部分元數(shù)據(jù)構(gòu)成的標(biāo)簽化結(jié)構(gòu)。
- 每個層都擁有自己的UUID,而且每個連續(xù)層都建立在其下的層之上。
- 每個Dockerfile指令都會生成一個新層。
看起來基本理念非常簡單,且不需要再做過多解釋,不過我曾經(jīng)遇上過這樣一個讓人如墜霧里的Dockerfile:
- FROM centos:7.1.1503
- RUN yum -y install java-1.8.0-openjdk-devel-1:1.8.0.65-2.b17.el7_1.x86_64
- RUN yum clean all
這個Dockerfile到底出了什么問題?這個嘛,其中第二個RUN命令并沒能真正影響到鏡像體積——雖然它看起來確實應(yīng)該有效削減鏡像大小。讓我們重新審視Docker鏡像與層的定義,并著重強(qiáng)調(diào)其中的語法表達(dá):
Docker鏡像是一套由只讀層外加部分元數(shù)據(jù)構(gòu)成的標(biāo)簽化結(jié)構(gòu)。
每個層都擁有自己的UUID,而且每個連續(xù)層都建立在其下的層之上。
每個Dockerfile指令都會生成一個新層。
現(xiàn)在,可以明確看到該Dockerfile并沒能優(yōu)化鏡像體積。無論如何,讓我們進(jìn)行深入探討,看看這些yum緩存是如何被從鏡像層當(dāng)中移除出去的。
不過為了保證文章淺顯易懂的特性,我們首先將關(guān)注范疇限定在AUFS存儲驅(qū)動程序身上。AUFS存儲驅(qū)動程序能夠添加一個疏排文件以覆蓋鏡像中底部只讀層內(nèi)文件的存在,從而將其從層內(nèi)刪除出去。除此之外,大家可以推斷出鏡像的大小相當(dāng)于各層體積的總和,而添加到Dockerfile中的每條附加指令都會進(jìn)一步增加鏡像的體積。
只要將兩項RUN指令加以合并,我們就能輕松對上面提到的Dockfile進(jìn)行修復(fù):
- FROM centos:7.1.1503
- RUN yum -y install java-1.8.0-openjdk-devel-1:1.8.0.65-2.b17.el7_1.x86_64 && \
- yum clean all
下面讓我們構(gòu)建并檢查這兩套鏡像以證明其瘦身效果。大家需要執(zhí)行docker build -t
- $ docker images
- REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
- combined-layers latest defa7a199555 4 seconds ago 407 MB
- separate-layers latest b605eff36c7b About a minute ago 471.8 MB
大家應(yīng)該能夠輕松判斷哪套鏡像是通過哪個Dockerfile構(gòu)建而成,但讓我們進(jìn)一步查看兩套鏡像各自包含的層:
- $ docker history --no-trunc separate-layers
- IMAGE CREATED CREATED BY SIZE
- b605eff36c7b418aa30e315dc0a1d809d08a1ebb8e574e934b11f5ad7cd490dc 2 minutes ago /bin/sh -c yum clean all 2.277 MB
- 4c363330dc9057ce6285be496aa02212759ecfc75c4ad3a9a74e6e2f3dacb1dd 2 minutes ago /bin/sh -c yum -y install java-1.8.0-openjdk-devel-1:1.8.0.65-2.b17.el7_1.x86_64 257.5 MB
- 173339447b7ec3e8cb93edc61f3815ff754ec66cfadf48f1953ab3ead6a754c5 8 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
- 4e1d113aa16e0631795a4b31c150921e35bd1a3d4193b22c3909e29e6f7c718d 8 weeks ago /bin/sh -c #(nop) ADD file:d68b6041059c394e0f95effd6517765405402b4302fe16cf864f658ba8b25a97 in / 212.1 MB
- a2c33fe967de5a01f3bfc3861add604115be0d82bd5192d29fc3ba97beedb831 7 months ago /bin/sh -c #(nop) MAINTAINER The CentOS Project - ami_creator 0 B
- $ docker history --no-trunc combined-layers
- IMAGE CREATED CREATED BY SIZE
- defa7a199555834ac5c906cf347eece7fa33eb8e90b30dfad5f9ab1380988ade 48 seconds ago /bin/sh -c yum -y install java-1.8.0-openjdk-devel-1:1.8.0.65-2.b17.el7_1.x86_64 && yum clean all 195 MB
- 173339447b7ec3e8cb93edc61f3815ff754ec66cfadf48f1953ab3ead6a754c5 8 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0 B
- 4e1d113aa16e0631795a4b31c150921e35bd1a3d4193b22c3909e29e6f7c718d 8 weeks ago /bin/sh -c #(nop) ADD file:d68b6041059c394e0f95effd6517765405402b4302fe16cf864f658ba8b25a97 in / 212.1 MB
- a2c33fe967de5a01f3bfc3861add604115be0d82bd5192d29fc3ba97beedb831 7 months ago /bin/sh -c #(nop) MAINTAINER The CentOS Project - ami_creator 0 B
這清楚地表明鏈?zhǔn)矫钅軒椭覀冊趯Ω鲗舆M(jìn)行提交前進(jìn)行清理工作,不過這并不意味著我們應(yīng)當(dāng)將一切都塞進(jìn)單一層當(dāng)中。如果大家重新審視以上命令的輸出結(jié)果,就會發(fā)現(xiàn)兩套鏡像的3個***層擁有著同樣的UUID,這意味著兩套鏡像共享這些層(有鑒于此,docker images命令才能夠報告各鏡像的虛擬體積)。
有人可能會說,如今磁盤空間的使用成本已經(jīng)如此低廉,我們真的有必要如此糾結(jié)于所謂鏡像體積嗎?但除了緩存鏡像層之外,大家還有其它方面需要關(guān)注。其中最重要的一點在于鏡像的構(gòu)建時間。簡單來講,如果大家能夠復(fù)用某個層,則無需對其進(jìn)行重新構(gòu)建。另外,如果大家的鏡像需要通過網(wǎng)絡(luò)進(jìn)行傳輸(例如在流程中分階段進(jìn)行構(gòu)建推進(jìn)),那么更為袖珍的鏡像體積與緩存層運用能夠顯著節(jié)約傳輸時長(并降低網(wǎng)絡(luò)流量負(fù)載),因為需要實際傳輸?shù)溺R像層中有相當(dāng)一部分已經(jīng)被整合在新版本鏡像當(dāng)中。
現(xiàn)在結(jié)論已經(jīng)顯而易見,大家應(yīng)當(dāng)盡可能減少Dockerfile頂層的指令數(shù)量,從而提高緩存復(fù)用比例并努力著眼于底層對Dockerfile進(jìn)行變更。
考慮到以上各項因素,有些朋友可以認(rèn)為優(yōu)化程度***的解決方案應(yīng)當(dāng)是將全部發(fā)生變更的元素塞進(jìn)各自不同的層當(dāng)中,從而清理每個層的執(zhí)行流程; 但一般來講,這種作法并不會簡化工作負(fù)擔(dān)。首先,Docker對層數(shù)做出了限制,即不可超過127個,而且大家***與上限之間保持一定距離。包含有大量層的Dockerfile既不易于后期維護(hù),也不太可能排除那些不必要的數(shù)據(jù); 相反,其結(jié)果是我們會面對一套非常臃腫的鏡像,且***將其拆分成多個不同鏡像。而更重要的是,層的實現(xiàn)并非毫無成本,具體取決于我們使用的存儲驅(qū)動程序,由此造成的額外負(fù)擔(dān)也有所區(qū)別。舉例來說,在AUFS當(dāng)中,每個層都會在面向鏡像層堆棧中各現(xiàn)有文件的***寫入時給容器寫入性能造成延遲,特別在文件體積龐大且存在于大量鏡像層之下的情況當(dāng)中。
因此在文章***,我們需要再次強(qiáng)調(diào):要想切實提升鏡像體積優(yōu)化效果并壓縮構(gòu)建時間,大家必須了解自己想要優(yōu)化什么,并有意識地做出必要妥協(xié)。
1. 如果大家需要了解或者深入掌握容器中的層概念,請點擊此處查看Docker說明文檔。
2. 查看存儲驅(qū)動程序說明文檔以了解與其性能表現(xiàn)相關(guān)的各項細(xì)節(jié)信息。
3. 如果大家發(fā)現(xiàn)自己需要處理高強(qiáng)度寫入工作負(fù)載,可以考慮使用數(shù)據(jù)分卷(它們會繞開存儲驅(qū)動程序)。
原文標(biāo)題:Optimizing Docker Images for Image Size and Build Time
【51CTO.com獨家譯稿,合作站點轉(zhuǎn)載請注明來源】