天天看點

一個controller調用另一個controller_如何在K8S中建立一個自定義Controller?

  • 目的
  • CRD資源定義
  • 代碼生成
  • Controller編寫

目的

Custom Resource

是擴充Kubernetes的一種方式(另外一種就是通過聚合層API

apiserver-aggregation

),而controller對指定的resource進行監聽和執行對應的動作(watch,diff,action)。Operator與Controller差別

  • 所有的Operator都是用了Controller模式,但并不是所有Controller都是Operator。隻有當它滿足: controller模式 + API擴充 + 專注于某個App/中間件時,才是一個Operator。
  • Operator就是使用CRD實作的定制化的Controller. 它與内置K8S Controller遵循同樣的運作模式(比如 watch, diff, action)
  • Operator是特定領域的Controller實作

讨論兩者差別:https://github.com/kubeflow/tf-operator/issues/300

是以先學習如何建構出一些自定義的Controller肯定是之後實作Operator的基礎。

實作一個自定義的Controller由兩部分組成:CRD和Controller邏輯代碼

這裡以sample-controller的代碼為例,同時我們自己寫的Controller也可以參考這個代碼結構。

CRD資源定義

以sample-controler中的為例,我們需要建立的一個

Foo

 如下example-foo.yaml:

建立該

Foo

 自定義資源後,期望建立出一個名稱為

example-foo

 ,副本數為

1

 的deployment。

它的CRD定義如下:

更多關于crd定義規則可以參考官方文檔:

https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/

當把這個crd資源apply到叢集中後,我們可以通過

kubectl get apiservice v1alpha1.samplecontroller.k8s.io -o yaml

 指令看到注冊這個

apiservice

代碼編寫

隻需要将我們

Foo

 resource相關的struct,其餘的類似自定義資源的

informers

 ,

listers

 ,

clientset

 以及

deepcopy

 的代碼都可以通過工具code-generator自動生成。

以及編寫我們自定義Controller的業務邏輯代碼就好了

struct資源定義

type.go

其中類似

+k8s:

 的注釋是代碼生成來識别的。

register.go 中将

Foo

 ,

FooList

 注冊進入

scheme

 中。

代碼生成

代碼生成能幫我處理大部分重複代碼,主要通過

https://github.com/kubernetes/code-generator

這個包進行解析tag并生成。

全局tag

必須在目标包的doc.go檔案中聲明,典型路徑是 pkg/apis///doc.go。

内容示例:

局部tag

  • +genclient: 為這個 package 建立 client。
  • +genclient:noStatus: 當建立 client 時,不存儲 status。
  • +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object: 為結構體生成 deepcopy 的代碼,實作了 runtime.Object 的 Interface。

代碼生成:

通過./hack/update-codegen.sh方法可以生成,client以及deepcopy代碼。

包含:

_前提:code-generator 已經在vendor中,執行

go mod vendor

 _

update-codegen.sh内容如下

完整使用說明:

Usage: generate-groups.sh     ...        the generators comma separated to run (deepcopy,defaulter,client,lister,informer) or "all".    the output package name (e.g. github.com/example/project/pkg/generated).      the external types dir (e.g. github.com/example/api or github.com/example/project/pkg/apis).   the groups and their versions in the format "groupA:v1,v2 groupB:v1 groupC:v2", relative
                      to .
  ...                 arbitrary flags passed to all generator binaries.
Examples:
  generate-groups.sh all             github.com/example/project/pkg/client github.com/example/project/pkg/apis "foo:v1 bar:v1alpha1,v1beta1"
  generate-groups.sh deepcopy,client github.com/example/project/pkg/client github.com/example/project/pkg/apis "foo:v1 bar:v1alpha1,v1beta1"
           

interface{}處理

場景:如果我們需要一個通用的類型的object,如下:

我們在spec裡面定義了一個fields字段,類型是object(即key ,value的形式),value的值可能是int也可能是string或者bool

在type定義的時候我是這麼寫的,定義為map[string]interface{}

當在代碼生成的時候,發現會報錯:

遇到這種問題,需要自己實作深拷貝,例如這種:

Controller編寫

在編寫Controller之前需要了解client-go中的informer機制:

一個controller調用另一個controller_如何在K8S中建立一個自定義Controller?
  • 黃色的部分是controller相關的架構,包括workqueue。
  • 藍色部分是client-go的相關内容,包括informer, reflector(其實就是informer的封裝), indexer。
  • 從流程上看,reflector從apiserver中通過list&watch機制接收事件變化,進入Delta FIFO隊列中,由informer進行處理。
  • informer會将delta FIFO隊列中的事件交給indexer元件,indexer元件會将事件持久化存儲在本地的緩存中。
  • 之後,由于使用者事先将為informer注冊各種事件的回調函數,這些回調函數将針對不同的元件做不同的處理。
  • 例如在controller中,将把object放入workqueue中,之後由controller的業務邏輯中進行處理。處理的時候将從緩存中擷取object的引用。即各元件對資源的處理僅限于本地緩存中,直到update資源的時候才與apiserver互動。

簡單來講通過list/watch機器提供了本地緩存避免每次去請求apiserver。

并且提供了Event Handler方法,在将資料儲存進入cache時,通過調用自定義handler方法,增加自定義處理。

是以Controller 的代碼結構,就是如下:

  • 在NewController中通過fooInformer添加

    AddEventHandler

     ,執行

    enqueueFoo

  • enqueueFoo 中将擷取的變更放入隊列
  • 啟動一個或多個work從隊列中取資料,最終通過syncHandler進行業務判斷
  • 在sample-controller中同時還監聽了deployment,在

    handleObject

     判斷是否歸屬

    Foo

     Kind ,同時執行

    enqueueFoo

      入隊。

具體代碼判斷參考controller.go

在main.go方法中調用如下:

就是調用通過coge-gen生成代碼建立informer并啟動,建立client。

思考:為什麼要通過隊列來控制資料的變化? 

我覺得用隊列一方面是解耦,因為往往一個Controller裡面可能要通過informer監聽各類資源對象,通過隊列借助了各個informer的依賴。另一友善可以通過不同類型的隊列比如限速隊列,延遲隊列達到不同的并發控制。

總結

  • 先定義crd,再實作Controller邏輯
  • 可以通過code-generator生成informer,client,listers代碼
  • 注意針對interface{}類型需要自己實作deepCopy方法
  • 實作一個Operator,內建更多更複雜的Controller的話,我們一般使用Operator架構,比如kubebuilder

參考

  • https://juejin.im/post/5de097bbf265da05d849ab53
  • https://blog.fatedier.com/2019/04/02/k8s-custom-controller/
  • https://blog.gmem.cc/crd

推薦閱讀

  • Linux、K8S、Go等是如何設計排程系統的?排程系統設計精要

喜歡本文的朋友,歡迎關注“Go語言中文網”:

一個controller調用另一個controller_如何在K8S中建立一個自定義Controller?

Go語言中文網啟用微信學習交流群,歡迎加微信:274768166,投稿亦歡迎