天天看點

Docker鏡像優化指南:如何有效地壓縮鏡像體積并縮短建構時間?

時至今日,大家已經能夠從多種docker支援的存儲驅動程式中做出選擇,進而確定其與我們的實際環境以及用例切實吻合——然而,除非深入了解鏡像層(更不用提鏡像與容器本身),否則一般使用者根本不會考慮這方面問題。很明顯,這些簡單而且缺乏吸引力的技術元素層雖然是構成鏡像的基本條件,但卻往往得不到高度關注——因為閃亮的新型工具往往比基本資訊更能抓人眼球。

Docker鏡像優化指南:如何有效地壓縮鏡像體積并縮短建構時間?

在今天的文章中,我們将探讨鏡像體積及建構時間方面的話題——而這兩項工作也已經成為使用者們迫切需要實作的目标。

讓我們首先着眼于鏡像與層,對其概念加以闡述:

docker鏡像是一套由隻讀層外加部分中繼資料構成的标簽化結構。

每個層都擁有自己的uuid,而且每個連續層都建立在其下的層之上。

每個dockerfile指令都會生成一個新層。

看起來基本理念非常簡單,且不需要再做過多解釋,不過我曾經遇上過這樣一個讓人如墜霧裡的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指令并沒能真正影響到鏡像體積——雖然它看起來确實應該有效削減鏡像大小。讓我們重新審視docker鏡像與層的定義,并着重強調其中的文法表達:

現在,可以明确看到該dockerfile并沒能優化鏡像體積。無論如何,讓我們進行深入探讨,看看這些yum緩存是如何被從鏡像層當中移除出去的。

不過為了保證文章淺顯易懂的特性,我們首先将關注範疇限定在aufs存儲驅動程式身上。aufs存儲驅動程式能夠添加一個疏排檔案以覆寫鏡像中底部隻讀層内檔案的存在,進而将其從層内删除出去。除此之外,大家可以推斷出鏡像的大小相當于各層體積的總和,而添加到dockerfile中的每條附加指令都會進一步增加鏡像的體積。

隻要将兩項run指令加以合并,我們就能輕松對上面提到的dockfile進行修複:

run yum -y install java-1.8.0-openjdk-devel-1:1.8.0.65-2.b17.el7_1.x86_64 &&

yum clean all

下面讓我們建構并檢查這兩套鏡像以證明其瘦身效果。大家需要執行docker build -t 以在dockerfile所容納的目錄當中建構一套鏡像。在此之後,我們會發現兩套鏡像的體積有所不同:

$ 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

大家應該能夠輕松判斷哪套鏡像是通過哪個dockerfile建構而成,但讓我們進一步檢視兩套鏡像各自包含的層:

$ 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

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

這清楚地表明鍊式指令能幫助我們在對各層進行送出前進行清理工作,不過這并不意味着我們應當将一切都塞進單一層當中。如果大家重新審視以上指令的輸出結果,就會發現兩套鏡像的3個最底層擁有着同樣的uuid,這意味着兩套鏡像共享這些層(有鑒于此,docker

images指令才能夠報告各鏡像的虛拟體積)。

有人可能會說,如今磁盤空間的使用成本已經如此低廉,我們真的有必要如此糾結于所謂鏡像體積嗎?但除了緩存鏡像層之外,大家還有其它方面需要關注。其中最重要的一點在于鏡像的建構時間。簡單來講,如果大家能夠複用某個層,則無需對其進行重新建構。另外,如果大家的鏡像需要通過網絡進行傳輸(例如在流程中分階段進行建構推進),那麼更為袖珍的鏡像體積與緩存層運用能夠顯著節約傳輸時長(并降低網絡流量負載),因為需要實際傳輸的鏡像層中有相當一部分已經被整合在新版本鏡像當中。

現在結論已經顯而易見,大家應當盡可能減少dockerfile頂層的指令數量,進而提高緩存複用比例并努力着眼于底層對dockerfile進行變更。

考慮到以上各項因素,有些朋友可以認為優化程度最高的解決方案應當是将全部發生變更的元素塞進各自不同的層當中,進而清理每個層的執行流程;

但一般來講,這種作法并不會簡化工作負擔。首先,docker對層數做出了限制,即不可超過127個,而且大家最好與上限之間保持一定距離。包含有大量層的dockerfile既不易于後期維護,也不太可能排除那些不必要的資料;

相反,其結果是我們會面對一套非常臃腫的鏡像,且最好将其拆分成多個不同鏡像。而更重要的是,層的實作并非毫無成本,具體取決于我們使用的存儲驅動程式,由此造成的額外負擔也有所差別。舉例來說,在aufs當中,每個層都會在面向鏡像層堆棧中各現有檔案的首次寫入時給容器寫入性能造成延遲,特别在檔案體積龐大且存在于大量鏡像層之下的情況當中。

是以在文章最後,我們需要再次強調:要想切實提升鏡像體積優化效果并壓縮建構時間,大家必須了解自己想要優化什麼,并有意識地做出必要妥協。

1. 如果大家需要了解或者深入掌握容器中的層概念,請點選此處檢視docker說明文檔。

2. 檢視存儲驅動程式說明文檔以了解與其性能表現相關的各項細節資訊。

3. 如果大家發現自己需要處理高強度寫入工作負載,可以考慮使用資料分卷(它們會繞開存儲驅動程式)。

本文轉自d1net(轉載)