通過(guò)前邊兩篇關(guān)于容器文章的介紹遵堵,大家應(yīng)該對(duì)容器的概念應(yīng)該理解比較透徹了,今天我們來(lái)繼續(xù)分析吮龄,如何將自己的Spring Cloud應(yīng)用程序打包,并部署到Docker平臺(tái)上咆疗。
請(qǐng)?jiān)陂喿x本篇文章之前漓帚,在自己的機(jī)器上下載Docker Desktop應(yīng)用,并完成安裝午磁,因?yàn)檫@個(gè)過(guò)程并不是太困難尝抖,因此筆者不會(huì)介紹如何安裝Docker到自己的機(jī)器上,如果實(shí)在不知道怎么安裝迅皇,建議參考官方文檔:http://docs.docker.com/install昧辽。
假設(shè)讀者已經(jīng)成功將Docker安裝到了自己的機(jī)器上,那么我們就可以使用docker的CLI客戶端命令行工具登颓。首先我們可以從DockerHub上拉取一個(gè)鏡像并在本地運(yùn)行起來(lái)搅荞,Dockerhub是公共開(kāi)發(fā)的鏡像倉(cāng)庫(kù),你可以訪問(wèn)到任何以public可見(jiàn)級(jí)別上傳的鏡像框咙,我們今天要使用的是一個(gè)叫busybox的進(jìn)項(xiàng)咕痛,我們可以用這個(gè)鏡像來(lái)執(zhí)行echo 命令。
注解:BusyBox是一個(gè)集成了三百多個(gè)最常用Linux命令和工具的進(jìn)項(xiàng)喇嘱,其中包含了一些簡(jiǎn)單的工具茉贡,如echo、ls婉称、cat等块仆,還包含了一些大的,更復(fù)雜的工具王暗,例grep、find庄敛、mount以及telnet俗壹。很多人將BusyBox稱為L(zhǎng)inux工具里的瑞士軍刀。
那么接下來(lái)我們就通過(guò)這個(gè)叫busybox的瑞士軍刀鏡像藻烤,在筆者的Mac上運(yùn)行起來(lái)绷雏,并且借著這個(gè)簡(jiǎn)單的場(chǎng)景來(lái)分析一下當(dāng)我們運(yùn)行docker run這個(gè)命令后头滔,具體發(fā)生了什么。
在Docker Desktop啟動(dòng)的前提下涎显,我們可以在自己的機(jī)器上運(yùn)行:docker run busybox echo “yunpan”, 結(jié)果如下圖所示:
上圖看起并不是讓人印象深刻坤检,但是你需要從這些日志輸出中看到:第一,由于docker沒(méi)有在本地找到busybox鏡像期吓,因此需要先下載這個(gè)進(jìn)項(xiàng)早歇,然后在本地將這個(gè)鏡像運(yùn)行起來(lái),并接受輸入的命令讨勤,最后的輸出就是運(yùn)行結(jié)果:yunpan箭跳,需要注意的是,如果你的機(jī)器上有這個(gè)叫busybox的鏡像潭千,那么就會(huì)跳過(guò)下載的這個(gè)步驟谱姓,因此為了確保你能看到相同的輸出,請(qǐng)通過(guò)docker images先看看本地是否有busybox這個(gè)鏡像刨晴,如果有的話屉来,就通過(guò)docker rmi -f ‘鏡像id’ 來(lái)從本地刪除這個(gè)鏡像。
大家要注意的另外一點(diǎn)是狈癞,這句命令并沒(méi)有什么安裝和部署的步驟茄靠,以及應(yīng)用程序以來(lái)的組件部署的步驟,因此通過(guò)這個(gè)簡(jiǎn)單的例子亿驾,你能看到通過(guò)容器打包來(lái)在多個(gè)環(huán)境運(yùn)行應(yīng)用程序是如此的便利嘹黔。
這里有另外一個(gè)細(xì)節(jié)需要大家注意,我們通過(guò)docker run運(yùn)行的應(yīng)用程序莫瞬,其實(shí)本質(zhì)上運(yùn)行在一個(gè)特殊的進(jìn)程中儡蔓,這個(gè)進(jìn)程和本地機(jī)器上的其他進(jìn)程隔離。為了更直觀的展示docker run執(zhí)行后具體發(fā)生了什么疼邀,請(qǐng)看下圖:
如上圖所示喂江,Docker的CLI工具將執(zhí)行發(fā)給Docker daemon來(lái)運(yùn)行busybox鏡像,daemon首先檢查本地的鏡像緩存是否有busybox進(jìn)項(xiàng)旁振,如果沒(méi)有获询,首先從Docker hub倉(cāng)庫(kù)下載這個(gè)鏡像.
鏡像下載到我的本地電腦后,Docker daemon基于剛剛下載的鏡像啟動(dòng)一個(gè)容器實(shí)例拐袜,并且在啟動(dòng)完成后吉嚣,在鏡像中運(yùn)行echo命令,這個(gè)命令只是簡(jiǎn)單的將輸入字符串打印出來(lái)到控制臺(tái)蹬铺,執(zhí)行完成后echo進(jìn)程就會(huì)退出尝哆,而進(jìn)程退出后容器的實(shí)例也會(huì)停止運(yùn)行。
由于筆者是在自己的Mac電腦上運(yùn)行甜攀,因此deamon和容器都運(yùn)行在一個(gè)Linux 虛擬機(jī)中琐馆,而如果讀者是在Linux機(jī)器上運(yùn)行這個(gè)docker的命令恒序,那么deamon和容器實(shí)例都是直接在這臺(tái)Linux機(jī)器上創(chuàng)建對(duì)應(yīng)的容器進(jìn)程滋饲。
注意:我們除了從默認(rèn)dockerhub上下載本地不存在鏡像之外了赌,我們也可以通過(guò)docker run abc.io/yunpan/images-sample來(lái)指定從這個(gè)叫abc.io的倉(cāng)庫(kù)地址下載image-sample來(lái)運(yùn)行。
好了逢并,到這里為止砍聊,關(guān)于Docker的理論性只是就這么多了玻蝌,接下來(lái)我們來(lái)通過(guò)創(chuàng)建一個(gè)Node js的的應(yīng)用程序,將它打包成一個(gè)容器鏡像许饿,并在本地運(yùn)行起來(lái)的例子陋率,來(lái)向大家詳細(xì)介紹通過(guò)Docker如何將一個(gè)web應(yīng)用運(yùn)行起來(lái),這個(gè)應(yīng)用程序會(huì)返回所運(yùn)行機(jī)器的hostname狸页。
通過(guò)這種方式,我們逐步會(huì)揭開(kāi)一些事實(shí):容器運(yùn)行的進(jìn)程實(shí)例所看到的hostname和宿主機(jī)是不一樣斋竞,即便是從宿主機(jī)上來(lái)看,容器進(jìn)程和運(yùn)行在宿主機(jī)上的其他進(jìn)程沒(méi)有什么區(qū)別鳄袍。這個(gè)例子也會(huì)為我們后續(xù)在Kubernetes部署多個(gè)應(yīng)用的實(shí)例打下基礎(chǔ),因?yàn)槟憧梢栽诓渴鹑齻€(gè)應(yīng)用實(shí)例的情況下哀九,每次訪問(wèn)都會(huì)命中不同的容器實(shí)例,因?yàn)榉祷氐膆ostname是不同的息裸。
我們的測(cè)試應(yīng)用其實(shí)非常簡(jiǎn)單呼盆,由一個(gè)js文件組成:application.js包含了這個(gè)應(yīng)用的所有代碼,如下圖所示:
上邊的Node JS代碼應(yīng)該不難理解,我們?cè)?080端口啟動(dòng)了一個(gè)HTTP服務(wù)器圈匆,當(dāng)請(qǐng)求到達(dá)的時(shí)候,將請(qǐng)求的信息輸出到日志纬傲,然后發(fā)送響應(yīng)信息叹括,格式是:’你好, 容器實(shí)例運(yùn)行機(jī)器的hostname是:os.hostname()净嘀。你的訪問(wèn)IP地址是:clientIP ∧っ撸‘,特別需要注意的是,請(qǐng)求響應(yīng)中返回的是服務(wù)器的真實(shí)hostname鸿脓,而不是請(qǐng)求中的hostname,大家一定要注意這個(gè)區(qū)別拨黔。
雖然說(shuō)我們可以通過(guò)Node來(lái)直接將上邊的JS代碼運(yùn)行起來(lái),但是我們有更好的辦法零截。接下來(lái)我們會(huì)把這個(gè)Node JS打成Docker鏡像,然后我們就可以在任意安裝了Docker的機(jī)器上運(yùn)行這個(gè)web應(yīng)用程序雁比,而不需要提前安裝Node js組件,這可真是方便啊蜕径。
【將Node JS應(yīng)用打包成鏡像】
為了將我們的Node JS應(yīng)用打包成鏡像, 我們首先要?jiǎng)?chuàng)建一個(gè)叫Dockerfile的文件梦染,這個(gè)文件的目的就是告訴Docker,如何將我們的Node JS打包成鏡像肮疗,我們可以將剛才創(chuàng)建的application.js文件保存在本地的目錄:/Users/gaopanqi/work/kubernetes/samples/sample1(可以替換為自己本地的實(shí)際目錄)钾怔,并在這個(gè)目錄下創(chuàng)建一個(gè)Dockerfile文件愚臀,如下圖所示:
我們來(lái)簡(jiǎn)單介紹一下文件中的這三句命令炭分。首先FROM指令告訴我們?nèi)萜麋R像會(huì)基于node:12這個(gè)基礎(chǔ)鏡像構(gòu)建,大家應(yīng)該還記得筆者在前邊文章講的鏡像分層和共享的內(nèi)容师痕,這就是分層共享機(jī)制的體現(xiàn),可以認(rèn)為有人已經(jīng)幫我們做好了node:12這個(gè)鏡像,我們的應(yīng)用會(huì)構(gòu)建在這個(gè)鏡像之上,很明顯這個(gè)鏡像中有我們的應(yīng)用運(yùn)行所需的Node JS環(huán)境,這就是為什么我們不需要在本地單獨(dú)安裝Node環(huán)境,才能運(yùn)行容器實(shí)例的原因格嘁。
另外我需要強(qiáng)調(diào)的是画机,由于我們的影響是基于這個(gè)node:12鏡像構(gòu)建,那么我們打包的時(shí)候界阁,如果本地有這個(gè)node:12鏡像贮竟,那么都不需要重新去下載技健,整個(gè)打包過(guò)程會(huì)非常的迅速和高效欣孤,這就會(huì)分層和鏡像共享給我?guī)?lái)的紅利和便利。
注意:node:12代表的是node鏡像中艾少,標(biāo)記有tag12的版本,關(guān)于鏡像和版本其實(shí)有特定的規(guī)則缚够,因?yàn)楸容^簡(jiǎn)單幔妨,如果不是很清楚,可以自行學(xué)習(xí)谍椅。
接著我們第二句命令A(yù)DD application.js /app.js的作用是將node js代碼文件從本地拷貝到鏡像的根目錄下误堡,并且進(jìn)行重新命名(app.js),最后一句相比大家都很熟悉了雏吭,制定應(yīng)用的入口函數(shù)锁施,也就是鏡像啟動(dòng)的時(shí)候,執(zhí)行什么代碼來(lái)啟動(dòng)應(yīng)用杖们,你可以看到在上邊的例子中悉抵,我們通過(guò)node app.js來(lái)啟動(dòng)我們的應(yīng)用程序。
注意:給自己的應(yīng)用選在一個(gè)基礎(chǔ)鏡像遠(yuǎn)遠(yuǎn)要比隨便在Dockerhub上找一個(gè)看起來(lái)可用的鏡像要復(fù)雜的多摘完,特別是對(duì)于企業(yè)級(jí)的用戶來(lái)講姥饰,考慮到安全性的問(wèn)題,大家務(wù)必和運(yùn)維團(tuán)隊(duì)的同學(xué)進(jìn)行溝通孝治,因?yàn)榇蟛糠制髽I(yè)都有自己的標(biāo)準(zhǔn)的鏡像列粪,以防止從外部下載的鏡像中攜帶惡意的代碼审磁。對(duì)于上邊的例子,因?yàn)槲覀兙褪菃渭儤闼氐南M幸粋€(gè)安裝了Node環(huán)境的基礎(chǔ)鏡像篱竭,因此選擇了這個(gè)node的鏡像力图,當(dāng)然你也可以基于標(biāo)準(zhǔn)的linux鏡像來(lái)編寫(xiě)Dockerfile,唯一不同的是要增加一句wget安裝NodeJS的指令掺逼,為了簡(jiǎn)化我們的討論吃媒,筆者就直接使用這個(gè)node js的鏡像了,但是讀者可以有自己的選擇吕喘。
【構(gòu)建容器鏡像】
在準(zhǔn)備好構(gòu)建應(yīng)用的代碼和Dockerfile之后赘那,我們就可以把代碼打包成標(biāo)準(zhǔn)的Docker鏡像了,在自己的機(jī)器上運(yùn)行:docker build -t qigaopan/web:v1.0 ., Docker工具就開(kāi)始幫助我們構(gòu)建鏡像了氯质,如下圖:
整個(gè)構(gòu)建過(guò)程會(huì)持續(xù)一段時(shí)間募舟,具體和你的網(wǎng)絡(luò)情況有關(guān),特別如果你本地有node:12這個(gè)鏡像的話闻察,整個(gè)過(guò)程會(huì)非彻敖福快,這個(gè)鏡像在筆者的機(jī)器上有800M左右辕漂。構(gòu)建命令中的-t參數(shù)指定了鏡像的名稱和標(biāo)簽呢灶,大家打包的時(shí)候一定要注意最后的“.",這是指定包含了docker build需要的Dockerfile的目錄钉嘹,很多初學(xué)者很容易這個(gè)點(diǎn)號(hào)鸯乃,需要特別注意。
當(dāng)整個(gè)打包過(guò)程完成后跋涣,在最后會(huì)輸出打包好的鏡像的名稱缨睡,我們可以在自己的機(jī)器上執(zhí)行docker images命令,列出所有本地的鏡像陈辱,其中就包含了剛才打包好的鏡像奖年,由于我們通過(guò)-t參數(shù)指定了具體的名稱,大家應(yīng)該不難找到這個(gè)剛剛打包好的鏡像性置,一般都在最上邊拾并,這個(gè)清單是按更新時(shí)間來(lái)排序的。
是不是感覺(jué)很神奇鹏浅,那么這個(gè)過(guò)程具體是如何發(fā)生的呢嗅义?我們其實(shí)只是簡(jiǎn)單的告訴Docker工具:請(qǐng)幫基于提供的Dockerfile來(lái)構(gòu)建一個(gè)叫web":v1.0的鏡像,并提供了Dockerfile和應(yīng)用程序源代碼的路徑隐砸,Docker就會(huì)讀取Dockerfile中的內(nèi)容之碗,然后要做的就是基于這個(gè)文件中包含的指令,來(lái)幫我們把鏡像構(gòu)建出來(lái)季希,如下圖所示:
如上圖所示褪那,真正執(zhí)行build操作的并不是CLI工具幽纷,而是將包含Dockerfile和源代碼的整個(gè)目錄上傳到Docker deamon,通過(guò)deamon來(lái)進(jìn)行鏡像的打包博敬,因此你可以看到友浸,Docker deamon和CLI客戶端工具根本不需要必須在一臺(tái)機(jī)器上,如果你是在mac上運(yùn)行打包的命令偏窝,那么CLI工具就運(yùn)行在這臺(tái)Mac宿主機(jī)上收恢,而Docker deamon運(yùn)行在一臺(tái)Linux虛擬機(jī)中,當(dāng)然我們完全可以專門(mén)找一臺(tái)機(jī)器祭往,在上邊運(yùn)行Docker deamon伦意,這樣一臺(tái)機(jī)器就可以當(dāng)做專門(mén)的build機(jī)器來(lái)對(duì)外提供服務(wù)。這種配置方式在CI/CD流水線配置中是主流硼补。
注意:我們?cè)趯?duì)應(yīng)打包的時(shí)候驮肉,請(qǐng)不要將無(wú)關(guān)的輔助性的配置,說(shuō)明和中間結(jié)果等文件打包到Docker鏡像中已骇,這不光會(huì)讓整個(gè)鏡像的尺寸變大离钝,也會(huì)讓整個(gè)構(gòu)建的流程變慢,同時(shí)會(huì)消耗額外的網(wǎng)絡(luò)流量褪储。
在build鏡像的時(shí)候奈辰,Docker deamon首先將基礎(chǔ)鏡像node:12從Dockerhub這個(gè)公共的鏡像倉(cāng)庫(kù)下載到本地,然后會(huì)基于這個(gè)這基礎(chǔ)鏡像來(lái)創(chuàng)建我們的應(yīng)用程序鏡像web:v1.0乱豆,這個(gè)構(gòu)建的過(guò)程由編寫(xiě)在Dockerfile的三行命令來(lái)驅(qū)動(dòng),每一行命令都會(huì)在基礎(chǔ)鏡像上增加一層吊趾,三個(gè)命令執(zhí)行完后宛裕,最后的鏡像會(huì)被命名為-t制定的名字和標(biāo)簽。說(shuō)到這里论泛,你可能會(huì)很好奇揩尸,打包過(guò)程中增減的每一層文件看起來(lái)長(zhǎng)啥樣啊屁奏?請(qǐng)繼續(xù)閱讀岩榆。
【組成鏡像的層具體是啥樣?】
筆者在前文中多次強(qiáng)調(diào)坟瓢,Docker的鏡像是由多個(gè)層組成勇边,那么你可能會(huì)覺(jué)得我們剛才創(chuàng)建的node js的應(yīng)用鏡像應(yīng)該由兩層組成,其中最底層是基礎(chǔ)鏡像折联,而最上邊就是Dockerfile中的三行命令粒褒。然而事實(shí)并非如此,當(dāng)我們構(gòu)建鏡像的時(shí)候诚镰,Dockerfile中的”每一行命令“都會(huì)創(chuàng)建一個(gè)新的層奕坟,并且先后順序疊加到基礎(chǔ)鏡像上祥款。
具體來(lái)說(shuō),在構(gòu)建web:1.0的過(guò)程中月杉,Docker deamon首先將基礎(chǔ)鏡像層加載到內(nèi)存刃跛,然后在這個(gè)基礎(chǔ)鏡像層之上創(chuàng)建新的一層來(lái)講application.js代碼文件保存進(jìn)去,最后再新建的代碼層之上創(chuàng)建另外一層來(lái)保存啟動(dòng)腳本苛萎,最后這一層會(huì)被打上web:v1.0的標(biāo)記桨昙。口說(shuō)無(wú)憑首懈,我們來(lái)驗(yàn)證一下绊率,在自己的機(jī)器可以通過(guò)命令:docker history web:v1.0來(lái)查看組成鏡像的各個(gè)層,如下圖所示:
如上圖所示究履,我們可以看到web:v1.0這個(gè)鏡像由16層組成(是不是遠(yuǎn)遠(yuǎn)大于你的預(yù)期?)滤否,大部分的層都是由基礎(chǔ)鏡像構(gòu)成,其中最上邊兩層是我們的Dockerfile文件中的最后兩條命令最仑,而剩余部分就是基礎(chǔ)鏡像提供的層藐俺,筆者在圖上進(jìn)行了標(biāo)記說(shuō)明,可以參考上圖對(duì)比Dockerfile文件和實(shí)際打包的鏡像泥彤。
另外上圖有個(gè)非常有意思的點(diǎn)欲芹,Create BY列向我展示了相關(guān)層是執(zhí)行什么命令產(chǎn)生的,除了使用ADD命令之外吟吝,我們還可以使用RUN來(lái)構(gòu)建鏡像的時(shí)候執(zhí)行某個(gè)命令菱父,如果你仔細(xì)看上圖,你會(huì)發(fā)現(xiàn)還有很多apt-get的命令剑逃,這些命令就是通過(guò)網(wǎng)絡(luò)來(lái)給鏡像安裝必須的軟件浙宜,也就是說(shuō)通過(guò)apt-get,我們可以在打包的時(shí)候蛹磺,給鏡像安裝某些應(yīng)用需要在運(yùn)行的時(shí)候使用的依賴粟瞬。
好了,相信通過(guò)上邊的內(nèi)容學(xué)習(xí)萤捆,你已經(jīng)對(duì)通過(guò)Docker工具打包這個(gè)事情理解的比較透徹了裙品,你是不是已經(jīng)按奈不住內(nèi)心的激動(dòng),想趕緊把這個(gè)應(yīng)用運(yùn)行起來(lái)俗或,俗話說(shuō)市怎,是騾子是馬,拉出來(lái)溜溜蕴侣。接下來(lái)焰轻,在把這個(gè)web:v1.0的鏡像在本地運(yùn)行起來(lái)吧。
【將鏡像運(yùn)行起來(lái)】
有了打包好的鏡像昆雀,那么把他運(yùn)行起來(lái)就非常的容易了辱志,在自己的機(jī)器上運(yùn)行:docker run --name yunpan-container -p 8080:8080 -d qigaopan/web:v1.0, 這句命令會(huì)輸出一行UUID蝠筑,這個(gè)就是本地運(yùn)行的容器實(shí)例的唯一標(biāo)識(shí)ID。
?? sample1 docker run --name yunpan-container -p 8080:8080 -d qigaopan/web:v1.0
e48ca2f1460d130ce95a532c1dca7c86fdcbd25cb204a419192c86503c464b12
docker run命令后邊的幾個(gè)參數(shù)值得我們稍微說(shuō)一下揩懒,我們可以通過(guò)--name來(lái)為啟動(dòng)的容器實(shí)例制定一個(gè)名字什乙,方便查看;通過(guò)-d標(biāo)記來(lái)讓這個(gè)容器運(yùn)行在后臺(tái)已球,而通過(guò)-p來(lái)進(jìn)行端口映射臣镣,在筆者的mac環(huán)境中,容器運(yùn)行在創(chuàng)建的Linux虛擬機(jī)中智亮,因此容器默認(rèn)是無(wú)法從從外部訪問(wèn)忆某,因此我們需要通過(guò)端口映射來(lái)從筆者的終端訪問(wèn)到這個(gè)容器提供的服務(wù)。
好了阔蛉,了解了這些必要的細(xì)節(jié)之后弃舒,我們來(lái)通過(guò)自己的瀏覽器,或者命令行curl工具状原,curl命令行工具來(lái)訪問(wèn)我們剛剛在Docker上部署的應(yīng)用服務(wù)聋呢,如下所示:
?? sample1 curl http://localhost:8080
你好, 容器實(shí)例運(yùn)行機(jī)器的hostname是: e48ca2f1460d。 你的訪問(wèn)IP地址是: ::ffff:172.17.0.1颠区。
漂亮削锰,如果你能看到預(yù)期的返回,那就說(shuō)明我們的應(yīng)用被成功打包毕莱,并部署到本地Docker環(huán)境中了器贩,并且也成功運(yùn)行起來(lái)了。接下來(lái)朋截,我們來(lái)通過(guò)下圖來(lái)聊聊端口映射磨澡,這是很多初學(xué)者不太容易理解的地方,筆者最近在現(xiàn)場(chǎng)給不同的同學(xué)講過(guò)客戶端质和,宿主機(jī),容器,擴(kuò)容器訪問(wèn)的網(wǎng)絡(luò)機(jī)制,覺(jué)得這部分才是很多人學(xué)習(xí)Kubernetes的最大的攔路虎乖仇,因?yàn)槟阈枰胁僮飨到y(tǒng)伐脖,計(jì)算機(jī)網(wǎng)絡(luò)的知識(shí),才能真正理解昧谊。
上圖的信息都已經(jīng)很完整了,筆者就不做累述了。
如果你還記得我們的代碼中其實(shí)有把請(qǐng)求端的IP地址寫(xiě)到日志中国夜,那么你的可能會(huì)問(wèn),我去哪里看這個(gè)容器運(yùn)行的日志呢短绸?好問(wèn)題车吹,我們可以通過(guò)命令:docker logs yunpan-container(請(qǐng)?zhí)鎿Q成你自己的容器名稱)來(lái)查看容器運(yùn)行過(guò)程中輸出的日志筹裕,如下圖所示:
好了,這篇文章就寫(xiě)到這里了窄驹。接下來(lái)筆者會(huì)繼續(xù)沿著容器的思路朝卒,來(lái)揭示組成容器的三大支柱,以及通過(guò)一個(gè)Spring Cloud的java程序的例子乐埠,來(lái)看看如何將主流的微服務(wù)打包成容器鏡像抗斤,并啟動(dòng)起來(lái),敬請(qǐng)期待丈咐。
如果你想直接體驗(yàn)這個(gè)例子瑞眼,可以從本地直接拉筆者已經(jīng)打包上傳到Dockerhub的鏡像:”docker pull qigaopan/web:1.0“。