通過(guò)容器(上)這篇文章的學(xué)習(xí)诅需,我們清晰的了解到Docker是如何把我們開(kāi)發(fā)的應(yīng)用程序代碼烛缔,打包成鏡像赔蒲,上傳到鏡像倉(cāng)庫(kù),并在其他目標(biāo)機(jī)器上運(yùn)行起來(lái)厘肮,那么不知道你是否有想過(guò),當(dāng)容器運(yùn)行起來(lái)后睦番,如果從容器的運(yùn)行環(huán)境內(nèi)部看类茂,它能看到什么?
我們的應(yīng)用在目標(biāo)機(jī)器上運(yùn)行起來(lái)就是一個(gè)進(jìn)程托嚣,而程序和進(jìn)程的概念其實(shí)不是計(jì)算機(jī)科班出身巩检,很難理解,我們就先來(lái)說(shuō)說(shuō)這兩個(gè)概念示启,為后續(xù)更加深入的討論打好基礎(chǔ)兢哭。
用自己熟悉的編程語(yǔ)言編寫(xiě)如下的C語(yǔ)言代碼:
#include <stdio.h>
main() {
? printf("hello world\n");
}
由于計(jì)算機(jī)只能識(shí)別0和1,因此我們需要首先將這個(gè)文件保存到本地磁盤(pán)夫嗓,比如說(shuō)helloword.c迟螺,然后我們用c語(yǔ)言的編譯器將上邊的高級(jí)語(yǔ)言翻譯成機(jī)器能夠讀懂的二進(jìn)制文件,在翻譯的過(guò)程中舍咖,我們的代碼邏輯會(huì)被翻譯成計(jì)算機(jī)的指令矩父,比如加法,加法等排霉,而需要輸出的數(shù)據(jù)字符串窍株,也會(huì)被一并編譯到最終的可執(zhí)行文件中。
最后變成形成的這個(gè)文件攻柠,我們就叫程序球订,或者也叫可執(zhí)行程序,比如在Windows上, 我們將代碼編譯后瑰钮,會(huì)形成可執(zhí)行文件helloword.exe冒滩,這個(gè)以exe為擴(kuò)展名的文件,就叫可執(zhí)行文件浪谴。而在類Unix系統(tǒng)上旦部,比如說(shuō)Mac上,我們用gcc將這個(gè)文件編譯后较店,會(huì)生成helloworld這個(gè)可執(zhí)行文件士八,無(wú)論是helloworld.exe還是helloword,我們都叫程序梁呈。
有了程序婚度,我們就可以講這個(gè)程序在操作系統(tǒng)上運(yùn)行起來(lái),比如在Mac下,我就可以使用./helloword來(lái)將剛才我們編譯好的程序加載起來(lái)蝗茁,操作系統(tǒng)會(huì)生成進(jìn)程并且從磁盤(pán)上讀取這個(gè)可執(zhí)行文件醋虏,然后從第一個(gè)指令開(kāi)始執(zhí)行,在代碼的執(zhí)行過(guò)程中哮翘,可能需要驅(qū)動(dòng)磁盤(pán)來(lái)讀取更多的指令和數(shù)據(jù)颈嚼。一旦程序在計(jì)算機(jī)上被運(yùn)行起來(lái),就從磁盤(pán)的二進(jìn)制文件饭寺,變成了計(jì)算機(jī)內(nèi)存中的數(shù)據(jù)阻课,寄存器中的指令,以及各種被打開(kāi)文件的句柄艰匙,而我們把程序運(yùn)行起來(lái)計(jì)算機(jī)的各個(gè)部件的狀態(tài)之和限煞,稱之為進(jìn)程。
介紹問(wèn)程序和進(jìn)程之后员凝,我們來(lái)從容器內(nèi)部的視角來(lái)看一下署驻,它能看到那些信息。對(duì)于在容器中運(yùn)行的應(yīng)用健霹,它其實(shí)能夠看到的就是我們?cè)谶M(jìn)項(xiàng)中打包的文件和文件夾旺上,以及在打包系統(tǒng)啟動(dòng)時(shí)候通過(guò)參數(shù)掛載的目錄結(jié)構(gòu),因此如果我們?cè)趩?dòng)的時(shí)候沒(méi)有掛載任何卷目錄糖埋,那么容器在任何一個(gè)環(huán)境抚官,看到的執(zhí)行環(huán)境和目錄文件結(jié)構(gòu)是完全相同的,即便是我們?cè)跍y(cè)試環(huán)境和生產(chǎn)環(huán)境運(yùn)行的是不同版本的Linux操作系統(tǒng)阶捆,因?yàn)閼?yīng)用是不被允許修改操作系統(tǒng)文件數(shù)據(jù)的凌节,這就給開(kāi)發(fā)人員帶來(lái)極大的便利性,因?yàn)榻K于不用說(shuō)那句“在我電腦上好好的洒试,怎么部署上去就不行了”倍奢。
我們來(lái)舉個(gè)例子說(shuō)明一下,假設(shè)我們將編寫(xiě)的代碼基于基礎(chǔ)鏡像Red Hat企業(yè)版進(jìn)行了打包垒棋,然后我們無(wú)論是在Fedora還是CoreOS運(yùn)行這個(gè)鏡像卒煞,應(yīng)用程序一直會(huì)覺(jué)得自己在Red Hat的環(huán)境中運(yùn)行,因?yàn)楫?dāng)容器運(yùn)行起來(lái)的時(shí)候叼架,看到的是Red Hat的操作系統(tǒng)文件夾目錄畔裕,而和宿主機(jī)上的操作系統(tǒng)不太相關(guān)(這么說(shuō)不嚴(yán)謹(jǐn),其實(shí)也相關(guān)乖订,就是操作系統(tǒng)內(nèi)核的版本要匹配)扮饶。
這種神奇的功能是如何實(shí)現(xiàn)的呢?接下來(lái)我們來(lái)深入分析一下容器鏡像的分層概念乍构。
【容器的分層機(jī)制】
虛擬機(jī)鏡像本質(zhì)就是一個(gè)巨大無(wú)比的文件甜无,應(yīng)用需要運(yùn)行的操作系統(tǒng)內(nèi)核和文件系統(tǒng)都被打包在一起,而容器鏡像對(duì)虛擬機(jī)的這種模式進(jìn)行了優(yōu)化,打包后的應(yīng)用被分成了多個(gè)層岂丘,層可以在多個(gè)鏡像之間共享陵究,這就意味著我們?cè)趩?dòng)某個(gè)鏡像的時(shí)候,可能并不需要把整個(gè)鏡像完整下載下來(lái)奥帘,大概率是你本地已經(jīng)有了這個(gè)鏡像的某些層铜邮,節(jié)省帶寬的同時(shí),可以加速應(yīng)用的啟動(dòng)速度寨蹋。
通過(guò)對(duì)鏡像進(jìn)行分層松蒜,鏡像分享就變得更加的高效,特別是從使用者的角度钥庇,每臺(tái)安裝docker的機(jī)器牍鞠,就對(duì)某個(gè)編號(hào)的層只保存一次咖摹,如下圖所示:
從上圖可以看到评姨,容器A和B共享了最上邊的層,因此容器A和容器B就可以讀取到這一層上相同的文件和數(shù)據(jù)萤晴,更進(jìn)一步吐句,容器A和B以及容器C,他們都可以訪問(wèn)下邊的三方庫(kù)文件所在的層店读,這種共享的機(jī)制對(duì)容器大規(guī)模部署和共享都有極大的促進(jìn)作用嗦枢。
不知道你是否意識(shí)到這里有個(gè)問(wèn)題,如果按照上圖這樣來(lái)在多個(gè)容器間共享屯断,那么容器之間的隔離性如何保障呢文虏?是不是容器B對(duì)最上邊的那層的某個(gè)文件進(jìn)行了修改,容器A也能看到這個(gè)修改殖演?
如果真如上邊所說(shuō)氧秘,肯定不行,容器的文件系統(tǒng)通過(guò)COW(copy on write)機(jī)制來(lái)保證隔離性趴久,也就是確保一個(gè)容器對(duì)共享層文件的修改丸相,不會(huì)讓另外一個(gè)容器看到。具體來(lái)說(shuō)彼棍,容器鏡像由多個(gè)層組成灭忠,但是這些層中,有很多都是只讀層座硕,以及在只讀層之上的讀寫(xiě)層弛作。
當(dāng)應(yīng)用程序A需要修改只讀層的某個(gè)文件的時(shí)候,整個(gè)文件會(huì)被拷貝到讀寫(xiě)層华匾,然后在讀寫(xiě)層對(duì)這個(gè)相同文件名的文件進(jìn)行修改和保存缆蝉,由于每個(gè)容器運(yùn)行起來(lái)之后,都有自己專屬的讀寫(xiě)層,因此通過(guò)這種方式刊头,容器A對(duì)某個(gè)文件的修改黍瞧,對(duì)容器B不具有可見(jiàn)性,從而實(shí)現(xiàn)了隔離原杂。
另外印颤,當(dāng)我們刪除只讀層的某個(gè)文件,我們其實(shí)只是在讀寫(xiě)層將這個(gè)文件進(jìn)行了標(biāo)記穿肄,不讓容器看到這個(gè)文件而已年局,文件本身沒(méi)有發(fā)生任何變化,因此我們?cè)谌萜髦袆h除文件咸产,其實(shí)并沒(méi)有辦法讓鏡像的尺寸變小矢否。
注意:基于上邊的介紹,看起來(lái)在容器中修改只讀層文件的權(quán)限和所屬信息只會(huì)造成文件被拷貝到讀寫(xiě)層而已脑溢,其實(shí)并不然僵朗,如果你在容器的只讀層進(jìn)行大量文件的權(quán)限修改,容器鏡像的尺寸會(huì)增長(zhǎng)的非承汲梗可觀验庙,具體原因我們后續(xù)介紹。
好了社牲,關(guān)于鏡像分層的相關(guān)內(nèi)容就這么多了粪薛,我們接下來(lái)看看Docker的這種打包操作系統(tǒng)文件系統(tǒng)的機(jī)制有什么缺陷。
【理解Docker鏡像打包機(jī)制的缺陷】
理論上來(lái)說(shuō)搏恤,通過(guò)Docker的鏡像打包機(jī)制構(gòu)建的應(yīng)用违寿,可以運(yùn)行在任何Linux操作系統(tǒng)上,但是這里有個(gè)坑熟空,主要是因?yàn)殓R像是沒(méi)有自己的操作系統(tǒng)內(nèi)核藤巢,只有操作系統(tǒng)文件夾結(jié)構(gòu)和文件,或者說(shuō)就是徒有操作系統(tǒng)的外表而已痛阻。
如果鏡像需要某個(gè)特殊版本操作系統(tǒng)內(nèi)核功能的支持才能運(yùn)行菌瘪,那么理論上能夠在任意操作系統(tǒng)上運(yùn)行這句話就不嚴(yán)謹(jǐn)了。如果運(yùn)行的操作系統(tǒng)內(nèi)核因?yàn)榘姹具^(guò)低阱当,沒(méi)有鏡像需要的功能模塊俏扩,那么就無(wú)法在這臺(tái)機(jī)器上運(yùn)行應(yīng)用程序。如下圖所示:
容器B在運(yùn)行的時(shí)候录淡,需要特定操作系統(tǒng)版本提供的功能模塊,而這個(gè)模塊在工作節(jié)點(diǎn)1上有油坝,在工作節(jié)點(diǎn)2上沒(méi)有嫉戚,當(dāng)我們將容器的實(shí)例調(diào)度到節(jié)點(diǎn)2上的時(shí)候刨裆,這個(gè)應(yīng)用就無(wú)法運(yùn)行起來(lái),由于缺少操作系統(tǒng)相關(guān)內(nèi)核模塊彬檀。
其實(shí)不光是內(nèi)核和內(nèi)核模塊會(huì)造成這種問(wèn)題帆啃,如果我們的應(yīng)用程序針對(duì)特定的硬件平臺(tái),那么硬件平臺(tái)的架構(gòu)也會(huì)對(duì)應(yīng)用的部署造成約束窍帝,比如說(shuō)我們的應(yīng)用是基于X86CPU架構(gòu)來(lái)構(gòu)建努潘,那么我們無(wú)法將這個(gè)應(yīng)用部署到ARMS機(jī)器上,如果我們非要在ARMS機(jī)器上運(yùn)行這個(gè)應(yīng)用坤学,只能通過(guò)在ARMS安裝虛擬機(jī)來(lái)模擬X86環(huán)境疯坤。
好了,以上就是關(guān)于鏡像打包機(jī)制缺陷的詳細(xì)介紹深浮。筆者反復(fù)強(qiáng)調(diào)過(guò)压怠,Docker從來(lái)都不是Kubernetes平臺(tái)上的默認(rèn)容器引擎,Kubernetes從一開(kāi)始就有更加宏偉的目標(biāo):從宏觀的角度飞苇,以統(tǒng)一的方式來(lái)定義不同的對(duì)象之間的關(guān)系菌瘫,并為復(fù)雜多變的場(chǎng)景預(yù)留空間。這就不難理解Kubernetes項(xiàng)目并沒(méi)有把Docker作為整個(gè)架構(gòu)的核心玄柠,而頂多就是底層運(yùn)行容器的一種方式而已突梦,而Kubernetes的核心就是在這些運(yùn)行時(shí)的上邊诫舅,如何處理編排羽利,調(diào)度,網(wǎng)絡(luò)刊懈,存儲(chǔ)这弧,安全,監(jiān)控等功能虚汛。
接下里我們來(lái)介紹一下除了Docker匾浪,在Kubernetes平臺(tái)上,還有哪些可選的容器實(shí)現(xiàn)方式卷哩。雖然說(shuō)Docker讓容器這個(gè)“老”技術(shù)換發(fā)青春蛋辈,但是由于Docker所依賴的技術(shù)和Docker沒(méi)有什么關(guān)系,特別是容器所依賴的隔離技術(shù)将谊,其實(shí)是操作系統(tǒng)內(nèi)核提供冷溶,Docker只是讓這些容器隔離技術(shù)用起來(lái)更加簡(jiǎn)單而已。
隨著Docker變?yōu)橹髁髯鹋ǎ琌pen Container Initiative(OCI)啟動(dòng)了標(biāo)準(zhǔn)化工作逞频,試圖創(chuàng)建公開(kāi)的容器格式和運(yùn)行時(shí)行業(yè)標(biāo)準(zhǔn),Docker公司也是這個(gè)標(biāo)準(zhǔn)化組織的一員栋齿,但是可以猜測(cè)到苗胀,基本屬于出工不出力的態(tài)度襟诸,因此OCI這個(gè)標(biāo)準(zhǔn)化根本沒(méi)辦法順利往前推進(jìn),雖然這個(gè)組織制定了OCI Image Format Specification基协,來(lái)規(guī)范容器鏡像的格式歌亲,以及OCI Runtime Specification,定義了標(biāo)準(zhǔn)的容器創(chuàng)建澜驮,配置和運(yùn)行的接口应结,但是很不幸的是,這些標(biāo)準(zhǔn)你沒(méi)有聽(tīng)說(shuō)過(guò)泉唁,我在寫(xiě)這篇文章之前也沒(méi)有聽(tīng)說(shuō)過(guò)鹅龄,從這個(gè)角度,你就知道Docker公司參與的這個(gè)標(biāo)準(zhǔn)組織幾乎沒(méi)有啥影響力亭畜。
隨著Kubernetes的崛起扮休,由于谷歌和紅帽公司的高瞻遠(yuǎn)矚,特別是谷歌公司多年在內(nèi)部踐行Borg系統(tǒng)的實(shí)戰(zhàn)經(jīng)驗(yàn)拴鸵,從一開(kāi)始就沒(méi)有把整座大廈建立在Docker的容器平臺(tái)上玷坠,雖然從Kubernetes剛開(kāi)始的時(shí)候,Docker是整個(gè)平臺(tái)進(jìn)行容器化戰(zhàn)略的主航道劲藐,原因并不是基于架構(gòu)考慮八堡,而是因?yàn)槟莻€(gè)時(shí)候Docker是被使用最廣泛的容器平臺(tái)。
但是隨著Kubernetes逐漸坐穩(wěn)容器化PASS平臺(tái)的頭把交易聘芜,Kubernetes隨即就標(biāo)準(zhǔn)化了容器運(yùn)行時(shí)兄渺,這就是筆者在前邊提到的CRI(Common Runtime interface),也叫通用運(yùn)行時(shí)接口汰现。Docker實(shí)現(xiàn)了這個(gè)接口挂谍,另外還有很多其他的比如CRI-O的實(shí)現(xiàn),作為Docker的另外一個(gè)選項(xiàng)瞎饲,可以用來(lái)在Kubernetes上部署容器化的應(yīng)用口叙。
除了這個(gè)CRI-O,還有諸如rkt嗅战,runC和kata contrainer等的OCI兼容的容器實(shí)現(xiàn)妄田,大家如果感興趣,可以自行學(xué)習(xí)驮捍。
好了疟呐,到這里為止,我們做了足夠的鋪墊厌漂,為了讓大家順利將自己的第一個(gè)應(yīng)用部署到容器平臺(tái)萨醒。接下來(lái),我們?cè)谙乱黄敿?xì)介紹如何把一個(gè)Spring Cloud的應(yīng)用進(jìn)行打包, 并部署到容器平臺(tái)Docker中富纸。