docker - image/layer 文件管理


Dockerfile由多條指令構成,隨着深入研究Dockerfile與鏡像的關系,很快大家就會發現,Dockerfile中的每一條指令都會對應於Docker鏡像中的一層。

 繼續以如下Dockerfile為例:

FROM ubuntu:14.04
ADD run.sh /
VOLUME /data
CMD ["./run.sh"]

通過docker build以上Dockerfile的時候,會在Ubuntu:14.04鏡像基礎上,添加三層獨立的鏡像,依次對應於三條不同的命令。鏡像示意圖如下:

 

Dockerfile中命令與鏡像層一一對應,那么是否意味着docker build完畢之后,鏡像的總大小=每一層鏡像的大小總和呢?答案是肯定的。依然以上圖為例:如果ubuntu:14.04鏡像的大小為200MB,而run.sh的大小為5MB,那么以上三層鏡像從上到下,每層大小依次為0、0以及5MB,那么最終構建出的鏡像大小的確為0+0+5+200=205MB。

雖然最終鏡像的大小是每層鏡像的累加,但是需要額外注意的是:Docker鏡像的大小並不等於容器中文件系統內容的大小(不包括掛載文件,/proc、/sys等虛擬文件)。個中緣由,就和聯合文件系統有很大的關系了。

假設本地鏡像存儲中只有一個ubuntu:14.04的鏡像,我們以兩個Dockerfile來說明鏡像復用:

FROM ubuntu:14.04
RUN apt-get update

FROM ubuntu:14.04
ADD compressed.tar /

假設最終docker build構建出來的鏡像名分別為image1和image2,由於兩個Dockerfile均基於ubuntu:14.04,因此,image1和image2這兩個鏡像均復用了鏡像ubuntu:14.04。 假設RUN apt-get update修改的文件系統內容為20MB,最終本地三個鏡像的大小關系應該如下:

ubuntu:14.04: 200MB

image1:200MB(ubuntu:14.04)+20MB=220MB

image2:200MB(ubuntu:14.04)+100MB=300MB

如果僅僅是單純的累加三個鏡像的大小,那結果應該是:200+220+300=720MB,但是由於鏡像復用的存在,實際占用的磁盤空間大小是:200+20+100=320MB,足足節省了400MB的磁盤空間。在此,足以證明鏡像復用的巨大好處。

下面具體分析一下docker image 文件系統:

docker支持多種graphDriver,包括vfs、devicemapper、overlay、overlay2、aufs等等,其中最常用的就是aufs了,但隨着linux內核3.18把overlay納入其中,overlay的地位變得更重目前docker默認的存儲類型就是overlay2,docker版本是1.8,如下

docker默認的存儲目錄是/var/lib/docker,我們只關心image和overlay2,image:主要存放鏡像中layer層的元數據overlay2:各層的具體信息

找一個實驗鏡像:

 這里的關鍵地方是imagedblayerdb目錄,看這個目錄名字,很明顯就是專門用來存儲元數據的地方,那為什么區分image和layer呢?因為在docker中,image是由多個layer組合而成的,換句話就是layer是一個共享的層,可能有多個image會指向某個layer。

那如何才能確認image包含了哪些layer呢?答案就在imagedb這個目錄中去找。比如上面啟動的nginx容器,我們可以先找到這個容器對應的鏡像:

 可以看到,imageID是573040df7059,再次記住這個id,我們打印/var/lib/docker/image/overlay2/imagedb/content/sha256這個目錄:

 第一行的573040df70596555bbbbd2bb113272101b4d7c873b8eed075fcbc0a951636094正是記錄鏡像元數據的文件,接下來cat一下這個文件,得到一個長長的json:

cat 573040df70596555bbbbd2bb113272101b4d7c873b8eed075fcbc0a951636094 | python  -mjson.tool

 可以看到rootfs的diff_ids是一個包含了3個元素的數組,其實這3個元素正是組成鏡像的9個layerID,從上往下看,就是底層到頂層,也就是說a2ae92ffcd29f7ededa0320f4a4fd709a723beae9a4e681696874932db7aee2c是image的最底層。既然得到了組成這個image的所有layerID,那么我們就可以帶着這些layerID去尋找對應的layer了。 接下來,我們返回到上一層的layerdb中,先打印一下這個目錄:

 在這里,我們僅僅發現a2ae92ffcd29f7ededa0320f4a4fd709a723beae9a4e681696874932db7aee2c這個最底層的layer,那么剩余的ayer為什么會沒有呢?那是因為docker使用了chainID的方式去保存這些layer,簡單來說就是chainID=sha256sum(H(chainID) diffid),也就是a2ae92ffcd29f7ededa0320f4a4fd709a723beae9a4e681696874932db7aee2c..的上一層的sha256 id是:

 echo -n "sha256:a2ae92ffcd29f7ededa0320f4a4fd709a723beae9a4e681696874932db7aee2c sha256:0eb22bfb707db44a8e5ba46a21b2ac59c83dfa946228f04be511aba313bdc090" |sha256sum -

這個時候,你能看到4cbc0ad7007fe8c2dfcf2cdc82fdb04f35070f0e2a04d5fa35093977a3cc1693這個layer層的目錄了吧?依次類推,我們就能找出所有的layerID的組合。

但是上面我們也說了,/var/lib/docker/image/overlay2/layerdb存的只是元數據,那么真實的rootfs到底存在哪里呢?其中cache-id就是我們關鍵所在了。我們打印一擦cat /var/lib/docker/image/overlay2/layerdb/sha256/4cbc0ad7007fe8c2dfcf2cdc82fdb04f35070f0e2a04d5fa35093977a3cc1693/cache-id:

 引申一下:

docker save:

Produces a tarred repository to the standard output stream. Contains all parent layers, and all tags + versions, or specified repo:tag, for each argument provided.

docker load:

如果load前有相同的layer層,實際還會導入這么多嗎,例如多個jar程序,都是基於java鏡像構建。

Docker的“層”解釋了為什么Docker鏡像只在第一次下載時那么慢,而之后的鏡像都很快,並且明明每份鏡像看起來都幾百兆,但是最終機器上的硬盤缺沒有占用那么多的原因。更小的磁盤空間、更快的加載速度,讓Docker的復用性有了非常顯著的提升。

參考:

https://blog.51cto.com/u_12182612/2476386

https://blog.csdn.net/shlazww/article/details/47375009

https://www.cnblogs.com/powertoolsteam/p/14954314.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM