經過前兩篇的學習與實操,也大緻掌握了一個k8s資源的Controller寫法了,如有不熟,可回顧
自己實作一個Controller——标準型
自己實作一個Controller——精簡型
但是目前也隻能對k8s現有資源再繼續擴充controller,萬一遇到了CRD呢,進過本篇的學習與實操,你就懂了。
先說說CRD-controller的作用,本CR原意是記錄雲主機ECS及node節點映射資訊,而本controller則把這個映射操作省略掉,隻為所有建立成功的CR打一個Label。而本篇為達成此目的,需要執行的步驟有以下幾個:
往k8s建立一個CRD
定義對應CRD的api,包含了struct
給CRD的api注冊到scheme
實作這個CRD的clinet,封裝其請求apiserver的相關操作
實作一個Informer和Lister
實作一個controller
通過上述步驟,可以繞開ApiBuilder腳手架,自己手捏一個CRD-Controller出來。可以更好的了解整個Informer機制的架構
建立CRD的manifest如下所示
這裡比較值得注意是ApiGroup需要定好,這個group到後續給scheme注冊資源類型時用到,影響往apiserver去互動管理資源。
這個api可能容易給人造成誤解,實際是定義CR的struct,包含什麼字段,檔案路徑api/v1/ecs-bing.go
自上而下定義EcsBinding和EcsBindingList兩個struct,由于要實作runtime.Object的接口,需要實作DeepCopyObject方法,如果用腳手架生成的代碼,這部分實作接口的代碼就不用手敲
scheme注冊這裡分兩部分,一部分是定義一個scheme,另一部分是在各個api裡面提供AddToScheme函數,這個函數用于把各種類型各種版本的api(也就是GVK)注冊到scheme
先看第一部分,檔案路徑client/versiond/scheme/register.go
在AddToScheme中就是調用各個kind的AddToScheme,盡管這裡隻有一個Kind。第二部分的又回去api/v1/ecs-bing.go
此處的Group需要和之前定義CRD時的group一緻
這裡實際定義了一個clientSet,clientset應該包含多個version,一個version包含多個資源類型,但是這裡隻有一個version,一個kind。clientSet的結構如下所示
clientSet位于client/versiond/clientset.go
EcsV1位于client/versiond/typed/ecsbinding/v1/ecs_client.go中,它的RESTClient也用于傳遞給EcsClient,用于EcsClient對apiserver通訊的http用戶端
EcsBindingClient位于client/version/typed/ecsbingding/v1/ecs-binding.go中,定義了client的各種操作方法,封裝了對apiserver的各個http請求。
各個client的初始化,則是由最外層把Config一層一層的往裡面具體的Client傳。整套client的代碼不在這裡展示,僅展示一下調用鍊
當調用EcsV1的EcsBinding方法(也就是擷取EcsClient)時,才調用newEcsbindings構造函數構造一個client
ecsbindv1.NewForConfig的代碼如下:
在這個函數中先給config設定預設參數,最後按照這些預設參數構造出一個RESTClient,這個RESTClient傳遞給EcsV1Client,一個作用是把它自己的一個成員restClient,另一個作用就是用于構造EcsClient所需的RESTClient。
setConfigDefaults函數定義如下
函數給config指定了groudversion這個gv就是hopegi.com v1;api的位址固定是"/apis",通過這兩句可以确定用戶端跟apiserver通訊時的位址是/apis/hopegi.com/v1,後面設定scheme的序列化器,用于把apiserver響應的json資料反序列化成struct資料。
EcsBindingClient接口定義的函數如下
以List方法實作作例子
client成員則是先前構造時傳入的RESTClient,Resource指定資源的名ecsbingding,當有CR傳回時需要執行SetGroupVersionKind,否則拿到的CR結構體會丢失GroupVersion和Kind資訊
在實作某個資源的Informer之前,要實作一個Informer的Factory。這個Factory的作用有幾個,其一是用于構造一個Informer;另外就是在start一個Controller的時候調用它Start方法,Start方法内部就會把它管理的所有Informer Run起來。
SharedInformerFactory接口的定義如下所示,代碼位于controller-demo/informers/factory.go
這裡主要是暴露一個構造并擷取各個Group的Interface,Start方法的接口則來源于它繼承的internalinterfaces.SharedInformerFactory接口,代碼位于 controller-demo/informers/internalinterface/factory_interfaces.go
除了Start方法,InformerFor跟構造一個Informer有關,實作Informer的時候會調用到factory的方法,後續會再介紹
V1的Interface則是涵蓋了這個版本下各個資源的用戶端接口,代碼位于controller-demo/informers/ecsbind/v1/interface.go
這樣也剛好跟k8s的api的層級相呼應,先是ApiGroup,再到Version,最後到Kind,就是GVK
一個Informer的最核心邏輯是List和Watch方法,不過我們實作一個Infomer時隻需要給SharedIndexInformer提供這兩個方法就可以,調用這兩個方法的邏輯由SharedIndexInformer統一實作
實際上僅僅是調用了client而已,client則是來源于這個CR的Informer——EcsBindingInformer,看看它的接口定義和結構
對外暴露的EcsBindingInformer僅僅是一個接口,暴露Informer和LIster兩個方法,實作則交由一個内部的結構實作,縱觀這個CRD的client,CR的client,clientset,Informer乃至後續的lister都是這樣的模式。
EcsBindingInformer的Informer()實作如下
如前面介紹Factory的時候所介紹的,Informer建立時需要調用factory的InformerFor方法,傳入資源的指針以及一個函數回調
回調的聲明在internalinterface處,controller-demo/informers/internalinterface/factory_interfaces.go
在這裡就是ecsBindingInformer.defaultInformer,調用這個方法時就會把factory的client傳遞到構造SharedIndexInformer函數,這樣List函數和Watch函數就有client使用,相當于整個構造過程是
建立一個client,将這個client傳遞給Factory
建立一個Informer時,會通過Factory經過GVK三個層次的接口調到對應資源的Informer,同時factory的執行個體也會經過每一級往下傳遞
調用Inform()方法獲得SharedIndexInformer,依次經過EcsBindingInformer.Informer()-->d.defaultInformer(即:NewInformerFunc回調)-->NewFilteredEcsBindingInformer
EcsBindingInformer接口的另一個方法就是擷取Lister,僅僅需要把SharedIndexInformer的Indexer傳遞過去則可,Lister的緩存機構已由SharedIndexInformer實作
作為apiserver的緩存,供controller調用快速擷取資源,是以它需要提供兩個查詢的方法,代碼位于controller-demo/listers/ecsbind/v1/ecs-binding-lister.go
controller所依賴的各個元件都已經實作完畢,現在可以實作這個crd的controller,完整的實作不展示,大緻跟上一篇NodeController類似。僅展示他的字段和構造函數
最後把controller加到controller的Start方法中,統一啟動
本篇雖然是說定義個CRD的controller,然而卻把更多的篇幅放到的controller外的一些元件上:api,client,informer。但正事如此自己編碼過一次,才會加深印象,後續在檢視K8S源碼時遇到controller的源碼摳出其核心邏輯,通過client去翻查api位址,才會快速上手。本篇的目的也就如此。
client-go源碼分析--informer機制流程分析
kubernetes client-go解析
深入淺出kubernetes之client-go的SharedInformer