徹底搞懂Docker鏡像分層的實現(xiàn)
創(chuàng)建測試鏡像
我們創(chuàng)建一個最簡單的鏡像:
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
:獲取鏡像的元數(shù)據(jù) - 使用
docker history
:查看鏡像的構建歷史 使用docker inspect
使用docker inspect
查看鏡像的元數(shù)據(jù)。
其中Parent
可以看到父鏡像, Layers
這一項下面可以看到鏡像的所有層。
使用docker history
使用docker history
可以看到鏡像的構建歷史。
我們每一行列出了鏡像包含的層。
使用
docker history
我們看到有一行很特別,就是鏡像ID為的行,這一行是什么呢?
看了 官方文檔 的描述,我們知道這些構建步驟要么是構建在另一個系統(tǒng),要么是鏡像的部分是從DockerHub上拉取下來的,要么是使用的是另一種構建工具BuildKit構建的。
很顯然,我們這里就是第二種情況,因為我們的Dockerfile中第一句指令就是FROM alpine:3.15.0
.
鏡像分層圖
根據(jù)上面的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也是這么實現(xiàn)的。
第一次下載redis:6.2時,因為之前沒有下載過,所以下載了所有的層,總共113M。網(wǎng)絡慢點的話還是需要花一些時間的!
第二次下載redis:7.0-rc,就變得快了很多!因為前面3層是redis:6.2是一樣的,這些層已經下載過了!
這種思想和我們常用的版本管理工具git也是一樣的!
如果版本2是基于版本1的基礎上,那么版本2不需要copy一份全量的數(shù)據(jù),只需一份和版本1差異化的增量數(shù)據(jù)即可!
這樣的最終好處是,可以體現(xiàn)在以下方面:
- 拉取更快:因為分層了,只需拉取本地不存在的層即可!
- 存儲更少:因為共同的層只需存儲一份即可!
- 運行時存儲更少:容器運行時可以共享相同的層!
對于第3點,多個基于相同鏡像運行的容器,都可以直接使用相同的鏡像層,每個容器只需一個自己的可寫層即可:
鏡像分層的實現(xiàn)
前面說過,Docker鏡像分層和Git的版本很像!我們不妨以此類比!只是為了方便我們理解:
Git | Docker |
---|---|
版本 | 分層 |
在前一個版本的基礎上 | 在前一層的基礎上 |
修改了代碼 | Dockerfile中加了RUN等指令 |
commit了修改,新增了一個版本 | 新建一個鏡像層 |
新版本只包含差異 | 新鏡像只包含了差異修改 |
已提交的commit是不能修改的 | 舊的鏡像層是不能修改的 |
總而言之,鏡像層是只讀的,新的鏡像層是基于前一個鏡像層的修改,只保留了增量修改的部分!
使用了聯(lián)合文件系統(tǒng),對文件系統(tǒng)的修改作為一次提交來一層層的疊加!
容器本質上也是在鏡像的基礎上加了一層可寫層!這個在另外的章節(jié)再詳細討論!
Copy-on-write策略
Copy-on-write是一種提高文件共享和復制效率的策略。
如果一個文件和目錄在低一層的鏡像層中存在,并且其它層想要讀取這個文件,就直接使用這個文件。
如果其它層想要修改這個文件(不管是構建鏡像時,還是在容器運行的過程中),這個文件都會被先拷貝到新的一層中,然后再修改它。
這樣做的好處是可以大大減少每一層的大??!
更具體的實現(xiàn)請參考官方文檔中:Copy-on-write策略
到此這篇關于徹底搞懂Docker鏡像分層的文章就介紹到這了,更多相關Docker鏡像分層內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
docker(alpine+golang)?中?hosts?不生效問題解決方法
這篇文章主要介紹了docker(alpine+golang)?中?hosts?不生效問題解決大全,本文給大家分享了三種解決方法,每種方法給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07docker容器狀態(tài)出現(xiàn)Exit(1)的問題及解決
這篇文章主要介紹了docker容器狀態(tài)出現(xiàn)Exit(1)的問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06以示例講解Clickhouse Docker集群部署以及配置
這篇文章主要介紹了Clickhouse Docker集群部署及配置,示例講解的非常詳細,希望可以幫助到有需要的小伙伴2021-08-08解決docker run中使用 ./ 相對路徑掛載文件或目錄失敗的問題
這篇文章主要介紹了解決docker run中使用‘./‘相對路徑掛載文件或目錄失敗的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2021-03-03在Ubuntu上使用Grafana監(jiān)控Docker的方法
如今越來越多的公司開始使用Docker,一談起Docker總是會跟著讓人聯(lián)想到輕量這個詞,甚至會有一種通過Docker啟動一個服務會節(jié)省很多資源的錯覺。然而Docker的「輕」也只是相對于傳統(tǒng)虛擬機而已。Docker如何監(jiān)控呢?本文就給大家介紹在Ubuntu上如何使用Grafana監(jiān)控Docker。2016-12-12