天天看點

自己實作一個Controller——終極型

經過前兩篇的學習與實操,也大緻掌握了一個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