RunC 簡介
RunC 是什么?
RunC 是一個輕量級的工具,它是用來運行容器的,只用來做這一件事,并且這一件事要做好。我們可以認為它就是個命令行小工具,可以不用通過 docker 引擎,直接運行容器。事實上,runC 是標準化的產物,它根據 OCI 標準來創建和運行容器。而 OCI(Open Container Initiative)組織,旨在圍繞容器格式和運行時制定一個開放的工業化標準。
安裝 runC
RunC 是用 golang 創建的項目,因此編譯它之前需要在本地安裝 golang 的開發環境。Golang 的安裝請參考《Golang 入門 : 打造開發環境》一文,這里不再贅述。
安裝 libseccomp-dev
RunC 默認的編譯配置是支持 seccomp 的,所以我們需要先安裝 libseccomp-dev:
$ sudo apt install libseccomp-dev
seccomp 的全稱為 secure computing mode,即安全計算模型,這是 Linux 內核提供的功能。我們可以通過它來限制容器中進程的行為。關于 seccomp 的更多內容,請參考 Seccomp security profiles for Docker。
獲取 runC 的代碼
先創建 $GOPATH/src/github.com 目錄:
$ mkdir -p $HOME/go/src/github.com
通過 go get 命令就可以從 github 上下載到 runC 的代碼,但是要保證事先安裝了 git:
$ go get github.com/opencontainers/runc
然后進入 $HOME/go/src/github.com/opencontainers/runc 目錄,并 checkout 最新的穩定狀態的代碼 tag v1.0.0-rc5:
$ cd $HOME/go/src/github.com/opencontainers/runc $ git checkout v1.0.0-rc5
查看代碼當前的狀態:
$ git status

v1.0.0-rc5 是當前最新的版本。
編譯并安裝
$ make $ sudo make install

如上圖所示,runC 被安裝在了 /usr/local/sbin/runc 目錄。
可以通過 -v 選項查看一下版本號:
$ runc -v

至此,runC 就算是安裝成功了。
準備 OCI bundle
RunC 是運行容器的運行時,它負責利用符合標準的文件等資源運行容器,但是它不包含 docker 那樣的鏡像管理功能。所以要用 runC 運行容器,我們先得準備好容器的文件系統。所謂的 OCI bundle 就是指容器的文件系統和一個 config.json 文件。有了容器的文件系統后我們可以通過 runc spec 命令來生成 config.json 文件。使用 docker 可輕松的生成容器的文件系統,因為 runC 本來就是 docker 貢獻給社區的嘛!
下面我們準備一個運行 busybox 容器所需的文件系統:
$ docker pull busybox $ mkdir -p /tmp/mycontainer/rootfs $ cd /tmp/mycontainer $ docker export $(docker create busybox) | tar -C rootfs -xvf -
現在 rootfs 目錄下就是 busybox 鏡像的文件系統,然后生成 config.json 文件:
$ runc spec

如果直接使用生成的 config.json,接下來的演示不會太流暢,所以簡單起見,我們稍微修改一下剛剛生成的 config.json 文件。就是把 "terminal": true 改為 false,把 "args": ["sh"] 改為 "args": ["sleep", "30"]:

理解容器狀態轉移
在運行 busybox 容器前讓我們先來看看 OCI 都定義了哪幾種容器狀態,以及這些狀態是如何轉移的。先看容器的狀態:
- creating:使用 create 命令創建容器,這個過程稱為創建中。
- created:容器已經創建出來,但是還沒有運行,表示鏡像文件和配置沒有錯誤,容器能夠在當前平臺上運行。
- running:容器里面的進程處于運行狀態,正在執行用戶設定的任務。
- stopped:容器運行完成,或者運行出錯,或者 stop 命令之后,容器處于暫停狀態。這個狀態,容器還有很多信息保存在平臺中,并沒有完全被刪除。
- paused:暫停容器中的所有進程,可以使用 resume 命令恢復這些進程的執行。
下圖則是對容器不同狀態間轉移的一個粗略描述:

RunC 命令
要想了解 runC 都能干什么,最好是通過它提供的命令來操作容器。下面是筆者整理的 runC 命令的主要使用場景。
查看幫助
$ runc -h
查看子命令的幫助
$ runc help subcommand
使用 create 命令創建容器
進入到 /tmp/mycontainer 目錄中:
$ cd /tmp/mycontainer
然后創建名為 mybusybox 的容器:
$ sudo runc create mybusybox
使用 list 命令查看當前存在的容器
$ sudo runc list

使用 state 命令查看容器的狀態
$ sudo runc state mybusybox

注意圖中的 "status": "created",當通過 create 成功創建了容器后,容器的狀態就是 "created"。
使用 ps 命令看看容器內運行的進程
$ sudo runc ps mybusybox

此時 mybusybox 容器內有一個名為 init 的進程在運行。
使用 start 命令執行容器中定義的任務
$ sudo runc start
使用 start 命令啟動容器后,讓我們再用 ps 命令看看容器內運行了什么進程:

此時我們在 config.json 中定義的 sleep 進程在運行。再用 state 命令看看容器此時的狀態,此時已經變成了 running!
使用 exec 命令在容器中執行命令
通過 exec 命令我們可以在處于 created 狀態和 running 狀態的容器中執行命令:
$ sudo runc exec mybusybox ls

當容器中的用戶任務結束后,容器會變成 stopped 狀態,這時就不能再通過 exec 執行其它的命令了。
使用 delete 命令刪除容器
我們可以通過 delete 命令刪除容器,當然,一般情況下是刪除 stopped 狀態的容器:
$ sudo runc delete mybusybox
使用 run 命令創建并運行容器
就像 docker run 命令一樣,它會創建容器并運行容器中的命令:
$ sudo runc run mybusybox
當容器中的命令退出后容器隨即被刪除。
使用 kill 命令停止容器中的任務
如果要停止一個容器中正在運行的任務,可以使用 kill 命令:
$ sudo runc kill mybusybox
默認它會優雅的結束容器中的進程,但是碰到特殊情況,你就得使用終極信號 9:
$ sudo runc kill mybusybox 9
使用 pause 命令暫停容器中的所有進程
我們先啟動容器 mybusybox,然后用 pause 命令暫停它:
$ sudo runc pause mybusybox

執行 pause 命令后,容器的狀態由 running 變成了 paused。然后我們再通過 resume 命令恢復容器中進程的執行:
$ sudo runc resume mybusybox

此時容器的狀態又恢復到了 running。
使用 events 命令獲取容器的資源使用情況
events 命令能夠向我們報告容器事件及其資源占用的統計信息:
$ sudo runc events mybusybox

rootless containers
前面我們運行的所有命令都是以 root 權限執行的。能不能以普通用戶的權限運行容器呢?答案是可以的,并被稱為 rootless。要想以 rootless 的方式運行容器,需要我們在生成容器的配置文件時就為 spec 命令指定 rootless 參數:
$ runc spec --rootless
并且在運行容器時通過 --root 參數指定一個存放容器狀態的路徑:
$ runc --root /tmp/runc run mybusybox
容器的熱遷移操作
RunC 支持容器的熱遷移操作,所謂熱遷移就是將一個容器進行 checkpoint 操作,并獲得一系列文件,使用這一系列文件可以在本機或者其他主機上進行容器的 restore 工作。這也是 checkpoint 和 restore 兩個命令存在的原因。熱遷移屬于比較復雜的操作,目前 runC 使用了 CRIU 作為熱遷移的工具。RunC 主要是調用 CRIU(Checkpoint and Restore in Userspace)來完成熱遷移操作。CIRU 負責凍結進程,并將作為一系列文件存儲在硬盤上。并負責使用這些文件還原這個被凍結的進程。
總結
RunC 作為標準化容器運行時的一個實現目前已經被 docker 內置為默認的容器運行時。相信隨著 runC 自身的成熟和完善會有越來越多的大廠把 runC 作為默認的容器運行時。

浙公網安備 33010602011771號