1.創(chuàng)建一個簡單的應(yīng)用程序
在安裝好Docker后,現(xiàn)在讓我們來創(chuàng)建一個簡單的應(yīng)用程序。
我們先創(chuàng)建一個簡單的Node.js Web應(yīng)用,然后將它打包到鏡像中。該應(yīng)用可以接受HTTP請求并返回主機(jī)名。雖然應(yīng)用進(jìn)程和其他進(jìn)程一樣都是運(yùn)行在宿主機(jī)上,但是在容器中運(yùn)行的應(yīng)用獲取到的是它所在容器的主機(jī)名,而不是宿主機(jī)名。
這個Node.js應(yīng)用只有一個App.js一個文件。
在~目錄下執(zhí)行如下命令:
mkdir test01
cd test01
vim app.js
復(fù)制如下代碼到app.js文件中:
const http = require('http');
const os = require('os');
console.log("開始運(yùn)行...");
var handler = function(request, response) {
console.log("收到來自 " + request.connection.remoteAddress + "的消息");
response.writeHead(200);
response.end("已發(fā)送消息至: " + os.hostname() + "n");
};
var www = http.createServer(handler);
www.listen(8080);
該程序會啟動一個監(jiān)聽8080端口的HTTP服務(wù)。當(dāng)收到來自外界的消息后,請求處理器會記錄并輸出請求方的IP地址,然后給每一個請求返回狀態(tài)碼200以及一段包含主機(jī)名的文本。
到這一步后,我們似乎可以下載并安裝Node.js環(huán)境來直接測試這個應(yīng)用程序了,但這就背離了本文的初衷了。因?yàn)槲覀円獙W(xué)習(xí)的是使用Docker來將這個應(yīng)用打包到容器鏡像中,然后讓它不需要下載和安裝就可以在任何主機(jī)上執(zhí)行,當(dāng)然如果想運(yùn)行這個鏡像,主機(jī)還是需要準(zhǔn)備好最基本的Docker環(huán)境。
2.創(chuàng)建鏡像的Dockerfile
要想將應(yīng)用程序打包的鏡像里,首先得創(chuàng)建一個叫做Dockerfile的文件。
在~/test01目錄下執(zhí)行如下命令:
touch Dockerfile
這個文件就是一個指令清單,指示Docker在構(gòu)建鏡像時需要執(zhí)行的命令。Dockerfile需要和app.js文件在同一個目錄下,而且應(yīng)該包含如下命令:
FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]
FROM關(guān)鍵字所在的行定義了鏡像構(gòu)建過程所使用的基礎(chǔ)鏡像。此處我們使用node鏡像的tag7版本。
第二行的作用是將app.js文件從本地目錄添加到鏡像的根目錄下,并保持相同的文件名。最后一行的作用是指定運(yùn)行鏡像的時候應(yīng)該執(zhí)行的命令。本例中,這個命令是node app.js。
或許你會好奇為什么要選擇這個node鏡像作為基礎(chǔ)鏡像。因?yàn)檫@個應(yīng)用是一個Node.js應(yīng)用,因此需要一個包含node二進(jìn)制可執(zhí)行文件的鏡像來運(yùn)行該應(yīng)用。你也可以使用任何包含這個二進(jìn)制文件的鏡像,甚至可以使用linux發(fā)行版的基礎(chǔ)鏡像,如fedora或ubuntu,然后在構(gòu)建鏡像之前指定安裝Node.js的命令,從而確保運(yùn)行容器的時候Node.js會被安裝到容器中。
但是,因?yàn)閚ode鏡像是專門用來運(yùn)行Node.js應(yīng)用的,而且包含運(yùn)行應(yīng)用所需的一切,因此我們使用它作為基礎(chǔ)鏡像。
3.構(gòu)建鏡像
在Dockerfile和app.js都準(zhǔn)備好了之后,我們就可以開始構(gòu)建鏡像了。在Dockerfile所在目錄下,通過如下命令構(gòu)建鏡像:
docker build -t test1 .
注意末尾還有一個點(diǎn)號。
這個命令會告訴Docker基于當(dāng)前目錄下的Dockerfile文件來構(gòu)建一個叫做test1的鏡像。Docker會查找目錄中的Dockerfile文件,然后基于文件中的指令構(gòu)建鏡像。
下圖展示了鏡像的構(gòu)建過程:
鏡像是如何構(gòu)建的
從上面可以看出,鏡像的構(gòu)建過程不是由Docker client執(zhí)行的。而是Docker client將整個目錄的的文件上傳到Docker daemon(守護(hù)進(jìn)程)并由它進(jìn)行構(gòu)建。Docker client和daemon不需要在同一臺機(jī)器上。
如果你在非Linux的操作系統(tǒng)上使用Docker,客戶端可以安裝在宿主機(jī)操作系統(tǒng),但是daemon需要運(yùn)行在VM中。因?yàn)闃?gòu)建目錄下的所有文件都會被上傳到daemon中,如果包含了很多大文件而且daemon不在本地運(yùn)行的話,上傳過程就會比較耗時。
需要注意的是,不要在構(gòu)建目錄下存放任何不需要的文件,因?yàn)檫@樣會減慢鏡像的構(gòu)建速度,特別是當(dāng)Docker daemon進(jìn)程位于遠(yuǎn)程機(jī)器上的時候。
在構(gòu)建的過程中,Docker會從公共鏡像倉庫(Docker Hub)拉取基礎(chǔ)鏡像(node:7),除非這個鏡像已經(jīng)被拉取且存到本機(jī)上了。
什么是鏡像層
一個鏡像并不是一個大的二進(jìn)制塊,而是由很多層組成的。不同的鏡像之間可能會共享某些層,這使得存儲和傳輸鏡像變得更加高效。
例如,如果你基于相同的基礎(chǔ)鏡像(比如本例中的node:7鏡像)創(chuàng)建了幾個鏡像,構(gòu)成這個基礎(chǔ)鏡像的所有層都只會被存儲一次。而且,當(dāng)拉取一個鏡像的時候,Docker會單獨(dú)的下載每一層。某些層可能已經(jīng)存儲在你的機(jī)器上了,因此Docker只會下載那些還未下載過的層。
你可能會認(rèn)為每個Dockerfile只會創(chuàng)建一個新的層,但是事實(shí)不是這樣。當(dāng)構(gòu)建鏡像的時候,Dockerfile中的每一條單獨(dú)的命令都會創(chuàng)建一個新的層。在鏡像構(gòu)建的過程中,在拉取了基礎(chǔ)鏡像的所有層之后,Docker會在這些層之上創(chuàng)建一個新的層并將app.js文件添加到這個層里,然后會創(chuàng)建另外一個新的層來指定鏡像被運(yùn)行的時候應(yīng)該執(zhí)行的命令。
最后一層會被標(biāo)記為test1:latest。如下圖所示:
other:latest的鏡像是與我們自己構(gòu)建的鏡像test1:latest共享Node.js的所有層。
當(dāng)完成鏡像構(gòu)建后,一個新的鏡像就被存儲到本地了??梢酝ㄟ^docker images命令列出所有本地的鏡像:
4.運(yùn)行鏡像
現(xiàn)在我們就可以通過如下命令運(yùn)行我們自己創(chuàng)建的鏡像了:
docker run --name test1-container -p 8080:8080 -d test1
該命令會告訴Docker通過test1鏡像啟動一個叫做test1-container的容器。-d標(biāo)志表示容器與終端脫離,也是就容器會在后臺運(yùn)行。本機(jī)的8080端口與容器的8080端口映射。
可以看到返回的16進(jìn)制數(shù)字就是容器的ID。
如果覺得本文對您有幫助,可以關(guān)注、轉(zhuǎn)發(fā)、點(diǎn)贊,您的支持是我持續(xù)創(chuàng)作的最大動力!