做者 | 賈之光 阿里巴巴高級開發工程師node
本文整理自《CNCF x Alibaba 雲原生技術公開課》第 30 講,點擊直達課程頁面。 關注「阿里巴巴雲原生」公衆號,回覆關鍵詞**「入門」**,便可下載從零入門 K8s 系列文章 PPT。api
咱們首先了解一下容器運行時的演進過程,整個過程大體分爲三個階段:安全
Kubernetes 正式開源,Docker 是當時惟一的、也是默認的容器運行時;less
rkt 合入 Kubernetes 主幹,成爲了第二個容器運行時。微服務
與此同時,愈來愈多的容器運行時也想接入到 Kubernetes 中。若是仍是按 rkt 和 Docker 同樣內置支持的話,會給 Kubernetes 的代碼維護和質量保障帶來嚴重挑戰。阿里雲
社區也意識到了這一點,因此在 1.5 版本時推出了 CRI,它的全稱是 Container Runtime Interface。這樣作的好處是:實現了運行時和 Kubernetes 的解耦,社區沒必要再爲各類運行時作適配工做,也不用擔憂運行時和 Kubernetes 迭代週期不一致所帶來的版本維護問題。比較典型的,好比 containerd 中的 cri-plugin 就實現了 CRI、kata-containers、gVisor 這樣的容器運行時只須要對接 containerd 就能夠了。spa
隨着愈來愈多的容器運行時的出現,不一樣的容器運行時也有不一樣的需求場景,因而就有了多容器運行時的需求。可是,如何來運行多容器運行時還須要解決如下幾個問題:插件
爲了解決上述提到的問題,社區推出了 RuntimeClass。它其實在 Kubernetes v1.12 中就已被引入,不過最初是以 CRD 的形式引入的。v1.14 以後,它又做爲一種內置集羣資源對象 RuntimeClas 被引入進來。v1.16 又在 v1.14 的基礎上擴充了 Scheduling 和 Overhead 的能力。3d
下面以 v1.16 版本爲例,講解一下 RuntimeClass 的工做流程。如上圖所示,左側是它的工做流程圖,右側是一個 YAML 文件。server
YAML 文件包含兩個部分:上部分負責建立一個名字叫 runv 的 RuntimeClass 對象,下部分負責建立一個 Pod,該Pod 經過 spec.runtimeClassName 引用了 runv 這個 RuntimeClass。
RuntimeClass 對象中比較核心的是 handler,它表示一個接收建立容器請求的程序,同時也對應一個容器運行時。好比示例中的 Pod 最終會被 runv 容器運行時建立容器;scheduling 決定 Pod 最終會被調度到哪些節點上。
結合左圖來講明一下 RuntimeClass 的工做流程:
咱們仍是以 Kubernetes v1.16 版本中的 RuntimeClass 爲例。首先介紹一下 RuntimeClass 的結構體定義。
一個 RuntimeClass 對象表明了一個容器運行時,它的結構體中主要包含 Handler、Overhead、Scheduling 三個字段。
在 Pod 中引用 RuntimeClass 的用法很是簡單,只要在 runtimeClassName 字段中配置好 RuntimeClass 的名字,就能夠把這個 RuntimeClass 引入進來。<br />
顧名思義,Scheduling 表示調度,但這裏的調度不是說 RuntimeClass 對象自己的調度,而是會影響到引用了 RuntimeClass 的 Pod 的調度。
Scheduling 中包含了兩個字段,NodeSelector 和 Tolerations。這兩個和 Pod 自己所包含的 NodeSelector 和 Tolerations 是極爲類似的。
NodeSelector 表明的是支持該 RuntimeClass 的節點上應該有的 label 列表。一個 Pod 引用了該 RuntimeClass 後,RuntimeClass admission 會把該 label 列表與 Pod 中的 label 列表作一次合併。若是這兩個 label 中有衝突的,會被 admission 拒絕。這裏的衝突是指它們的 key 相同,可是 value 不相同,這種狀況就會被 admission 拒絕。另外須要注意的是,RuntimeClass 並不會自動爲 Node 設置 label,須要用戶在使用前提早設置好。
Tolerations 表示 RuntimeClass 的容忍列表。一個 Pod 引用該 RuntimeClass 以後,admission 也會把 toleration 列表與 Pod 中的 toleration 列表作一個合併。若是這兩處的 Toleration 有相同的容忍配置,就會將其合併成一個。
上圖左邊是一個 Docker Pod,右邊是一個 Kata Pod。咱們知道,Docker Pod 除了傳統的 container 容器以外,還有一個 pause 容器,但咱們在計算它的容器開銷的時候會忽略 pause 容器。對於 Kata Pod,除了 container 容器以外,kata-agent, pause, guest-kernel 這些開銷都是沒有被統計進來的。像這些開銷,多的時候甚至能超過 100MB,這些開銷咱們是無法忽略的。
這就是咱們引入 Pod Overhead 的初衷。它的結構體定義以下:
它的定義很是簡單,只有一個字段 PodFixed。它這裏面也是一個映射,它的 key 是一個 ResourceName,value 是一個 Quantity。每個 Quantity 表明的是一個資源的使用量。所以 PodFixed 就表明了各類資源的佔用量,好比 CPU、內存的佔用量,均可以經過 PodFixed 進行設置。
Pod Overhead 的使用場景主要有三處:
在沒有引入 Overhead 以前,只要一個節點的資源可用量大於等於 Pod 的 requests 時,這個 Pod 就能夠被調度到這個節點上。引入 Overhead 以後,只有節點的資源可用量大於等於 Overhead 加上 requests 的值時才能被調度上來。
它是一個 namespace 級別的資源配額。假設咱們有這樣一個 namespace,它的內存使用量是 1G,咱們有一個 requests 等於 500 的 Pod,那麼這個 namespace 之下,最多能夠調度兩個這樣的 Pod。而若是咱們爲這兩個 Pod 增添了 200MB 的 Overhead 以後,這個 namespace 下就最多隻可調度一個這樣的 Pod。
引入 Overhead 以後,Overhead 就會被統計到節點的已使用資源中,從而增長已使用資源的佔比,最終會影響到 Kubelet Pod 的驅逐。
以上是 Pod Overhead 的使用場景。除此以外,Pod Overhead 還有一些使用限制和注意事項:
目前阿里雲 ACK 安全沙箱容器已經支持了多容器運行時,咱們以上圖所示環境爲例來講明一下多容器運行時是怎麼工做的。
如上圖所示有兩個 Pod,左側是一個 runc 的 Pod,對應的 RuntimeClass 是 runc,右側是一個 runv 的Pod,引用的 RuntimeClass 是 runv。對應的請求已用不一樣的顏色標識了出來,藍色的表明是 runc 的,紅色的表明是 runv 的。圖中下半部分,其中比較核心的部分是 containerd,在 containerd 中能夠配置多個容器運行時,最終上面的請求也會到達這裏進行請求的轉發。
咱們先來看一下 runc 的請求,它先到達 kube-apiserver,而後 kube-apiserver 請求轉發給 kubelet,最終 kubelet 將請求發至 cri-plugin(它是一個實現了 CRI 的插件),cri-plugin 在 containerd 的配置文件中查詢 runc 對應的 Handler,最終查到是經過 Shim API runtime v1 請求 containerd-shim,而後由它建立對應的容器。這是 runc 的流程。
runv 的流程與 runc 的流程相似。也是先將請求到達 kube-apiserver,而後再到達 kubelet,再把請求到達 cri-plugin,cri-plugin 最終還回去匹配 containerd 的配置文件,最終會找到經過 Shim API runtime v2 去建立 containerd-shim-kata-v2,而後由它建立一個 Kata Pod。
下面咱們再看一下 containerd 的具體配置。
containerd 默認放在 file:///etc/containerd/config.toml 這個位置下。比較核心的配置是在 plugins.cri.containerd 目錄下。其中 runtimes 的配置都有相同的前綴 plugins.cri.containerd.runtimes,後面有 runc, runv 兩種 RuntimeClass。這裏面的 runc 和 runv 和前面 RuntimeClass 對象中 Handler 的名字是相對應的。除此以外,還有一個比較特殊的配置 plugins.cri.containerd.runtimes.default_runtime,它的意思是說,若是一個 Pod 沒有指定 RuntimeClass,可是被調度到當前節點的話,那麼就默認使用 runc 容器運行時。
下面的例子是建立 runc 和 runv 這兩個 RuntimeClass 對象,咱們能夠經過 kubectl get runtimeclass 看到當前全部可用的容器運行時。
下圖從左至右分別是一個 runc 和 runv 的 Pod,比較核心的地方就是在 runtimeClassName 字段中分別引用了 runc 和 runv 的容器運行時。
最終將 Pod 建立起來以後,咱們能夠經過 kubectl 命令來查看各個 Pod 容器的運行狀態以及 Pod 所使用的容器運行時。咱們能夠看到如今集羣中有兩個 Pod:一個是 runc-pod,另外一個是 runv-pod,分別引用的是 runc 和 runv 的 RuntimeClass,而且它們的狀態都是 Running。
本文的主要內容就到此爲止了,這裏爲你們簡單總結一下:
活動報名連接:https://yqh.aliyun.com/live/CloudNative
「阿里巴巴雲原生關注微服務、Serverless、容器、Service Mesh 等技術領域、聚焦雲原生流行技術趨勢、雲原生大規模的落地實踐,作最懂雲原生開發者的公衆號。」