1.創(chuàng)建一個簡單的應用程序
在安裝好Docker后,現(xiàn)在讓我們來創(chuàng)建一個簡單的應用程序。
我們先創(chuàng)建一個簡單的Node.js Web應用,然后將它打包到鏡像中。該應用可以接受HTTP請求并返回主機名。雖然應用進程和其他進程一樣都是運行在宿主機上,但是在容器中運行的應用獲取到的是它所在容器的主機名,而不是宿主機名。
這個Node.js應用只有一個App.js一個文件。
在~目錄下執(zhí)行如下命令:
mkdir test01
cd test01
vim app.js
復制如下代碼到app.js文件中:
const http = require('http');
const os = require('os');
console.log("開始運行...");
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服務。當收到來自外界的消息后,請求處理器會記錄并輸出請求方的IP地址,然后給每一個請求返回狀態(tài)碼200以及一段包含主機名的文本。
到這一步后,我們似乎可以下載并安裝Node.js環(huán)境來直接測試這個應用程序了,但這就背離了本文的初衷了。因為我們要學習的是使用Docker來將這個應用打包到容器鏡像中,然后讓它不需要下載和安裝就可以在任何主機上執(zhí)行,當然如果想運行這個鏡像,主機還是需要準備好最基本的Docker環(huán)境。
2.創(chuàng)建鏡像的Dockerfile
要想將應用程序打包的鏡像里,首先得創(chuàng)建一個叫做Dockerfile的文件。
在~/test01目錄下執(zhí)行如下命令:
touch Dockerfile
這個文件就是一個指令清單,指示Docker在構建鏡像時需要執(zhí)行的命令。Dockerfile需要和app.js文件在同一個目錄下,而且應該包含如下命令:
FROM node:7
ADD app.js /app.js
ENTRYPOINT ["node", "app.js"]

FROM關鍵字所在的行定義了鏡像構建過程所使用的基礎鏡像。此處我們使用node鏡像的tag7版本。
第二行的作用是將app.js文件從本地目錄添加到鏡像的根目錄下,并保持相同的文件名。最后一行的作用是指定運行鏡像的時候應該執(zhí)行的命令。本例中,這個命令是node app.js。
或許你會好奇為什么要選擇這個node鏡像作為基礎鏡像。因為這個應用是一個Node.js應用,因此需要一個包含node二進制可執(zhí)行文件的鏡像來運行該應用。你也可以使用任何包含這個二進制文件的鏡像,甚至可以使用linux發(fā)行版的基礎鏡像,如fedora或ubuntu,然后在構建鏡像之前指定安裝Node.js的命令,從而確保運行容器的時候Node.js會被安裝到容器中。
但是,因為node鏡像是專門用來運行Node.js應用的,而且包含運行應用所需的一切,因此我們使用它作為基礎鏡像。
3.構建鏡像
在Dockerfile和app.js都準備好了之后,我們就可以開始構建鏡像了。在Dockerfile所在目錄下,通過如下命令構建鏡像:
docker build -t test1 .
注意末尾還有一個點號。

這個命令會告訴Docker基于當前目錄下的Dockerfile文件來構建一個叫做test1的鏡像。Docker會查找目錄中的Dockerfile文件,然后基于文件中的指令構建鏡像。
下圖展示了鏡像的構建過程:

鏡像是如何構建的
從上面可以看出,鏡像的構建過程不是由Docker client執(zhí)行的。而是Docker client將整個目錄的的文件上傳到Docker daemon(守護進程)并由它進行構建。Docker client和daemon不需要在同一臺機器上。
如果你在非Linux的操作系統(tǒng)上使用Docker,客戶端可以安裝在宿主機操作系統(tǒng),但是daemon需要運行在VM中。因為構建目錄下的所有文件都會被上傳到daemon中,如果包含了很多大文件而且daemon不在本地運行的話,上傳過程就會比較耗時。
需要注意的是,不要在構建目錄下存放任何不需要的文件,因為這樣會減慢鏡像的構建速度,特別是當Docker daemon進程位于遠程機器上的時候。
在構建的過程中,Docker會從公共鏡像倉庫(Docker Hub)拉取基礎鏡像(node:7),除非這個鏡像已經(jīng)被拉取且存到本機上了。
什么是鏡像層
一個鏡像并不是一個大的二進制塊,而是由很多層組成的。不同的鏡像之間可能會共享某些層,這使得存儲和傳輸鏡像變得更加高效。
例如,如果你基于相同的基礎鏡像(比如本例中的node:7鏡像)創(chuàng)建了幾個鏡像,構成這個基礎鏡像的所有層都只會被存儲一次。而且,當拉取一個鏡像的時候,Docker會單獨的下載每一層。某些層可能已經(jīng)存儲在你的機器上了,因此Docker只會下載那些還未下載過的層。
你可能會認為每個Dockerfile只會創(chuàng)建一個新的層,但是事實不是這樣。當構建鏡像的時候,Dockerfile中的每一條單獨的命令都會創(chuàng)建一個新的層。在鏡像構建的過程中,在拉取了基礎鏡像的所有層之后,Docker會在這些層之上創(chuàng)建一個新的層并將app.js文件添加到這個層里,然后會創(chuàng)建另外一個新的層來指定鏡像被運行的時候應該執(zhí)行的命令。
最后一層會被標記為test1:latest。如下圖所示:

other:latest的鏡像是與我們自己構建的鏡像test1:latest共享Node.js的所有層。
當完成鏡像構建后,一個新的鏡像就被存儲到本地了。可以通過docker images命令列出所有本地的鏡像:

4.運行鏡像
現(xiàn)在我們就可以通過如下命令運行我們自己創(chuàng)建的鏡像了:
docker run --name test1-container -p 8080:8080 -d test1
該命令會告訴Docker通過test1鏡像啟動一個叫做test1-container的容器。-d標志表示容器與終端脫離,也是就容器會在后臺運行。本機的8080端口與容器的8080端口映射。

可以看到返回的16進制數(shù)字就是容器的ID。
如果覺得本文對您有幫助,可以關注、轉發(fā)、點贊,您的支持是我持續(xù)創(chuàng)作的最大動力!