天天看點

自己實作一個Controller——标準型

上一篇通過一個簡單的例子,編寫了一個controller-manager,以及一個極簡單的controller。進而對controller的開發有個最基本的認識,但是細心觀察前一篇實作的controller僅僅是每次全量擷取了所有資源,雖然都是從緩存中擷取速度是比較快的,如果單次處理一個資源時的時間比較長,而且沒必要每次都把所有資源都掃描一遍,上一篇實作的controller顯然不符合使用場景了,本篇将繼續用另一種方式開發一個結構較為标準的Controller,并介紹支撐controller功能實作的Informer架構。

先介紹一下本次實作的controller需要實作的功能,本controller通過監控Node節點的新增,發現新增是通過特定方式加入叢集的,就給該node打上label。

看下本次controller的結構包含的成員

下面是Controller的構造函數

傳入構造函數的Informer除了提供Lister和HasSynced函數以外,多了一個事件注冊的操作,注冊了node資源的Add事件和Delete事件。這個AddEventHandler除了注冊這兩種事件外,還可以注冊Update事件,由于在這次例子中不需要處理這方面的事件,是以沒有用上。此處通過Informer的事件回調實作了哪個資源有變更,就會觸發事件,通知到controller來,與前一個controller相比這種就能立馬定位到有變更的資源,無需将資源全量掃描才找到對應的資源。代碼中兩個事件處理函數都是擷取到變更的node,列印出node的名字和變更事件,最後調用syncNodes方法

syncNodes單純給資源求了一個key,再把這個key塞到workqueue中。接收informer傳遞過來的變更事件處理也就此結束。

然後到啟動controller的Run方法

接收終止信号和緩存處理與之前的controller沒多大差別,差別就在于啟動了若幹個worker協程,worker方法就從workqueue中取出key,然後處理node

由于workqueue裡存的也隻是代表資源的key,此時需要将key轉換回資源的name,還是通過lister從cache中把資源取出。當取不出來,就表明資源已經被删除了,如果有相應的删除相關的邏輯就可以在此處執行;如果能取出來,目前的資源已經是最新版本的資源(更新時會有新舊兩個版本的資源,這裡拿到的是新版本的),當然在本例中沒有更新這個事件的處理;整個過程中出現了非期望的錯誤(如因資源被删除導緻的IsNotFound error),處理邏輯需要重試,重試的方式就是将延時重新入隊,延時隊列的作用在此處得以發揮,因為有可能出現這種極端場景:目前隻有一個資源需要被處理,而且該資源在剛建立的時候因為其狀态未就緒确實會一直處理失敗,假設當其被處理失敗時馬上又重新入隊,那整個controller就會陷入一個類似于死循環的狀态中,直到資源狀态就緒,這樣會浪費不少計算資源。

最後的processNodeAddIntoCluster就是給node打上label并調用kubeclient更新之,這裡就不展示了。

看到上面使用了workqueue,頗有生産者消費者的模式,從informer的事件處理函數中擷取到變更的資源将其放入隊列,這個是生産者;worker處理方法從workqueue裡取出變更資源處理,這個是消費者。使用了這個workqueue而不是直接在事件處理函數直接處理的目的在于事件觸發的速度與資源處理的速度不一緻,有workqueue在其中起到緩沖作用,免除了因實際處理變更的邏輯造成了事件觸發方的阻塞導緻影響了事件的實時性。

類似的隊列和生産者消費者模式在informer中也有出現,作為controller處理資源變更調諧資源狀态的上遊,它又如何提供及時的變更事件給下遊的controller,以及給下遊的controller提供與apiserver一緻的資源緩存。

Informer機制架構如下圖所示

自己實作一個Controller——标準型

從該架構中還涉及了幾個相關元件:

Reflector:用于監控apiserver相關資源的變化,及時把變更擷取回來,觸發相關的變更事件,變更的資源存放到Delta FIFO裡面;

Delta FIFO:一個存放資源變更的FIFO隊列,裡面元素是以 Add/Update/Delete 為key,變更的資源為value這樣的一個對象;

Indexder:用于存放從Delta FIFO隊列取出後,經過處理好的值,是apiserver的一個緩存,無需每次從apiserver以及Etcd中擷取,減輕兩者的壓力,裡面的緩存應與apiserver(實質是Etcd)保持一緻。

從上面的結構中,尤其是Delta FIFO,模式與上面使用了WorkQueue的controller是一緻的,生産者消費者模式:

生産者方面:主要操作由Reflector執行,它主要依靠ListAndWatch方法對apiserver的資源進行監控,ListAndWatch方法中就調用了由Informer提供的List方法和Watch方法,一旦獲得變更的資源,就将資源及其變更的方式(Add,Update,Delete)一同存入Delta FIFO,如上面介紹Delta FIFO時所述;

消費者方面:主要操作由Controller執行,它主要調用Delta FIFO的Pop方法,從隊列中取出變更的資源及其變更方式,調用之前注冊到Informer裡面相關的事件(即例子中NewNodeController函數中AddEventHandler方法注冊進去的事件處理函數),将變更分發出去,在這裡Delta FIFO除了記錄變更的資源本身資料,也附帶記錄變更方式的作用展現出來了。最後将這個資源緩存到Indexer中,緩存時也是通過變更方式對緩存進行操作,比如是Add變更,則直接往緩存中Add一個記錄;是Update則把緩存記錄更新;是Delete則删除記錄。這也是另一個展現記錄變更方式的地方。

Informer和自定義的Controller(這個controller并非消費者的controller)的互動主要是兩處,其一是AddEventHandler時,把變更資源收到自己的workerqueue中供後續執行調諧操作;其二是在調諧期間從Indexer(這個并非是直接調用,是通過調用Informer時嵌套調用的)擷取緩存的資源值。整個過程所有涉及的成員如下圖所示

Informer在整個過程中起到銜接各方的作用。Reflector對apiserver的監控是由Informer提供的ListAndWatch方法,資源從apiserver接口拿回來資源資料由注冊進去Informer的反序列化器進行反序列化(這部分跟Schema有關)。而資料進入Indexer緩存前,的事件分發也是通過Informer調用各個已經注冊上來的事件處理函數。

了解了Informer機制,後面則進行自定義資源的Controller開發,裡面還需要給自定義資源擴充一個Informer,及封裝跟apiserver通路自定義資源的client,實作完整的手捏Controller.