從Docker 1.11開始,Docker容器運行已經(jīng)不是簡單的通過Docker daemon來啟動,而是集成了containerd、runC等多個組件。Docker服務(wù)啟動之后,我們也可以看見系統(tǒng)上啟動了dockerd、docker-containerd等進(jìn)程,本文主要介紹新版Docker(1.11以后)每個部分的功能和作用。
Docker Daemon
作為Docker容器管理的守護(hù)進(jìn)程,Docker Daemon從最初集成在docker命令中(1.11版本前),到后來的獨立成單獨二進(jìn)制程序(1.11版本開始),其功能正在逐漸拆分細(xì)化,被分配到各個單獨的模塊中去。從Docker服務(wù)的啟動腳本,也能看見守護(hù)進(jìn)程的逐漸剝離:
在Docker 1.8之前,Docker守護(hù)進(jìn)程啟動的命令為:
docker -d這個階段,守護(hù)進(jìn)程看上去只是Docker client的一個選項。
Docker 1.8開始,啟動命令變成了:
docker daemon這個階段,守護(hù)進(jìn)程看上去是docker命令的一個模塊。
Docker 1.11開始,守護(hù)進(jìn)程啟動命令變成了:
dockerd此時已經(jīng)和Docker client分離,獨立成一個二進(jìn)制程序了。
當(dāng)然,守護(hù)進(jìn)程模塊不停的在重構(gòu),其基本功能和定位沒有變化。和一般的CS架構(gòu)系統(tǒng)一樣,守護(hù)進(jìn)程負(fù)責(zé)和Docker client交互,并管理Docker鏡像、容器。
下面就來介紹下獨立分拆出來的其他幾個模塊。
Containerd
containerd是容器技術(shù)標(biāo)準(zhǔn)化之后的產(chǎn)物,為了能夠兼容OCI標(biāo)準(zhǔn),將容器運行時及其管理功能從Docker Daemon剝離。理論上,即使不運行dockerd,也能夠直接通過containerd來管理容器。(當(dāng)然,containerd本身也只是一個守護(hù)進(jìn)程,容器的實際運行時由后面介紹的runC控制。)
最近,Docker剛剛宣布開源containerd。從其項目介紹頁面可以看出,containerd主要職責(zé)是鏡像管理(鏡像、元信息等)、容器執(zhí)行(調(diào)用最終運行時組件執(zhí)行)。
containerd向上為Docker Daemon提供了gRPC接口,使得Docker Daemon屏蔽下面的結(jié)構(gòu)變化,確保原有接口向下兼容。向下通過containerd-shim結(jié)合runC,使得引擎可以獨立升級,避免之前Docker Daemon升級會導(dǎo)致所有容器不可用的問題。
Docker、containerd和containerd-shim之間的關(guān)系,可以通過啟動一個Docker容器,觀察進(jìn)程之間的關(guān)聯(lián)。首先啟動一個容器,
docker run -d busybox sleep 1000然后通過pstree命令查看進(jìn)程之間的父子關(guān)系(其中20708是dockerd的PID):
pstree -l -a -A 20708輸出結(jié)果如下:
dockerd -H fd:// --storage-driver=overlay2 |-docker-containe -l unix:///var/run/docker/libcontainerd/docker-containerd.sock --metrics-interval=0 --start-timeout 2m --state-dir /var/run/docker/libcontainerd/containerd --shim docker-containerd-shim --runtime docker-runc | |-docker-containe b9a04a582b66206492d29444b5b7bc6ec9cf1eb83eff580fe43a039ad556e223 /var/run/docker/libcontainerd/b9a04a582b66206492d29444b5b7bc6ec9cf1eb83eff580fe43a039ad556e223 docker-runc | | |-sleep 1000雖然pstree命令截斷了命令,但我們還是能夠看出,當(dāng)Docker daemon啟動之后,dockerd和docker-containerd進(jìn)程一直存在。當(dāng)啟動容器之后,docker-containerd進(jìn)程(也是這里介紹的containerd組件)會創(chuàng)建docker-containerd-shim進(jìn)程,其中的參數(shù)b9a04a582b66206492d29444b5b7bc6ec9cf1eb83eff580fe43a039ad556e223就是要啟動容器的id。最后docker-containerd-shim子進(jìn)程,已經(jīng)是實際在容器中運行的進(jìn)程(既sleep 1000)。
docker-containerd-shim另一個參數(shù),是一個和容器相關(guān)的目錄/var/run/docker/libcontainerd/b9a04a582b66206492d29444b5b7bc6ec9cf1eb83eff580fe43a039ad556e223,里面的內(nèi)容有:
.├── config.json├── init-stderr├── init-stdin└── init-stdout其中包括了容器配置和標(biāo)準(zhǔn)輸入、標(biāo)準(zhǔn)輸出、標(biāo)準(zhǔn)錯誤三個管道文件。
RunC
OCI定義了容器運行時標(biāo)準(zhǔn),runC是Docker按照開放容器格式標(biāo)準(zhǔn)(OCF, Open Container Format)制定的一種具體實現(xiàn)。
runC是從Docker的libcontainer中遷移而來的,實現(xiàn)了容器啟停、資源隔離等功能。Docker默認(rèn)提供了docker-runc實現(xiàn),事實上,通過containerd的封裝,可以在Docker Daemon啟動的時候指定runc的實現(xiàn)。
我們可以通過啟動Docker Daemon時增加--add-runtime參數(shù)來選擇其他的runC現(xiàn)。例如:
docker daemon --add-runtime "custom=/usr/local/bin/my-runc-replacement"下面就讓我們看下這幾個模塊如何工作。
舉個例子
這里通過Docker一些命令,實現(xiàn)不使用Docker Daemon直接啟動一個鏡像,以便了解Docker Daemon每個模塊的作用。
首先,需要創(chuàng)建容器標(biāo)準(zhǔn)包,這部分實際上由containerd的bundle模塊實現(xiàn),將Docker鏡像轉(zhuǎn)換成容器標(biāo)準(zhǔn)包。
mkdir my_containercd my_containermkdir rootfsdocker export $(docker create busybox) | tar -C rootfs -xvf -上述命令將busybox鏡像解壓縮到指定的rootfs目錄中。如果本地不存在busybox鏡像,containerd還會通過distribution模塊去遠(yuǎn)程倉庫拉取。
現(xiàn)在整個my_container目錄結(jié)構(gòu)如下:
$ tree -d my_container/my_container/└── rootfs ├── bin ├── dev │ ├── pts │ └── shm ├── etc ├── home ├── proc ├── root ├── sys ├── tmp ├── usr │ └── sbin └── var ├── spool │ └── mail └── www17 directories此時,標(biāo)準(zhǔn)包所需的容器數(shù)據(jù)已經(jīng)準(zhǔn)備完畢,接下來我們需要創(chuàng)建配置文件:
docker-runc spec此時會生成一個名為config.json的配置文件,該文件和Docker容器的配置文件類似,主要包含容器掛載信息、平臺信息、進(jìn)程信息等容器啟動依賴的所有數(shù)據(jù)。
最后,可以通過runc命令來啟動容器:
runc run busybox注意,runc必須使用root權(quán)限啟動。
執(zhí)行之后,我們可以看見容器已經(jīng)啟動:
localhost my_container # runc run busybox/ # ps auxPID USER TIME COMMAND 1 root 0:00 sh 9 root 0:00 ps aux此時,事實上已經(jīng)可以不依賴Docker本身,如果系統(tǒng)上安裝了runc包,即可運行容器。對于Gentoo系統(tǒng)來說,安裝app-emulation/runc包即可。
當(dāng)然,也可以使用docker-runc命令來啟動容器:
localhost my_container # docker-runc run busybox/ # ps auxPID USER TIME COMMAND 1 root 0:00 sh 7 root 0:00 ps aux從這里可以看到標(biāo)準(zhǔn)化的重要性。
總結(jié)
從Docker 1.11之后,Docker Daemon被分成了多個模塊以適應(yīng)OCI標(biāo)準(zhǔn)。拆分之后,結(jié)構(gòu)分成了以下幾個部分。
其中,containerd獨立負(fù)責(zé)容器運行時和生命周期(如創(chuàng)建、啟動、停止、中止、信號處理、刪除等),其他一些如鏡像構(gòu)建、卷管理、日志等由Docker Daemon的其他模塊處理。
Docker的模塊塊擁抱了開放標(biāo)準(zhǔn),希望通過OCI的標(biāo)準(zhǔn)化,容器技術(shù)能夠有很快的發(fā)展。