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