Kubernetes CRD Operator 實現指南
來自專欄東嶽網路工作室團隊28 人贊了文章
8102 年了,Kubernetes 已經成為了集群調度領域最炙手可熱的開源項目之一。而多工作負載支持,是討論到集群調度時不得不談的一個話題。CRD 是 Kubernetes 的一個特性,通過它,集群可以支持自定義的資源類型,這是在 Kubernetes 集群上支持多工作負載的方式之一。本文希望討論在實現一個 Kubernetes CRD Operator 時可能遇到的問題以及解決方案,拋磚引玉,探索實現的最佳實踐。文章其餘部分如下安排:首先在「導論」中,討論了多工作負載的意義以及不同架構的調度系統的支持方式。其次在「預熱」一節詳細介紹了在 Kubernetes 上對多工作負載的不同支持方案,進一步劃定本文的討論範圍。最後在「正文」一節介紹實現 CRD Operator 的注意事項。本文主要內容來自筆者在實現 kubeflow/tf-operator 時的經驗教訓。
導論
集群資源的調度問題,是雲計算領域一直很火熱的研究方向。學術界與工業界目前也有諸多如 Borg, Docker Swarm, Firmament, Fuxi, Hawk, Kubernetes, Mercury, Mesos, Nomad, Omega, Sparrow, Yarn 等等各具特色的調度器進入我們的視線。機器集群在調度器的幫助下混合運行多種工作負載,大幅度提高了整個集群的利用率。不同的工作負載之間相互地削峰填谷,這也是集群化的管理能夠提高機器利用率的一大原因。舉例說明,在線任務白天利用大部分資源,而晚上業務空閑時讓渡資源給離線計算任務。因此多工作負載的支持對於一個生產環境可用的集群管理系統而言,是非常重要的特性。
單體架構的調度器,對於多工作負載的支持並不是特別讓人滿意。由於只有一個單體的調度器,所有的工作負載都需要被統一地調度。在大規模的場景下會成為系統的瓶頸。因此目前 Kubernetes 也在探索支持多調度器,但目前在解決調度衝突問題時尚不成熟。以 Mesos 為代表的兩層式調度器架構,對多工作負載的支持相對較好,但也有受制於中心化的調度器介面限制,難以獲取集群全局狀態的問題。以 Sparrow 為代表的去中心化架構,是為了短時批處理任務的負載優化的調度器,因此對於長時任務並不友好。以 Hawk 為代表的混合式調度器架構,和以 Nomad 為代表的共享狀態的調度器架構,是對多工作負載支持最好的調度器,但是,它們都沒有 Kubernetes 火= =
目前的 Kubernetes 在筆者心中,並不是最完美的集群調度系統,但是架構並不能說明一切,它的開發速度,社區活躍程度是其他項目完全不能媲美的,因此討論在 Kubernetes 上的多工作負載支持是最具有實際意義的。
預熱
Kubernetes 對多工作負載的支持見仁見智,有著各種不同的實現方式。這裡舉一些比較具有代表性的例子。首先就利用 Kubernetes 自帶的資源類型,如 Pod, Service, Job 等,在 Kubernetes 之上構建出滿足工作負載的應用。Kubernetes 本身的資源類型足以支撐大多數互聯網應用的需求,比如前後端應用等。之前介紹的 kubeflow/katib 也是這樣的實現方式。這樣的實現優點在於簡單方便。這樣的方式適合以網站前端後端為代表的應用邏輯。
而基礎資源類型的表達能力是有限的,哪怕是經過了內置的 controller 的擴展。因此 Kubernetes 支持 Custom Resource Definition,也就是我們一直提到的 CRD。通過這一特性,用戶可以自己定義資源類型,並對其提供支持。這種方式也是我們本次討論的重點。相比於之前的方式,這樣的實現更加原生一點,Kubernetes 會將其視為資源的一種,apiserver 中的各種檢查對其也是起作用的。因此 CRD 是可以起到四兩撥千斤的作用,與其相關的生命周期的管理是由用戶代碼進行的,與此同時 Kubernetes 的一些公共特性,比如 kubectl 展示,namespace,許可權管理等等都是可以被複用的。以 kubeflow/tf-operator,coreos/prometheus-operator,coreos/etcd-operator 為代表的 operator 都是利用了 CRD 這一特性對 TensorFlow,Prometheus 或 etcd 等不同的系統或框架進行了支持。
第三種實現方式,是 custom API Server。這種方式是復用 Kubernetes 的一些特性的同時,自由度最高的方式。在這種方式中,你可以自定義存儲,以及其他在上述方式中不能自定義的內容,同時保有一定程度的公共特性。據我所知,Cloud TiDB 的實現就是通過自定義 API server 進行的。
本文主要聚焦在第二種方式上,大多數的工作負載的需求通過第二種方式即可得到滿足。因此這裡首先大致介紹下第二種方式的實現思路。讓我們從一個 Kubernetes 用戶的角度,來看第二種方式的支持。首先,與聲明其他資源的方式一樣,在創建自定義的資源時,我們需要一個定義的文件,通常是 YAML 格式的。隨後,我們的會利用 kubectl create
命令來創建出對應的資源,這時,Kubernetes 會負責處理一系列對象創建的工作,隨後我們可以利用 kubectl describe
命令來獲得創建的對象的相關信息。
從一個程序員的角度來看,用戶的資源請求到達 Kubernetes API server 後,需要被處理,處理特定資源請求的控制器,一般被稱為 controller 或者 operator(關於 controller 和 operator 的異同的討論可見 kubeflow/tf-operator#300,以下統稱為 operator)。Operator 會監聽 API server,當有關於該 CRD 的請求到來時,operator 會通過回調函數處理請求,隨後更新資源的狀態。
這種方式始於 coreos 的 etcd operator,目前有了非常多的實踐,在這篇文章中,主要介紹一下在實現過程中的一些問題,拋磚引玉地討論 operator 的實現的最佳實踐。
正文
正文終於開始了,這部分將從 CRD 定義,校驗,狀態管理,容錯問題和架構等方面對 operator 的實現進行介紹。
CRD 定義
CRD 是一切的開始,因此從這裡出發。CRD 的定義並沒有太多值得注意的地方,只是有一些慣例需要稍微關注一下。比如 CRD 的 name 通常是 plural 和 group 的結合。另外,一般來說 CRD 的作用域是 namespaced 就可以了。還有 kind 一般採用駝峰命名法等等。這裡給出一個例子,不再贅述。
apiVersion: apiextensions.k8s.io/v1beta1kind: CustomResourceDefinitionmetadata: name: tfjobs.kubeflow.orgspec: group: kubeflow.org version: v1alpha2 scope: Namespaced names: kind: TFJob singular: tfjob plural: tfjobs
CRD Validation
CRD validation 是 Kubernetes 1.9 的新特性,這是由一個學生的 Google Summer of Code 項目。通過這一特性,API server 會檢查來自用戶的輸入是否是合法的,如果不合法會擋在 API server 門外,operator 可以不需要處理非法輸入。這一特性是通過 OpenAPI 3.0 支持的,但是並不是完整的實現,諸如 $ref
和 additionalProperties
等等欄位,在這一特性中是不支持使用的,可以說在 1.9 中是部分的實現。而仍有一部分函需支持的功能正在實現當中。
由於不支持 $ref
,因此對於某些使用了 Kubernetes 的結構定義(如 PodTemplate)的 CRD 而言,就相對不是非常友好。因為如果要完全校驗輸入的合法,使用到的 Kubernetes 資源也需要定義 schema 進行校驗,這相當於是重複性的工作。因此這裡的建議是利用 kubernetes/kubernetes#54579 提供的方法,將 Kubernetes 的對象的引用進行一次自動 inline,參考實現可見 gaocegege/crd-validation。生成的 validation 定義如下所示:
validation: openAPIV3Schema: description: TFJob represents the configuration of signal TFJob properties: apiVersion: description: APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#resources type: string ...
另外,如果 CRD 定義中存在 map 數據結構,比如這樣:
type TFJobSpec struct { // TFReplicaSpecs is map of TFReplicaType and TFReplicaSpec // specifies the TF replicas to run. // For example, // { // "PS": TFReplicaSpec, // "Worker": TFReplicaSpec, // } TFReplicaSpecs map[TFReplicaType]*TFReplicaSpec `json:"tfReplicaSpecs"`}
由於缺乏對 additionalProperties
的支持,對於其的校驗目前也是無法進行的,具體可見 kubernetes/kubernetes#59485。在 Kubernetes 1.11 之後這一問題會得到解決。
狀態管理
由於 Kubernetes API Server 背後的存儲是 etcd,對於頻繁讀寫的需求並不是特別友好,因此這對 CRD 的狀態管理也有一些建議。在實現時,盡量使用狀態的 summary 來描述狀態。以 Job 為例,Job 本身並不會維護其創建的所有的 pod 的狀態,而是以匯總的方式維護一共有多少 running 的 pod,多少 succeeded 的 pod。這樣可以盡量避免頻繁的寫。
除此之外,盡量用 Condition 而非 Phase 來描述狀態,這也是 Kubernetes 社區推崇的方式。之前的很多資源都用 Phase 定義了一套狀態機,這樣並不方便處理,具體細節可以參考 kubernetes/kubernetes#7856。
容錯性
由於目前 Kubernetes 對 CRD validation 的支持還存在一定的局限性,因此在 operator 得到的輸入中仍有可能是非法的。而在非法的輸入中,存在一些類型錯誤,比如在應該輸入 string 的地方,得到的是一個 integer。這樣的錯誤可以 crash 整個 operator,使得 operator 的可用性降低。
一個臨時的解決方案是使用一個無類型的 informer,代替由 API 生成的 typed informer,隨後再進行一次 convert 轉為對應類型。具體可見 kubeflow/tf-operator#561。
架構
因為 operator 本身其實是將對某種類型的應用的運維邏輯固化在了其中,所以很容易就會被實現成有狀態的方式。比如在 kubeflow/tf-operator 的第一版實現中,會在 operator 的內存中維護一個 cache,來記錄當前由 operator 處理的請求的狀態,並且基於 cache,在新的請求到來時也會根據 cache 決定如何處理新的請求。這樣的實現方式乍一看好像沒什麼問題,但是當 operator 遇到問題 crash 需要被重新運行時,因為內存中的 cache 是不會被持久化的,因此就會在處理新請求時出現問題。
因此更推薦的方式是利用 informer,完全以事件驅動的方式處理請求。舉例說明,如果不是以事件驅動的架構來處理一個新的請求「創建一個新的 TensorFlow 分散式訓練任務」,operator 會在內存里維護從訓練任務的名字到訓練任務對象的映射,在檢查過發現映射不存在後,會創建出對應的對象,並且在 Kubernetes 中創建對應的資源。當任務完成時,通過在內存中的映射找到對應的對象,更新它的狀態為 completed。
而如果是事件驅動的架構,在受到請求後,發現 Kubernetes 上並沒有對應的資源,這時會創建出來,然後結束邏輯。隨後對應的資源被創建時,會產生新的創建事件,operator 再基於新的事件,更新訓練任務的狀態。在訓練完成後,從 Kubernetes 中獲取資源並且更新它的狀態。不知是否解釋地夠清楚,總結來說就是保證 operator 是無狀態的,所有的狀態都應該依賴 API Server。
結語
本篇文章介紹了在實現 Kubernetes CRD operator 時可能遇到的一些問題以及對應的解決方案,希望能夠對各位有所幫助。
關於作者
高策,上海交通大學軟體學院研究生,預計 2019 年畢業,找工作中,歡迎聯繫。
參考資料
- The evolution of cluster scheduler architectures
- Container Camp AU 2017 - Building a Kubernetes Operators
- 唐瑞. 基於Kubernetes的容器雲平台資源調度策略研究[D].電子科技大學,2017.
- kubeflow/tf-operator
License
- This article is licensed under CC BY-NC-SA 3.0.
- Please contact me for commercial use.
推薦閱讀:
※華為雲在 K8S 大規模場景下的 Service 性能優化實踐
※喜憂參半的 Kubernetes 生產之路
※AWS 中國區部署 Kubernetes 1.9.3
※基於CentOS 7部署Rancher 2.0
※kubernetes kustomize 初體驗
TAG:Kubernetes | 分散式系統 | 雲計算 |