從零開始入門 K8s | 理解 RuntimeClass 與使用多容器運行時

2021-02-14 阿里巴巴雲原生

本文整理自《CNCF x Alibaba 雲原生技術公開課》第 30 講,點擊「閱讀原文」直達課程頁面。關注「阿里巴巴雲原生」公眾號,回復關鍵詞「入門」,即可下載從零入門 K8s 全系列文章 PPT。

RuntimeClass 需求來源

我們首先了解一下容器運行時的演進過程,整個過程大致分為三個階段:Kubernetes 正式開源,Docker 是當時唯一的、也是默認的容器運行時。rkt 合入 Kubernetes 主幹,成為了第二個容器運行時。與此同時,越來越多的容器運行時也想接入到 Kubernetes 中。如果還是按 rkt 和 Docker 一樣內置支持的話,會給 Kubernetes 的代碼維護和質量保障帶來嚴重挑戰。社區也意識到了這一點,所以在 1.5 版本時推出了 CRI,它的全稱是 Container Runtime Interface。這樣做的好處是:實現了運行時和 Kubernetes 的解耦,社區不必再為各種運行時做適配工作,也不用擔心運行時和 Kubernetes 迭代周期不一致所帶來的版本維護問題。比較典型的,比如 containerd 中的 cri-plugin 就實現了 CRI,kata-containers、gVisor 這樣的容器運行時只需要對接 containerd 就可以了。隨著越來越多的容器運行時的出現,不同的容器運行時也有不同的需求場景,於是就有了多容器運行時的需求。但是,如何來運行多容器運行時還需要解決以下幾個問題:如何讓 Pod 調度到裝有指定容器運行時的節點上?容器運行時在運行容器時會產生有一些業務運行以外的額外開銷,這種「額外開銷」需要怎麼統計?為了解決上述提到的問題,社區推出了 RuntimeClass。它其實在 Kubernetes v1.12 中就已被引入,不過最初是以 CRD 的形式引入的。v1.14 之後,它又作為一種內置集群資源對象 RuntimeClas 被引入進來。v1.16 又在 v1.14 的基礎上擴充了 Scheduling 和 Overhead 的能力。下面以 v1.16 版本為例,講解一下 RuntimeClass 的工作流程。如上圖所示,左側是它的工作流程圖,右側是一個 YAML 文件。YAML 文件包含兩個部分:上部分負責創建一個名字叫 runv 的 RuntimeClass 對象,下部分負責創建一個 Pod,該Pod 通過 spec.runtimeClassName 引用了 runv 這個 RuntimeClass。RuntimeClass 對象中比較核心的是 handler,它表示一個接收創建容器請求的程序,同時也對應一個容器運行時。比如說,比如示例中的 Pod 最終會被 runv 容器運行時創建容器;scheduling 決定 Pod 最終會被調度到哪些節點上。結合左圖來說明一下 RuntimeClass 的工作流程:K8s-master 接收到創建 Pod 的請求;方格部分表示三種類型的節點。每個節點上都有 Label 標識當前節點支持的容器運行時,節點內會有一個或多個 handel,每個 handle 對應一種容器運行時。比如第二個方格表示節點內有支持 runc 和 runv 兩種容器運行時的 handler;第三個方格表示節點內有支持 runhcs 容器運行時的 handler;根據 scheduling.nodeSelector, Pod 最終會調度到中間方格節點上,並最終由 runv handler 來創建 Pod。

RuntimeClass 功能介紹

我們還是以 Kubernetes v1.16 版本中的 RuntimeClass 為例。首先介紹一下 RuntimeClass 的結構體定義。一個 RuntimeClass 對象代表了一個容器運行時,它的結構體中主要包含 Handler、Overhead、Scheduling 三個欄位。在之前的例子中我們也提到過 Handler,它表示一個接收創建容器請求的程序,同時也對應一個容器運行時;Overhead 是 v1.16 中才引入的一個新的欄位,它表示 Pod 中的業務運行所需資源以外的額外開銷;第三個欄位Scheduling 也是在 v1.16 中被引入的,該 Scheduling 配置會被自動注入到 Pod 的 nodeSelector 中。在 Pod 中引用 RuntimeClass 的用法非常簡單,只要在 runtimeClassName 欄位中配置好 RuntimeClass 的名字,就可以把這個 RuntimeClass 引入進來。顧名思義,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 進行設置。在沒有引入 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 還有一些使用限制和注意事項:Pod Overhead 最終會永久注入到 Pod 內並且不可手動更改。即便是將 RuntimeClass 刪除或者更新,Pod Overhead 依然存在並且有效;Pod Overhead 只能由 RuntimeClass admission 自動注入(至少目前是這樣的),不可手動添加或更改。如果這麼做,會被拒絕;HPA 和 VPA 是基於容器級別指標數據做聚合,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。

本文總結

本文的主要內容就到此為止了,這裡為大家簡單總結一下:RuntimeClass 是 Kubernetes 一種內置的集群資源,主要用來解決多個容器運行時混用的問題;RuntimeClass 中配置 Scheduling 可以讓 Pod 自動調度到運行了指定容器運行時的節點上。但前提是需要用戶提前為這些 Node 設置好 label;RuntimeClass 中配置 Overhead,可以把 Pod 中業務運行所需以外的開銷統計進來,讓調度、ResourceQuota、Kubelet Pod 驅逐等行為更準確。



系列文章推薦閱讀

K8s 網絡模型進階 | K8s API 編程利器 | K8s API編程範式

存儲架構及插件 | GPU 管理和 Device Plugin 工作機制

K8s API 編程 | K8s 調度 | etcd 核心設計 | etcd 性能優化

Pod 與容器設計模式 | 應用編排與管理 | 應用配置管理

Linux 容器 | 有狀態應用編排 | 基本網絡模型 | 服務發現與負載均衡

應用健康 | 監控與日誌 | Job & DaemonSet | 存儲快照機制

相關焦點

  • 深入理解Objective-C的Runtime機制
    下面通過分析Apple開源的Runtime代碼(我使用的版本是objc4-646.tar)來深入理解Objective-C的Runtime機制。注意:根據Apple的官方文檔Key-Value Observing Implementation Details提及,key-value observing是使用isa-swizzling的技術實現的,isa指針在運行時被修改,指向一個中間類而不是真正的類。所以,你不應該使用isa指針來確定類的關係,而是使用class方法來確定實例對象的類。
  • 從零開始入門 K8s | 理解 CNI 和 CNI 插件
    本文整理自《CNCF x Alibaba 雲原生技術公開課》第 26 講,點擊
  • 從零開始用Python實現k近鄰算法(附代碼、數據集)
    分解--KNN的偽代碼從零開始的Python實現和Scikit-learn比較什麼情況使用KNN算法?KNN算法既可以用於分類也可以用於回歸預測。然而,業內主要用於分類問題。如圖所示,對於訓練樣本而言,K=1時的錯誤率總是為零。這是因為對任何訓練數據點來說,最接近它的點就是其本身。因此,K=1時的預測總是準確的。如果驗證錯誤曲線也是這樣的形狀,我們只要設定K為1就可以了。
  • 談Runtime機制和使用的整體化梳理
    學習流程圖一.基本概念RunTime簡稱運行時,就是系統在運行的時候的一些機制,其中最主要的是消息機制。對於C語言,函數的調用在編譯的時候會決定調用哪個函數( C語言的函數調用請看這裡 )。編譯完成之後直接順序執行,無任何二義性。OC的函數調用成為消息發送。屬於動態調用過程。
  • k-means聚類算法從入門到精通
    小批量處理的k-means聚類算法5. k值的選取6. k-means聚類算法不適用的幾個場景7. k-means與knn區別8. 小結聚類算法性能度量的文章提到若簇類相似度好簇間的相似度差,則聚類算法的性能較好。我們基於此定義k-means聚類算法的目標函數: 其中
  • runtime是什麼
    究竟是什麼,首先runtime在英文裡是合成單詞,無論是英文還是中文都容易在文字層面被誤解,中文直譯「運行時」,中文的斷句容易引起歧義,究竟是「運行、時」還是「運行時」傻傻分不清。為了準確描述runtime的實際意思,我認為runtime換成execution environment理解起來更容易:即位應用程式的執行準備運行環境。運行時庫是在編譯時使用的特殊庫,用於在電腦程式的執行中實現內置於程式語言中的功能,包括:輸入、輸出、內存管理。crt0比如C語言需要的最小runtime叫做crt0(C runtime)。
  • Runtime那些事兒(消息機制)
    之前在項目中有遇到過用runtime解決改變全局字體的問題,所以再一次感受到了runtime黑魔法的強大,趁現在有機會分享一下對runtime的一些理解。在對象調用方法是Objective-C中經常使用的功能,也就是消息的傳遞,而Objective-C是C的超集,所以和C不同的是,Objective-C使用的是動態綁定,也就是runtime。Objective-C的消息傳遞和消息機制也就不多說了,今天主要說的是動態方法,也就是函數的調用。
  • 方舟編譯器 Toy Runtime 可以運行 Hello World 了
    方舟編譯器 runtime 參考實現 pacific 發布了 0.1 版本,支持運行基於方舟編譯器的 Hello World 程序。發布公告介紹,pacific 開發團隊使用 QEMU 提供 AArch64 架構支持,將方舟編譯器的 Java 環境巧妙地用 GNU/Linux 的方式仿製了這套可以跑 Hello World 程序的 Toy Runtime。據了解,pacific 是目前業內首個方舟編譯器 runtime 實現,「實現了從 0 到 1 的一個跨越」。
  • go runtime debug 小技巧
    前言本意是打算研究一下go程序的啟動流程,然後就去網上搜索了一下入門教程。
  • Python 從零開始--入門篇
    ,目的只有一個是大家一起能夠使用 python 寫自己的爬蟲,能夠達到公司要求的基本水平。就是很簡答相比於 C語言 的指針的混亂,和 Java 的繁瑣,python 更加適合作為非計算機專業的第一個入門的程式語言。(計算機專業最好還是以C語言為第一門語言入門為好,後面寫文章出來介紹)從語法上面來說也擁有更多的第三方庫,避免了很多「重複製造輪子「。
  • k-means聚類算法原理總結
    小批量處理的k-means聚類算法5. k值的選取6. k-means聚類算法不適用的幾個場景7. k-means與knn區別8. 小結聚類算法性能度量的文章提到若簇類相似度好簇間的相似度差,則聚類算法的性能較好。我們基於此定義k-means聚類算法的目標函數: 其中
  • 關於Java中Runtime.class.getClass()的細節分析
    ,答案已經呼之欲出了:Runtime.class獲取的是class java.lang.Runtime,而該Class調用getClass()時,運行時確定的類型為Class而非方法擁有者Object,所以得到的第二個Class為class java.lang.Class。
  • Gradle 與 Android 構建入門
    class 文件轉換為 .dex 文件6. dx xxx.class R.class xxx.jar7. # 打包成 apk8. zip xxx.apk [需要打包的代碼和資源]一切似乎都盡在掌握之中,真的嗎?
  • 探秘Runtime - Runtime源碼分析
    這個變量中存儲了對象實例化時,所有變量所佔的內存大小,這個大小是在編譯器就已經決定的,不能在運行時進行動態改變。獲取到instanceSize後,對獲取到的size進行地址對其。需要注意的是,CF框架要求所有對象大小最少是16位元組,如果不夠則直接定義為16位元組。
  • 使用開源 AutoML 庫 AutoGluon 進行機器學習
    在本文的下半部分,我將介紹一個端到端代碼示例,說明如何使用 AutoGluon-Tabular 憑藉幾行代碼在數據科學競賽中獲得排名前 1% 的分數,且無需機器學習經驗。如果您想快速入門並開始學習本示例,請轉到「在下一次數據科學競賽中搶佔先機」部分。
  • 使用K-means 算法進行客戶分類
    在本部分中,你將理解並學習到如何實現K-Means聚類。部分使用案例如下:  k - means聚類算法步驟1為了找到數據中的集群數量,用戶需要運行K-means聚類算法對K個值的範圍進行聚類並比較結果。一般來說,沒有確定K的精確值的方法,但是可以使用以下技術得到精確的估計值。通常用於比較不同K值之間的結果的度量之一是:數據點與它們的集群中心之間的平均距離。
  • Go 經典入門系列 32:panic 和 recover
    在 Go 語言中,程序中一般是使用錯誤[2]來處理異常情況。對於程序中出現的大部分異常情況,錯誤就已經夠用了。但在有些情況,當程序發生異常時,無法繼續運行。在這種情況下,我們會使用 panic 來終止程序。
  • 一線網際網路大廠普遍使用的Docker,這份筆記幫你入門到深入
    >3.2 運行我們的第一個容器3.3 使用第一一個容器3.4 容器命名3.5 重新啟動已經停止的容器運行自己的Docker Registry4.9 其他可選Registry服務4.10 小結第5章 在測試中使用Docker5.1 使用Docker