在part 2中,我們談到了Controller概念,探討了它是怎麼使用到了Reclector提供的功能。如果你沒還有關注並瞭解它的全部內容,建議你從part 1開始閱讀。
在這一節中,我們將詳細介紹一下亮點:
1.controller類型的標準實現(嚴格來講,這只是衆多可能性的一種,但不幸的是,它對Controller概念的期望添加了色彩);
2.informer和SharedInformer的概念,尤其是SharedIndexInformer;
controller–struct-Backed Controller Implementation
從前面的文章看,Controller類型並不明確。Controller只要實現3個未說明的函數:Run,HasSynced和LastSyncResourceVersion即可。從技術上講,你可以以任何方式實現它。實際上,這種controller-struct-backed是有標準參考實現的,也表明存在一個隱含的要求,假定任何Controller都將實現Run函數作爲其一部分,同時也要處理Queue,另外Reflector也必須要有。這是很多假設,當我們用Java對比進行建模時,我們將正式提到他們。
讓我們仔細觀察queue處理的細節。首先,回想下Queue只是一個具有Pop功能的Store(詳情見part 2)。
在controller.go中,我們單獨查看processLoop函數:
// processLoop drains the work queue.
// TODO: Consider doing the processing in parallel. This will require a little thought
// to make sure that we don't end up processing the same object multiple times
// concurrently.
//
// TODO: Plumb through the stopCh here (and down to the queue) so that this can
// actually exit when the controller is stopped. Or just give up on this stuff
// ever being stoppable. Converting this whole package to use Context would
// also be helpful.
func (c *controller) processLoop() {
for {
obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process))
if err != nil {
if err == FIFOClosedError {
return
}
if c.config.RetryOnError {
// This is the safe way to re-enqueue.
c.config.Queue.AddIfNotPresent(obj)
}
}
}
}
由於這是go函數並且以小寫p開頭,因此我們知道它對於controller-struct支持的實現是私有的。現在,我們相信在其它的函數中調用了它。
在這個loop中,我們可以直觀的看到Obj從Queue中popoed,如果發生錯誤會重新將obj入隊。
更具體的說,PopProcessFunc(c.config.Process)的結果是go類型轉換,在這種情況下,將創建時提供給此Controller實現的Config中存儲的ProcessFunc轉換爲fifo.go文件定義的PopProcessFunc。回想一下c.config.Process的類型是ProcessFunc。 (無論如何,對於這個Go菜鳥來說,這兩種類型似乎相當。)
從Java程序員的角度來看,我們開始看到了一個抽象類-而不是一個接口。具體來說,其強制執行的Run函數顯然”應該”與上面的processLoop中的概述具有相同的調用邏輯方式。並且可以以一種或另一種方式預期Controlle實現,將Queue中的內容耗盡,並且期望Run並行的完成這些。我們暫時將其歸檔,但現在我們已經非常詳細地瞭解了Controller的預期實現,即使這些內容在contract中沒有真正被提及。
在這一點上你可能會遇到的一個問題是:我們不是這樣做的嗎?我們是否通過一種機制reflect k8s的apiserver通過lists和watches到cache,以及我們對Controller實現的瞭解,處理cache的能力?這不是給我們一個框架來接收和處理k8s specification消息 ?
現在,答案是否定的,隨意到目前爲止,所有的東西都推到了Controller的總稱之下。回想一下,隱藏在它下面的是Reflectors,Stores,Queues,ListerWatchers,ProcessFuncs等概念,但是現在你可以將它們全部包裝到Controller中,並釋放一些空間用於下一步:informers。
Informers
此時你可能知道了文件命名格式:Controller定義在controller.go中,ListerWatcher在listwatch.go中定義,依此類推。 但是你找不到一個informer.go文件。 你不會找到一個Informer類型。然而,informer的概念,你可以從它的邏輯中拼湊起來,雖然不具體。
首先,讓我們查看NewInformer function,它定義在controller.go:
// NewInformer returns a Store and a controller for populating the store
// while also providing event notifications. You should only used the returned
// Store for Get/List operations; Add/Modify/Deletes will cause the event
// notifications to be faulty.
//
// Parameters:
// * lw is list and watch functions for the source of the resource you want to
// be informed of.
// * objType is an object of the type that you expect to receive.
// * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate
// calls, even if nothing changed). Otherwise, re-list will be delayed as
// long as possible (until the upstream source closes the watch or times out,
// or you stop the controller).
// * h is the object you want notifications sent to.
//
func NewInformer(
lw ListerWatcher,
objType runtime.Object,
resyncPeriod time.Duration,
h ResourceEventHandler,
) (Store, Controller) {
// This will hold the client state, as we know it.
clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc)
// This will hold incoming changes. Note how we pass clientState in as a
// KeyLister, that way resync operations will result in the correct set
// of update/delete deltas.
fifo := NewDeltaFIFO(MetaNamespaceKeyFunc, nil, clientState)
cfg := &Config{
Queue: fifo,
ListerWatcher: lw,
ObjectType: objType,
FullResyncPeriod: resyncPeriod,
RetryOnError: false,
Process: func(obj interface{}) error {
// from oldest to newest
for _, d := range obj.(Deltas) {
switch d.Type {
case Sync, Added, Updated:
if old, exists, err := clientState.Get(d.Object); err == nil && exists {
if err := clientState.Update(d.Object); err != nil {
return err
}
h.OnUpdate(old, d.Object)
} else {
if err := clientState.Add(d.Object); err != nil {
return err
}
h.OnAdd(d.Object)
}
case Deleted:
if err := clientState.Delete(d.Object); err != nil {
return err
}
h.OnDelete(d.Object)
}
}
return nil
},
}
return clientState, New(cfg)
}
正如你所看到的,沒有informer這樣的東西。但是你也可以看到這裏有一個隱式構造:一種特殊的Controller,實際上,它的相關隊列叫做DeltaFIFO(後面我們會講到),因此可以在增量上運行,並且有某種類型的在給定增量之後被通知的事件處理程序的概念被“轉換”回其各自的對象 - 動詞組合。我們看到ListerWatcher和Config也在那裏:你可以看到你正在爲Reflector及其包含的Controller提供原材料,以及自定義ProcessFunc通過委託方式給事件處理程序。
實際上,NewInformer函數只是利用Go的能力來擁有多個返回類型並返回一個Controller及其關聯的Store,而不是一個Informer,因爲沒有這樣的東西,並添加了一些未強制要求的Store的使用(即不要修改它)。客戶端可以“丟棄”Controller並僅使用返回的Store,可能與它提供的事件處理程序一起使用,也可能不使用,並且Store可以作爲Kubernetes apiserver的cache。
從這一切我們可以假定概念上informer”是一個Controller,它具有將其Queue相關的操作分發到適當的事件處理(event handler)的能力。當我們在Java中對此進行建模時,這將有助於我們,對Java程序員來說,應該開始看起來有點像舊的JavaBeans事件監聽器。這是一個特別好的見解,因爲我們最終想要讓一些Java程序員編寫某種方法,在發現添加,修改或刪除時調用,而不需要程序員擔心所有多線程到目前爲止我們遇到的隊列操作。 它也有幫助,因爲這種情況是Java最終可以幫助我們編寫更簡單的代碼的情況之一。
正如您所料,有不同的具體類型的informers。 我們將特別關注一個,但肯定還有其他的informers。 我們將看到的那個叫做SharedIndexInformer,可以在shared_informer.go文件中找到。
SharedIndexInformer
SharedIndexInformer本身就是一個SharedInformer實現,它增加了索引其內容的能力。 我提到它的所有之前的參考只是在set階段:你應該問一些問題:什麼是共享的? 什麼被索引? 爲什麼我們需要分享東西等?
我們看下SharedInformer定義:
// SharedInformer has a shared data cache and is capable of distributing notifications for changes
// to the cache to multiple listeners who registered via AddEventHandler. If you use this, there is
// one behavior change compared to a standard Informer. When you receive a notification, the cache
// will be AT LEAST as fresh as the notification, but it MAY be more fresh. You should NOT depend
// on the contents of the cache exactly matching the notification you've received in handler
// functions. If there was a create, followed by a delete, the cache may NOT have your item. This
// has advantages over the broadcaster since it allows us to share a common cache across many
// controllers. Extending the broadcaster would have required us keep duplicate caches for each
// watch.
type SharedInformer interface {
// AddEventHandler adds an event handler to the shared informer using the shared informer's resync
// period. Events to a single handler are delivered sequentially, but there is no coordination
// between different handlers.
AddEventHandler(handler ResourceEventHandler)
// AddEventHandlerWithResyncPeriod adds an event handler to the shared informer using the
// specified resync period. Events to a single handler are delivered sequentially, but there is
// no coordination between different handlers.
AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration)
// GetStore returns the Store.
GetStore() Store
// GetController gives back a synthetic interface that "votes" to start the informer
GetController() Controller
// Run starts the shared informer, which will be stopped when stopCh is closed.
Run(stopCh <-chan struct{})
// HasSynced returns true if the shared informer's store has synced.
HasSynced() bool
// LastSyncResourceVersion is the resource version observed when last synced with the underlying
// store. The value returned is not synchronized with access to the underlying store and is not
// thread-safe.
LastSyncResourceVersion() string
}
這告訴我們所有的SharedInformer(Controller和事件分發機制的組合)必須做什麼。SharedInformer是一種可以支持許多事件處理程序的informer。由於這個特定的informer構造可以有許多事件處理程序(event handlers),然後構建它的單個緩存 - 由它構建的Controller所包含的Queue,在大多數情況下 - 在這些處理程序之間變爲“共享”。
所有SharedIndexInformer添加到picture中都是爲了能夠通過各種鍵在其緩存中查找items:
type SharedIndexInformer interface {
SharedInformer
// AddIndexers add indexers to the informer before it starts.
AddIndexers(indexers Indexers) error
GetIndexer() Indexer
}
在這裏你需要非常注意單數和複數名詞。 請注意,您添加的是一個索引器(Indexers),複數(plural),您得到的是一個索引器(Indexer),單數(singular)。 讓我們來看看它們是什麼。
我們將從複數形式的索引器開始,因爲奇怪的是它原來是單個項目,而不是,最值得注意的是,它是一堆Indexer實例!
這實際上只是一種map(單數)類型,可以在index.go文件中找到:
// Indexers maps a name to a IndexFunc
type Indexers map[string]IndexFunc
反過來,IndexFunc只是一個映射函數:
// IndexFunc knows how to provide an indexed value for an object.
type IndexFunc func(obj interface{}) ([]string, error)
因此,將它交給Kubernetes資源,它將以某種方式返回一組(我假設)與其對應的字符串值。因此,索引器是具有複數名稱的單個對象,因此是這些函數的簡單映射,其中每個函數依次在其自己的字符串鍵下索引。因此,您可以將一堆這些映射添加到SharedIndexInformer中以供其使用。Indexer是一個描述聚合概念(!)的單數名詞,是這類Indexers實例的集合(!),其中一些額外的聚合行爲分層在頂層:
// Indexer is a storage interface that lets you list objects using multiple indexing functions
type Indexer interface {
Store
// Retrieve list of objects that match on the named indexing function
Index(indexName string, obj interface{}) ([]interface{}, error)
// IndexKeys returns the set of keys that match on the named indexing function.
IndexKeys(indexName, indexKey string) ([]string, error)
// ListIndexFuncValues returns the list of generated values of an Index func
ListIndexFuncValues(indexName string) []string
// ByIndex lists object that match on the named indexing function with the exact key
ByIndex(indexName, indexKey string) ([]interface{}, error)
// GetIndexer return the indexers
GetIndexers() Indexers
// AddIndexers adds more indexers to this store. If you call this after you already have data
// in the store, the results are undefined.
AddIndexers(newIndexers Indexers) error
}
因此,從SharedIndexInformer可以獲得一個真正的索引器,該索引器在邏輯上由通過(希望)添加的SharedIndexInformer的AddIndexers函數添加的各種Indexers實例組成,儘管也可以直接從Indexer添加它們。
另一個非常重要的事情是,Indexer也是Store! 但要非常小心地注意它作爲Store的使用方式。
具體來說,讓我們首先回想起一個非共享的informer - 它沒有Go語言的具體化 - 在概念上是Controller和Store的組合。 例如,我們已經看到NewInformer函數返回一個Controller和一個附加到該Controller的Store; 該組合爲您提供自動填充緩存的能力。
在SharedIndexInformer中,反映Kubernetes apiserver事件的Store是DeltaFIFO,而不是Indexer。但是Indexer被提供給DeltaFIFO,並且作爲SharedIndexInformer的GetStore函數的返回值! 這告訴我們更多關於GetStore隱含的信息:顯然它返回的Store不得用於修改!
另一個非常重要的事情是任何Store也是KeyListerGetter,KeyLister和KeyGetter。 KeyListerGetter是KeyLister和KeyGetter類型的組合。KeyLister可以列出其所有keys包含的,KeyGetter可以通過key檢索(就像map一樣)。
因此,當SharedIndexInformer創建一個新的DeltaFIFO用作其Store時,然後爲該DeltaFIFO提供一個Indexer,它僅以KeyListerGetter的身份提供該Indexer。 類似地,它的GetStore方法可能真的應該返回比實際Store更接近KeyListerGetter的東西,因爲禁止調用它的修改方法。
我們將不得不潛入DeltaFIFO,我們現在已經看到它是所有這一切的核心,並將成爲下一篇文章的主題。
讓我們回顧一下到目前爲止我們在這裏提出的問題:
- Controller實際上必須像引用controller–struct-backed的Controller實現一樣。 具體來說,它需要管理一個Queue,它通常是DeltaFIFO,並且填充和排空它,幷包含一個事件監聽器機制。
- 存在informer,它是Controller和它所連接的Store的組合,但除了“共享”形式外,它不存在。
- SharedIndexInformer實例化具有多個事件處理程序的informer的概念,並且根據定義,它們共享一個Queue,一個DeltaFIFO。
我們的視圖如下所示: