Docker 組件之間的關(guān)系

參考文獻(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);

image

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
image

上面的輸出信息指出,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)系:

image

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`

image

然后重啟 docker.service:

systemctl daemon-reload
systemctl restart docker.service
image

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
image

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

image

上圖中黃線(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é)作方式。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容