您好,登錄后才能下訂單哦!
今天小編給大家分享一下Docker鏡像分層怎么實現的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。
我們創建一個最簡單的鏡像:
1.構建測試鏡像v1.0:docker build -t image_test:1.0 .
FROM alpine:3.15.0 #除了繼承基礎鏡像,啥也不做
2.構建測試鏡像v2.0:docker build -t image_test:2.0 .
FROM alpine:3.15.0 RUN dd if=/dev/zero of=file1 bs=10M count=1 #添加一個10M的文件file1
3.構建測試鏡像v3.0:docker build -t image_test:3.0 .
FROM alpine:3.15.0 RUN dd if=/dev/zero of=file1 bs=10M count=1 #添加一個10M的文件file1 RUN dd if=/dev/zero of=file2 bs=10M count=1 #添加一個10M的文件file2
這樣本地就構建了3個測試鏡像:
我們有2種方法查看鏡像:
使用docker inspect
:獲取鏡像的元數據使用docker history
:查看鏡像的構建歷史 使用docker inspect
查看鏡像的元數據。
其中Parent
可以看到父鏡像, Layers
這一項下面可以看到鏡像的所有層。
使用docker history
可以看到鏡像的構建歷史。
我們每一行列出了鏡像包含的層。
根據上面的docker history
命令,我們可以輕松的畫出三個鏡像的分層圖:
從上面的圖可以看到,我們的鏡像是分層的,我們的Dockerfile中新增一條指令,就會新增一層!
如果我們將多個命令合成一個,那么也只會生成一層。修改一下上面的image_test:3.0,把兩條RUN合并成一條:
FROM alpine:3.15.0 RUN dd if=/dev/zero of=file1 bs=10M count=1 && \ dd if=/dev/zero of=file2 bs=10M count=1
使用docker history
查看image_test:4.0,可以看到,只有2層了!
知道了鏡像是分層的,那么我們是不是好奇為啥要這么設計呢?
試想一下我們如果不分層會有什么問題?
以拉取鏡像為例!
拉取鏡像的鏡像很大,比如Redis的鏡像有100多M
第一次我們拉取6.2版本的Redis,下載了完成的100M到本地,下次我要下載6.2.6版本的,是不是又得下載100M。
盡管可能兩個版本之間就改了幾行配置文件。
這樣是非常低效的。如果能只下載有差異的部分就好了!
這個痛點,也就是鏡像分層要解決的問題。實際上,Docker也是這么實現的。
第一次下載redis:6.2時,因為之前沒有下載過,所以下載了所有的層,總共113M。網絡慢點的話還是需要花一些時間的!
第二次下載redis:7.0-rc,就變得快了很多!因為前面3層是redis:6.2是一樣的,這些層已經下載過了!
如果版本2是基于版本1的基礎上,那么版本2不需要copy一份全量的數據,只需一份和版本1差異化的增量數據即可!
這樣的最終好處是,可以體現在以下方面:
拉取更快:因為分層了,只需拉取本地不存在的層即可!
存儲更少:因為共同的層只需存儲一份即可!
運行時存儲更少:容器運行時可以共享相同的層!
對于第3點,多個基于相同鏡像運行的容器,都可以直接使用相同的鏡像層,每個容器只需一個自己的可寫層即可:
下面這張圖想必各位是不陌生了,再往下還有一張。那我們要如何在這么多不陌生的人里面脫穎而出呢?就看誰能把這兩張圖說出花來了哈。
bootfs(boot file system) 主要包含 bootloader 和 kernel, bootloader 主要是引導加載 kernel,Linux 剛啟動時會加載 bootfs 文件系統,在Docker 鏡像的最底層是引導文件系統 bootfs。這一層與我們典型的 Linux/Unix 系統是一樣的,包含 boot 加載器和內核。當 boot 加載完成之后整個內核就都在內存中了,此時內存的使用權已由 bootfs 轉交給內核,此時系統也會卸載 bootfs。
rootfs (root file system) ,在 bootfs 之上。包含的就是典型 Linux 系統中的 /dev, /proc, /bin, /etc 等標準目錄和文件。rootfs 就是各種不同的操作系統發行版,比如 Ubuntu,Centos 等等。
對于一個精簡的 OS,rootfs 可以很小,只需要包括最基本的命令、工具和程序庫就可以了,因為底層直接用 Host 的 kernel,自己只需要提供 rootfs 就行了。由此可見對于不同的 linux 發行版, bootfs 基本是一致的, rootfs 會有差別, 因此不同的發行版可以公用 bootfs。
Docker鏡像層都是只讀的,容器層是可寫的。 當容器啟動時,一個新的可寫層被加載到鏡像的頂部。 這一層通常被稱作“容器層”,“容器層”之下的都叫“鏡像層”。所有對容器的改動,無論添加、刪除、還是修改文件都只會發生在容器層中。
那么,先讓我們來重新認識一下與 Docker 鏡像相關的 4 個概念:rootfs、Union mount、image 以及 layer。
Rootfs:代表一個 Docker Container 在啟動時(而非運行后)其內部進程可見的文件系統視角,或者是 Docker Container 的根目錄。當然,該目錄下含有 Docker Container 所需要的系統文件、工具、容器文件等。
傳統來說,Linux 操作系統內核啟動時,內核首先會掛載一個只讀(read-only)的 rootfs,當系統檢測其完整性之后,決定是否將其切換為讀寫(read-write)模式,或者最后在 rootfs 之上另行掛載一種文件系統并忽略 rootfs。Docker 架構下,依然沿用 Linux 中 rootfs 的思想。當 Docker Daemon 為 Docker Container 掛載 rootfs 的時候,與傳統 Linux 內核類似,將其設定為只讀(read-only)模式。在 rootfs 掛載完畢之后,和 Linux 內核不一樣的是,Docker Daemon 沒有將 Docker Container 的文件系統設為讀寫(read-write)模式,而是利用 Union mount 的技術,在這個只讀的 rootfs 之上再掛載一個讀寫(read-write)的文件系統,掛載時該讀寫(read-write)文件系統內空無一物。
正如 read-only 和 read-write 的含義那樣,該容器中的進程對 rootfs 中的內容只擁有讀權限,對于 read-write 讀寫文件系統中的內容既擁有讀權限也擁有寫權限。容器雖然只有一個文件系統,但該文件系統由“兩層”組成,分別為讀寫文件系統和只讀文件系統。
Union mount:代表一種文件系統掛載的方式,允許同一時刻多種文件系統掛載在一起,并以一種文件系統的形式,呈現多種文件系統內容合并后的目錄。
一般情況下,通過某種文件系統掛載內容至掛載點的話,掛載點目錄中原先的內容將會被隱藏。而 Union mount 則不會將掛載點目錄中的內容隱藏,反而是將掛載點目錄中的內容和被掛載的內容合并,并為合并后的內容提供一個統一獨立的文件系統視角。通常來講,被合并的文件系統中只有一個會以讀寫(read-write)模式掛載,而其他的文件系統的掛載模式均為只讀(read-only)。實現這種 Union mount 技術的文件系統一般被稱為 Union Filesystem,較為常見的有 UnionFS、AUFS、OverlayFS 等。
Docker 實現容器文件系統 Union mount 時,提供多種具體的文件系統解決方案,如 Docker 早版本沿用至今的的 AUFS,還有在 docker 1.4.0 版本中開始支持的 OverlayFS 等。
AUFS 掛載 Ubuntu 文件系統示意圖
使用鏡像 ubuntu 創建的容器中,可以暫且將該容器整個 rootfs 當成是一個文件系統。上文也提到,掛載時讀寫(read-write)文件系統中空無一物。既然如此,從用戶視角來看,容器內文件系統和 rootfs 完全一樣,用戶完全可以按照往常習慣,無差別的使用自身視角下文件系統中的所有內容;然而,從內核的角度來看,兩者在有著非常大的區別。追溯區別存在的根本原因,那就不得不提及 AUFS 等文件系統的 COW(copy-on-write)特性。
COW 文件系統和其他文件系統最大的區別就是:從不覆寫已有文件系統中已有的內容。由于通過 COW 文件系統將兩個文件系統(rootfs 和 read-write filesystem)合并,最終用戶視角為合并后的含有所有內容的文件系統,然而在 Linux 內核邏輯上依然可以區別兩者,那就是用戶對原先 rootfs 中的內容擁有只讀權限,而對 read-write filesystem 中的內容擁有讀寫權限。
既然對用戶而言,全然不知哪些內容只讀,哪些內容可讀寫,這些信息只有內核在接管,那么假設用戶需要更新其視角下的文件 /etc/hosts,而該文件又恰巧是 rootfs 只讀文件系統中的內容,內核是否會拋出異常或者駁回用戶請求呢?答案是否定的。當此情形發生時,COW 文件系統首先不會覆寫 read-only 文件系統中的文件,即不會覆寫 rootfs 中 /etc/hosts,其次反而會將該文件拷貝至讀寫文件系統中,即拷貝至讀寫文件系統中的 /etc/hosts,最后再對后者進行更新操作。如此一來,縱使 rootfs 與 read-write filesystem 中均由 /etc/hosts,諸如 AUFS 類型的 COW 文件系統也能保證用戶視角中只能看到 read-write filesystem 中的 /etc/hosts,即更新后的內容。
當然,這樣的特性同樣支持 rootfs 中文件的刪除等其他操作。例如:用戶通過 apt-get 軟件包管理工具安裝 Golang,所有與 Golang 相關的內容都會被安裝在讀寫文件系統中,而不會安裝在 rootfs。此時用戶又希望通過 apt-get 軟件包管理工具刪除所有關于 MySQL 的內容,恰巧這部分內容又都存在于 rootfs 中時,刪除操作執行時同樣不會刪除 rootfs 實際存在的 MySQL,而是在 read-write filesystem 中刪除該部分內容,導致最終 rootfs 中的 MySQL 對容器用戶不可見,也不可訪。
掌握 Docker 中 rootfs 以及 Union mount 的概念之后,再來理解 Docker 鏡像,就會變得水到渠成。
Docker 中 rootfs 的概念,起到容器文件系統中基石的作用。對于容器而言,其只讀的特性,也是不難理解。神奇的是,實際情況下 Docker 的 rootfs 設計與實現比上文的描述還要精妙不少。
繼續以 ubuntu 為例,雖然通過 AUFS 可以實現 rootfs 與 read-write filesystem 的合并,但是考慮到 rootfs 自身接近 200MB 的磁盤大小,如果以這個 rootfs 的粒度來實現容器的創建與遷移等,是否會稍顯笨重,同時也會大大降低鏡像的靈活性。而且,若用戶希望擁有一個 ubuntu 的 rootfs,那么是否有必要創建一個全新的 rootfs,畢竟 ubuntu 和 ubuntu 的 rootfs 中有很多一致的內容。
Docker 中 image 的概念,非常巧妙的解決了以上的問題。最為簡單的解釋 image,就是 Docker 容器中只讀文件系統 rootfs 的一部分。換言之,實際上 Docker 容器的 rootfs 可以由多個 image 來構成。多個 image 構成 rootfs 的方式依然沿用 Union mount 技術。
多個 Image 構成 rootfs 的示意圖。圖中,rootfs 中每一層 image 中的內容劃分只為了闡述清楚 rootfs 由多個 image 構成,并不代表實際情況中 rootfs 中的內容劃分。
從上圖可以看出,舉例的容器 rootfs 包含 4 個 image,其中每個 image 中都有一些用戶視角文件系統中的一部分內容。4 個 image 處于層疊的關系,除了最底層的 image,每一層的 image 都疊加在另一個 image 之上。另外,每一個 image 均含有一個 image ID,用以唯一的標記該 image。
基于以上的概念,Docker Image 中又抽象出兩種概念:Parent Image 以及 Base Image。除了容器 rootfs 最底層的 image,其余 image 都依賴于其底下的一個或多個 image,而 Docker 中將下一層的 image 稱為上一層 image 的 Parent Image。imageID_0 是 imageID_1 的 Parent Image,imageID_2 是 imageID_3 的 Parent Image,而 imageID_0 沒有 Parent Image。對于最下層的 image,即沒有 Parent Image 的鏡像,在 Docker 中習慣稱之為 Base Image。
通過 image 的形式,原先較為臃腫的 rootfs 被逐漸打散成輕便的多層。Image 除了輕便的特性,同時還有上文提到的只讀特性,如此一來,在不同的容器、不同的 rootfs 中 image 完全可以用來復用。
多 image 組織關系與復用關系如圖下圖(圖中鏡像名稱的舉例只為將 image 之間的關系闡述清楚,并不代表實際情況中相應名稱 image 之間的關系):
多 image 組織關系示意圖
圖中共羅列了 11 個 image,這 11 個 image 之間的關系呈現一副森林圖。森林中含有兩棵樹,左邊樹中包含 5 個節點,即含有 5 個 image;右邊樹中包含 6 個節點,即含有 6 個 image。圖中,有些 image 標記了紅色字段,意味該 image 代表某一種容器鏡像 rootfs 的最上層 image。如圖中的 ubuntu:14.04,代表 imageID_3 為該類型容器 rootfs 的最上層,沿著該節點找到樹的根節點,可以發現路徑上還有 imageID_2,imageID_1 和 imageID_0。特殊的是,imageID_2 作為 imageID_3 的 Parent Image,同時又是容器鏡像 ubuntu:12.04 的 rootfs 中的最上層,可見鏡像 ubuntu:14.04 只是在鏡像 ubuntu:12.04 之上,再另行疊加了一層。因此,在下載鏡像 ubuntu:12.04 以及 ubuntu:14.04 時,只會下載一份 imageID_2、imageID_1 和 imageID_0,實現 image 的復用。同時,右邊樹中 mysql:5.6、mongo:2.2、debian:wheezy 和 debian:jessie 也呈現同樣的關系。
Docker 術語中,layer 是一個與 image 含義較為相近的詞。容器鏡像的 rootfs 是容器只讀的文件系統,rootfs 又是由多個只讀的 image 構成。于是,rootfs 中每個只讀的 image 都可以稱為一層 layer。
除了只讀的 image 之外,Docker Daemon 在創建容器時會在容器的 rootfs 之上,再 mount 一層 read-write filesystem,而這一層文件系統,也稱為容器的一層 layer,常被稱為 top layer。
因此,總結而言,Docker 容器中的每一層只讀的 image,以及最上層可讀寫的文件系統,均被稱為 layer。如此一來,layer 的范疇比 image 多了一層,即多包含了最上層的 read-write filesystem。
有了 layer 的概念,大家可以思考這樣一個問題:容器文件系統分為只讀的 rootfs,以及可讀寫的 top layer,那么容器運行時若在 top layer 中寫入了內容,那這些內容是否可以持久化,并且也被其它容器復用?
上文對于 image 的分析中,提到了 image 有復用的特性,既然如此,再提一個更為大膽的假設:容器的 top layer 是否可以轉變為 image?
答案是肯定的。Docker 的設計理念中,top layer 轉變為 image 的行為(Docker 中稱為 commit 操作),大大釋放了容器 rootfs 的靈活性。Docker 的開發者完全可以基于某個鏡像創建容器做開發工作,并且無論在開發周期的哪個時間點,都可以對容器進行 commit,將所有 top layer 中的內容打包為一個 image,構成一個新的鏡像。Commit 完畢之后,用戶完全可以基于新的鏡像,進行開發、分發、測試、部署等。不僅 docker commit 的原理如此,基于 Dockerfile 的 docker build,其追核心的思想,也是不斷將容器的 top layer 轉化為 image。
爽,酣暢淋漓啊這波讀下來!!!
Docker Image 作為 Docker 生態中的精髓,下載過程中需要 Docker 架構中多個組件的協作。Docker 鏡像的下載流程如圖:
1、docker client發送鏡像的tag到registry。
2、registry根據鏡像tag,得到鏡像的manifest文件,返回給docker client。
3、docker client拿到manifest文件后,根據其中的config的digest,也就是image ID,檢查下鏡像在本地是否存在。
4、如果鏡像不存在,則下載config文件,并根據config文件中的diff_ids得到鏡像每一層解壓后的digest。
5、然后根據每層解壓后的digest文件,檢查本地是否存在,如果不存在,則通過manifest文件中的6、layer的digest下載該層并解壓,然后校驗解壓后digest是否匹配。
7、下載完所有層后,鏡像就下載完畢。
Docker Daemon 執行鏡像下載任務時,從 Docker Registry 處下載指定鏡像之后,仍需要將鏡像合理地存儲于宿主機的文件系統中。更為具體而言,存儲工作分為兩個部分:
(1) 存儲鏡像內容;
(2) 在 graph 中注冊鏡像信息。
說到鏡像內容,需要強調的是,每一層 layer 的 Docker Image 內容都可以認為有兩個部分組成:鏡像中每一層 layer 中存儲的文件系統內容,這部分內容一般可以認為是未來 Docker 容器的靜態文件內容;另一部分內容指的是容器的 json 文件,json 文件代表的信息除了容器的基本屬性信息之外,還包括未來容器運行時的動態信息,包括 ENV 等信息。
存儲鏡像內容,意味著 Docker Daemon 所在宿主機上已經存在鏡像的所有內容,除此之外,Docker Daemon 仍需要對所存儲的鏡像進行統計備案,以便用戶在后續的鏡像管理與使用過程中,可以有據可循。為此,Docker Daemon 設計了 graph,使用 graph 來接管這部分的工作。graph 負責記錄有哪些鏡像已經被正確存儲,供 Docker Daemon 調用。
在本地起一個registry服務,然后推送三個鏡像到鏡像倉庫。可以得到registry中的文件內容如下所示。registry中包含三個鏡像: xxx/library/debian:latest,xxx/repo:tag和xxx/busybox:v1
└── registry └── v2 ├── blobs │ └── sha256 │ ├── 0d │ │ └── 0d96da54f60b86a4d869d44b44cfca69d71c4776b81d361bc057d6666ec0d878 │ │ └── data │ ├── 34 │ │ └── 34efe68cca33507682b1673c851700ec66839ecf94d19b928176e20d20e02413 │ │ └── data │ ... └── repositories ├── busybox │ ├── _layers │ │ └── sha256 │ │ ├── 7138284460ffa3bb6ee087344f5b051468b3f8697e2d1427bac1a20c8d168b14 │ │ │ └── link │ │ └── e685c5c858e36338a47c627763b50dfe6035b547f1f75f0d39753db71e319016 │ │ └── link │ ├── _manifests │ │ ├── revisions │ │ │ └── sha256 │ │ │ └── 34efe68cca33507682b1673c851700ec66839ecf94d19b928176e20d20e02413 │ │ │ └── link │ │ └── tags │ │ └── v1 │ │ ├── current │ │ │ └── link │ │ └── index │ │ └── sha256 │ │ └── 34efe68cca33507682b1673c851700ec66839ecf94d19b928176e20d20e02413 │ │ └── link │ └── _uploads ├── library │ └── debian │ ├── _layers │ │ └── sha256 │ │ ├── 41c22baa66ecf728c1ea0c5405ebe72c5b2606ef66b4565a209e23e1ab05fe80 │ │ │ └── link │ │ ├── 67283bbdd4a0dd32f555b4279fd546b3c69251342f0c6715b075cc72049d28a1 │ │ │ └── link │ │ ... │ ├── _manifests │ │ ├── revisions │ │ │ └── sha256 │ │ │ └── 57c1e4ff150e2782a25c8cebb80b574f81f06b74944caf972f27e21b76074194 │ │ │ └── link │ │ └── tags │ │ └── latest │ │ ├── current │ │ │ └── link │ │ └── index │ │ └── sha256 │ │ └── 57c1e4ff150e2782a25c8cebb80b574f81f06b74944caf972f27e21b76074194 │ │ └── link │ └── _uploads └── repo ├── _layers │ └── sha256 │ ├── 0d96da54f60b86a4d869d44b44cfca69d71c4776b81d361bc057d6666ec0d878 │ │ └── link │ ├── 3790aef225b922bc97aaba099fe762f7b115aec55a0083824b548a6a1e610719 │ │ └── link │ ... ├── _manifests │ ├── revisions │ │ └── sha256 │ │ └── 36cb5b157911061fb610d8884dc09e0b0300a767a350563cbfd88b4b85324ce4 │ │ └── link │ └── tags │ └── tag │ ├── current │ │ └── link │ └── index │ └── sha256 │ └── 36cb5b157911061fb610d8884dc09e0b0300a767a350563cbfd88b4b85324ce4 │ └── link └── _uploads
對上面的文件結構進行整理,可以得到如下圖所示的結構:
registry有兩個目錄,分別為blobs和repositories,其中blobs保存的是鏡像的manifest文件、config文件和layer文件內容,文件名字均為data,每個文件可能是manifest、config、layer中的一種。repositories保存的是鏡像的repo、tag、layer摘要等信息。其中的_manifests文件夾下包含著鏡像的 tags 和 revisions 信息,每一個鏡像的每一個 tag 對應 tag 名相同的目錄。每個 tag名目錄下面有 current 目錄和 index 目錄, current 目錄下的 link 文件保存了該 tag 目前的 manifest 文件的 sha256 編碼,對應在 blobs 中的 sha256 目錄下的 data 文件,而 index 目錄則列出了該 tag 歷史上傳的所有版本的 sha256 編碼信息。_revisions 目錄里存放了該 repository 歷史上上傳版本的所有 sha256 編碼信息。
鏡像在本地存儲目錄為/var/lib/docker/image/overlay2,查看下面的文件結構:
tree -L 4 /var/lib/docker/image/overlay2/ /var/lib/docker/image/overlay2/ ├── distribution │ ├── diffid-by-digest │ │ └── sha256 │ │ ├── 0240c3db9dedbfe40ec02d465375aa5b059bf8ac78dc249d1f1c91b9429fce44 │ │ ├── 41c22baa66ecf728c1ea0c5405ebe72c5b2606ef66b4565a209e23e1ab05fe80 │ │ ├── 4cdd12619cf5ed0ae43b41cd51f26fbdbd1f5ded860e4188822ec29158218263 │ │ ├── ... │ └── v2metadata-by-diffid │ └── sha256 │ ├── 00188c48b6d80656e2344142a77bccf6927123e7492baf43df68e280b2baf7f2 │ ├── 04fefa2a1a8fefaafde3b966f11d547e3bbaa2bb36bf90c58e33c1d305052fa9 │ ├── ... ├── imagedb │ ├── content │ │ └── sha256 │ │ ├── 7138284460ffa3bb6ee087344f5b051468b3f8697e2d1427bac1a20c8d168b14 │ │ ├── ... │ └── metadata │ └── sha256 │ ├── b8604a3fe8543c9e6afc29550de05b36cd162a97aa9b2833864ea8a5be11f3e2 │ └── dabbfbe0c57b6e5cd4bc089818d3f664acfad496dc741c9a501e72d15e803b34 ├── layerdb │ ├── mounts │ │ ├── 2d534be7517fb3efd9c14248eefdb4781924095fe304f5aa0c848f2e76c6bf08 │ │ │ ├── init-id │ │ │ ├── mount-id │ │ │ └── parent │ │ ├──... │ ├── sha256 │ │ ├── 0e16a5a61bcb4e6b2bb2d746c2d6789d6c0b66198208b831f74b52198d744189 │ │ │ ├── cache-id │ │ │ ├── diff │ │ │ ├── parent │ │ │ ├── size │ │ │ └── tar-split.json.gz │ │ ├── 0ee0aa554b8be64c963aaaf162df152784d868d21a7414146cb819a93e4bdb9e │ │ │ ├── cache-id │ │ │ ├── diff │ │ │ ├── parent │ │ │ ├── size │ │ │ └── tar-split.json.gz │ │ ├── ... │ └── tmp └── repositories.json
對上面的文件結構進行整理,可以得到如下圖所示的結構:
以上就是“Docker鏡像分層怎么實現”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。