Docker 進階之鏡像分層方案詳解
導讀
可以想象,像 ubuntu等基礎鏡像,體積必然不小。那麼,思考以下幾個問題:
- 我們基於同一個鏡像(ubuntu 18.4)啟動瞭兩個容器,會占用兩倍磁盤空間嗎?
- 我們在容器內修改或者新建瞭某個文件,要修改原鏡像嗎?
- 我們基於某鏡像(ubuntu 18.04)新建一個鏡像(myubuntu),需要將原鏡像文件全部拷貝到新鏡像中嗎?
首先,讓我們嘗試思考下,如果我們去做,該如何高效的解決這些問題?
- 方案一,…
- 方案二,…
- 方案三,…
入門圖解
創建測試鏡像
我們創建一個最簡單的鏡像:
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
使用docker inspect
查看鏡像的元數據。
其中Parent
可以看到父鏡像, Layers
這一項下面可以看到鏡像的所有層。
使用docker history
使用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點,多個基於相同鏡像運行的容器,都可以直接使用相同的鏡像層,每個容器隻需一個自己的可寫層即可:
Docker鏡像加載原理
下面這張圖想必各位是不陌生瞭,再往下還有一張。那我們要如何在這麼多不陌生的人裡面脫穎而出呢?就看誰能把這兩張圖說出花來瞭哈。
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
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:代表一種文件系統掛載的方式,允許同一時刻多種文件系統掛載在一起,並以一種文件系統的形式,呈現多種文件系統內容合並後的目錄。
一般情況下,通過某種文件系統掛載內容至掛載點的話,掛載點目錄中原先的內容將會被隱藏。而 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 鏡像,就會變得水到渠成。
image
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 也呈現同樣的關系。
layer
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 鏡像下載
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 進階之鏡像分層詳解的文章就介紹到這瞭,更多相關Docker鏡像分層內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Docker容器實戰之鏡像與容器的工作原理
- docker容器下配置jupyter notebook的操作
- Docker 鏡像分層及dockerfile 編寫技巧
- centos搭建部署docker環境的詳細步驟
- 聊聊Docker中容器的創建與啟停問題