咱們都知道,容器技術其實在好久之前就已經出現,但只是在最近十年因爲雲計算的發展才逐漸進入大衆的視野。對於容器運行時,傳統意義上來講就是表明容器從拉取鏡像到啓動運行再到停止的整個生命週期,較相似於 Java 中的 Java hotspot 運行時。在本文中我會介紹容器運行時相關概念及組件原理,梳理下咱們常聽到的 OCI、runc、containerd 等名詞之間的關係。html
容器運行時顧名思義就是要掌控容器運行的整個生命週期,以 docker 爲例,其做爲一個總體的系統,主要提供的功能以下:linux
docker build
docker images
docker ps
docker run
docker pull/push
然而這些功能都可由小的組件單獨實現,且沒有相互依賴。然後 Docker 公司與 CoreOS 和 Google 共同建立了 OCI (Open Container Initial),並提供了兩種規範:git
filesystem bundle
filesystem bundle(文件系統束): 定義了一種將容器編碼爲文件系統束的格式,即以某種方式組織的一組文件,幷包含全部符合要求的運行時對其執行全部標準操做的必要數據和元數據,即config.json 與 根文件系統。github
然後,Docker、Google等開源了用於運行容器的工具和庫 runc,做爲 OCI 的一種實現參考。在此以後,各類運行時工具和庫也慢慢出現,例如 rkt、containerd、cri-o 等,然而這些工具所擁有的功能卻不盡相同,有的只有運行容器(runc、lxc),而有的除此以外也能夠對鏡像進行管理(containerd、cri-o)。目前較爲流行的說法是將容器運行時分紅了 low-level 和 high-level 兩類。docker
low-level: 指的是僅關注運行容器的容器運行時,調用操做系統,使用 namespace 和 cgroup 實現資源隔離和限制。high-level: 指包含了更多上層功能,例如 grpc調用,鏡像存儲管理等。json
不一樣工具的關係以下圖:安全
low-level runtime 關注如何與操做系統交互,建立並運行容器。目前常見的 low-level runtime有:架構
容器在 linux 中使用 namesapce 實現資源隔離,使用 cgroup 實現資源限制,這部分在k8s基礎--容器篇中對原理詳細介紹,此處不作贅述。這裏咱們詳細介紹下如何建立一個簡單的 runtime。函數
咱們以 busybox 鏡像做爲運行時的一個根文件系統,首先建立一個臨時目錄並將 busybox 中的全部文件解壓縮到目錄中工具
CID=$(docker create busybox)
ROOTFS=$(mktemp -d)
docker export $CID | tar -xf - -C $ROOTFS複製代碼
限制咱們須要建立 cgroup 對內存和cpu進行限制
UUID=$(uuidgen)
cgcreate -g cpu,memory:$UUID
# 內存限制設置爲 100MB
cgset -r memory.limit_in_bytes=100000000 $UUID
# cpu 限制設置爲 512m
cgset -r cpu.shares=512 $UUID複製代碼
上面 cpu.shares
是相對於同時運行的其餘進程的CPU。單獨運行的容器可使用整個CPU,可是若是其餘容器正在運行,它們會按照比例分配cpu資源。除此之外,還能夠對cpu內核數量的使用進行限制:
# 設置檢查CPU使用狀況的頻率,單位是微秒
cgset -r cpu.cfs_period_us=1000000 $UUID
# 設置任務在一個時間段內在一個核心上運行的時間量,單位是微秒
cgset -r cpu.cfs_quota_us=2000000 $UUID複製代碼
而後咱們使用 unshare
命令在 cgroug 中執行命令,它能夠實現 namespace 的隔離。
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
複製代碼
最後,在執行結束後,經過下面的指令清理環境
cgdelete -r -g cpu,memory:$UUID
rm -r $ROOTFS複製代碼
這一部分的實踐,主要參考自文章 https://www.ianlewis.org/en/container-runtimes-part-2-anatomy-low-level-contai
High-level runtimes相較於low-level runtimes位於堆棧的上層。low-level runtimes負責實際運行容器,而High-level runtimes負責傳輸和管理容器鏡像,解壓鏡像,並傳遞給low-level runtimes來運行容器。目前主流的 high-level runtime 有:
這裏咱們以 containerd 爲例具體解析整個架構以及工做原理。
containerd 的架構圖如圖
其中,grpc 模塊向上層提供服務接口,metrics 則提供監控數據(cgroup 相關數據),二者均向上層提供服務。containerd 包含一個守護進程,該進程經過本地 UNIX 套接字暴露 grpc 接口。
storage 部分負責鏡像的存儲、管理、拉取等metadata 管理容器及鏡像的元數據,經過bootio存儲在磁盤上task -- 管理容器的邏輯結構,與 low-level 交互event -- 對容器操做的事件,上層經過訂閱能夠知道發生了什麼事情Runtimes -- low-level runtime(對接 runc)
containerd 主要流程以下:(圖片來源於阿里雲的公開課)
圖中的 containerEngine 在 docker 中就是 docker-containerd 組件,建立容器記錄的metadata,並請求 containerd 的 task 模塊,task 模塊會在 runtime 中建立 task 實例,分別會加入 task list, 監控 cgroup 等操做,每一個 task 實例則調用 shim 去建立container。
containerd-shim 是 containerd 的一個組件,主要是用於剝離 containerd 守護進程與容器進程。containerd 經過 shim 調用 runc 的包函數來啓動容器。當咱們執行 pstree
命令時,能夠看到以下的進程關係:引入shim,容許runc 在建立和運行容器以後退出,並將 shim 做爲容器的父進程,而不是 containerd 做爲父進程,這樣作的目的是當 containerd 進程掛掉,因爲 shim 還正常運行,所以能夠保證容器不受影響。此外,shim 也能夠收集和報告容器的退出狀態,不須要 containerd 來 wait 容器進程。
當咱們有需求去替換 runc 運行時工具庫時,例如替換爲安全容器 kata container 或 Google 研發的 gViser,則須要增長對應的shim(kata-shim等),以上二者均有本身實現的 shim。
目前已有 shim v1 和 shim v2 兩個版本,至於 K8s 如何使用 CRI 與 containerd 和 shim 交互,這部分將在下一篇博文中介紹。若是喜歡,請關注個人公衆號,或者查看個人博客 http://packyzbq.coding.me. 我會不定時的發送我本身的學習記錄,你們互相學習交流哈~