2.1.1 安裝Docker并運行Hello World容器
基本內容基于原書,代碼部分稍有修改,以適應最新版本
CentOS一鍵安裝docker
curl -fsSL https://get.docker.com | bash -s docker --mirror aliyun
開啟docker并設置自啟動
sudo systemctl start docker
systemctl enable docker
VirtualBox 動態(tài)磁盤擴容 vboxmanage modifyhd "/home/matrix/VirtualBox VMs/ubuntu/ubuntu.vdi" --resize 10240
運行Hello World容器
busybox是一個單一可執(zhí)行文件丑掺,包含多種標準UNIX命令行工具兼丰,如:echo黍翎、ls氮双、gzip 等砰粹。除了包含 echo 命令的 busybox 命令唧躲,也可以使用如Fedora造挽、Ubuntu等功能完備的鏡像。
如何才能運行 busybox 鏡像呢弄痹?無須下載或者安裝任何東西饭入。使用 docker run
命令然后指定需要運行的鏡像的名字肛真,以及需要執(zhí)行的命令(可選)谐丢,如下面這段代碼。
代碼清單2.1 使用Docker運行一個Hello world容器
[root@localhost test]# docker run busybox echo 'Hello world'
Unable to find image 'busybox:latest' locally
latest: Pulling from library/busybox
b71f96345d44: Pull complete
Digest: sha256:930490f97e5b921535c153e0e7110d251134cc4b72bbb8133c6a5065cc68580d
Status: Downloaded newer image for busybox:latest
Hello world
背后的原理
首先蚓让,Docker會檢查busybox:latest 鏡像是否已經(jīng)存在于本機乾忱。如果沒有,Docker會從http://docker.io的Docker鏡像中心拉取鏡像历极。鏡像下載到本機之后窄瘟,Docker基于這個鏡像創(chuàng)建一個容器并在容器中運行命令。echo 命令打印文字到標準輸出流趟卸,然后進程終止蹄葱,容器停止運行。
運行其他鏡像
運行其他的容器鏡像和運行busybox鏡像是一樣的锄列,甚至可能更簡單图云,因為你可以不需要指定執(zhí)行命令。就像例子中的 echo "Hello world"邻邮,被執(zhí)行的命令通常都會被包含在鏡像中竣况,但也可以根據(jù)需要進行覆蓋。在瀏覽器中搜索http://hub.docker.com或其他公開的鏡像中心的可用鏡像之后筒严,可以像這樣在Docker中運行鏡像:
$ docker run <image>
容器鏡像的版本管理
當然帕翻,所有的軟件包都會更新鸠补,所以通常每個包都不止一個版本。Docker支持同一鏡像的多個版本嘀掸。每一個版本必須有唯一的tag名紫岩。當引用鏡像沒有顯式地指定tag時,Docker會默認指定tag為latest睬塌。如果想要運行別的版本的鏡像泉蝌,需要像這樣指定鏡像的版本:
$ docker run <image>:<tag>
2.1.2 創(chuàng)建一個簡單的 Node.js 應用
現(xiàn)在有了一個可以工作的Docker環(huán)境來創(chuàng)建應用。接下來會構建一個簡單的Node.js Web應用揩晴,并把它打包到容器鏡像中勋陪。這個應用會接收HTTP請求并響應應用運行的主機名。這樣硫兰,應用運行在容器中诅愚,看到的是自己的主機名而不是宿主機名,即使它也像其他進程一樣運行在宿主機上劫映。這在后面會非常有用违孝,當應用部署在Kubernetes上并進行伸縮時(水平伸縮,復制應用到多個節(jié)點)泳赋,你會發(fā)現(xiàn)HTTP請求切換到了應用的不同實例上雌桑。
應用包含一個名為app.js的文件,詳見下面的代碼清單祖今。
代碼清單2.2 一個簡單的Node.js應用:app.js
const http = require('http');
const os = require('os');
const hostname = '0.0.0.0';
const port = 888;
const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end("You've hit " + os.hostname() + "\n");
});
server.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});
代碼清晰地說明了實現(xiàn)的功能校坑。這里在8080端口啟動了一個HTTP服務器。服務器會以狀態(tài)碼 200 OK
和文字 "You've hit <hostname>"
來響應每個請求千诬。請求handler會把客戶端的IP打印到標準輸出耍目,以便日后查看。注意 返回的主機名是服務器真實的主機名徐绑,不是客戶端發(fā)出的HTTP請求中頭的 Host 字段邪驮。
現(xiàn)在可以直接下載安裝Node.js來測試代碼了,但是這不是必需的泵三,因為可以直接用Docker把應用打包成鏡像耕捞,這樣在需要運行的主機上就無須下載和安裝其他的東西(當然不包括安裝Docker來運行鏡像)。
2.1.3 為鏡像創(chuàng)建Dockerfile
為了把應用打包成鏡像烫幕,首先需要創(chuàng)建一個叫Dockerfile的文件俺抽,它包含了一系列構建鏡像時會執(zhí)行的指令。Dockerfile文件需要和app.js文件在同一目錄较曼,并包含下面代碼清單中的命令磷斧。
代碼清單2.3 構建應用容器鏡像的Dockerfile
FROM node:12
ADD app.js /app.js
ENTRYPOINT ["node","app.js"]
From 行定義了鏡像的起始內容(構建所基于的基礎鏡像)。這個例子中使用的是 node 鏡像的tag 7 版本。第二行中把app.js文件從本地文件夾添加到鏡像的根目錄弛饭,保持app.js這個文件名冕末。最后一行定義了當鏡像被運行時需要被執(zhí)行的命令,這個例子中侣颂,命令是 node app.js档桃。
選擇基礎鏡像
你或許在想虽画,為什么要選擇這個鏡像作為基礎鏡像撒犀。因為這個應用是Node.js應用,鏡像需要包含可執(zhí)行的 node 二進制文件來運行應用间学。你也可以使用任何包含這個二進制文件的鏡像拒担,或者甚至可以使用Linux發(fā)行版的基礎鏡像嘹屯,如 fedora或ubuntu,然后在鏡像構建的時候安裝Node.js从撼。但是由于 node鏡像是專門用來運行Node.js應用的州弟,并且包含了運行應用所需的一切,所以把它當作基礎鏡像低零。
2.1.4 構建容器鏡像
現(xiàn)在有了Dockerfile和app.js文件,這是用來構建鏡像的所有文件毁兆。運行下面的Docker命令來構建鏡像:
$ docker build -t kubia .
刪除鏡像必須刪除容器
//查詢容器
#docker ps -a
//刪除容器
#docker rm 67jhgf***
//查詢鏡像
#docker images
//刪除鏡像
#docker rmi 99fkjh***
用戶告訴Docker需要基于當前目錄(注意命令結尾的點)構建一個叫kubia的鏡像宿百,Docker會在目錄中尋找Dockerfile,然后基于其中的指令構建鏡像洪添。
鏡像是如何構建的
構建過程不是由Docker客戶端進行的垦页,而是將整個目錄的文件上傳到Docker守護進程并在那里進行的。Docker客戶端和守護進程不要求在同一臺機器上干奢。如果你在一臺非Linux操作系統(tǒng)中使用Docker痊焊,客戶端就運行在你的宿主操作系統(tǒng)上,但是守護進程運行在一個虛擬機內。由于構建目錄中的文件都被上傳到了守護進程中薄啥,如果包含了大量的大文件而且守護進程不在本地運行辕羽,上傳過程會花費更多的時間。
提示 不要在構建目錄中包含任何不需要的文件垄惧,這樣會減慢構建的速度——尤其當Docker守護進程運行在一個遠端機器的時候刁愿。
在構建過程中,Docker首次會從公開的鏡像倉庫(Docker Hub)拉取基礎鏡像(node:12)到逊,除非已經(jīng)拉取過鏡像并存儲在本機上了酌毡。
鏡像分層
鏡像不是一個大的二進制塊,而是由多層組成的蕾管,在運行busybox例子時你可能已經(jīng)注意到(每一層有一行Pull complete)枷踏,不同鏡像可能會共享分層,這會讓存儲和傳輸變得更加高效掰曾。比如旭蠕,如果創(chuàng)建了多個基于相同基礎鏡像(比如例子中的 node:7)的鏡像,所有組成基礎鏡像的分層只會被存儲一次旷坦。拉取鏡像的時候掏熬,Docker會獨立下載每一層。一些分層可能已經(jīng)存儲在機器上了秒梅,所以Docker只會下載未被存儲的分層旗芬。
你或許會認為每個Dockerfile只創(chuàng)建一個新層,但是并不是這樣的捆蜀。構建鏡像時疮丛,Dockerfile中每一條單獨的指令都會創(chuàng)建一個新層。鏡像構建的過程中辆它,拉取基礎鏡像所有分層之后誊薄,Docker在它們上面創(chuàng)建一個新層并且添加app.js。然后會創(chuàng)建另一層來指定鏡像被運行時所執(zhí)行的命令锰茉。最后一層會被標記為kubia:latest呢蔫。圖2.3 展示了這個過程,同時也展示另外一個叫 other:latest 的鏡像如何與我們構建的鏡像共享同一層Node.js鏡像飒筑。
構建完成時片吊,新的鏡像會存儲在本地。下面的代碼展示了如何通過Docker列出本地存儲的鏡像:
代碼清單2.4 列出本地存儲的鏡像
s docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
kubia latest d30ecc7419e7 1 minute ago 637.1 MB
比較使用Dockerfile和手動構建鏡像
Dockerfile是使用Docker構建容器鏡像的常用方式协屡,但也可以通過運行已有鏡像容器來手動構建鏡像俏脊,在容器中運行命令,退出容器著瓶,然后把最終狀態(tài)作為新鏡像联予。用Dockerfile構建鏡像是與此相同的啼县,但是是自動化且可重復的,隨時可以通過修改Dockerfile重新構建鏡像而無須手動重新輸入命令沸久。
2.1.5 運行容器鏡像
以下的命令可以用來運行鏡像:
$ docker run --name kube-container -p 888:888 -d kubia
關閉后運行的話
$ docker restart kube-container
這條命令告知Docker基于 kubia 鏡像創(chuàng)建一個叫 kubia-container 的新容器季眷。這個容器與命令行分離(-d 標志),這意味著在后臺運行卷胯。本機上的888端口會被映射到容器內的888端口(-p 888:888 選項)子刮,所以可以通過http://localhost:888
訪問這個應用。
如果沒有在本機上運行Docker守護進程(比如使用的是Mac或Windows系統(tǒng)窑睁,守護進程會運行在VM中)挺峡,需要使用VM的主機名或IP來代替localhost運行守護進程〉Eィ可以通過 DOCKER_HOST 這個環(huán)境變量查看主機名橱赠。
訪問應用
現(xiàn)在試著通過 http://localhost:888
訪問你的應用(確保使用Docker主機名或IP替換localhost):
$ curl localhost:888
You've hit 44d76963e8e1
這是應用的響應。現(xiàn)在應用運行在容器中箫津,與其他東西隔離狭姨。可以看到苏遥,應用把 44d76963e8e1 作為主機名返回饼拍,這并不是宿主機的主機名。這個十六進制數(shù)是Docker容器的ID田炭。
列出所有運行中的容器
下面的代碼清單列出了所有的運行中的容器师抄,可以查看列表(為了更好的可讀性,列表被分成了兩行顯示)教硫。
代碼清單2.5 列出運行中的容器
# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
9d3e6e24ab3c kubia "node app.js" 4 minutes ago Up 4 minutes 0.0.0.0:8080->8080/tcp kubia-container
有一個容器在運行叨吮。Docker會打印出每一個容器的ID和名稱、容器運行所使用的鏡像栋豫,以及容器中執(zhí)行的命令挤安。
獲取更多的容器信息
docker ps 只會展示容器的大部分基礎信息谚殊∩パ欤可以使用 docker inspect查看更多的信息:
$ docker inspect kubia-container
Docker會打印出包含容器底層信息的長JSON。
2.1.6 探索運行容器的內部
我們來看看容器內部的環(huán)境嫩絮。由于一個容器里可以運行多個進程丛肢,所以總是可以運行新的進程去看看里面發(fā)生了什么。如果鏡像里有可用的shell二進制可執(zhí)行文件剿干,也可以運行一個shell蜂怎。
在已有的容器內部運行shell
鏡像基于的Node.js鏡像包含了bash shell,所以可以像這樣在容器內運行shell:
$ docker exec -it kubia-container bash
這會在已有的kubia-container容器內部運行bash置尔。bash 進程會和主容器進程擁有相同的命名空間杠步。這樣可以從內部探索容器,查看Node.js和應用是如何在容器里運行的。-it 選項是下面兩個選項的簡寫:
-
-i
幽歼,確保標準輸入流保持開放朵锣。需要在shell中輸入命令。 -
-t
甸私,分配一個偽終端(TTY)诚些。
如果希望像平常一樣使用shell,需要同時使用這兩個選項(如果缺少第一個選項就無法輸入任何命令皇型。如果缺少第二個選項诬烹,那么命令提示符不會顯示,并且一些命令會提示 TERM 變量沒有設置)弃鸦。
從內部探索容器
下面的代碼展示了如何使用shell查看容器內運行的進程绞吁。
代碼清單2.6 從容器內列出進程
# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.7 565712 30100 ? Ssl 08:22 0:00 node app.js
root 14 0.1 0.0 18188 3344 pts/0 Ss 08:25 0:00 bash
root 22 0.0 0.0 36640 2764 pts/0 R+ 08:25 0:00 ps -aux
只看到了三個進程,宿主機上沒有看到其他進程唬格。
容器內的進程運行在主機操作系統(tǒng)上
如果現(xiàn)在打開另一個終端掀泳,然后列出主機操作系統(tǒng)上的進程,連同其他的主機進程依然會發(fā)現(xiàn)容器內的進程西轩,如代碼清單2.7所示员舵。
注意 如果使用的是Mac或者Windows系統(tǒng),需要登錄到Docker守護進程運行的VM查看這些進程藕畔。
代碼清單2.7 運行在主機操作系統(tǒng)上的容器進程
$ ps aux | grep app.js
USER PID CPU MEM vsz RSS TTY STAT START TIME COMMAND
root 382 0.0 0.1 676380 16504 ? S1 12:31 0:00 node app.js
這證明了運行在容器中的進程是運行在主機操作系統(tǒng)上的马僻。如果你足夠敏銳,會發(fā)現(xiàn)進程的ID在容器中與主機上不同注服。容器使用獨立的PID Linux命名空間并且有著獨立的系列號韭邓,完全獨立于進程樹。
容器的文件系統(tǒng)也是獨立的
正如擁有獨立的進程樹一樣溶弟,每個容器也擁有獨立的文件系統(tǒng)女淑。在容器內列出根目錄的內容,只會展示容器內的文件辜御,包括鏡像內的所有文件鸭你,再加上容器運行時創(chuàng)建的任何文件(類似日志文件),如下面的代碼清單所示擒权。
代碼清單2.8 容器擁有完整的文件系統(tǒng)
root@44d76963e8el:/# 1s
app.js boot etc lib mediabin dev home 1ib64 mnt
opt root sbin sys usrpros run srv tmp var
其中包含app.js文件和其他系統(tǒng)目錄袱巨,這些目錄是正在使用的 node:7 基礎鏡像的一部分√汲可以使用 exit 命令來退出容器返回宿主機(類似于登出ssh session)愉老。
提示 進入容器對于調試容器內運行的應用來說是非常有用的。出錯時剖效,需要做的第一件事是查看應用運行的系統(tǒng)的真實狀態(tài)嫉入。需要記住的是焰盗,應用不僅擁有獨立的文件系統(tǒng),還有進程咒林、用戶姨谷、主機名和網(wǎng)絡接口。
2.1.7 停止和刪除容器
可以通過告知Docker停止 kubia-container 容器來停止應用:
$ docker stop kubia-container
因為沒有其他的進程在容器內運行映九,這會停止容器內運行的主進程梦湘。容器本身仍然存在并且可以通過 docker ps-a 來查看。-a 選項打印出所有的容器件甥,包括運行中的和已經(jīng)停止的捌议。想要真正地刪除一個容器,需要運行 docker rm :
$ docker rm kubia-container
這會刪除容器引有,所有的內容會被刪除并且無法再次啟動瓣颅。
2.1.8 向鏡像倉庫推送鏡像
現(xiàn)在構建的鏡像只可以在本機使用。為了在任何機器上都可以使用譬正,可以把鏡像推送到一個外部的鏡像倉庫宫补。為了簡單起見,不需要搭建一個私有的鏡像倉庫曾我,而是可以推送鏡像到公開可用的Docker Hub(http://hub.docker.com)鏡像中心粉怕。另外還有其他廣泛使用的鏡像中心,如阿里云鏡像服務
登陸阿里云個人版的鏡像服務
sudo docker login --username={你的郵箱} registry.cn-hangzhou.aliyuncs.com
使用附加標簽標注鏡像
一旦知道了自己的ID抒巢,就可以重命名鏡像贫贝,現(xiàn)在鏡像由 kubia 改為 registry.cn-hangzhou.aliyuncs.com/bernard/kubia(其中bernard是我自己定義的命名空間):
$ docker tag kubia registry.cn-hangzhou.aliyuncs.com/bernard/kubia
這不會重命名標簽,而是給同一個鏡像創(chuàng)建一個額外的標簽蛉谜≈赏恚可以通過docker images 命令列出本機存儲的鏡像來加以確認,如下面的代碼清單所示型诚。
代碼清單2.9 一個容器鏡像可以有多個標簽
# docker images | head
REPOSITORY TAG IMAGE ID CREATED SIZE
kubia v2 8af4d2d1750a 2 minutes ago 82.7MB
正如所看到的客燕,kubia 和 改為 registry.cn-hangzhou.aliyuncs.com/bernard/kubia 指向同一個鏡像ID,所以實際上是同一個鏡像的兩個標簽狰贯。
向阿里云鏡像服務推送鏡像
$ docker push registry.cn-hangzhou.aliyuncs.com/bernard/kubia
在不同機器上運行鏡像
在推送完成之后也搓,鏡像便可以給任何人使用∧合郑可以在任何機器上運行下面的命令來運行鏡像:
$ docker run -p 888:888 registry.cn-hangzhou.aliyuncs.com/bernard/kubia
這非常簡單还绘。最棒的是應用每次都運行在完全一致的環(huán)境中。如果在你的機器上正常運行栖袋,也會在所有的Linux機器上正常運行。無須擔心主機是否安裝了Node.js抚太。事實上塘幅,就算安裝了昔案,應用也并不會使用,因為它使用的是鏡像內部安裝的电媳。