時至今日,大家已經(jīng)能夠從多種Docker支持的存儲驅(qū)動程序中做出選擇,從而確保其與我們的實際環(huán)境以及用例切實吻合——然而,除非深入理解鏡像層(更不用提鏡像與容器本身),否則一般用戶根本不會考慮這方面問題。很明顯,這些簡單而且缺乏吸引力的技術(shù)元素層雖然是構(gòu)成鏡像的基本條件,但卻往往得不到高度關(guān)注——因為閃亮的新型工具往往比基本信息更能抓人眼球。
在今天的文章中,我們將探討鏡像體積及構(gòu)建時間方面的話題——而這兩項工作也已經(jīng)成為用戶們迫切需要實現(xiàn)的目標。
讓我們首先著眼于鏡像與層,對其概念加以闡述:
Docker鏡像是一套由只讀層外加部分元數(shù)據(jù)構(gòu)成的標簽化結(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鏡像與層的定義,并著重強調(diào)其中的語法表達:
Docker鏡像是一套由只讀層外加部分元數(shù)據(jù)構(gòu)成的標簽化結(jié)構(gòu)。
每個層都擁有自己的UUID,而且每個連續(xù)層都建立在其下的層之上。
每個Dockerfile指令都會生成一個新層。
現(xiàn)在,可以明確看到該Dockerfile并沒能優(yōu)化鏡像體積。無論如何,讓我們進行深入探討,看看這些yum緩存是如何被從鏡像層當(dāng)中移除出去的。
不過為了保證文章淺顯易懂的特性,我們首先將關(guān)注范疇限定在AUFS存儲驅(qū)動程序身上。AUFS存儲驅(qū)動程序能夠添加一個疏排文件以覆蓋鏡像中底部只讀層內(nèi)文件的存在,從而將其從層內(nèi)刪除出去。除此之外,大家可以推斷出鏡像的大小相當(dāng)于各層體積的總和,而添加到Dockerfile中的每條附加指令都會進一步增加鏡像的體積。
只要將兩項RUN指令加以合并,我們就能輕松對上面提到的Dockfile進行修復(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 以在Dockerfile所容納的目錄當(dāng)中構(gòu)建一套鏡像。在此之后,我們會發(fā)現(xiàn)兩套鏡像的體積有所不同:
$ 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)建而成,但讓我們進一步查看兩套鏡像各自包含的層:
$ 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
這清楚地表明鏈式命令能幫助我們在對各層進行提交前進行清理工作,不過這并不意味著我們應(yīng)當(dāng)將一切都塞進單一層當(dāng)中。如果大家重新審視以上命令的輸出結(jié)果,就會發(fā)現(xiàn)兩套鏡像的3個最底層擁有著同樣的UUID,這意味著兩套鏡像共享這些層(有鑒于此,docker images命令才能夠報告各鏡像的虛擬體積)。
有人可能會說,如今磁盤空間的使用成本已經(jīng)如此低廉,我們真的有必要如此糾結(jié)于所謂鏡像體積嗎?但除了緩存鏡像層之外,大家還有其它方面需要關(guān)注。其中最重要的一點在于鏡像的構(gòu)建時間。簡單來講,如果大家能夠復(fù)用某個層,則無需對其進行重新構(gòu)建。另外,如果大家的鏡像需要通過網(wǎng)絡(luò)進行傳輸(例如在流程中分階段進行構(gòu)建推進),那么更為袖珍的鏡像體積與緩存層運用能夠顯著節(jié)約傳輸時長(并降低網(wǎng)絡(luò)流量負載),因為需要實際傳輸?shù)溺R像層中有相當(dāng)一部分已經(jīng)被整合在新版本鏡像當(dāng)中。
現(xiàn)在結(jié)論已經(jīng)顯而易見,大家應(yīng)當(dāng)盡可能減少Dockerfile頂層的指令數(shù)量,從而提高緩存復(fù)用比例并努力著眼于底層對Dockerfile進行變更。
考慮到以上各項因素,有些朋友可以認為優(yōu)化程度最高的解決方案應(yīng)當(dāng)是將全部發(fā)生變更的元素塞進各自不同的層當(dāng)中,從而清理每個層的執(zhí)行流程; 但一般來講,這種作法并不會簡化工作負擔(dān)。首先,Docker對層數(shù)做出了限制,即不可超過127個,而且大家最好與上限之間保持一定距離。包含有大量層的Dockerfile既不易于后期維護,也不太可能排除那些不必要的數(shù)據(jù); 相反,其結(jié)果是我們會面對一套非常臃腫的鏡像,且最好將其拆分成多個不同鏡像。而更重要的是,層的實現(xiàn)并非毫無成本,具體取決于我們使用的存儲驅(qū)動程序,由此造成的額外負擔(dān)也有所區(qū)別。舉例來說,在AUFS當(dāng)中,每個層都會在面向鏡像層堆棧中各現(xiàn)有文件的首次寫入時給容器寫入性能造成延遲,特別在文件體積龐大且存在于大量鏡像層之下的情況當(dāng)中。
因此在文章最后,我們需要再次強調(diào):要想切實提升鏡像體積優(yōu)化效果并壓縮構(gòu)建時間,大家必須了解自己想要優(yōu)化什么,并有意識地做出必要妥協(xié)。
1. 如果大家需要了解或者深入掌握容器中的層概念,請點擊此處查看Docker說明文檔。
2. 查看存儲驅(qū)動程序說明文檔以了解與其性能表現(xiàn)相關(guān)的各項細節(jié)信息。
3. 如果大家發(fā)現(xiàn)自己需要處理高強度寫入工作負載,可以考慮使用數(shù)據(jù)分卷(它們會繞開存儲驅(qū)動程序)。