Docker鏡像的基本知識(shí)
1.1 什么是Docker鏡像
從整體的角度來(lái)講,一個(gè)完整的Docker鏡像可以支撐一個(gè)Docker容器的運(yùn)行,在 Docker容器運(yùn)行過(guò)程中主要提供文件系統(tǒng)視角。例如一個(gè)ubuntu:14.04的鏡像,提供了一個(gè)基本的ubuntu:14.04的發(fā)行版,當(dāng)然此 鏡像是不包含操作系統(tǒng)Linux內(nèi)核的。
說(shuō)到此,可能就需要注意一下,linux內(nèi)核和ubuntu:14.04Docker鏡像的區(qū)別了。傳統(tǒng)虛擬機(jī)安裝ubuntu:14.04會(huì)包含兩部分,第一,某一個(gè)Linux內(nèi)核的發(fā)行版本,比如Linux 3.8版本的內(nèi)核;第二,第一個(gè)特定的Ubuntu發(fā)行版,這部分內(nèi)容不包含Linux內(nèi)核,但是包含Linux之外的軟件管理方式,軟件驅(qū)動(dòng),如 apt-get軟件管理包等。
理解以上內(nèi)容之后,就可以理解,為什么在一個(gè)Linux內(nèi)核版本為3.8的ubuntu:14.04基礎(chǔ)上,可以把Linux內(nèi)核版本升級(jí)到3.18,而ubuntu的版本依然是14.04。最主要的就是:Linux內(nèi)核版本與ubuntu操作系統(tǒng)發(fā)行版之間的區(qū)別。
Linux內(nèi)核+ubuntu操作系統(tǒng)發(fā)行版,組成一臺(tái)工作的機(jī)器讓用戶體驗(yàn)。那么靈活替換ubuntu操作系統(tǒng)發(fā)行版,那是不是也可以實(shí)現(xiàn)呢。那么Docker很方便的利用了這一點(diǎn),技術(shù)手段就是Docker鏡像。
Docker的架構(gòu)中,Docker鏡像就是類似于“ubuntu操作系統(tǒng)發(fā)行版”,可 以在任何滿足要求的Linux內(nèi)核之上運(yùn)行。簡(jiǎn)單一點(diǎn)有“Debian操作系統(tǒng)發(fā)行版”Docker鏡像、“Ubuntu操作系統(tǒng)發(fā)行版”Docker鏡 像;如果在Debian鏡像中安裝MySQL 5.6,那我們可以將其命名為Mysql:5.6鏡像;如果在Debian鏡像中安裝有Golang 1.3,那我們可以將其命名為golang:1.3鏡像;以此類推,大家可以根據(jù)自己安裝的軟件,得到任何自己想要的鏡像。
那么鏡像最后的作用是什么呢?很好理解,回到Linux內(nèi)核上來(lái)運(yùn)行,通過(guò)鏡像來(lái)運(yùn)行時(shí)我們常常將提供的環(huán)境稱為容器。
以上內(nèi)容是從宏觀的角度看看Docker鏡像是什么,我們?cè)購(gòu)奈⒂^的角度進(jìn)一步深入 Docker鏡像。剛才提到了“Debian鏡像中安裝MySQL 5.6,就成了mysql:5.6鏡像”,其實(shí)在此時(shí)Docker鏡像的層級(jí)概念就體現(xiàn)出來(lái)了。底層一個(gè)Debian操作系統(tǒng)鏡像,上面疊加一個(gè) mysql層,就完成了一個(gè)mysql鏡像的構(gòu)建。層級(jí)概念就不難理解,此時(shí)我們一般debian操作系統(tǒng)鏡像稱為mysql鏡像層的父鏡像。
層級(jí)管理的方式大大便捷了Docker鏡像的分發(fā)與存儲(chǔ)。說(shuō)到分發(fā),大家自然會(huì)聯(lián)想到 Docker鏡像的靈活性,傳輸?shù)谋憬菪?,以及高超的移植性。Docker Hub,作為全球的鏡像倉(cāng)庫(kù),作為Docker生態(tài)中的數(shù)據(jù)倉(cāng)庫(kù),將全世界的Docker數(shù)據(jù)匯聚在一起,是Docker生態(tài)的命脈。
Docker有兩方面的技術(shù)非常重要,第一是Linux 容器方面的技術(shù),第二是Docker鏡像的技術(shù)。從技術(shù)本身來(lái)講,兩者的可復(fù)制性很強(qiáng),不存在絕對(duì)的技術(shù)難點(diǎn),然而Docker Hub由于存在大量的數(shù)據(jù)的原因,導(dǎo)致Docker Hub的可復(fù)制性幾乎不存在,這需要一個(gè)生態(tài)的營(yíng)造。
1.2 Docker鏡像的內(nèi)容
大致介紹了Docker鏡像是什么,我們來(lái)看看Docker鏡像中有哪些內(nèi)容?
介紹之前,我先分享一下,我個(gè)人在接觸Docker的兩年時(shí)間中,對(duì)Docker鏡像內(nèi)容認(rèn)識(shí)的變化。
第一階段:初步接觸Docker。相信很多愛(ài)好者都會(huì)和我一樣,有這樣一個(gè)認(rèn)識(shí):Docker 鏡像代表一個(gè)容器的文件系統(tǒng)內(nèi)容;
第二階段:初步接觸聯(lián)合文件系統(tǒng)。聯(lián)合文件系統(tǒng)的概念,讓我意識(shí)到鏡像層級(jí)管理的技術(shù),每一層鏡像都是容器文件系統(tǒng)內(nèi)容的一部分。
第三階段:研究鏡像與容器的關(guān)系:容器是一個(gè)動(dòng)態(tài)的環(huán)境,每一層鏡像中的文件屬于靜態(tài)內(nèi) 容,然而 Dockerfile 中的 ENV、VOLUME、CMD 等內(nèi)容最終都需要落實(shí)到容器的運(yùn)行環(huán)境中,而這些內(nèi)容均不可能直接坐落到每一層鏡像所包含的文件系統(tǒng)內(nèi)容中,那此時(shí)每一個(gè)Docker鏡像還會(huì)包含 json文件記錄與容器之間的關(guān)系。
因此,Docker鏡像的內(nèi)容主要包含兩個(gè)部分:第一,鏡像層文件內(nèi)容;第二,鏡像json文件。
1.3 Docker鏡像存儲(chǔ)位置
既然是說(shuō)鏡像存儲(chǔ)的位置,那么應(yīng)該包含:鏡像層文件和鏡像json文件。如一個(gè)ubuntu:14.04鏡像,包含4個(gè)鏡像層,在aufs存儲(chǔ)驅(qū)動(dòng)的情況下,在磁盤上的情況可以如以下圖所示:
1.3.1 查看鏡像層組成:
我們可以通過(guò)命令 docker history ubuntu:14.04 查看 ubuntu:14.04,結(jié)果如下:
1.3.2 鏡像層文件內(nèi)容存儲(chǔ)
Docker 鏡像層的內(nèi)容一般在 Docker 根目錄的 aufs 路徑下,為 /var/lib/docker/aufs/diff/,具體情況如下:
圖中顯示了鏡像 ubuntu:14.04 的 4 個(gè)鏡像層內(nèi)容,以及每個(gè)鏡像層內(nèi)的一級(jí)目錄情況。需要額外注意的是:鏡像層 d2a0ecffe6fa 中沒(méi)有任何內(nèi)容,也就是所謂的空鏡像。
1.3.3 鏡像 json 文件存儲(chǔ)
對(duì)于每一個(gè)鏡像層,Docker 都會(huì)保存一份相應(yīng)的 json 文件,json 文件的存儲(chǔ)路徑為 /var/lib/docker/graph,ubuntu:14.04 所有鏡像層的 json 文件存儲(chǔ)路徑展示如下:
除了 json 文件,大家還看到每一個(gè)鏡像層還包含一個(gè) layersize 文件,該文件主要記錄鏡像層內(nèi)部文件內(nèi)容的總大小。既然談到了鏡像 json 文件,為了給下文鋪墊,以下貼出 ubuntu:14.04 中空鏡像層 d2a0ecffe6fa 的 json 文件:
Docker鏡像存儲(chǔ),就和大家一起先看到這。同時(shí)介紹Docker鏡像的基本知識(shí)也告一段落。以下我們進(jìn)入此次分享的第二部分。
第二部分 Dockerfile、Docker鏡像和Docker容器的關(guān)系
Dockerfile 是軟件的原材料,Docker 鏡像是軟件的交付品,而 Docker 容器則可以認(rèn)為是軟件的運(yùn)行態(tài)。從應(yīng)用軟件的角度來(lái)看,Dockerfile、Docker 鏡像與 Docker 容器分別代表軟件的三個(gè)不同階段,Dockerfile 面向開(kāi)發(fā),Docker 鏡像成為交付標(biāo)準(zhǔn),Docker 容器則涉及部署與運(yùn)維,三者缺一不可,合力充當(dāng) Docker 體系的基石。
簡(jiǎn)單來(lái)講,Dockerfile構(gòu)建出Docker鏡像,通過(guò)Docker鏡像運(yùn)行Docker容器。
我們可以從Docker容器的角度,來(lái)反推三者的關(guān)系。首先可以來(lái)看下圖:
我們假設(shè)這個(gè)容器的鏡像通過(guò)以下Dockerfile構(gòu)建而得:
FROM ubuntu:14.04
ADD run.sh /
VOLUME /data
CMD ["./run.sh"]
2.1 Dockerfile與Docker鏡像
首先,我們結(jié)合上圖來(lái)看看Dockerfile與Docker鏡像之間的關(guān)系。
FROM ubuntu:14.04:設(shè)置基礎(chǔ)鏡像,此時(shí)會(huì)使用基礎(chǔ)鏡像 ubuntu:14.04 的所有鏡像層,為簡(jiǎn)單起見(jiàn),圖中將其作為一個(gè)整體展示。
ADD run.sh /:將 Dockerfile 所在目錄的文件 run.sh 加至鏡像的根目錄,此時(shí)新一層的鏡像只有一項(xiàng)內(nèi)容,即根目錄下的 run.sh。
VOLUME /data:設(shè)定鏡像的 VOLUME,此 VOLUME 在容器內(nèi)部的路徑為 /data。需要注意的是,此時(shí)并未在新一層的鏡像中添加任何文件,即構(gòu)建出的磁層鏡像中文件為空,但更新了鏡像的 json 文件,以便通過(guò)此鏡像啟動(dòng)容器時(shí)獲取這方面的信息。
CMD ["./run.sh"]:設(shè)置鏡像的默認(rèn)執(zhí)行入口,此命令同樣不會(huì)在新建鏡像中添加任何文件,僅僅在上一層鏡像 json 文件的基礎(chǔ)上更新新建鏡像的 json 文件。
因此,通過(guò)以上分析,以上的Dockerfile可以構(gòu)建出一個(gè)新的鏡像,包含4個(gè)鏡像層,每一條命令會(huì)和一個(gè)鏡像層對(duì)應(yīng),鏡像之間會(huì)存在父子關(guān)系。圖中很清楚的表明了這些關(guān)系。
2.2 Docker鏡像與Docker容器的關(guān)系
Docker鏡像是Docker容器運(yùn)行的基礎(chǔ),沒(méi)有Docker鏡像,就不可能有Docker容器,這也是Docker的設(shè)計(jì)原則之一。
可以理解的是:Docker鏡像畢竟是鏡像,屬于靜態(tài)的內(nèi)容;而Docker容器就不一樣了,容器屬于動(dòng)態(tài)的內(nèi)容。動(dòng)態(tài)的內(nèi)容,大家很容易聯(lián)想到進(jìn)程,內(nèi)存,CPU等之類的東西。的確,Docker容器作為動(dòng)態(tài)的內(nèi)容,都會(huì)包含這些。
為了便于理解,大家可以把Docker容器,理解為一個(gè)或多個(gè)運(yùn)行進(jìn)程,而這些運(yùn)行進(jìn)程將占有相應(yīng)的內(nèi)存,相應(yīng)的CPU計(jì)算資源,相應(yīng)的虛擬網(wǎng)絡(luò)設(shè)備以及相應(yīng)的文件系統(tǒng)資源。而Docker容器所占用的文件系統(tǒng)資源,則通過(guò)Docker鏡像的鏡像層文件來(lái)提供。
那么作為靜態(tài)的鏡像,如何才有能力轉(zhuǎn)化為一個(gè)動(dòng)態(tài)的Docker容器呢?此時(shí),我們可以想象:第一,轉(zhuǎn)化的依據(jù)是什么;第二,由誰(shuí)來(lái)執(zhí)行這個(gè)轉(zhuǎn)化操作。
其實(shí),轉(zhuǎn)化的依據(jù)是每個(gè)鏡像的json文件,Docker可以通過(guò)解析Docker鏡像的json的文件,獲知應(yīng)該在這個(gè)鏡像之上運(yùn)行什么樣的進(jìn)程,應(yīng)該為進(jìn)程配置怎么樣的環(huán)境變量,此時(shí)也就實(shí)現(xiàn)了靜態(tài)向動(dòng)態(tài)的轉(zhuǎn)變。
誰(shuí)來(lái)執(zhí)行這個(gè)轉(zhuǎn)化工作?答案是Docker守護(hù)進(jìn)程。也許大家早就理解這樣一句 話:Docker容器實(shí)質(zhì)上就是一個(gè)或者多個(gè)進(jìn)程,而容器的父進(jìn)程就是Docker守護(hù)進(jìn)程。這樣的,轉(zhuǎn)化工作的執(zhí)行就不難理解了:Docker守護(hù)進(jìn)程 手握Docker鏡像的json文件,為容器配置相應(yīng)的環(huán)境,并真正運(yùn)行Docker鏡像所指定的進(jìn)程,完成Docker容器的真正創(chuàng)建。
Docker容器運(yùn)行起來(lái)之后,Docker鏡像json文件就失去作用了。此時(shí)Docker鏡像的絕大部分作用就是:為Docker容器提供一個(gè)文件系統(tǒng)的視角,供容器內(nèi)部的進(jìn)程訪問(wèn)文件資源。
再次回到上圖,我們?cè)賮?lái)看看容器和鏡像之間的一些特殊關(guān)系。首先,之前已經(jīng)提及Docker鏡像是分層管理的,管理Docker容器的時(shí)候,Docker鏡像仍然是分層管理的。由于此時(shí)動(dòng)態(tài)的容器中已經(jīng)存在進(jìn)程,進(jìn)程就會(huì)對(duì)文件系統(tǒng)視角內(nèi)的文件進(jìn)行讀寫(xiě)操作,因此,就會(huì)涉及一個(gè)問(wèn)題:容器是否會(huì)篡改Docker鏡像的內(nèi)容?
答案自然是不會(huì)的。統(tǒng)一來(lái)講,正如上圖,所有的Docker鏡像層對(duì)于容器來(lái)說(shuō),都是只讀的,容器對(duì)于文件的寫(xiě)操作絕對(duì)不會(huì)作用在鏡像中。
既然如此,實(shí)現(xiàn)的原理就很重要,究其根本:Docker守護(hù)進(jìn)程會(huì)在Docker鏡像的 最上層之上,再添加一個(gè)可讀寫(xiě)層,容器所有的寫(xiě)操作都會(huì)作用到這一層中。而如果Docker容器需要寫(xiě)底層Docker鏡像中的文件,那么此時(shí)就會(huì)涉及一 個(gè)叫Copy-on-Write的機(jī)制,即aufs等聯(lián)合文件系統(tǒng)保證:首先將此文件從Docker鏡像層中拷貝至最上層的可讀寫(xiě)層,然后容器進(jìn)程再對(duì)讀 寫(xiě)層中的副本進(jìn)行寫(xiě)操縱。對(duì)于容器進(jìn)程來(lái)講,它只能看到最上層的文件。
那最后我們?cè)賮?lái)說(shuō)說(shuō):Docker容器的文件系統(tǒng)視角中,到底是不是存在一些內(nèi)容,不是存儲(chǔ)于Docker鏡像中的?
這次的答案依舊是肯定的。
再次重申一點(diǎn),Docker鏡像中存儲(chǔ)的都是一些靜態(tài)文件。這些文件原則上應(yīng)該和容器具體信息以及主機(jī)信息完全解藕。那么Docker容器中不存在Docker鏡像中的內(nèi)容主要有以下幾點(diǎn):
1. /proc以及/sys等虛擬文件系統(tǒng)的內(nèi)容
2. 容器的hosts文件,hostname文件以及resolv.conf文件,這些事具體環(huán)境的信息,原則上的確不應(yīng)該被打入鏡像。
3. 容器的Volume路徑,這部分的視角來(lái)源于從宿主機(jī)上掛載到容器內(nèi)部的路徑
4. 部分的設(shè)備文件
Docker中容器的備份、恢復(fù)和遷移
1. 備份容器
首先,為了備份Docker中的容器,我們會(huì)想看看我們想要備份的容器列表。要達(dá)成該目的,我們需要在我們運(yùn)行著Docker引擎,并已創(chuàng)建了容器的Linux機(jī)器中運(yùn)行 docker ps 命令。
# docker ps
在此之后,我們要選擇我們想要備份的容器,然后去創(chuàng)建該容器的快照。我們可以使用 docker commit 命令來(lái)創(chuàng)建快照。
# docker commit -p 30b8f18f20b4 container-backup
該命令會(huì)生成一個(gè)作為Docker鏡像的容器快照,我們可以通過(guò)運(yùn)行 docker images 命令來(lái)查看Docker鏡像,如下。
# docker images
正如我們所看見(jiàn)的,上面做的快照已經(jīng)作為Docker鏡像保存了?,F(xiàn)在,為了備份該快照,我們有兩個(gè)選擇,一個(gè)是我們可以登錄進(jìn)Docker注冊(cè)中心,并推送該鏡像;另一個(gè)是我們可以將Docker鏡像打包成tar包備份,以供今后使用。
如果我們想要在Docker注冊(cè)中心上傳或備份鏡像,我們只需要運(yùn)行 docker login 命令來(lái)登錄進(jìn)Docker注冊(cè)中心,然后推送所需的鏡像即可。
# docker login
# docker tag a25ddfec4d2a arunpyasi/container-backup:test
# docker push arunpyasi/container-backup
如果我們不想備份到docker注冊(cè)中心,而是想要將此鏡像保存在本地機(jī)器中,以供日后使用,那么我們可以將其作為tar包備份。要完成該操作,我們需要運(yùn)行以下 docker save 命令。
# docker save -o ~/container-backup.tar container-backup
要驗(yàn)證tar包是否已經(jīng)生成,我們只需要在保存tar包的目錄中運(yùn)行 ls 命令即可。
2. 恢復(fù)容器
接下來(lái),在我們成功備份了我們的Docker容器后,我們現(xiàn)在來(lái)恢復(fù)這些制作了Docker鏡像快照的容器。如果我們已經(jīng)在注冊(cè)中心推送了這些Docker鏡像,那么我們僅僅需要把那個(gè)Docker鏡像拖回并直接運(yùn)行即可。
# docker pull arunpyasi/container-backup:test
但是,如果我們將這些Docker鏡像作為tar包文件備份到了本地,那么我們只要使用 docker load 命令,后面加上tar包的備份路徑,就可以加載該Docker鏡像了。
# docker load -i ~/container-backup.tar
現(xiàn)在,為了確保這些Docker鏡像已經(jīng)加載成功,我們來(lái)運(yùn)行 docker images 命令。
# docker images
在鏡像被加載后,我們將用加載的鏡像去運(yùn)行Docker容器。
# docker run -d -p 80:80 container-backup
3. 遷移Docker容器
遷移容器同時(shí)涉及到了上面兩個(gè)操作,備份和恢復(fù)。我們可以將任何一個(gè)Docker容器從一臺(tái)機(jī)器遷移到另一臺(tái)機(jī)器。在遷移過(guò)程中,首先我們將把容器備份為Docker鏡像快照。然后,該Docker鏡像或者是被推送到了Docker注冊(cè)中心,或者被作為tar包文件保存到了本地。如果我們將鏡像推送到了Docker注冊(cè)中心,我們簡(jiǎn)單地從任何我們想要的機(jī)器上使用 docker run 命令來(lái)恢復(fù)并運(yùn)行該容器。但是,如果我們將鏡像打包成tar包備份到了本地,我們只需要拷貝或移動(dòng)該鏡像到我們想要的機(jī)器上,加載該鏡像并運(yùn)行需要的容器即可。
尾聲
最后,我們已經(jīng)學(xué)習(xí)了如何快速地備份、恢復(fù)和遷移Docker容器,本教程適用于各個(gè)可以成功運(yùn)行Docker的操作系統(tǒng)平臺(tái)。真的,Docker是一個(gè)相當(dāng)簡(jiǎn)單易用,然而功能卻十分強(qiáng)大的工具。它的命令相當(dāng)易記,這些命令都非常短,帶有許多簡(jiǎn)單而強(qiáng)大的標(biāo)記和參數(shù)。上面的方法讓我們備份容器時(shí)很是安逸,使得我們可以在日后很輕松地恢復(fù)它們。這會(huì)幫助我們恢復(fù)我們的容器和鏡像,即便主機(jī)系統(tǒng)崩潰,甚至意外地被清除。如果你還有很多問(wèn)題、建議、反饋,請(qǐng)?jiān)谙旅娴脑u(píng)論框中寫(xiě)出來(lái)吧,可以幫助我們改進(jìn)或更新我們的內(nèi)容。謝謝大家!享受吧 :-)