Registry 容器映象服務端細節

騰訊雲原生發表於2020-10-13

引言

通常我們在使用叢集或者容器的時候,都會接觸到儲存在本地的映象,也或多或少對本地映象儲存有一定的瞭解。但是服務端的映象儲存細節呢?本文主要介紹容器映象的服務端儲存結構,對於自建映象服務或是對容器映象底層原理或優化有興趣的同學可以瞭解一下。

相關開源專案

目前容器映象服務相關的開源專案主要有以下兩個。

Registry具有基本的映象上傳、下載以及對接第三方鑑權的能力。Harbor則基於Registry做了相應的企業級擴充套件的專案。提供了更多許可權、審計、映象等功能,目前是CNCF孵化專案之一。其他詳情參考相關文章。這篇文章主要講解Registry專案的儲存細節。

映象細節

在瞭解服務端之前,我們來了解一下客戶端的映象容器的儲存環境。

聯合檔案系統 UnionFS(Union File System)

Docker的儲存驅動的實現是基於UnionFS。簡單列舉一下UnionFS下儲存映象的一些特點。

首先,UnionFS是一個分層的檔案系統。一個Docker映象可能有多個層組成(注意他們是有順序的)。

其次,只有頂層是可寫的,其它層都是隻讀的。這樣的機制帶來的好處是映象層可以被多個映象共享。對於Docker映象來說,所有層都是隻讀的。當一個映象執行時,會在該映象上增加一個容器層。十個相同的映象啟動,僅僅是增加十個容器層。銷燬容器時也僅僅是銷燬一個容器層而已。

  • UnionFS是一個分層的檔案系統。一個Docker映象可能有多個層組成(注意他們是有順序的)。

  • 只有頂層是可寫的,其它層都是隻讀的。這樣的機制帶來的好處是映象層可以被多個映象共享。對於Docker映象來說,所有層都是隻讀的。當一個映象執行時,會在該映象上增加一個容器層。十個相同的映象啟動,僅僅是增加十個容器層。銷燬容器時也僅僅是銷燬一個容器層而已。

    • 當容器需要讀取檔案的時候:從最上層映象開始查詢,往下找,找到檔案後讀取並放入記憶體,若已經在記憶體中了,直接使用。(即,同一臺機器上執行的docker容器共享執行時相同的檔案)。
    • 當容器需要新增檔案的時候:直接在最上面的容器層可寫層新增檔案,不會影響映象層。
    • 當容器需要修改檔案的時候:從上往下層尋找檔案,找到後,複製到容器可寫層,然後,對容器來說,可以看到的是容器層的這個檔案,看不到映象層裡的檔案。容器在容器層修改這個檔案。
    • 當容器需要刪除檔案的時候:從上往下層尋找檔案,找到後在容器中記錄刪除。即,並不會真正的刪除檔案,而是軟刪除。這將導致映象體積只會增加,不會減少。

由此可以思考很多安全和映象優化上的問題。

  • 在映象構建中記錄敏感資訊然後再下一個構建指令中刪除安全嗎?(不安全)
  • 在映象構建中安裝軟體包然後再下一個構建指令中清理軟體包能減小映象體積嗎?(並不能)

UnionFS一般有兩種實現方案:1. 基於檔案實現。檔案整體的覆蓋重寫。2. 基於塊實現,對檔案的修改只修改少量塊。

映象的服務端儲存細節

提供一個映象元資訊(manifest)用於參考:

➜  ~ docker pull ccr.ccs.tencentyun.com/paas/service-controller:7b1c981c7b1c981c: Pulling from paas/service-controllerDigest: sha256:e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419Status: Image is up to date for ccr.ccs.tencentyun.com/paas/service-controller:7b1c981cccr.ccs.tencentyun.com/paas/service-controller:7b1c981c
{   "schemaVersion": 2,   "mediaType": "application/vnd.docker.distribution.manifest.v2+json",   "config": {      "mediaType": "application/vnd.docker.container.image.v1+json",      "size": 4671,      "digest": "sha256:785f4150a5d9f62562f462fa2d8b8764df4215f0f2e3a3716c867aa31887f827"   },   "layers": [      {         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 44144090,         "digest": "sha256:e80174c8b43b97abb6bf8901cc5dade4897f16eb53b12674bef1eae6ae847451"      },      {         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 529,         "digest": "sha256:d1072db285cc5eb2f3415891381631501b3ad9b1a10da20ca2e932d7d8799988"      },      {         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 849,         "digest": "sha256:858453671e6769806e0374869acce1d9e5d97f5020f86139e0862c7ada6da621"      },      {         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 170,         "digest": "sha256:3d07b1124f982f6c5da7f1b85a0a12f9574d6ce7e8a84160cda939e5b3a1faad"      },      {         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 8461461,         "digest": "sha256:994dade28a14b2eac1450db7fa2ba53998164ed271b1e4b0503b1f89de44380c"      },      {         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 22178452,         "digest": "sha256:60a5bd5c14d0f37da92d2a5e94d6bbfc1e2a942d675aee24f055ced76e8a208f"      },      {         "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",         "size": 22178452,         "digest": "sha256:60a5bd5c14d0f37da92d2a5e94d6bbfc1e2a942d675aee24f055ced76e8a208f"      }   ]}

*接下來是本文最為重要的內容,通過對上面這張圖的理解,我們就可以瞭解到Registry服務端儲存的細節。*

  • 圖中藍色的是服務端儲存的目錄。文字是目錄名稱,這個名稱是固定的。

  • 圖中紫色的是服務端儲存的檔案。文字是檔名稱,link檔案的內容都是一個sha256的雜湊值。data檔案儲存了真正的元檔案和映象層。

  • 圖中橙色的是服務端的動態目錄。目錄的名稱和倉庫名、映象標籤或者sha256有關的。

整個圖是從上往下的。舉個例子,我們上面描述的manifest如果是儲存在服務端的話(檔案雜湊:sha256:e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419)。它儲存的路徑應該是:/docker/registry/v2/blobs/sha256/e8/e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419/data。對應圖上應該是沿著左側一直向下。

我們開始拆解分析其結構細節。

  • 左側是映象所有內容的實際儲存,其幾乎佔據的絕大部分儲存的空間,包括了映象層和映象元資訊Manifest。

    • 例如映象層sha256:e80174c8b43b97abb6bf8901cc5dade4897f16eb53b12674bef1eae6ae847451的儲存位置,應該在/docker/registry/v2/blobs/sha256/e8/e80174c8b43b97abb6bf8901cc5dade4897f16eb53b12674bef1eae6ae847451/data

  • 右側是映象元資訊儲存的地方。映象元資訊是按照名稱空間和倉庫名稱分兩級目錄儲存的。

    • 每一個倉庫下面又分為_layers_manifests兩個部分

    • _layers負責記錄該倉庫引用了哪些映象層檔案。

    • _manifests負責記錄映象的元資訊

      • revisions包含了倉庫下曾經上傳過的所有版本的映象元資訊

      • tags包含了倉庫中的所有標籤

        • current記錄了當前標籤指向的映象
        • index目錄則記錄了標籤指向的歷史映象。
    • 對上述提供的manifest計算sha256,會得到元資訊檔案的雜湊值sha256:e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419,這個元資訊的儲存位置應該在/docker/registry/v2/blobs/sha256/e8/e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419/data

舉個映象下載的例子:

我們想要知道ccr.ccs.tencentyun.com/paas/service-controller:7b1c981c這個映象現在的元資訊,如何在服務端儲存中找到。

  1. 找到/docker/registry/v2/paas/service-controller/_manifests/tags/7b1c981c/current/link檔案。裡面有元資訊的sha256資訊。內容應該是sha256:e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419
  2. 找到實際儲存檔案(/docker/registry/v2/blobs/sha256/e8/e8b84ce6c245f04e6e453532d676f7c7f0a94b3122f93a89a58f9ae49939e419/data)。前文中給出了該檔案的json內容。
  3. 根據原始檔資訊,客戶端依次下載對應檔案就可以了。(鑑權過程參考參考文件)
  • ImageConfig

    sha256:785f4150a5d9f62562f462fa2d8b8764df4215f0f2e3a3716c867aa31887f827

  • ImageLayer

    sha256:e80174c8b43b97abb6bf8901cc5dade4897f16eb53b12674bef1eae6ae847451 sha256:d1072db285cc5eb2f3415891381631501b3ad9b1a10da20ca2e932d7d8799988 sha256:858453671e6769806e0374869acce1d9e5d97f5020f86139e0862c7ada6da621 sha256:3d07b1124f982f6c5da7f1b85a0a12f9574d6ce7e8a84160cda939e5b3a1faad sha256:994dade28a14b2eac1450db7fa2ba53998164ed271b1e4b0503b1f89de44380c sha256:60a5bd5c14d0f37da92d2a5e94d6bbfc1e2a942d675aee24f055ced76e8a208f

Tips:

  1. 很明顯同樣的映象層檔案在儲存中只會有一個副本。使用相同基礎映象將節省大量的儲存成本。
  2. 如果想要算上述元資訊檔案的雜湊值,請保證你複製的檔案內容尾部沒有EOL。[noeol]

基於儲存的幾個問題

映象構建如何優化?

根據UnionFS的特性,針對性的進行優化:

  1. 構建時,一個構建指令會生成一個映象層,儘量避免在映象層中出現垃圾檔案,例如在安裝軟體之後刪除軟體包。
  2. 刪除敏感資源並不能使得該內容真正消失,避免敏感內容造成的安全問題。例如編譯映象最後刪除程式碼是無效的欺騙自己的行為。
  3. 通過多階段構建,減少中間產物以及編譯環境中的依賴內容。

上傳到服務端映象,再上傳到其他倉庫需要重新上傳嗎?

需要,在Registry的設計中倉庫是許可權的最小單位,使用者是根據倉庫進行許可權管理與隔離的。考慮如果這裡忽略了這一塊的設計,映象層存在就避免重複上傳的話,客戶端可以通過構造虛假映象元資訊的方式,越權獲取到其他使用者的映象。_layers中記錄了倉庫有許可權獲取的所有映象層的目的就在於此。

映象複製場景下如何優化?

複製映象的場景和上傳場景的區別在於,源映象是使用者確實已經擁有的。這裡可以通過上述問題的思考進行復制的優化,當映象層在目的地址已經存在時,直接標記倉庫擁有該層避免不必要的上傳。

映象歷史版本

根據儲存結構的特點,可以較為輕鬆的回答這個問題。理論上只要不做Registry GC,不刪除倉庫元資訊,倉庫歷史版本的映象都會在倉庫中一直儲存的。

怎麼拿到映象的元資訊?

這裡不做詳細講解了,有興趣的同學可以參考以下文件:

雲服務的儲存對接

Registry作為一個開源軟體,適配各種雲端儲存產品屬於標配功能了。Registry提供了標準的儲存驅動介面,只要實現了這一套介面就能適配執行起來了。

相關文章

【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公眾號,及時獲取更多幹貨!!

相關文章