1. 程式人生 > >Docker 映象的儲存機制

Docker 映象的儲存機制

近幾年 Docker 風靡技術圈,不少從業人員都或多或少使用過,也瞭解如何通過 Dockerfile 構建映象,從遠端映象倉庫拉取自己所需映象,推送構建好的映象至遠端倉庫,根據映象執行容器等。這個過程十分簡單,只需執行 docker build、docker pull、docker push、docker run 等操作即可。但大家是否想過映象在本地到底是如何儲存的?容器又是如何根據映象啟動的?推送映象至遠端映象倉庫時,伺服器又是如何儲存的呢?下面我們就來簡單聊一聊。



Docker 映象本地儲存機制及容器啟動原理


Docker 映象不是一個單一的檔案,而是有多層構成。我們可通過 docker images 

獲取本地的映象列表及對應的元資訊, 接著可通過docker history <imageId> 檢視某個映象各層內容及對應大小,每層對應著 Dockerfile 中的一條指令。Docker 映象預設儲存在 /var/lib/docker/<storage-driver>中,可通過 DOCKER_OPTS 或者 docker daemon 執行時指定 --graph= 或 -g 指定。


Docker 使用儲存驅動來管理映象每層內容及可讀寫的容器層,儲存驅動有 devicemapper、aufs、overlay、overlay2、btrfs、zfs 等,不同的儲存驅動實現方式有差異,映象組織形式可能也稍有不同,但都採用棧式儲存,並採用 Copy-on-Write(CoW) 策略。且儲存驅動採用熱插拔架構,可動態調整。那麼,儲存驅動那麼多,該如何選擇合適的呢?大致可從以下幾方面考慮:


  • 若核心支援多種儲存驅動,且沒有顯式配置,Docker 會根據它內部設定的優先順序來選擇。優先順序為 aufs > btrfs/zfs > overlay2 > overlay > devicemapper。若使用 devicemapper 的話,在生產環境,一定要選擇 direct-lvm, loopback-lvm 效能非常差。

  • 選擇會受限於 Docker 版本、作業系統、系統版本等。例如,aufs 只能用於 Ubuntu 或 Debian 系統,btrfs 只能用於 SLES (SUSE Linux Enterprise Server, 僅 Docker EE 支援)。

  • 有些儲存驅動依賴於後端的檔案系統。例如,btrfs 只能運行於後端檔案系統 btrfs 上。

  • 不同的儲存驅動在不同的應用場景下效能不同。例如,aufs、overlay、overlay2 操作在檔案級別,記憶體使用相對更高效,但大檔案讀寫時,容器層會變得很大;devicemapper、btrfs、zfs 操作在塊級別,適合工作在寫負載高的場景;容器層數多,且寫小檔案頻繁時,overlay 效率比 overlay2 更高;btrfs、zfs 更耗記憶體。


Docker 容器其實是在映象的最上層加了一層讀寫層,通常也稱為容器層。在執行中的容器裡做的所有改動,如寫新檔案、修改已有檔案、刪除檔案等操作其實都寫到了容器層。容器層刪除了,最上層的讀寫層跟著也刪除了,改動自然也丟失了。若要持久化這些改動,須通過 docker commit <containerId> [repository[:tag]] 將當前容器儲存成為一個新映象。若想將資料持久化,或是多個容器間共享資料,需將資料儲存在 Docker volume 中,並將 volume 掛載到相應容器中。


儲存驅動決定了映象及容器在檔案系統中的儲存方式及組織形式,下面分別對常見的 aufs、overlay 作一簡單介紹。


AUFS


AUFS 簡介


AUFS 是 Debian (Stretch 之前的版本,Stretch預設採用 overlay2) 或 Ubuntu 系統上 Docker 的預設儲存驅動,也是 Docker 所有儲存驅動中最為成熟的。具有啟動快,記憶體、儲存使用高效等特點。如果使用的 Linux 核心版本為 4.0 或更高,且使用的是 Docker CE,可考慮使用overlay2 (比 AUFS 效能更佳)。


配置 AUFS 儲存驅動


① 驗證核心是否支援 AUFS

$ grep aufs /proc/filesystems

nodev aufs


② 若核心支援,可在 docker 啟動時通過指定引數 --storage-driver=aufs 選擇 AUFS


AUFS 儲存驅動工作原理


採用 AUFS 儲存驅動時,有關映象和容器的所有層資訊都儲存在 /var/lib/docker/aufs/ 目錄下,下面有三個子目錄:


  • /diff:每個目錄中儲存著每層映象包含的真實內容

  • /layers:儲存有關映象層組織的元資訊,檔案內容儲存著該映象的組建映象列表

  • /mnt:掛載點資訊儲存,當建立容器後,mnt 目錄下會多出容器對應的層及該容器的 init 層。目錄名稱與容器 Id 不一致。實際的讀寫層儲存在 /var/lib/docker/aufs/diff直到容器刪除,此讀寫層才會被清除掉。


採用 AUFS 後容器如何讀寫檔案?


讀檔案


容器進行讀檔案操作有以下三種場景:


  • 容器層不存在: 要讀取的檔案在容器層中不存在,儲存驅動會從映象層逐層向下找,多個映象層中若存在同名檔案,上層的有效。

  • 檔案只存在容器層:讀取容器層檔案

  • 容器層與映象層同時存在:讀取容器層檔案


修改檔案或目錄


容器中進行檔案的修改同樣存在三種場景:


  • 第一次寫檔案:若待修改的檔案在某個映象層中,aufs 會先執行 copy_up 操作將檔案從只讀的映象層拷貝到可讀寫的容器層,然後進行修改。在檔案非常大的情況下效率比較低下。

  • 刪除檔案:刪除檔案時,若檔案在映象層,其實是在容器層建立一個特殊的 writeout 檔案,容器層訪問不到,並沒有實際刪掉。

  • 目錄重新命名:目前 AUFS 還不支援目錄重新命名。


OverlayFS


OverlayFS 簡介


OverlayFS 是一種類似 AUFS 的現代聯合檔案系統,但實現更簡單,效能更優。OverlayFS 嚴格說來是 Linux 核心的一種檔案系統,對應的 Docker 儲存驅動為 overlay 或者 overlay2,overlay2 需 Linux 核心 4.0 及以上,overlay 需核心 3.18 及以上。且目前僅 Docker 社群版支援。條件許可的話,儘量使用 overlay2,與 overlay 相比,它的 inode 利用率更高。


容器如何使用 overlay/overlay2 讀寫檔案


讀檔案


讀檔案存在以下三種場景:


  • 檔案不存在容器層:若容器要讀的檔案不在容器層,會繼續從底層的映象層找

  • 檔案僅在容器層:若容器要讀的檔案在容器層,直接讀取,不用在底層的映象層查詢

  • 檔案同時在容器層和映象層:若容器要讀的檔案在容器層和映象層中都存在,則從容器層讀取


修改檔案或目錄


寫檔案存在以下三種場景:


  • 首次寫檔案:若要寫的檔案位於映象層中,則執行 copy_up 將檔案從映象層拷貝至容器層,然後進行修改,並在容器層儲存一份新的。若檔案較大,效率較低。OverlayFS 工作在檔案級別而不是塊級別,這意味著即使對檔案稍作修改且檔案很大,也須將整個檔案拷貝至容器層進行修改。但需注意的是,copy_up 操作僅發生在首次,後續對同一檔案進行修改,操作容器層檔案即可

  • 刪除檔案或目錄:容器中刪除檔案或目錄時,其實是在容器中建立了一個 writeout 檔案,並沒有真的刪除檔案,只是使其對使用者不可見

  • 目錄重新命名:僅當源路徑與目標路徑都在容器層時,呼叫 rename(2) 函式才成功,否則返回 EXDEV



遠端映象倉庫如何儲存映象?


不少人可能經常使用 docker,那麼有沒有思考過映象推送至遠端映象倉庫,是如何儲存的呢?Docker 客戶端是如何與遠端映象倉庫互動的呢?


我們平時本地安裝的 docker 其實包含兩部分:docker client 與 docker engine,docker client 與 docker engine 間通過 API 進行通訊。Docker engine 提供的 API 大致有認證、容器、映象、網路、卷、swarm 等,具體呼叫形式請參考:Docker Engine API(https://docs.docker.com/engine/api/v1.27/#)


Docker engine 與 registry (即:遠端映象倉庫)的通訊也有一套完整的 API,大致包含 pull、push 映象所涉及的認證、授權、映象儲存等相關流程,具體請參考:Registry API(https://github.com/docker/distribution/blob/master/docs/spec/api.md)。目前常用 registry 版本為 v2,registry v2 擁有斷點續傳、併發拉取映象多層等特點。能併發拉取多層是因為映象的元資訊與映象層資料分開儲存,當 pull 一個映象時,先進行認證獲取到 token 並授權通過,然後獲取映象的 manifest 檔案,進行 signature 校驗。校驗完成後,依據 manifest 裡的層資訊併發拉取各層。其中 manifest 包含的資訊有:倉庫名稱、tag、映象層 digest 等, 更多,請參考:manifest 格式文件(https://github.com/docker/distribution/blob/master/docs/spec/manifest-v2-1.md)


各層拉下來後,也會先在本地進行校驗,校驗演算法採用 sha256。Push 過程則先將映象各層併發推至 registry,推送完成後,再將映象的 manifest 推至 registry。Registry 其實並不負責具體的儲存工作,具體儲存介質根據使用方來定,registry 只是提供一套標準的儲存驅動介面,具體儲存驅動實現由使用方實現。


目前官方 registry 預設提供的儲存驅動包括:微軟 azure、Google gcs、Amazon s3、Openstack swift、阿里雲 oss、本地儲存等。若需要使用自己的物件儲存服務,則需要自行實現 registry 儲存驅動。網易雲目前將映象儲存在自己的物件儲存服務 nos 上,故專門針對 nos 實現了一套儲存驅動,另外認證服務也對接了網易雲認證服務,並結合自身業務實現了一套認證、授權邏輯,並有效地限制了倉庫配額。


Registry 乾的事情其實很簡單,大致可分為:① 讀配置 ;② 註冊 handler ;③ 監聽。本質上 registry 是個 HTTP 服務,啟動後,監聽在配置檔案設定的某埠上。當 http 請求過來後,便會觸發之前註冊過的 handler。Handler 包含 manifest、tag、blob、blob-upload、blob-upload-chunk、catalog 等六類,具體請可參考 registry 原始碼: /registry/handlers/app.go:92。配置檔案包含監聽埠、auth 地址、儲存驅動資訊、回撥通知等。


參考資料:


  • 儲存驅動選擇 https://docs.docker.com/engine/userguide/storagedriver/selectadriver/

  • https://docs.docker.com/engine/userguide/storagedriver/selectadriver/#a-pluggable-storage-driver-architecture

  • 五種儲存驅動對比 http://dockone.io/article/1513

  • Docker儲存方式選型:http://blog.dataman-inc.com/docker-shurenyun-178/

  • http://www.chinastor.org/GuoNeiXinWen/9443.html