Docker映象及其相關技術的原理和本質
R
5月15日(週三)晚20:30,Kubernetes Master Class線上培訓第6期課程《 在Kubernetes中建立高可用應用 》即將開播,點選文末【閱讀原文】即可免費預約註冊!
作為雲端計算的當紅明星, Docker 來勢洶洶,成為了很多IT人員現時的必備技能。但是對於新手而言, 在理解 Docker命令時常常 存在一些問題,尤其是在 Docker 映象 底層的工作原理和容器與容器映象的關係上。
一般情況下只有真正理解了某門技術的原理才能真正掌握這一門技術,然後才能去深入地使用這門技術。在本文中,我們會由淺入深出地帶大家瞭解下Docker 映象及其相關技術的原理和本質。
容器VS.容器映象
在說Docker 映象的原理知識之前,我們先看下docker 容器和docker 映象的區別,因為這部分我們後面會涉及到。
簡單說來,我們可以將Docker 映象看成是Docker 容器的靜態時,也可將Docker 容器看成是Docker映象的執行時。
從Docker 的官方文件來看,Docker 容器的定義和 Docker 映象的定義幾乎是相同,Docker 容器和Docker 映象的區別主要在於docker 容器多出了一個可寫層。
容器中的程序就執行在這個可寫層,這個可寫層有兩個狀態,即執行態和退出態。當我們docker run 執行容器後,docker 容器就進入了執行態,當我們停止正在執行中的容器時,docker 容器就進入了退出態。
我們將容器從執行態轉為退出態時,期間發生的變更都會寫入到容器的檔案系統中(需要注意的是,此處不是寫入到了docker 映象中),這方面的變化下文中我們還會再細說。
Docker儲存簡介
簡單說來Docker 映象就是一組只讀的目錄,或者叫只讀的 Docker 容器模板,映象中含有一個Docker 容器執行所需要的檔案系統,所以我們說Docker 映象是啟動一個Docker 容器的基礎。
如果這樣不是很好理解,我們可以通過一個圖一起看下:
從圖中可以看出除了最上面的一層為讀寫層之外,下面的其他的層都是隻讀的映象層,並且除了最下面的一層外,其他的層都有會有一個指標指向自己下面的一層映象。
雖然統一檔案系統(union file system)技術將不同的映象層整合成一個統一的檔案系統,為構成一個完整容器映象的層提供了一個統一的視角,隱藏了多個層的複雜性,對使用者來說只存在一個檔案系統,但圖中的這些層並不是不能看到的,如果需要檢視的話可以進入執行Docker的機器上進行檢視,從這些層中可以看到Docker 內部實現的一些細節,接下來我們一起看下。
一般剛接觸Docker 不久的話可能會不太清楚Docker 的儲存方式是怎樣的,並且可能也不太清楚Docker 容器的映象到底儲存在什麼路徑下,比較迷茫。
有些同學想了解下Docker 的映象資料儲存在什麼位置,然後谷歌了下幾篇博文,說是在/var/lib/docker 下有個aufs目錄,結果在自己機器上進到這個路徑後發現沒有aufs相關的目錄,然後以為是版本的問題,其實不然。
以Linux伺服器為例,其實Docker 的容器映象和容器本身的資料都存放在伺服器的 /var/lib/docker/ 這個路徑下。不過不同的linux發行版儲存方式上有差別,比如,在ubuntu發行版上儲存方式為AUFS,CentOS發行版上的儲存方式為device mapper。
/var/lib/docker 路徑下的資訊在不同的階段會有變化,從筆者個人經驗來看,瞭解這幾個階段中新增的資料以及容器與映象的儲存結構的變化非常有利於我們對Docker容器以及Docker映象的理解。
在下文中,我們將一起看下Docker執行後、下載映象後、執行容器後三個階段中Docker 儲存的變化。
環境資訊:
系統發行版:CentOS7.2。
核心版本:3.10.0-327.36.1.el7.x86_64
Docker 版本:1.8
啟動Docker後
在此我們假設大家已經安裝好了Docker環境,具體安裝的過程不再贅述。
# 啟動Docker 服務
[root@influxdb ~]# systemctl start docker
# 檢視/var/lib/docker路徑下的檔案結構
[root@localhost docker]# tree .
├── containers
├── devicemapper
│ ├── devicemapper
│ │ ├── data
│ │ └── metadata
│ └── metadata
│ ├── base
│ ├── deviceset-metadata
│ └── transaction-metadata
├── graph
├── linkgraph.db
├── repositories-devicemapper
├── tmp
├── trust
└── volumes
8 directories, 7 files
注 : 必須 啟動 Docker 服務 後檢視, 如果 沒有啟動 Docker 程序 則路徑 /var/li b /docker 不存在 。
前文中我們已經提到過,CentOS發行版中Docker 服務使用的儲存方式為devicemapper,所以我們從前面tree命令的結果中可以看到出現了目錄devicemapper。
有些同學可能會問什麼是 devicemapper?
太陽底下無新鮮事,devicemapper 並不是伴隨著Docker 才出現的,早在linux2.6版本的核心時其實就已經引入了devicemapper,且當時是作為一個很重要的技術出現的。
簡單說來devicemapper 就是Docker 服務的一個儲存驅動,或者叫Docker 服務的儲存後端。Devicemapper 其實是一個基於核心的框架,這個框架中對linux上很多的功能進行了增強,比如對linux上高階卷管理功能的增強。
Devicemapper 儲存驅動將我們的每個docker映象和docker容器都存在在自己的虛擬裝置中,devicemapper的這些裝置相當於我們常見的一般的寫時複製快照裝置的超配版本。通過上面的介紹,有些同學可能以為devicemapper 儲存驅動是工作在塊級別的,但是devicempper 實際是工作在檔案級別的,也就是說devicemapper 儲存驅動和寫時複製操作都是直接操作塊,而不是對檔案進行操作。
以上是我們關於Docker 服務的devicemapper 儲存驅動的一個簡單的介紹,Docker 官方文件中提供了很多的有關Docker 儲存驅動的介紹,感興趣的同學可以自行查閱。
進一步的檢視的話,可以看到路徑/var/lib/docker/devicemapper下面有兩個目錄,分別為devicemapper和data,其中目錄devicemapper 下存在兩個名為data和metadata的檔案,兩個檔案從名稱即可看出一個是映象資料的儲存池,一個為映象相關的元資料。接下去我們會逐個看下這個路徑下的檔案。
進入上面提到的目錄metadata下,可以看到這個目錄中已經存在三個檔案,分別為:base、deviceset-metadata和transction-metadata,這三個文分別用來存放上文中我們提到的元資料的id、大小和uuid等資訊。
[root@localhost metadata]# pwd
/var/lib/docker/devicemapper/metadata
[root@localhost metadata]# ls
base deviceset-metadata transaction-metadata
除了上面提到的幾個目錄,上文中tree的結果中還有幾個目錄,分別為:containers、devicemapper、graph、linkgraph.db、repositories-devicemapper、tmp、trust和volumes。這幾個檔案對於docker 的映象儲存來說都很重要,我們以檔案repositories-devicemapper為例,看下這個檔案對於映象儲存所起到的作用。
[root@localhost docker]# pwd
/var/lib/docker
[root@localhost docker]# ls
containers devicemapper graph linkgraph.db repositories-devicemapper tmp trust volumes
我們可以先看下repositories-devicemapper 這個檔案中在當前的階段中有什麼:
root@localhost docker]# cat repositories-devicemapper
{"Repositories":{}}[root@localhost docker]#
從上面cat的結果中我們不難看出,檔案repositories-devicemapper 中其實記錄的就是docker 映象的屬性資訊,比如映象名稱、映象標籤、映象的ID等資訊,如果映象剛好沒有標籤的話預設會以lastet作為標籤。
以上是對Docker 服務執行後pull映象之前/var/lib/docker 路徑下資料的一個簡單的解讀,相信大家通過上面的描述已經對docker映象有了一些更深入的認識。下面我們看下在我們pull 自己的第一個docker映象之後路徑/var/lib/docker 之下的資料會發生怎樣的變化。
Pull 映象後:
在此我們以一個 nginx映象 為例一起看下這個階段的變化。
# pull 示例映象
[root@localhost docker]# docker pull nginx
Using default tag: latest
latest: Pulling from library/nginx
22f3bf58cd09: Pull complete
ea2fc476f5f0: Pull complete
81d728438afe: Pull complete
303a6dec1190: Pull complete
d43816b44a22: Pull complete
dc02db50a25a: Pull complete
6f650a34b308: Pull complete
a634e96a9de9: Pull complete
72f3ebe1e4d7: Pull complete
c2c9e418b22c: Pull complete
Digest: sha256:a82bbaf63c445ee9b854d182254c62e34e6fa92f63d7b4fdf6cea7e76665e06e
Status: Downloaded newer image for nginx:latest
# 檢視映象是否已經在本地
[root@localhost docker]# docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
nginx latest c2c9e418b22c 2 weeks ago 109.3 MB
[root@localhost docker]#
在此我們沒有指定nginx映象的tag,因此預設拉去了最新的版本。然後我們看下路徑/var/lib/docker 下是否有變化:
[root@localhost docker]# tree .
.
├── containers
├── devicemapper
│ ├── devicemapper
│ │ ├── data
│ │ └── metadata
……
省略若干資料
……
│ │ ├── checksum
│ │ ├── json
│ │ ├── layersize
│ │ ├── tar-data.json.gz
│ │ └── v1Compatibility
│ ├── 303a6dec11900c97f5d7555d31adec02d2e5e4eaa1a77537e7a5ebd45bb7fcd2
│ │ ├── checksum
│ │ ├── json
│ │ ├── layersize
│ │ ├── tar-data.json.gz
│ │ └── v1Compatibility
│ ├── 6f650a34b3083c96cf8b7babc7a391227c0f78e0d07067071c46e31bd834de3a
│ │ ├── checksum
│ │ ├── json
│ │ ├── layersize
│ │ ├── tar-data.json.gz
│ │ └── v1Compatibility
│ ├── 72f3ebe1e4d793a50836d4e070c94ef7497c80111d178e867014981f64696a39
│ │ ├── checksum
│ │ ├── json
│ │ ├── layersize
│ │ ├── tar-data.json.gz
│ │ └── v1Compatibility
│ ├── 81d728438afe98602e2e692c20299ecf41b93173fb12351c1b59820b17fb16b9
│ │ ├── checksum
│ │ ├── json
│ │ ├── layersize
│ │ ├── tar-data.json.gz
│ │ └── v1Compatibility
│ ├── a634e96a9de9f1e280efaecdd43c7273ac43e109a42ab6c76ab2d2261c8cdc50
│ │ ├── checksum
│ │ ├── json
│ │ ├── layersize
│ │ ├── tar-data.json.gz
│ │ └── v1Compatibility
│ ├──
……
省略若干資料
……
│ ├── ea2fc476f5f055f9e44963987903ecfe0cb480b7e387d8b5cb64006832110afc
│ │ ├── checksum
│ │ ├── json
│ │ ├── layersize
│ │ ├── tar-data.json.gz
│ │ └── v1Compatibility
│ └── _tmp
├── linkgraph.db
├── repositories-devicemapper
├── tmp
├── trust
└── volumes
30 directories, 67 files
[root@localhost docker]#
從結尾的目錄數和檔案數也可以看出,在我們拉取映象後/var/lib/docker 下多出了很多的檔案(拉取映象之前,只有8個目錄,7個檔案),下面我們一步步的分析。
如果這個時候看下路徑/var/lib/docker下的檔案的話,可以很容易的看到發生變化的主要是下面這三個檔案:/var/lib/docker/devicemapper/metadata、/var/lib/docker/devicemapper/mnt以及/var/lib/docker/graph。我們先看下metadata這個檔案:
從結果可以看出除了上一個階段中已經有的base、deviceset-metadata等幾個檔案外,還多出了一些名字較長的檔案,我們挨個看下這幾個檔案中的資料:
[root@localhost metadata]# cat base
{ "device_id":1 ,"size":107374182400,"transaction_id":1,"initialized":true} [root@localhost metadata]# cat 22f3bf58cd0949b57df2dc161e7026a8cc77699b6a8be7d0e3085e252a5439c3
{ "device_id":2 ,"size":107374182400,"transaction_id":2,"initialized":false}
[root@localhost metadata]# cat ea2fc476f5f055f9e44963987903ecfe0cb480b7e387d8b5cb64006832110afc
{ "device_id":3 ,"size":107374182400,"transaction_id":3,"initialized":false}
[root@localhost metadata]# cat 81d728438afe98602e2e692c20299ecf41b93173fb12351c1b59820b17fb16b9
{"device_id":4 ,"size":107374182400,"transaction_id":4,"initialized":false} [root@localhost metadata]# cat d43816b44a2280148da8d9b6ce2f357bff9b2e59ef386181f36a4a433a9aad6c
{ "device_id":6 ,"size":107374182400,"transaction_id":6,"initialized":false} [root@localhost metadata]# cat 303a6dec11900c97f5d7555d31adec02d2e5e4eaa1a77537e7a5ebd45bb7fcd2
{ "device_id":5 ,"size":107374182400,"transaction_id":5,"initialized":false} [root@localhost metadata]# cat a634e96a9de9f1e280efaecdd43c7273ac43e109a42ab6c76ab2d2261c8cdc50
{ "device_id":9 ,"size":107374182400,"transaction_id":9,"initialized":false} [root@localhost metadata]# cat dc02db50a25a87ca227492197721e97d19f1822701fe3ec73533a0811a6393a7
{ "device_id":7 ,"size":107374182400,"transaction_id":7,"initialized":false} [root@localhost metadata]# cat 6f650a34b3083c96cf8b7babc7a391227c0f78e0d07067071c46e31bd834de3a
{ "device_id":8 ,"size":107374182400,"transaction_id":8,"initialized":false} [root@localhost metadata]# cat 72f3ebe1e4d793a50836d4e070c94ef7497c80111d178e867014981f64696a39
{ "device_id":10 ,"size":107374182400,"transaction_id":10,"initialized":false} [root@localhost metadata]# cat c2c9e418b22ca5a0b02ef0c2bd02c34ad792d1fc271e5580fdb3252979ccc092
{ "device_id":11 ,"size":107374182400,"transaction_id":11,"initialized":false}
從上面的結果可以看出上面的幾個檔案中的device_id數值是按照順序排列下來的,換句話說就是除了上一個階段中已經存在的base檔案,上面結果中其他的幾個檔案都是nginx映象的中間映象層,也就是我們經常執行的命令docker images –a 的結果中看到的構成當前映象的各個映象層。
接下來我們再看一個變化較大的檔案/var/lib/docker/graph。
[root@localhost graph]# tree .
.
├── 22f3bf58cd0949b57df2dc161e7026a8cc77699b6a8be7d0e3085e252a5439c3
│ ├── checksum
│ ├── json
│ ├── layersize
│ ├── tar-data.json.gz
│ └── v1Compatibility
├── 303a6dec11900c97f5d7555d31adec02d2e5e4eaa1a77537e7a5ebd45bb7fcd2
│ ├── checksum
│ ├── json
│ ├── layersize
│ ├── tar-data.json.gz
│ └── v1Compatibility
├── 6f650a34b3083c96cf8b7babc7a391227c0f78e0d07067071c46e31bd834de3a
│ ├── checksum
│ ├── json
│ ├── layersize
│ ├── tar-data.json.gz
│ └── v1Compatibility
├── 72f3ebe1e4d793a50836d4e070c94ef7497c80111d178e867014981f64696a39
│ ├── checksum
│ ├── json
│ ├── layersize
│ ├── tar-data.json.gz
│ └── v1Compatibility
├── 81d728438afe98602e2e692c20299ecf41b93173fb12351c1b59820b17fb16b9
│ ├── checksum
│ ├── json
│ ├── layersize
│ ├── tar-data.json.gz
│ └── v1Compatibility
├── a634e96a9de9f1e280efaecdd43c7273ac43e109a42ab6c76ab2d2261c8cdc50
│ ├── checksum
│ ├── json
│ ├── layersize
│ ├── tar-data.json.gz
│ └── v1Compatibility
├── c2c9e418b22ca5a0b02ef0c2bd02c34ad792d1fc271e5580fdb3252979ccc092
│ ├── checksum
│ ├── json
│ ├── layersize
│ ├── tar-data.json.gz
│ └── v1Compatibility
├── d43816b44a2280148da8d9b6ce2f357bff9b2e59ef386181f36a4a433a9aad6c
│ ├── checksum
│ ├── json
│ ├── layersize
│ ├── tar-data.json.gz
│ └── v1Compatibility
├── dc02db50a25a87ca227492197721e97d19f1822701fe3ec73533a0811a6393a7
│ ├── checksum
│ ├── json
│ ├── layersize
│ ├── tar-data.json.gz
│ └── v1Compatibility
├── ea2fc476f5f055f9e44963987903ecfe0cb480b7e387d8b5cb64006832110afc
│ ├── checksum
│ ├── json
│ ├── layersize
│ ├── tar-data.json.gz
│ └── v1Compatibility
└── _tmp
11 directories, 50 files
從上面的結果中可以很明顯的看到我們的映象nginx及其每一個nginx映象的中間層對應的目錄下都包含如下幾個檔案:checksum、json、layerrize、tar-data.json.gz和v1Compatibility。我們任意找一個檔案看下這幾個檔案中存放了什麼資料。
(1) Checksum
其實從檔名稱即可看出每個映象層中的checksum檔案存放的是當前映象層的md5值,用於核對當前映象層的資料是否完整。
(2) json
從cat 的結果中可以看出json檔案中存放的資料較多,比如Hostname、Domainname、User、Image等資訊,而且很多引數和我們經常接觸的Dockerfile中的引數相似。相比較前面的checksum檔案這個檔案中的內容相對較複雜,在此我們也解釋下。
此處的json檔案中一般主要用於存放映象中涉及的動態資訊,但需要注意的是此處的json檔案並不僅僅被用於儲存docker映象的動態資訊(很多同學可能會認為此處的json檔案只是被用來描述Docker容器的動態資訊的),我們在使用Dockerfile 構建映象時,Dockerfile 構建過程中涉及到的所有操作基本都被記錄到這個json檔案中。
說到這兒,有些讀者可能會問這個json是在什麼階段被使用到的,好問題。通過下面這個圖我想大家應該就能看明白了:
從圖中我們可以看出每個映象層的json檔案其實是由Docker 守護程序進行解析的。Docker 守護程序通過json檔案可以解析出執行容器需要的各種資料,比如環境變數、workdir以及容器啟動時需要執行的ENTRYPOINT或者CMD命令等。Docker 守護程序從json檔案中獲取到這些資料後,接下來就開始進行容器程序的初始化。
(3) layersize
從檔名稱即可看出,這個檔案中存放的為當前映象層的佔用空間大小:
(4) repositories-devicemapper
上一階段中我們解釋過這個檔案中記錄的為當前映象層的屬性資訊,比如映象名稱資訊、映象標籤資訊、映象的ID資訊等:
以上是對 pull 映象之後執行容器之前映象儲存資訊的簡單介紹,相信大家在看下之後對 docker容器 映象已經有了更加深入的認識。下面我們看下本文中我們要說的最後一個階段,即執行容器後 docker 的 儲存又發生了哪些變化。
執行容器後:
我們執行下前面從dockerhub pull的映象nginx:latest:
[root@localhost metadata]# docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
nginx latest c2c9e418b22c 2 weeks ago 109.3 MB
[root@localhost metadata]#
[root@localhost metadata]#
[root@localhost metadata]#
[root@localhost metadata]# docker run --name nginx -d nginx:latest
814ec80839669e235c94978ed3d07eab0e2b2bebd7d7a64fd6488cddca51be41
[root@localhost metadata]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
814ec8083966 nginx:latest "nginx -g 'daemon off" 3 seconds ago Up 2 seconds 80/tcp nginx
按照慣例,然後我們看下/var/lib/docker路徑下的檔案結構:
和上一階段不同,這個階段發生變化的檔案主要是:/var/lib/docker/devicemapper/metadata、/var/lib/docker/devicemapper/mnt以及/var/lib/docker/container,下面我們逐個看下。
(1) metadata
我們看下metadata這個目錄下的檔案:
從圖中的結果可以看出,相比上一個階段,當前階段中metadata目錄下多出了兩個檔案,即以51be4e和51b44e-init結尾的兩個檔案。
我們都知道docker 藉助容器映象執行起容器之後,會在當前映象的最頂層新增一個特殊的層,和其他的層相比這個層不但有可讀的許可權還有可寫的許可權。說到這,相信多出的兩個檔案的功能就不難理解了。
(2) mnt
在檢視mnt下的資料之前,我們先看下這個目錄下的檔案結構:
對比上面說過的metadata目錄,發現這兩個目錄下的檔案是一樣的,相比前一個階段的話也是新增了兩個檔案,即以51be4e和51b44e-init結尾的兩個檔案。
(3) container
我們先看下當前目錄下的檔案結構:
Container目錄為容器本身的目錄,此目錄中存放了諸如容器的配置檔案等檔案。如果我們刪掉這個目錄 ( docker 程序 hang死導致docker rm、docker kill殺不掉 容器時常用此種方式處理 )的 話正在執行的容器就會被刪掉,我們看下這幾個檔案都存放了什麼資料。
(1) xxx.json.log、config.json
從檔名稱即可看出,這兩個檔案存放的為當前容器的配置資訊及其資料:
(2) hosts
hosts配置資訊,在此不再贅述。
(3) hostname
容器host名稱,可以cat檢視後再進入容器檢視hostname,核對下看是否是一樣的。
(4) resolv.conf
dns配置資訊。
結 語
前面分析了那麼多涉及到docker 儲存的檔案,在查閱各個檔案或者目錄作用時可能不是很方便,在此我們給大家總結了一下各個檔案的作用(每個檔案都是在/var/lib/docker路徑下):
(1) devicemapper/devicemapper/data: 儲存儲存池相關的資料
(2) devicemapper/devicemapper/metdata: 儲存元資料
(3) devicemapper/metadata/: 儲存device_id、layersize等資訊
(4) devicemapper/mnt: 儲存掛載相關的資訊
(5) container/: 儲存容器本身的資訊
(6) graph/: 儲存各個映象層的詳細資訊
(7) repositores-devicemapper: 儲存映象的一些基本資訊
(8) tmp: 儲存docker的臨時目錄
(9) trust: 儲存docker的信任目錄
(10) volumes: 儲存docker的卷目錄
推薦閱讀
About Rancher Labs
Rancher Labs由矽谷雲端計算泰斗、CloudStack之父樑勝建立,致力於打造創新的開源軟體,幫助企業在生產環境中執行容器與Kubernetes。旗艦產品Rancher是一個開源的企業級Kubernetes平臺,是業界首個且唯一可以管理所有云上、所有發行版、所有Kubernetes叢集的平臺。解決了生產環境中企業使用者可能面臨的基礎設施不同的困境,改善Kubernetes原生UI易用性不佳以及學習曲線陡峭的問題,是企業落地Kubernetes的不二之選。
Rancher在全球擁有超過一億的下載量,超過20000家企業客戶。全球知名企業如中國人壽、華為、中國平安、民生銀行、興業銀行、上汽集團、海爾、米其林、天合光能、豐田、本田、霍尼韋爾、金風科技、普華永道、海南航空、廈門航空、恆大人壽、中國太平、巴黎銀行、美國銀行、HSCIS恆生指數、中國水利、暴雪、CCTV等均是Rancher的付費客戶。
第六期 Kubernetes Master Class 傳送門
↓↓↓