天天看點

Docker實戰-鏡像和容器的存儲結構

一、鏡像、容器和存儲驅動的關系

    前面也已經講過說,鏡像是程式和檔案的集合,容器是鏡像的運作執行個體。

    Docker為了節約存儲空間共享資料會對鏡像和容器進行分層,不同鏡像可以共享相同資料,并且在鏡像上為容器配置設定一個RW層來加快容器的啟動順序

1. 鏡像層和容器層

    每個鏡像都由多個鏡像層組成,從下往上以棧的方式組合在一起形成容器的根檔案系統,Docker的存儲驅動用于管理這些鏡像層,對外提供單一的檔案系統

Docker實戰-鏡像和容器的存儲結構

    當容器啟動時,Docker就會建立thin類型的可讀寫容器層,使用預配置設定存儲空間,也就是開始時并不配置設定存儲空間,當需要建立檔案或修改檔案時,才從存儲池中配置設定一部分存儲空間

    每個容器運作時都有自己的容器層,儲存容器運作相關資料(所有檔案變化資料),因為鏡像層是隻讀的,是以多個容器可以共享同一個鏡像

    删除容器時,Docker Daemon會删除容器層,保留鏡像層

2. 鏡像存儲方式

    為了差別鏡像層,Docker為每個鏡像層都計算了UUID,在Docker 1.10之前根據鏡像層中的資料産生一個随機碼作為UUID;Docker 1.10版本中則是根據鏡像層中的資料使用加密雜湊演算法生成UUID,這種方式避免了UUID沖突并且也保證了在pull、push、load等操作中的鏡像資料完整性,而容器層仍然是采用随機數方式生成UUID

    在Docker 1.10版本前,鏡像之間不能共享鏡像層,而在Docker 1.10版本中,隻要鏡像層的資料相同就可以在不同鏡像之間共享。當Docker更新到1.10版本後,主控端上可能存在以前下載下傳的鏡像仍使用舊的存儲方式,當Docker 1.10版本的Docker Daemon第一次啟動時會自動遷移鏡像,使用加密雜湊演算法重新計算每個鏡像層的UUID,當重新計算UUID會花費很長時間并且遷移過程中Docker Daemon不能響應其他Docker指令,在實際中并不推薦

    可以通過Docker 官方的鏡像更新工具完成這項工作,這樣更新Docker後,Docker Daemon看可以立即響應Docker指令

過程描述:

          下載下傳遷移工具的鏡像 docker pull docker/v1.10-migrator

          啟動鏡像 docker run –rm -v /var/lib/docker:/var/lib/docker docker/v1.10-migrator

提示:在啟動鏡像時,需要把主控端存放鏡像的目錄挂載到容器中即/var/lib/docker

3. 寫時複制政策(Copy On Write)

    Docker中的存儲驅動用于管理鏡像層和容器層,不同的存儲驅動采用不同的算法和管理方式,在管理中使用的兩大技術是棧層式管理和寫時複制

    寫時複制采用了共享和複制技術,針對相同的資料系統隻保留一份,所有操作都通路這一份資料。當有操作需要修改或添加資料時,作業系統會把這部分資料複制到新的地方再進行修改或添加,而其他操作仍然通路原資料區資料,通過這項技術節約了鏡像的存儲空間,加快了系統啟動時間

(1)使用共享技術減少鏡像存儲空間

    所有鏡像層和容器層都儲存在主控端的檔案系統/var/lib/docker/中,由存儲驅動進行管理

Docker實戰-鏡像和容器的存儲結構

    在拉取ubutnu的時候可以看到有如下輸出,表明該版本Ubuntu由五個鏡像層組成,下載下傳鏡像時實際上就是下載下傳了這五個鏡像層,這些鏡像層都在主控端的檔案系統中,每層都有獨立的目錄,在Docker 1.10之前的版本中,目錄的名字和鏡像的UUID相同,而Docker 1.10後則采用了新的存儲方式

Docker實戰-鏡像和容器的存儲結構

    可以看到目錄名和下載下傳鏡像的UUID并不相同

    盡管存儲方式不盡相同,但在所有版本的Docker中都可以共享鏡像層。在下載下傳鏡像時,Docker Daemon會檢查景象中的鏡像層與主控端檔案系統中的鏡像層進行對比,如果存在則不下載下傳,隻下載下傳不存在的鏡像層

Docker實戰-鏡像和容器的存儲結構

    通過Dockfile建立了一個Changed-ubuntu鏡像,通過docker history可以檢視鏡像的鏡像層及鏡像大小,可以看到建立的鏡像層占用空間為0B,也就是說changed-ubuntu鏡像隻會占用額外的一丢丢空間而不需要為其他的五個鏡像層配置設定存儲空間

Docker實戰-鏡像和容器的存儲結構

    由此可以看出,Docker通過共享技術,非常節約存儲空間

(2)使用複制技術加快容器啟動時間

    容器中所有的寫操作都是發生在容器層,而下面的鏡像層都是隻讀的。當容器修改檔案時,Docker通過存儲驅動,發起一個寫時複制操作

    如果容器中存在許多修改檔案或建立檔案相關操作會使得容器占用更多存儲空間(容器修改時都會将原有内容複制到容器層,不同存儲驅動複制的基本機關不同),當寫操作數量較多時,最好使用挂載卷(挂載卷的讀寫操作都會繞過存儲驅動)

    但這種複制的系統開銷僅發生一次,之後的操作都是在容器層中完成,不會造成額外開銷

    當啟動容器時,Docker Daemon隻需要為容器建立一個可寫的資料層,而不用複制所有鏡像層,進而使得容器啟動時間減少

4. 資料卷和存儲驅動

    Docker使用資料卷(主控端的檔案或目錄)來保證資料持久性,在啟動容器時,會把資料卷挂載到容器中,所有資料卷讀寫操作直接工作在主控端的檔案系統,不受存儲驅動管理

    删除容器時,資料卷的資料儲存在主控端中,不會删除;所有不在資料卷的資料都會被删除

    存儲驅動管理所有Docker Daemon生成的容器,每種存儲驅動都是基于Linux檔案系統或卷管理工具,通過指令

docker info

可以檢視目前使用的存儲驅動

Docker實戰-鏡像和容器的存儲結構

    本機Docker Daemon使用的存儲驅動為Overlay,後端檔案系統為extfs,即Overlay存儲驅動工作在extfs檔案系統上

    在啟動Docker Daemon時可以通過–storage-driver=<驅動名>來設定存儲驅動,常見的檔案驅動有AUFS、Devicemapper、Btrfs、ZFS、Overlay … …

    在選擇存儲驅動的時候,需要考慮穩定性和成熟性,不同Linux發行版本安裝Docker時會根據作業系統的不同選擇對應的存儲驅動,所有預設的存儲驅動對于目前機器是最穩定的

二、AUFS存儲驅動

    AUFS是一種Union FS,它将不同的目錄合并為一個目錄做成一個虛拟檔案系統

    AUFS是Docker最早使用的存儲驅動,非常穩定,但是很多Linux發行版本都不支援AUFS

1. AUFS中的鏡像

    AUFS使用聯合挂載技術,通過棧的方式将目錄組裝起來挂載在一個單一的挂載點對外提供服務,其中每個目錄都叫一個分支

    在Docker中,AUFS的每個目錄都對應了鏡像中的一層,聯合挂載點對外提供一個統一的通路路徑

Docker實戰-鏡像和容器的存儲結構

2. AUFS中的容器讀寫

    AUFS工作在檔案層,也就是說即使檔案隻有一丢丢改動,AUFS的寫時複制也會複制整個檔案,當檔案特别大的時候或目錄層級很深時,對容器性能影響顯著;在複制檔案前,如果檔案放在底層的鏡像層中,那麼搜尋檔案也會花費大量時間,檔案的複制隻會進行一次

3. AUFS中删除檔案

    鏡像是隻讀的,無法删除其中檔案,AUFS在删除一個檔案時會在容器中生成一個空白檔案覆寫鏡像層中的對應檔案,實作假删除

4. 配置AUFS

    可以在安裝了AUFS的Linux系統中使用AUFS存儲驅動,而本機是CentOS,并不支援AUFS,是以第一步就挂掉!這裡隻是寫出操作步驟

grep aufs /proc/filesystem 檢視本機是否支援AUFS,如果不支援則什麼也不出現

通過docker daemon –storage-driver=aufs & 指令行啟動Docker Daemon或者在Docker Daemon配置檔案中配置

最後使用docker info檢視AUFS是否配置成功

5. 鏡像存儲方式

    當使用AUFS作為存儲驅動時,鏡像層和容器層都儲存在/var/lib/docker/aufs/diff/目錄中;/var/lib/docker/aufs/layers/目錄中則儲存了鏡像層的中繼資料,用來描述鏡像層如何疊加在一起,在該目錄中,每個鏡像層和容器層都有一個描述檔案,記錄在該層下所有鏡像層的名字

    舉個栗子,某個鏡像有ABC三個鏡像層,A在最上層,則/var/lib/docker/aufs/layers/A檔案的内容為

B C

;同理/var/lib/docker/aufs/layers/B檔案内容為

C

;而C即最下層鏡像層的原資料檔案為空

6. 容器存儲方式

    容器運作時,容器中的檔案系統被挂載在/var/lib/docker/aufs/mnt/<容器ID>,也就是說不同的容器有不同的挂載點

    所有容器(包括停止的容器)在/var/lib/docker/containers/中都有對應的子目錄,容器的中繼資料和配置檔案的儲存位置在對應的子目錄中,包括日志檔案xxx-json.log

Docker實戰-鏡像和容器的存儲結構

    當容器停止時,容器目錄仍然存在,當容器被删除時,容器目錄才會被删除(/var/lib/docker/aufs/diff中的容器層也是如此,因為CentOS沒法弄AUFS,是以隻能示範/var/lib/docker/containers/目錄)

Docker實戰-鏡像和容器的存儲結構

7. AUFS性能

    在PaaS應用場景下,AUFS是種很好的選擇。AUFS能夠在容期間共享鏡像,加速容器啟動,節約存儲空間;但是大量寫操作時性能較差(複制檔案)

三、Devicemapper存儲驅動

    因為在RedHat中不能使用AUFS,而Docker流行速度又十分快速,是以Redhat和Docker Inc聯合開發了适用于Redhat的存儲驅動Devicemapper

    Devicemapper是Linux 2.6核心提供的一套邏輯卷管理架構,Docker中的Devicemapper存儲驅動就是基于該架構實作,使用了按需配置設定和快照功能管理鏡像和容器

1. Devicemapper中的鏡像

    Devicemapper将容器和鏡像存儲在虛拟裝置上,對塊裝置進行操作而不是整個檔案

Docker實戰-鏡像和容器的存儲結構

    Devicemapper建立鏡像的流程

建立一個thin pool(可以建立在塊裝置也可以是sparse)

建立一個基礎裝置(按需配置設定的裝置),并在該裝置上建立檔案系統

建立鏡像,會在基礎裝置上做快照(每建立一個鏡像層就會做一次快照,使用寫時複制技術,當内容變化時才會在thin pool中配置設定存儲空間儲存資料)

    在Devicemapper中,每個鏡像層都是在前一個鏡像層上做了一個快照,第一個鏡像層是在基礎裝置(Devicemapper裝置,而不是Docker鏡像層)上做了一個快照,容器則是鏡像的一個快照

    thin pool的塊裝置配置在Docker實戰-Docker Daemon中有簡單說明

2. Devicemapper中的讀寫操作

    容器層隻是鏡像層的一個快照,沒有儲存資料,而是儲存了資料塊映射表,指向鏡像層中真正的資料塊。

    當應用程式發起讀請求後,Devicemapper通過資料塊映射表找到資料在哪個鏡像層的哪個資料塊上後将該資料塊的内容複制到容器的記憶體區中,然後再将需要的資料放回給應用程式

    Devicemapper作為存儲驅動時的寫操作有兩種情況,一種是寫入新資料(通過按需配置設定技術實作);另一種是更新已有資料(寫時複制技術實作)。Devicemapper是管理資料塊的,也就是說這兩種技術都是工作在資料塊,更新資料時隻複制變化的資料塊而不是整個檔案

    Devicemapepr中,每個資料塊的大小為64KB。當應用程式發起寫新資料請求後,Devicemapper通過按需配置設定技術,在容器層配置設定一塊或多個資料塊給應用程式,應用程式把資料寫入新配置設定的資料塊

    當應用程式發起更新資料請求後,Devicemapper定位到需要修改的資料塊,在容器層中配置設定新的存儲區,使用寫時複制技術将修改的資料塊複制到新配置設定的存儲區供應用程式更新

    Devicemapper使用的按需配置設定和寫時複制技術對容器中應用程式而言是透明的,不需要了解Devicemapper是如何管理資料塊

3. Devicemapper存儲方式

    Devicemapper工作在資料塊層面,在主控端上很難發現鏡像層和容器層的差別

    主控端/var/lib/docker/devicemapper/mnt儲存了鏡像層和容器層的挂載點;/var/lib/docker/devicemapper/metadata儲存了鏡像層和容器層的配置資訊

4. Devicemapper的性能

    Devicemapper使用了按需配置設定技術,并且使用的資料塊大小為64K,就算應用程式請求資料小于64K,也會配置設定一個資料塊大小,當容器中發生了大量小資料寫操作時,會影響容器性能

    當需要在大檔案中更新小資料時,Devicemapper比AUFS效果好,但是執行大量小資料寫操作時,Devicemapper的性能就不如AUFS

四、Overlay存儲驅動

    OverlayFS是一種聯合檔案系統,類似AUFS,更有優勢但目前還不太成熟

1. Overlay中的鏡像

    OverlayFS在Linux主機上用到兩個目錄,一個在下層儲存鏡像層(lowerdir),一個在上層儲存容器層(upperdir),再使用聯合挂載技術将這兩個目錄組合起來對外提供一個檔案系統(merged),容器層中的檔案會覆寫鏡像層中的檔案

Docker實戰-鏡像和容器的存儲結構

    運作容器時,存儲驅動将所有鏡像層對應目錄組合起來再添加上容器層,其中鏡像中最上面一層在lowerdir中,容器層在upperdir中

    Overlay存儲驅動隻能使用兩層,多層鏡像不能再OverlayFS中映射為多個層,每個鏡像層在/var/lib/docker/overlay都有對應的目錄,使用硬連結與底層關聯資料

Docker實戰-鏡像和容器的存儲結構

    啟動容器可以看到容器層同樣儲存在/var/lib/docker/overlay/目錄下,進入容器層的目錄可以看到檔案lower-id,merged,upper,work

Docker實戰-鏡像和容器的存儲結構

    其中lower-id中包含鏡像頂層ID(頂層鏡像儲存在lowerdir中)

Docker實戰-鏡像和容器的存儲結構

    容器層儲存在upper目錄中,運作過程中修改的資料都儲存在該目錄中;merged目錄是容器的挂載點,合并了lowerdir和upperdir;work目錄是供OverlayFS使用

Docker實戰-鏡像和容器的存儲結構

    可以使用mount指令檢視挂載關系

Docker實戰-鏡像和容器的存儲結構

2. Overlay2中的鏡像

    在使用Overlay存儲驅動時,隻使用lowerdir目錄儲存鏡像,有需要的時候則在該目錄中使用硬連結儲存多層鏡像。而Overlay2原生支援多個lowerdir,最多可以到128層

    Overlay2中,鏡像和容器都存儲在/var/lib/docker/overlay2目錄下。最底層的鏡像層包含了link檔案和diff目錄,其中link檔案儲存了短辨別符名稱(用于解決mount參數中長字元串超過頁大小限制問題),diff目錄包含了鏡像層的内容;倒數第二個層包含了link,lower,diff,merged,work,容器層的組成結構也類似

    其中lower檔案儲存鏡像層的組成資訊,檔案中的鏡像層使用:分隔,放在上面的鏡像層排在前面,也就是說加入鏡像有ABC三個鏡像層,其中A為最頂層,則A檔案的lower檔案資訊為

B:C

3. Overlay中的讀寫操作

    當應用程式讀取檔案時,若目标檔案在容器層中,直接從容器層讀取檔案;如果目标檔案不在容器層,就會從鏡像層中讀取檔案

    OverlayFS檔案系統工作在檔案層而不是資料塊層。當容器中第一次修改檔案時,Overlay/Overlay2存儲驅動會從鏡像層複制檔案到容器層修改,OverlayFS隻有兩層,在很深的目錄樹中搜尋檔案時,Overlay比AUFS速度更快

    當需要在容器中删除檔案時,Overlay存儲驅動會在容器層建立一個without檔案/opaque目錄用于隐藏鏡像層中的目标檔案/目錄,而不會真正删除鏡像層中的檔案或目錄

4. 配置Overlay/Overlay2

    若要使用Overlay存儲驅動需要主控端Linux核心為3.18版本;而要使用Overlay2存儲驅動則需要Linux核心為4.0或以上版本

過程描述:

          service docker stop/systemctl stop docker.service 停止Docker Daemon

          uname -r 檢查Linux核心版本

          lsmod | grep overlay 加載Overlay子產品

          docker daemon –storage-driver=overlay & 配置存儲驅動或在Docker Daemon配置檔案中配置

          docker info 檢查Overlay是否生效

    使用Overlay/Overlay2存儲驅動後,Docker會自動建立Overlay挂載點,并在其中建立lowerdir、upperdir、merged、workdir

5. Overlay的性能

    總體來講,Overlay/Overlay2存儲驅動很快,支援共享頁緩存;隻是當檔案特别大時會影響寫操作性能,但OverlayFS的複制操作比AUFS快,因為AUFS有很多層,目錄樹很深時會産生很大開銷;同樣當需要大量讀寫時,最好将資料儲存在資料卷中,所有讀寫操作都會繞過存儲驅動,不會增加額外開銷