容器運行時分析

什麼是RunC

Docker、Google、CoreOS 和其他供應商創建了OCI 開放容器計劃。目前有兩個標準文檔:

  • 容器運行時標準(runtime spec)
  • 容器鏡像標準(image spec)

image.png

OCI 對容器runtime的標準主要是指定容器的運行狀態,如runtime需要提供的命令。下圖是容器狀態轉換圖:
image.png

  • init 狀態:這個狀態並不再標準中,僅表示沒有容器存在的初始狀態
  • creating:使用create 命令創建容器,狀態爲創建中
  • created:容器創建出來,但是還沒運行,表示鏡像和配置沒有錯誤,容器可以運行在當前平臺
  • running: 容器的運行狀態,裏面的進程處理up狀態
  • stopped:容器運行完成、出錯或者stop命令之後,容器處於暫停狀態。

RunC就是一個命令行工具,可以操作宿主機的內核來管理Namespace和CGroups。使用runc,我們可以創建,啓動,停止,刪除容器。

Runc的由來

runc是從docker的libcontianer中遷移出來的。實現了容器啓動,停止,資源隔離等功能。Docker講runc捐贈給OCI作爲OCI容器運行時標準的參考實現。當我們用Docker運行一個容器時,經歷瞭如下步驟:
image.png
圖片來源:《Kubernetes In Action》 2.1

  1. Docker會檢查busybox:latest鏡像是否己經存在於本機。如果沒有, Docker會從http://docker.io的Docker鏡像中心拉取鏡像。
  2. 將鏡像下載到本地並解壓爲符合OCI標準的bundle文件, 將一個文件系統拆分成多層(overlay)
  3. Docker基於這個鏡像bundle文件創建一個容器並在容器中運行命令。echo命令打印文字到標準輸出流, 然後進程終止,容器停止運行。

RunC標準化的僅僅是第三步,也是Docker 貢獻出來的部分。

怎麼使用Runc

 create the bundle
$ mkdir -p /mycontainer/rootfs

# [ab]use Docker to copy a root fs into the bundle
$ docker export $(docker create busybox) | tar -C /mycontainer/rootfs -xvf -

# create the specification, by default sh will be the entrypoint of the container
$ cd /mycontainer
$ runc spec

# launch the container
$ sudo -i
$ cd /mycontainer
$ runc run mycontainerid

# list containers
$ runc list

# stop the container
$ runc kill mycontainerid

# cleanup
$ runc delete mycontainerid

想象一下,我們用runc啓動一個容器後,我們要怎麼去跟蹤他們的狀態。我們要啓動其他幾個容器來跟蹤他們的狀態,其中一些需要在失敗時重新啓動,需要在終止時釋放資源,且要從遠程倉庫中拉去鏡像,並配置容器的網絡和內存資源。如果我們要自動話這個過程,我們就需要一個容器管理器。如何用實現一個簡單的runc "Building a container from scratch in Go"

Low-level & High-level

當我們討論容器運行時的時候,我們可能會想到:runc、lxc、lmctfy、docker、rkt、cri-o。這些中的每一個都是爲不同的情況構建的,實現了不同的特性功能。如 contianerd,cri-o,實際上時使用runc來運行容器,在High-level實現了鏡像管理和API層。如,鏡像推送,鏡像拉去,鏡像管理,鏡像解包和API。這些被視爲High-level的功能。每一個High-level的實現都囊括了low-level。
image.png

從實際出發,通常只關注於正在運行的容器的runtime通常稱爲“low-level”容器運行時,支持更多高級功能如(鏡像管理和gRPC/REST API)的運行時通常稱爲“High-leve”容器運行時。二者在根本上是解決不同的問題。

Low-level容器運行時

容器是通過Linux namespace和Cgroups實現的。Namespace能讓你爲每個容器提供虛擬化系統資源,如文件系統,網絡;Cgroups提供了限制每個容器所能使用的資源的方法,如CPU和內存。在低級別容器運行時中,其主要負責爲容器建立ns和cgroups,然後在其中運行命令。一個健壯的低級容器運行時會做更多的事情,比如允許在 cgroup 上設置資源限制,設置根文件系統,以及將容器的進程 chroot 到根文件系統。
以下爲幾種Low-level容器運行時實現

  • runc: 最常見且被廣泛使用容器運行時,代表實現就是Docker runc
  • runv: runv是一個基於虛擬機管理程序的運行時,它通過虛擬化guest kernel,將容器和主機隔離開,使其邊界更加清晰,這種方式很容器就能幫助加強主機和容器的安全性,代表實是kataFirecracker
  • runsc: runc+safety 典型就是google的gvisor,通過攔截應用程序的所有系統調用,提供安全隔離的輕量級容器運行時沙箱。截止目前,貌似沒有生產環境使用案例
  • wasm: Wasm的沙箱機制帶來的隔離型和安全性都比Docker做的更好。但是wasm處於草案階段。

High-level容器運行時

image.png
通常情況下,開發人員想要運行一個容器不僅僅需要Low-Level容器運行時提供的這些特性,同時也需要與鏡像格式、鏡像管理和共享鏡像相關的API接口和特性,而這些特性一般由High-Level容器運行時提供。就日常使用來說,Low-Level容器運行時提供的這些特性可能滿足不了日常所需,因爲這個緣故,唯一會使用Low-Level容器運行時的人是那些實現High-Level容器運行時以及容器工具的開發人員。那些實現Low-Level容器運行時的開發者會說High-Level容器運行時比如containerd和cri-o不像真正的容器運行時,因爲從他們的角度來看,他們將容器運行的實現外包給了runc。但是從用戶的角度來看,它們只是提供容器功能的單個組件,可以被另一個的實現替換,因此從這個角度將其稱爲runtime仍然是有意義的。即使containerd和cri-o都使用runc,但是它們是截然不同的項目,支持的特性也是非常不同的。dockershim, containerd 和cri-o都是遵循CRI的容器運行時,我們稱他們爲高層級運行時(High-level Runtime)。
以下爲幾種High-level容器運行時實現

dockerd

image.png
Docker 是一個容器運行時,它包含生成、打包、共享和運行容器。Docker 是C/S架構,dockerd爲服務端是一個守護進程,docker client客戶端負責接收命令發送給dockerd。守護程序提供了構建容器、管理映像和運行容器的大部分邏輯,以及 API。可以運行命令行客戶端來發送命令並從守護程序獲取信息。
現在創建一個docker容器的時候,Docker Daemon 並不能直接幫我們創建了,而是請求 containerd 來創建一個容器。當containerd 收到請求後,也不會直接去操作容器,而是創建一個叫做 containerd-shim 的進程。讓這個進程去操作容器,我們指定容器進程是需要一個父進程來做狀態收集、維持 stdin 等 fd 打開等工作的,假如這個父進程就是 containerd,那如果 containerd 掛掉的話,整個宿主機上所有的容器都得退出了,而引入 containerd-shim 這個墊片就可以來規避這個問題了,就是提供的live-restore的功能。這裏需要注意systemd的 MountFlags=slave。
然後創建容器需要做一些 namespaces 和 cgroups 的配置,以及掛載 root 文件系統等操作。runc 就可以按照這個 OCI 文檔來創建一個符合規範的容器。
image.png
真正啓動容器是通過 containerd-shim 去調用 runc 來啓動容器的,runc 啓動完容器後本身會直接退出,containerd-shim 則會成爲容器進程的父進程, 負責收集容器進程的狀態, 上報給 containerd, 並在容器中 pid 爲 1 的進程退出後接管容器中的子進程進行清理, 確保不會出現殭屍進程.
image.png

containerd

image.png
containerd也是從docker中分離出來的項目。與docker-runc不同的是,containerd是一個常駐守護進程,負責管理由runc創建的容器。監聽上層請求,來啓動、停止或者上報容器的狀態。負責管理容器的生命週期。除此之外,還負責鏡像的推拉和鏡像的本地存儲,跨容器網絡管理等其他功能。containerd的底層Low-level是runc,但並不侷限於runc。可以用其他Low-level實現來替代runc。contianerd是一個工業級標準容器運行時,強調簡單性、健壯性和可移植性。主要負責如下事情:

  • 管理容器的生命週期
  • 拉去和推送容器鏡像
  • 存儲管理
  • 調用runc運行容器及其他交互
  • 管理容器網絡接口和請求

contianerd與docker的區別是contianerd專注容器的管理,而docker關注於用戶端的使用。支持編譯構建鏡像並定義了鏡像的格式。
從k8s的角度看,選擇containerd作爲運行時組建,它的調用鏈更短,組建更少,更穩定,佔用節點資源更少。
image.png

參考

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