天天看點

Kubernetes資源編排系列之四: CRD+Operator篇

作者 炯思(鐘炯恩) 雪堯(郭耀星)

這是我們的《Kubernetes資源編排系列》的第四篇——CRD+Operator篇。在前面的文章中,常常會提到CRD和k8s operator,但并沒有對此進行深入的探讨。作為k8s中的一大亮點,在本篇文章中,我們會詳細展開講講。

1. 什麼是CRD

如果 K8S 中的自帶資源類型不足以滿足業務需求,需要定制開發資源怎麼辦?自定義資源(Custom Resource)由此産生。那麼,如何讓Kubernetes認識這些自定義的資源呢?CRD(Custom Resource Definition)就承擔了一個說明書的角色,讓Kubernetes 來認識這個自定義資源CR。

那麼CRD是怎麼來的呢?最早是谷歌提出Third Party Resource的概念,希望開發者以插件化形式擴充 K8s API 對象模型,以增強整個k8s的生态。基于Third Party Resource這一概念,Kubernetes 社群在 1.7 版本中提出了CRD 的概念。

随便打開一個CRD的YAML可以看到,其主體部分是使用 OpenAPI v3 schema 來描述CR的字段結構,類似程式設計語言中的強類型聲明。

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: humans.human.sreworks.io
spec:
  group: human.sreworks.io
  names:
    kind: Human
    plural: humans
  scope: Namespaced
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          description: ...
          type: object
          properties:
            spec:
              type: object
              properties:
                company:
                  type: string
                ...      

有了CRD之後,我們可以自由地增加各種與Pod平級的資源,很多之前需要落在CMDB中的資料,也可以被放在k8s叢集中。這極大地拓寬了我們的想象力,什麼交換機、作業、路由等各種關聯的資源都一股腦地放進叢集裡面去。

在各種自定義資源被放進去之後,就會有人問,這放進去是挺友善的,但是放進去就會生效嗎?是的,資源的生效就是Operator的功勞。下面我們就開始介紹Operator。

2. 什麼是Operator

首先随便翻看一本詞典看一下operator這個詞的定義:操作員/運算符,是個名詞。那麼,operator描述的應該是一個圍繞"操作、控制"概念的東西。為了讓大家有個更直覺的認識,我們來舉一個例子,比如 1 + 2 = 3,這個 "+" 就是一個operator(運算符),這個 "+" 讓兩個數字發生了一些互動(相加)。

有了詞典裡的概念鋪墊後,我們繼續往下分析,既然是一種操作或運算,那麼在k8s中,是誰來操作?而被操作的對象又是什麼呢?讓我們來看一下OperatorFramework官網上對于Operator的解釋:

WHAT IS AN OPERATOR AFTER ALL?

An Operator represents human operational knowledge in software, to reliably manage an application. They are methods of packaging, deploying, and managing a Kubernetes application.

從這個定義中,我們可以看到,這個operator是指由人發出的,對k8s應用(Kubernetes application)展開的操作。一般圍繞應用的操作有哪些?部署、更新、擴縮容、解除安裝等等。我們可以先這樣了解,operator應該就是一個類似控制器的東西,裡面含有一些運維操作(後面會繼續展開,其實不僅僅是這些)。

較真一點的讀者可能會問,既然這樣,這東西叫controller是不是會更貼切一點呢?事實上,問出這個問題的讀者,和真相很接近了,每個operator基本都會有個控制器,但又不僅僅隻有一個控制器,還會有前面提到過的資源定義: CRD (CustomResourceDefinition) 。每種自定義資源背後都會有一個或多個控制器,讓這些資源看起來像活的一樣,如下面的YAML樣例:

這個叫Bob的人,生日和性别是不可變屬性,無法修改;而位置是可以修改的,可以從家改到公司,但是改完之後會有一段時間處于不Ready的狀态,因為他正在去上班的路上。去哪個公司上班呢,他在helloworld公司工作,是以他是去這家公司上班。

apiVersion: v1
kind: Human
metadata:
  name: Bob
spec: 
  birth: 648489237
  sex: male
  location: home
  company: helloworld
status:
  - lastProbeTime: null
  lastTransitionTime: "2022-07-20T08:41:04Z"
  status: "True"
  type: Ready      

通過上面這段YAML我們可以發現,當我們關注于對象終态的時候,我們就不太關注這個控制過程,這個Bob怎麼去上班的,是開車還是地鐵去的,其實我們并不關心。如果類比到普通的日常實踐,也是這樣:做一個應用的存儲位置遷移,我們隻需要設定這個應用新的存儲位置,至于怎麼遷移過去,是用網絡指令傳輸過去的還是實體上用硬碟拷貝過去我們不關心,遷移過程中資料一緻性我們也不關心,隻知道會有operator把這些都給搞定。

是以,operator其實是一種架構理念,它差別于常見的shell等運維腳本方案:operator希望應用能夠自己管理自己,而不是由運維人員寫一堆腳本從外圍來控制他們。不過,如果僅僅是這樣,可能operator也隻能叫controller了,隻是一些自控制的邏輯而已。從最前面提到的operator的概念可以看出,operator能夠讓兩種以上的資源産生一些互動關系,那麼這是如何實作的呢?

我們繼續用上面Human的例子再加個YAML:

這個helloworld公司也可以用一個資源對象來描述。如果我們把這個公司的isOpen改成true,這個company會有個控制器來周遊所有的Human資源,把spec.company="helloworld"的人(比如Bob)的location全改成公司,這樣就會讓每個公司的人都動起來,想各種辦法來公司上班。

apiVersion: v1
kind: Company
metadata:
  name: helloworld
spec: 
  startTime: 932488234
  isOpen: false      
Kubernetes資源編排系列之四: CRD+Operator篇

從上面的例子可以看出,每個控制器隻負責自己的那部分,但從頂層往下看,已經實作了級聯控制,能夠實作牽一發而動全身的效果。這個就是上面所提到的operator的更深一層的機制:能夠像運算符一樣,讓幾種資源産生某種互動關系,一起協作完成一些複雜的工程動作。

3. 如何實作K8S Operator

不管是原生 YAML / Helm 還是 Kustomize,都是通過配置來搞定各類事情。然而 CRD + Operator 就不一樣了,它們讓你直接接入 apiserver,作為 K8S 的一部分監聽所有你關心的對象,并通過代碼進行狀态維持及管理。因為 CRD 的開發是非常複雜的,除了業務邏輯之外,還需要做很多基礎的工作,非常不便,是以有了 Operator 的開發架構(常見的有 KubeBuilder 和 Operator-SDK),讓開發人員專注于 CRD 的業務代碼開發。

我們可以來看一下operator的架構實作,這個有助于我們了解operator的工作原理:

Kubernetes資源編排系列之四: CRD+Operator篇

雖然有了 KubeBuilder 或 Operator-SDK 開發架構,但 Operator 的開發在目前所有的幾類元件托管方案當中仍然是最為複雜的。前前後後需要 CRD 設計及安裝,編譯 Operator 及部署到叢集,最後再下發 CR,外圍為了配套這些内容可能還需要上面 Helm 或 Kustomize 的協助,配合對應的 CICD 流程及工具。

Spark Operator

Spark Operator是大資料分布式系統在k8s場景一次經典的實踐。原本Spark的作業送出是需要通過spark-submit指令,但有了Spark Operator之後,我們可以直接向k8s送出作業YAML,然後Spark Operator監聽CR,将這一作業送出給控制器。實作了我們前文提到的,将作業資源放在k8s叢集進行管理這一目标。

Kubernetes資源編排系列之四: CRD+Operator篇

4. 大資料通用Operator設計與實踐

上文講述了operator實作的複雜性。不過,我們發現,越是這樣複雜的應用,越是會有一些共通性:因為這些複雜應用基本都是分布式應用,隻是在某些狀态或部署順序上的有些特殊需求。于是,我們針對這個現狀,開發了一款通用的大資料Operator。

這個通用Operator的架構設計如下:

Kubernetes資源編排系列之四: CRD+Operator篇

與市面上常見的golang編寫的operator不同的是,我們鼓勵使用者不編寫代碼,而是直接用yaml來描述控制邏輯,按照 感覺/決策/執行 三大環節來進行控制器的邏輯分解和編排設計。同時,因為有這幾個環節抽象的輔助,使用者在設計operator的時候能夠更有目的性,對于複雜場景,不引入過多的複雜邏輯流,盡量用無狀态的方式解決問題。

同時,我們還借鑒了前端架構React中的VirtualDOM的設計,在雲原生場景下,引入了VirtualResource這樣的一個概念。VirtualResource能夠将雲原生對象資源映射進行Operator的記憶體資料庫中,讓控制器能夠用SQL文法快速查詢和操作這些資源對象,簡化Reconcile(調和)場景的邏輯複雜性。對照React架構中生命周期的概念,VirtualResource也存在生命周期的概念,使用者能夠控制在資源變化的不同階段,追加一些自定義的運維描述動作。

我們在大量使用helm的情況下,發現golang template文法在進行模闆渲染的時候,還是不夠靈活。于是我們把整體架構棧切換到python,采用jinja2進行控制器的文法渲染,同時我們也保留helm在渲染架構中,使用者能夠無縫切換兩種渲染引擎。

這個通用Operator的控制器将原本需要golang編寫的控制層邏輯,簡化成使用 cmd(指令) + yaml(資源) 的方式進行描述。控制器的描述示例如下:通過helm将vvp這個應用的所有yaml下發,監聽service的狀态變化,同步更新ingress資源的狀态。

default:
  def: crd.yaml
  deploy:
    - cmd: helm
      chart: vvp/vvp
      values: vvp/values.yaml
  maintain:
    - watch:
        category: ResourceDidChange
        kind: Service
        apiVersion: v1
      action:
        - cmd: kube-patch
          file: ingressUpdate.yaml      

5. 總結

對于承載元件 (Component) 這個概念而言,CRD+Operator 可以說是最為複雜的,但是又是最萬能的,如果 Helm 或者 Kustomize 無法滿足需求,Operator 基本上是唯一的選擇。另一方面來說,CRD+Operator 一般又會和 Helm / Kustomize 相輔相成一起出現,最難搞的事情通過 Operator 與 apiserver 互動解決,剩下的膠水粘合,各種 YAML 拼接之類的交給 Helm / Kustomize 搞定。

同時,我們也可以看出,CRD+Operator是雲原生演進時期的方案,特别适合原本非k8s的軟體架構上k8s。那些原本就在k8s架構下出現的軟體方案,會逐漸淡化Operator這個概念: 所有的工作負載都有能力和k8s apiserver互動。

繼續閱讀