Registry 容器鏡像服務端細節

引言

一般咱們在使用集羣或者容器的時候,都會接觸到存儲在本地的鏡像,也或多或少對本地鏡像存儲有必定的瞭解。可是服務端的鏡像存儲細節呢?本文主要介紹容器鏡像的服務端存儲結構,對於自建鏡像服務或是對容器鏡像底層原理或優化有興趣的同窗能夠了解一下。html

相關開源項目

目前容器鏡像服務相關的開源項目主要有如下兩個。git

Registry具備基本的鏡像上傳、下載以及對接第三方鑑權的能力。Harbor則基於Registry作了相應的企業級擴展的項目。提供了更多權限、審計、鏡像等功能,目前是CNCF孵化項目之一。其餘詳情參考相關文章。這篇文章主要講解Registry項目的存儲細節。github

鏡像細節

在瞭解服務端以前,咱們來了解一下客戶端的鏡像容器的存儲環境。docker

聯合文件系統 UnionFS(Union File System)

Docker的存儲驅動的實現是基於UnionFS。簡單列舉一下UnionFS下存儲鏡像的一些特色。json

首先,UnionFS是一個分層的文件系統。一個Docker鏡像可能有多個層組成(注意他們是有順序的)。api

其次,只有頂層是可寫的,其它層都是隻讀的。這樣的機制帶來的好處是鏡像層能夠被多個鏡像共享。對於Docker鏡像來講,全部層都是隻讀的。當一個鏡像運行時,會在該鏡像上增長一個容器層。十個相同的鏡像啓動,僅僅是增長十個容器層。銷燬容器時也僅僅是銷燬一個容器層而已。安全

  • UnionFS是一個分層的文件系統。一個Docker鏡像可能有多個層組成(注意他們是有順序的)。
  • 只有頂層是可寫的,其它層都是隻讀的。這樣的機制帶來的好處是鏡像層能夠被多個鏡像共享。對於Docker鏡像來講,全部層都是隻讀的。當一個鏡像運行時,會在該鏡像上增長一個容器層。十個相同的鏡像啓動,僅僅是增長十個容器層。銷燬容器時也僅僅是銷燬一個容器層而已。
    • 當容器須要讀取文件的時候:從最上層鏡像開始查找,往下找,找到文件後讀取並放入內存,若已經在內存中了,直接使用。(即,同一臺機器上運行的docker容器共享運行時相同的文件)。
    • 當容器須要添加文件的時候:直接在最上面的容器層可寫層添加文件,不會影響鏡像層。
    • 當容器須要修改文件的時候:從上往下層尋找文件,找到後,複製到容器可寫層,而後,對容器來講,能夠看到的是容器層的這個文件,看不到鏡像層裏的文件。容器在容器層修改這個文件。
    • 當容器須要刪除文件的時候:從上往下層尋找文件,找到後在容器中記錄刪除。即,並不會真正的刪除文件,而是軟刪除。這將致使鏡像體積只會增長,不會減小。

由此能夠思考不少安全和鏡像優化上的問題。app

  • 在鏡像構建中記錄敏感信息而後再下一個構建指令中刪除安全嗎?(不安全)
  • 在鏡像構建中安裝軟件包而後再下一個構建指令中清理軟件包能減少鏡像體積嗎?(並不能)

UnionFS通常有兩種實現方案:1. 基於文件實現。文件總體的覆蓋重寫。2. 基於塊實現,對文件的修改只修改少許塊。優化

鏡像的服務端存儲細節

提供一個鏡像元信息(manifest)用於參考:spa

➜  ~ 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提供了標準的存儲驅動接口,只要實現了這一套接口就能適配運行起來了。

相關文章

【騰訊雲原生】雲說新品、雲研新術、雲遊新活、雲賞資訊,掃碼關注同名公衆號,及時獲取更多幹貨!!
相關文章
相關標籤/搜索