參考文獻(xiàn):
https://www.cnblogs.com/sparkdev/p/9129334.html
https://segmentfault.com/a/1190000009309297
https://www.codercto.com/a/47652.html
1、Docker的主要組件 有哪些
docker cli , 就是 docker命令
dockerd, 俗稱(chēng),docker引擎
docker-init
docker-proxy
docker-containerd, 就是containerd
docker-containerd-shim, 就是containerd-shim
docker-containerd-ctr, 就是ctr
docker-runc, 就是 runc
像docker cli, dockerd, docker-init, docker-proxy 應(yīng)該是docker 公司 專(zhuān)屬的,并非標(biāo)準(zhǔn);
2、Docker CLI (docker)
docker 程序是一個(gè)客戶(hù)端工具,用來(lái)把用戶(hù)的請(qǐng)求發(fā)送給 docker daemon(dockerd)。該程序的安裝路徑為:
/usr/bin/docker
3、Dockerd
docker daemon(dockerd),一般也會(huì)被稱(chēng)為 docker engine。該程序的安裝路徑為:
/usr/bin/dockerd
4、Containerd
詳情請(qǐng)參考《Containerd到底是干啥的?》(http://www.itdecent.cn/p/5ca02db248ee)。該程序的安裝路徑為:
/usr/bin/docker-containerd
5、Containerd-shim
它是 containerd 的組件,是容器的運(yùn)行時(shí)載體,
我們?cè)?docker 宿主機(jī)上看到的 shim 也正是代表著一個(gè)個(gè)通過(guò)調(diào)用 containerd 啟動(dòng)的 docker 容器。
該程序的安裝路徑為:
/usr/bin/docker-containerd-shim
6、RunC
詳情請(qǐng)參考《RunC到底是干啥的》(http://www.itdecent.cn/p/2f6296190049)。
該程序的安裝路徑為:
/usr/bin/docker-runc
7、從hello world 開(kāi)始
Docker 很貼心的為我們提供了 hello-world 鏡像來(lái)驗(yàn)證安裝是否成功,但是透過(guò)這個(gè)鏡像我們還能看到更多的信息:
docker run hello-world
上面的輸出信息指出,hello-world 容器的運(yùn)行經(jīng)歷了如下四步:
1.Docker 客戶(hù)端向
docker daemon發(fā)送請(qǐng)求2.Docker daemon 從 Docker Hub 上拉取鏡像
3.Docker daemon 使用
鏡像運(yùn)行了一個(gè)容器并產(chǎn)生了輸出4.Docker daemon 把輸出的內(nèi)容發(fā)送給了 docker 客戶(hù)端
這是一個(gè)很抽象也很容器理解的過(guò)程,但是我們還想知道更多:
docker daemon 是如何創(chuàng)建并運(yùn)行容器的?
其實(shí)容器部分的操作和管理都被 dockerd 外包給 containerd 了,
下圖描述了運(yùn)行一個(gè)容器時(shí)各個(gè)組件之間的關(guān)系:
8、Docker Engine API
https://github.com/moby/moby/tree/master/api
從本質(zhì)上說(shuō),docker 是一個(gè)客戶(hù)端/服務(wù)器架構(gòu)的應(yīng)用。
Dockerd 以 Engine API (REST)的方式對(duì)外提供服務(wù),Engine API 里描述了 dockerd 支持的所有請(qǐng)求。
Docker 客戶(hù)端與 dockerd 之間就是通過(guò) REST 的方式通信的。
在 centos7中,dockerd 默認(rèn)是不監(jiān)聽(tīng) tcp 端口的,為了方便演示,我們讓 dockerd 監(jiān)聽(tīng) tcp 端口。
這樣就可以使用 curl 代替 docker 客戶(hù)端向 dockerd 發(fā)送請(qǐng)求了。
具體的操作為,先修改 /lib/systemd/system/docker.service 文件,
注釋掉默認(rèn)的 ExecStart 并添加新的 ExecStart 配置:
# ExecStart=/usr/bin/dockerd -H fd://
ExecStart=/usr/bin/dockerd -H tcp://0.0.0.0:2375 -H unix:///var/run/docker.sock`
然后重啟 docker.service:
systemctl daemon-reload
systemctl restart docker.service
9、Docker 與 Dockerd的交互
Docker 客戶(hù)端與 dockerd 之間就是通過(guò) REST的方式通信的。
前面我們已經(jīng)讓 dockerd 監(jiān)聽(tīng) tcp 端口了,所以我們可以使用 curl 來(lái)代替 docker 客戶(hù)端。
這里我們簡(jiǎn)單的演示如何請(qǐng)求 dockerd 從 docker hub 上下載 hello-world 鏡像:
curl '127.0.0.1:2375/v1.37/images/create?fromImage=hello-world&tag=latest' -X POST
10、創(chuàng)建容器
容器
鏡像的下載是由 dockerd 完成的,但容器的創(chuàng)建和運(yùn)行就需要 containerd(docker-containerd) 來(lái)完成了。 //容器鏡像的下載,難道不是由containerd完成的么?Dockerd 與 docker-containerd 之間是通過(guò)
grpc協(xié)議通信的。
當(dāng) docker-containerd 收到 dockerd 啟動(dòng)容器的請(qǐng)求之后,會(huì)做一些初始化工作,然后啟動(dòng)docker-containerd-shim進(jìn)程,并將相關(guān)配置作為參數(shù)傳給它。docker-containerd 負(fù)責(zé)管理
所有本機(jī)正在運(yùn)行的容器,而一個(gè) docker-containerd-shim 進(jìn)程只負(fù)責(zé)管理一個(gè)運(yùn)行的容器,它相當(dāng)于 docker-runc 的一個(gè)封裝,充當(dāng) docker-containerd 和 docker-runc 之間的橋梁,docker-runc 能干的就交給 docker-runc 來(lái)做,docker-runc 做不了的就放到這里來(lái)做。
下面我們用 ubuntu 鏡像運(yùn)行一個(gè)容器:
docker run -id busybox bash
上圖中黃線(xiàn)框起來(lái)的是幾個(gè)主要的進(jìn)程,它們之間是有父子關(guān)系的(systemd 沒(méi)有出現(xiàn)在上圖):
systemd---dockerd---docker-containerd---docker-containerd-shim---bash
上圖中沒(méi)有出現(xiàn) docker-runc 進(jìn)程,這是為什么呢?
實(shí)際上,在容器啟動(dòng)的過(guò)程中,docker-runc 進(jìn)程是作為 docker-containerd-shim 的子進(jìn)程存在的。
docker-runc 進(jìn)程根據(jù)配置找到容器的 rootfs 并創(chuàng)建子進(jìn)程 bash 作為容器中的第一個(gè)進(jìn)程。
當(dāng)這一切都完成后 docker-runc 進(jìn)程退出,然后容器進(jìn)程 bash 由 docker-runc 的父進(jìn)程 docker-containerd-shim 接管。
11、為啥需要docker-containerd-shim?
也許大家會(huì)問(wèn),為什么在容器的啟動(dòng)或運(yùn)行過(guò)程中需要一個(gè) docker-containerd-shim 進(jìn)程呢?把它移除掉整個(gè)架構(gòu)會(huì)更簡(jiǎn)潔也更優(yōu)美一些!事實(shí)上 docker-containerd-shim 的存在是非常有必要的,其目的有如下幾點(diǎn):
- 它允許
容器運(yùn)行時(shí)(即 runC)在啟動(dòng)容器之后退出,簡(jiǎn)單說(shuō)就是不必為每個(gè)容器一直運(yùn)行一個(gè)容器運(yùn)行時(shí)(runC); - 即使在 containerd 和 dockerd 都
掛掉的情況下,容器的標(biāo)準(zhǔn) IO 和其它的文件描述符也都是可用的 - 向 containerd 報(bào)告`容器的退出狀態(tài)``
前兩點(diǎn)尤其重要,有了它們就可以在不中斷容器運(yùn)行的情況下升級(jí)或重啟 dockerd(這對(duì)于生產(chǎn)環(huán)境來(lái)說(shuō)意義重大)。
從這里可以看到對(duì) containerd-shim 的一些解釋。
或者按照下面的方式:
允許runc在
創(chuàng)建&運(yùn)行容器之后退出用shim作為容器的
父進(jìn)程,而不是直接用containerd作為容器的父進(jìn)程,是為了防止這種情況:當(dāng)containerd掛掉的時(shí)候,shim還在,因此可以保證容器打開(kāi)的文件描述符不會(huì)被關(guān)掉依靠shim來(lái)
收集&報(bào)告容器的退出狀態(tài),這樣就不需要containerd來(lái)wait子進(jìn)程
使用shim的主要作用?
就是將containerd和真實(shí)的容器(里的進(jìn)程)解耦,這是第二點(diǎn)和第三點(diǎn)所描述的。
而第一點(diǎn),為什么要允許runc退出呢?
因?yàn)?,Go編譯出來(lái)的二進(jìn)制文件,默認(rèn)是靜態(tài)鏈接,因此,如果一個(gè)機(jī)器上起N個(gè)容器,那么就會(huì)占用M*N的內(nèi)存,其中M是一個(gè)runc所消耗的內(nèi)存。
但是出于上面描述的原因又不想直接讓containerd來(lái)做容器的父進(jìn)程,因此,就需要一個(gè)比runc占內(nèi)存更小的東西來(lái)作父進(jìn)程,也就是shim。
但實(shí)際上, shim仍然比較占內(nèi)存( 參考這里 ),因此,比較好的方式是:
- 用C重寫(xiě)并且默認(rèn)使用動(dòng)態(tài)鏈接庫(kù)
- 打開(kāi) Go 的動(dòng)態(tài)鏈接支持然后重新編譯
12、docker-init
我們都知道UNIX系統(tǒng)中,1號(hào)進(jìn)程是init進(jìn)程,也是所有孤兒進(jìn)程的父進(jìn)程。
而使用docker時(shí),如果不加 --init 參數(shù),容器中的1號(hào)進(jìn)程 就是所給的ENTRYPOINT,例如下面例子中的 sh 。
而加上 --init 之后,1號(hào)進(jìn)程就會(huì)是 tini :
[email protected]:~$ docker run -it busybox sh
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 sh
6 root 0:00 ps aux
/ # exit
[email protected]:~$ docker run -it --init busybox sh
/ # ps aux
PID USER TIME COMMAND
1 root 0:00 /dev/init -- sh
6 root 0:00 sh
7 root 0:00 ps aux
/ # exit
13、docker-proxy
這個(gè)是用來(lái)做端口映射的, 從名稱(chēng)中就可以看出來(lái),下面驗(yàn)證一下:
[email protected]:~$ docker run -d -p 10010:10010 busybox sleep 10000
be88279118ad7f8cfd3d418db00872aa4f3b1753278b67c28727f16d68f37ae5
[email protected]:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
be88279118ad busybox "sleep 10000" 2 seconds ago Up 1 second 0.0.0.0:10010->10010/tcp awesome_jackson
[email protected]:~$ ps aux | grep docker
root 897 0.1 3.8 736592 78444 ? Ssl 06:20 0:33 /usr/bin/dockerd -H fd://
root 1188 0.0 1.8 665876 37964 ? Ssl 06:20 0:25 docker-containerd --config /var/run/docker/containerd/containerd.toml
root 5579 0.0 0.1 378868 3076 ? Sl 14:57 0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 10010 -container-ip 172.17.0.2 -container-port 10010
root 5585 0.0 0.1 7376 3808 ? Sl 14:57 0:00 docker-containerd-shim -namespace moby -workdir /var/lib/docker/containerd/daemon/io.containerd.runtime.v1.linux/moby/be88279118ad7f8cfd3d418db00872aa4f3b1753278b67c28727f16d68f37ae5 -address /var/run/docker/containerd/docker-containerd.sock -containerd-binary /usr/bin/docker-containerd -runtime-root /var/run/docker/runtime-runc
jiajun 5666 0.0 0.0 13136 1076 pts/0 S+ 14:57 0:00 grep --color=auto docker
可以看到這么一行
/usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 10010 -container-ip 172.17.0.2 -container-port 10010
其底層是使用iptables來(lái)完成的,參考:
https://windsock.io/the-docker-proxy/。
12、總結(jié)
本文則通過(guò) demo 演示了在創(chuàng)建、運(yùn)行容器的過(guò)程中這些組件如何配合 docker engine 完成相關(guān)的任務(wù),以及相關(guān)進(jìn)程之間的關(guān)系和作用。
希望本文可以幫助大家理解 docker 的整體架構(gòu)及其組件間的協(xié)作方式。