1. 程式人生 > >Docker實戰-映象和容器的儲存結構

Docker實戰-映象和容器的儲存結構

一、映象、容器和儲存驅動的關係

    前面也已經講過說,映象是程式和檔案的集合,容器是映象的執行例項。

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

1. 映象層和容器層

    每個映象都由多個映象層組成,從下往上以棧的方式組合在一起形成容器的根檔案系統,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/中,由儲存驅動進行管理

這裡寫圖片描述

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

這裡寫圖片描述

    可以看到目錄名和下載映象的UUID並不相同

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

這裡寫圖片描述

    通過Dockfile建立了一個Changed-ubuntu映象,通過docker history可以檢視映象的映象層及映象大小,可以看到新建的映象層佔用空間為0B,也就是說changed-ubuntu映象只會佔用額外的一丟丟空間而不需要為其他的五個映象層分配儲存空間

這裡寫圖片描述

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

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

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

    如果容器中存在許多修改檔案或新建檔案相關操作會使得容器佔用更多儲存空間(容器修改時都會將原有內容複製到容器層,不同儲存驅動複製的基本單位不同),當寫運算元量較多時,最好使用掛載卷(掛載卷的讀寫操作都會繞過儲存驅動)

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

    當啟動容器時,Docker Daemon只需要為容器新建一個可寫的資料層,而不用複製所有映象層,從而使得容器啟動時間減少

4. 資料卷和儲存驅動

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

    刪除容器時,資料卷的資料儲存在宿主機中,不會刪除;所有不在資料卷的資料都會被刪除

    儲存驅動管理所有Docker Daemon生成的容器,每種儲存驅動都是基於Linux檔案系統或卷管理工具,通過命令docker info可以檢視當前使用的儲存驅動

這裡寫圖片描述

    本機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的每個目錄都對應了映象中的一層,聯合掛載點對外提供一個統一的訪問路徑

這裡寫圖片描述

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

這裡寫圖片描述

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

這裡寫圖片描述

7. AUFS效能

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

三、Devicemapper儲存驅動

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

    Devicemapper是Linux 2.6核心提供的一套邏輯卷管理框架,Docker中的Devicemapper儲存驅動就是基於該框架實現,使用了按需分配和快照功能管理映象和容器

1. Devicemapper中的映象

    Devicemapper將容器和映象儲存在虛擬裝置上,對塊裝置進行操作而不是整個檔案

這裡寫圖片描述

    Devicemapper建立映象的流程

建立一個thin pool(可以建立在塊裝置也可以是sparse)
建立一個基礎裝置(按需分配的裝置),並在該裝置上建立檔案系統
建立映象,會在基礎裝置上做快照(每建立一個映象層就會做一次快照,使用寫時複製技術,當內容變化時才會在thin pool中分配儲存空間儲存資料)

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

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),容器層中的檔案會覆蓋映象層中的檔案

這裡寫圖片描述

    執行容器時,儲存驅動將所有映象層對應目錄組合起來再新增上容器層,其中映象中最上面一層在lowerdir中,容器層在upperdir中

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

這裡寫圖片描述

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

這裡寫圖片描述

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

這裡寫圖片描述

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

這裡寫圖片描述

    可以使用mount命令檢視掛載關係

這裡寫圖片描述

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有很多層,目錄樹很深時會產生很大開銷;同樣當需要大量讀寫時,最好將資料儲存在資料卷中,所有讀寫操作都會繞過儲存驅動,不會增加額外開銷