雲原生製品那些事(1):容器鏡像

題圖攝於北京頤和園

(未經授權,請勿轉載本公衆號文章)

上篇文章和大家說到 Kubernetes 無法根本性移除 Docker的影響,原因是 Docker 發明的鏡像格式極具革命性,無可替代。不管 Kubernetes 那邊風吹浪打,Docker 我自巍然不動。從本篇開始和大家說說鏡像那些事,共分四次連載,從《Harbor權威指南》一書節選的純技術乾貨,敬請關注、轉發和收藏。

第一篇:容器鏡像的結構

第二篇:OCI 鏡像規範

第三篇:OCI 製品

第四篇:Registry 的作用原理

《Harbor權威指南》目前噹噹網低於半價優惠中,點擊下圖直接購買。

相關活動:

2020雲原生生態大會,最值得期待的技術盛會!

容器鏡像的結構

容器有不可改變性(immutability)和可移植性(portability)。容器把應用的可執行文件、依賴文件及操作系統文件等打包成鏡像,使應用的運行環境固定下來不再變化;同時,鏡像可在其他環境下重現同樣的運行環境。這些特性給運維和應用的發佈帶來極大的便利,這要歸功於封裝應用的鏡像。

1.1  鏡像的發展

2013 年,Docker推出容器管理工具,同時發佈了封裝應用的鏡像。這是 Docker與之前各種方案的重大區別,也是 Docker 得以勝出和迅速流傳的主要原因。可以說,鏡像體現了 Docker 容器的核心價值。

2014年,Docker 把其鏡像格式歸納和定義爲 Docker 鏡像規範v1。在這個規範中,鏡像的每個層文件(layer)都包含一個存放元數據的 JSON 文件,並且用父ID來指明上一層鏡像。這個規範有兩個缺點:鏡像的 ID 是隨機生成的,可近似認爲具有唯一性,可以用來標識鏡像,但是用相同內容構建出來的層文件的ID並不一樣,通過ID無法確認完全相同的層,不利於層的共享;每層都綁定了父層,緊耦合的結構不利於獨立存放層文件。(本文來自公衆號:亨利筆記)

2016年,Docker 制定了鏡像規範v2,並在 Docker 1.10 中實現了這個規範。鏡像規範v2分爲 Schema 1和 Schema 2。Schema 1主要兼容使用 v1 規範的 Docker 客戶端,如 Docker 1.9 及之前的客戶端。Schema 2 主要實現了兩個功能:支持多體系架構的鏡像和可通過內容尋址的鏡像,其中最大的改進就是根據內容的SHA256 摘要生成 ID,只要內容相同,ID 就是一樣的,可區分相同的層文件(即可內容尋址)。從2017年2月起,鏡像規範v1不再被 Registry 支持,用戶需要把已有的v1鏡像轉化爲v2鏡像才能推送到Registry中。

OCI 在 2017 年 7 月發佈了 OCI 鏡像規範1.0。因爲 Docker v2 的鏡像規範已經成爲事實上的標準,OCI 鏡像規範實質上是以 Docker 鏡像規範v2爲基礎制定的,因此二者在絕大多數情況下是兼容或相似的。

1.2  Docker 鏡像的結構

Docker 容器鏡像主要包含的內容是應用程序所依賴的根文件系統(rootfs)。這個根文件系統是分層存儲的,基礎層通常是操作系統的文件,然後在基礎層上不斷疊加新的層文件,最終將這些層組合起來形成一個完整的鏡像。當通過鏡像啓動容器時,鏡像所有的層都轉化成容器裏的只讀(read only)文件系統。同時,容器會額外增加一個讀寫層,給應用程序運行時讀寫文件使用。這樣的層文件結構可由聯合文件系統(UnionFS)實現。(本文來自公衆號:亨利筆記)

Docker容器鏡像通常由 “docker build” 命令依照 Dockerfile 構建的,Dockerfile 描述了鏡像包含的所有內容和配置信息(如啓動命令等)。下面是一個簡單的Dockerfile 例子:

FROM ubuntu:20.04

RUN apt update && apt install -y python

RUN apt install -y python-numpy

ADD myApp.py /opt/

在這個例子中,容器鏡像的基礎鏡像是操作系統 Ubuntu 20.04,然後安裝Python 軟件包,再安裝 Python 庫 NumPy,最後增加應用程序 myApp。在鏡像構建完成之後會有4個層文件,如下圖所示。

圖中的鏡像層在容器創建時作爲只讀文件系統加載到容器中,此外,容器運行時會爲每個容器實例都創建一個可讀寫層,疊加在文件系統的最上層,用於應用讀寫文件。容器的不可改變性就是通過鏡像的鏡像層(只讀)實現的。另外,無論鏡像在哪種環境下啓動,始終有相同的鏡像層,從而實現了應用的可移植性。

Docker使用分層來管理鏡像,有以下好處。(本文來自公衆號:亨利筆記)

(1)方便基礎層和依賴軟件層的共享(如包含操作系統文件、軟件包等),不同的鏡像可以共享基礎層或軟件層,在同一臺機器上存放公共層的鏡像時只需保存一份層文件,可以大大減少文件存儲空間。

(2)在構建鏡像時,已構建過的層會被保存在緩存中,再次構建時如果下面的層不變,則可以通過構建緩存來縮短構建時間。

(3)因爲很多時候同一個應用的鏡像更新時變化的只是最上層(應用層),所以分層可以減少同種鏡像的分發時間。

(4)分層可以更加方便地跟蹤鏡像的變化,因爲每一層都是和構建命令關聯的,所以可以更好地管理鏡像的變化歷史。

Docker容器的文件系統分層機制主要靠聯合文件系統(UnionFS)來實現。聯合文件系統保證了文件的堆疊特性,即上層通過增加文件來修改依賴層文件,在保證鏡像的只讀特性時還能實現容器文件的讀寫特性。

1.3  Docker鏡像的倉庫存儲結構

Docker 容器鏡像的存儲分爲本地存儲和鏡像倉庫(Registry)存儲。其中,本地存儲指鏡像下載到本地後是如何在本地文件系統中存儲的;鏡像倉庫存儲指鏡像以什麼方式存儲在遠端的鏡像倉庫中。

鏡像存儲的本質還是分層存儲,但是本地存儲和鏡像倉庫存儲的方式不完全一樣,最大的區別是,鏡像倉庫存儲的核心是方便鏡像快速上傳和拉取,所以鏡像存儲使用了壓縮格式,並且按照鏡像層獨立壓縮和存儲,然後使用鏡像清單(manifest)包含所有的層,通過鏡像摘要(digest)和Tag關聯起來;鏡像在本地存儲的核心是快速加載和啓動容器,鏡像層存儲是非壓縮的(即源文件)。另外,容器在啓動時需要將鏡像層按照順序堆疊作爲容器的運行環境,所以鏡像在本地存儲中需要使用非壓縮形式存放。(本文來自公衆號:亨利筆記)

在說明鏡像的存儲格式之前,先介紹拉取同一個 Docker 鏡像時可使用的兩種不同命令格式。如下所示,latest 是鏡像的 Tag,“sha256:46d659…a3ee9a”是鏡像的摘要,在支持 Docker 鏡像規範 v2 Schema 2 的鏡像倉庫中,二者都標識同一個鏡像:

$ docker pull debian:latest

$ docker pull \ debian:@sha256:46d659005ca1151087efa997f1039ae45a7bf7a2cbbe2d17d3dcbda632a3ee9a

在鏡像倉庫上存儲容器鏡像的簡化結構如下圖所示,主要由三部分組成:清單文件(manifest)、鏡像配置(configuration)和層文件(layers)。上面命令中的鏡像摘要就是依據鏡像清單文件內容計算 SHA256 哈希值而來的,在鏡像清單文件中存放了配置文件的摘要和層文件的摘要,這些摘要都是通過具體的文件內容計算而來的,所以鏡像存儲也叫作內容尋址。

這樣做的好處是,除了可以唯一標識不同的文件,還可以在傳輸過程中通過摘要做文件校驗。在文件下載完成後,計算所下載文件的摘要值,然後與下載時的摘要標識進行對比,如果二者一致,即可判斷下載的文件是正確的。需要指出的是,由於文件在鏡像倉庫端是以壓縮形式存放的,所以摘要值也是基於壓縮文件計算而來的。

最後簡單說明鏡像的 Tag 的作用。鏡像的 Tag 主要用於對鏡像賦予一定的標記,格式是 “<repository>:<Tag>”,可以標識鏡像的版本或其他信息,也可以標識一個鏡像,如 ubuntu:20.0、centos:latest 等。(本文來自公衆號:亨利筆記)

Tag 在鏡像倉庫中可與鏡像清單或者鏡像索引關聯,多個 Tag 可以對應同一個鏡像清單或鏡像索引,由鏡像倉庫維護着它們的映射關係,可參考上圖(圖中未包含鏡像索引)。當客戶端拉取鏡像時,既可用 Tag,也可用鏡像摘要獲取同樣的鏡像。

1.4  Docker鏡像的本地存儲結構

Docker客戶端從鏡像倉庫拉取一個鏡像並存儲到本地文件系統的過程大約如下。

(1)向鏡像倉庫請求鏡像的清單文件。

(2)獲取鏡像ID,查看鏡像ID是否在本地存在。

(3)若不存在,則下載配置文件 config,在 config 文件中含有每個層文件未壓縮的文件摘要DIFF_ID。

(4)檢查層文件是否在本地存在,若不存在,則從鏡像倉庫中拉取每一層的壓縮文件。

(5)拉取時,使用鏡像清單中壓縮層文件的摘要作爲內容尋址下載。

(6)下載完一層的文件後,解壓並按照摘要校驗。

(7)當所有層文件都拉取完畢時,鏡像就下載完成了。

下載鏡像後,在本地查看鏡像 debian:latest 的信息,結果如下:

$ docker images debian:latest

REPOSITORY        TAG          IMAGE ID          CREATED          SIZE

debian            latest      1b686a95ddbf     2 weeks ago        114MB

在 IMAGE ID(鏡像ID)列顯示的 1b686a95ddbf 是本地鏡像的唯一標識ID,可以在“Docker”命令中使用。這個 ID 和鏡像倉庫中鏡像摘要(sha256:46d659…a3ee9a)的形式類似,但是數值不一樣,這是因爲該ID是鏡像配置文件的摘要,所以和鏡像倉庫使用的清單文件摘要不同。(本文來自公衆號:亨利筆記)

使用配置文件的摘要作爲本地鏡像的標識,主要是因爲本地鏡像存放的文件都是非壓縮的文件,而鏡像倉庫存放的是壓縮文件,因此層文件在本地和鏡像倉庫中有不同的摘要值。因爲壓縮文件的內容會受到壓縮算法等因素的影響,所以同樣內容的層無法保證壓縮後摘要的唯一性,而鏡像清單文件包含壓縮層文件的摘要(參考上文示例),因此通過鏡像清單文件的摘要(即鏡像摘要)無法確定鏡像的唯一性。配置文件則不同,其中包含的層信息是未壓縮的摘要值,因此相同鏡像的各層內容必然相同,配置文件的摘要值是唯一確定的。

(未完待續,歡迎點“再看”或轉發、分享、收藏)

《Harbor權威指南》目前噹噹網低於半價優惠中,點擊上圖直接購買。

要想了解雲原生、區塊鏈和人工智能等技術原理,請立即長按以下二維碼,關注本公衆號亨利筆記 ( henglibiji ),以免錯過更新。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章