理解Kubernetes’ tools/cache包: part 3

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。
    我們的視圖如下所示:
    這裏寫圖
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章