技術頭條:幹貨、簡潔、多維全面。更多雲計算精華知識盡在眼前,get要點、solve難題,統統不在話下!
作為雲計算的當紅明星docker 來勢洶洶,它就像一場森林大火,燒到了我們中間。因為工作的原因,每天都會和容器雲打交道。但是,我發現經常會有些新加入的同事在理解Docker命令方面存在一些問題,尤其是在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/lib/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的卷目錄。
,更多精彩资讯请关注tft每日頭條,我们将持续为您更新最新资讯!