docker,containerd,runc,docker-shim相關概念

從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主要職責是鏡像管理(鏡像、元信息等)、容器執行(調用最終運行時組件執行)。

containerd向上爲Docker Daemon提供了gRPC接口,使得Docker Daemon屏蔽下面的結構變化,確保原有接口向下兼容。向下通過containerd-shim結合runC,使得引擎可以獨立升級,避免之前Docker Daemon升級會導致所有容器不可用的問題。

Docker、containerd和containerd-shim之間的關係,可以通過啓動一個Docker容器,觀察進程之間的關聯。首先啓動一個容器,

docker run -d busybox sleep 1000  // -d 表示在後臺執行

然後通過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組件)會創建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

其中包括了容器配置和標準輸入、標準輸出、標準錯誤三個管道文件。
docker-shim
docker-shim是一個真實運行的容器的真實墊片載體,每啓動一個容器都會起一個新的docker-shim的一個進程,
他直接通過指定的三個參數:容器id,boundle目錄(containerd的對應某個容器生成的目錄,一般位於:/var/run/docker/libcontainerd/containerID),
運行是二進制(默認爲runc)來調用runc的api創建一個容器(比如創建容器:最後拼裝的命令如下:runc create 。。。。。)

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每個模塊的作用。

首先,需要創建容器標準包,這部分實際上由containerd的bundle模塊實現,將Docker鏡像轉換成容器標準包。

mkdir my_container
cd my_container
mkdir rootfs
docker 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
        └── www
17 directories

此時,標準包所需的容器數據已經準備完畢,接下來我們需要創建配置文件:

docker-runc spec

此時會生成一個名爲config.json的配置文件,該文件和Docker容器的配置文件類似,主要包含容器掛載信息、平臺信息、進程信息等容器啓動依賴的所有數據。

最後,可以通過runc命令來啓動容器:

runc run busybox

注意,runc必須使用root權限啓動。

執行之後,我們可以看見容器已經啓動:

localhost my_container # runc run busybox
/ # ps aux
PID   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 aux
PID   USER     TIME   COMMAND
    1 root       0:00 sh
    7 root       0:00 ps aux

從這裏可以看到標準化的重要性。

總結

從Docker 1.11之後,Docker Daemon被分成了多個模塊以適應OCI標準。拆分之後,結構分成了以下幾個部分。

其中,containerd獨立負責容器運行時和生命週期(如創建、啓動、停止、中止、信號處理、刪除等),其他一些如鏡像構建、卷管理、日誌等由Docker Daemon的其他模塊處理。

在這裏插入圖片描述
Docker的模塊塊擁抱了開放標準,希望通過OCI的標準化,容器技術能夠有很快的發展

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章