Container Runtime (二) :低級容器運行時

引言

這是有關Container Runtime(容器運行時)系列文章的第二篇,在上篇文章中,我概述了Container Runtime,並討論了低級和高級運行時之間的區別,在這篇文章中,我將詳細介紹低級(low-level)Container Runtime。linux

低級運行時具備有限的功能特性集合,一般會執行用於運行容器的低級別任務,絕大多數開發人員不該將它們用於平常工做。低級運行時一般被實現爲簡單的工具或庫,高級運行時和工具的開發人員能夠將其用於低級功能。儘管大多數開發人員不會直接使用低級運行時,但最好了解它們的工做原理便於故障排除和調試。

正如在上篇文章中所說的,容器是基於Linux namespaces(命名空間)和cgroups實現的,命名空間可使你虛擬化系統資源,例如每一個容器的文件系統和網絡資源;另外一方面,cgroup能夠提供了一種方法限制每一個容器的資源使用量,例如CPU或者內存。低級容器運行時的核心功能是負責爲容器建立這些namespaces和cgroups,而後在這些命名空間和cgroups中運行系統命令,大多數容器運行時實現了更多的特性,可是這些功能是必不可少的。必定要去看下Liz Rice的演講《Building a container from scratch in Go》,她的演講中很好地展現了低級容器運行時是如何實現的,Liz完成了許多像這樣的步驟,可是你能夠想象即便是那些勉強能夠稱得上"container runtime"的容器運行時也能夠作如下的事:git

建立cgroup
在cgroup裏面運行命令
利用Unshare將其移動到本身的命名空間
命令完成後清理cgroup(正在運行的進程未引用命名空間時,它們會自動刪除)

可是,一個健壯的低級容器運行時會作更多的事情,例如容許在cgroup上設置資源限制,設置根文件系統以及將容器的進程chroot到根文件系統。github

建立一個Runtime的範例

讓咱們逐步運行一個簡單的臨時容器運行時用於建立一個容器。咱們能夠執行如下標準的Linux命令執行這些步驟:docker

- cgcreate
- cgset
- cgexec
- chroot
- unshare

上面大多數命令須要你以root身份執行。首先爲容器建立根文件系統,以busybox Docker容器爲基礎,這裏建立一個臨時目錄並將busybox提取到這個目錄,執行這些命令須要以root用戶運行。json

$ CID=$(docker create busybox)
$ ROOTFS=$(mktemp -d)
$ docker export $CID | tar -xf - -C $ROOTFS

如今,建立cgroup並設置對內存和CPU的限制,內存限制以字節爲單位設置,設置爲100MB。bash

$ UUID=$(uuidgen)
$ cgcreate -g cpu,memory:$UUID
$ cgset -r memory.limit_in_bytes=100000000 $UUID
$ cgset -r cpu.shares=512 $UUID
能夠經過兩種方法的一種限制CPU使用率,這裏使用CPU共享(shares)來限制CPU使用率:在同一時間與其餘進程按比例劃分CPU時間,獨立運行的容器能夠享有全部的CPU時間,可是若是有其餘的容器也在運行,就能使用CPU shares按比例劃分CPU時間。基於CPU核數的CPU限制要複雜一些,它們能讓你對容器的CPU核數設置硬性的限制。想要限制CPU核數須要兩個步驟:cfs_period_us和cfs_quota_us。cfs_period_us用來指定cpu分配的週期,默認爲100000;cfs_quota_us指定單個任務在一個CPU週期內佔用的時間,默認爲-1,表示不限制。若是設爲50000,表示佔用50000/10000=50%的CPU;二者都以微秒爲單位指定。

在本例中,若是想將容器的CPU內核限制爲2個,則能夠指定設置cfs_period_us爲100000(100000ms),cfs_quota_us設置爲200000,這樣設置的話進程就能在一秒內有效使用2個cpu時間,本文將深刻剖析這個概念。服務器

$ cgset -r cpu.cfs_period_us = 1000000  $ UUID 
$ cgset -r cpu.cfs_quota_us = 2000000  $ UUID

接着,在容器中執行如下命令,這條命令會在前面建立的cgroup裏面執行,unshare指定的命名空間,設置hostname,同時將chroot設置爲容器的根文件系統。網絡

$ cgexec -g cpu,memory:$UUID \
>     unshare -uinpUrf --mount-proc \
>     sh -c "/bin/hostname $UUID && chroot $ROOTFS /bin/sh"
/ # echo "Hello from in a container"
Hello from in a container
/ # exit

最後,在上述命令執行完畢後,刪除以前建立的cgroup和臨時目錄完成清理。app

$ cgdelete -r -g cpu,memory:$UUID
$ rm -r $ROOTFS

爲了進一步論證其工做原理,我在bash裏面寫了一個簡單的runtime叫作execc,它支持mount、user、pid、ipc、uts和network命名空間,同時設置了內存限制和CPU核數限制,掛載了proc文件系統同時讓容器運行在本身的根文件系統之上。tcp

一些低級容器運行時的例子

爲了更好地理解低級容器運行時,最好來看一些示例,這些不一樣的運行時實現了不一樣的特性並強調了容器化的不一樣方面。

imctfy

儘管沒有獲得普遍使用,Imctfy也是記錄在冊一個容器運行時。它是google一個內部項目,brog系統內部使用的容器運行時正是Imctfy,它最有趣的一個特性是支持經過容器名稱使用cgroup層級的容器層次結構。例如,一個叫busybox的父容器能夠在"busybox/sub1"或者"busybox/sub2"下面建立子容器,這些名稱之間構成一種路徑結構。這樣作的結果就是每一個子容器都有本身的cgroups但同時也受到父容器cgroup的限制。這是受Borg啓發的,它使lmctfy中的容器可以在服務器上預先分配的一組資源下運行子任務容器,從而實現了比運行時自己更嚴格的SLOs。

儘管lmctfy提供了一些有趣的特性,但其餘運行時的特性更優異,所以Google決定讓社區將精力集中在Docker的libcontainer而不是lmctfy上。

runc

runc是目前使用最普遍的容器運行時,最初是做爲Docker的一部分,後來被剝離爲單獨的工具和庫。在內部,runc運行容器的方式與我上面描述的方式相似,可是runc實現了OCI運行時規範。 這意味着它以特定的「 OCI bundle」規範運行容器,bundle軟件有用於配置的config.json文件和用於容器的根文件系統,能夠經過閱讀GitHub上的OCI運行時規範瞭解更多信息,也能夠從runc GitHub項目中學習如何安裝runc。
首先建立根文件系統,這裏再次使用busybox。

$ mkdir rootfs
$ docker export $(docker create busybox) | tar -xf - -C rootfs

接着建立一個config.json文件。

$ runc spec

這個命令會爲容器建立一個config.json文件的模版,看起來是這個樣子:

{
        "ociVersion": "1.0.0",
        "process": {
                "terminal": true,
                "user": {
                        "uid": 0,
                        "gid": 0
                },
                "args": [
                        "sh"
                ],
                "env": [
                        "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                        "TERM=xterm"
                ],
                "cwd": "/",
                "capabilities": {
...

默認狀況下,它會在有着根文件系統的容器中運行sh命令,默認位置是./rootfs。 由於這正是咱們想要的設置,因此繼續運行容器。

默認狀況下,它會在有着根文件系統的容器中運行sh命令,默認位置是./rootfs。 由於這正是咱們想要的設置,因此繼續運行容器。

rkt

rkt是CoreOS主持開發,用於替代Docker/runc的流行方案。 rkt有點難以分類,由於它提供了其餘低級運行時(如runc)提供的全部功能,並且還提供了高級運行時的典型功能。 在這裏,我將描述rkt的低級功能,將高級功能留給下一篇文章。

rkt最初使用的是應用程序容器(appc)標準,該標準是做爲Docker容器格式的開放替代標準而開發的。 Appc從未以容器格式得到普遍採用,而且再也不積極開發appc來實現其目標,以確保社區可使用開放標準。在將來, rkt使用OCI容器格式來替代appc。

應用程序容器鏡像(ACI)是Appc的鏡像格式, 格式是tar.gz,其中包含一個manifest文件目錄和根文件系統的rootfs目錄,能夠在此處閱讀有關ACI的更多信息。
你也可使用acbuild工具構建容器鏡像,在可運行的Docker腳本中使用acbuild,就像運行Dockerfiles同樣。

acbuild begin
acbuild set-name example.com/hello
acbuild dep add quay.io/coreos/alpine-sh
acbuild copy hello /bin/hello
acbuild set-exec /bin/hello
acbuild port add www tcp 5000
acbuild label add version 0.0.1
acbuild label add arch amd64
acbuild label add os linux
acbuild annotation add authors "Carly Container <carly@example.com>"
acbuild write hello-0.0.1-linux-amd64.aci
acbuild end
相關文章
相關標籤/搜索