This page looks best with JavaScript enabled

Kubernetes Reflector 我在盯著你 ( I )

 ·  ☕ 4 min read

首先本文所有的 source code 基於 kubernetes 1.19 版本,所有 source code 為了版面的整潔會精簡掉部分 log 相關的程式碼,僅保留核心邏輯,如果有見解錯誤的地方,還麻煩觀看本文的大大們提出,感謝!

幾本上這一塊相當的複雜與龐大,有些部分我認為不需要深入理解其運作的機制,我們就來看看一個 Reflector 是透過哪裡零件組裝起來的吧!

Informer

實際上我不知道這裡到底要叫 Reflector 又或是 Informer ,anyway 我想要了解這麼龐大的東西要先從範例入手,我從 client go 的範例可以看到 Reflector/Informer 的一些蛛絲馬跡。
source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

    // 先不管他底層是如何實作的,這裡就是會監聽kubernetes api server 
    // 監控 pod 的變化,會帶一些條件例如: 要監聽的 namespace ,label等等
	podListWatcher := cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "pods", v1.NamespaceDefault, fields.Everything())
    
    // 傳入監控pod的物件、pod的資料結構,resync的時間
    // 處理事件的控制function以及儲存資料的地方(local cache)
        indexer, informer := cache.NewIndexerInformer(podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
		...
        ,
	}, cache.Indexers{})    
    
    
    //建立 infomer 物件
func NewIndexerInformer(
	lw ListerWatcher,
	objType runtime.Object,
	resyncPeriod time.Duration,
	h ResourceEventHandler,
	indexers Indexers,
) (Indexer, Controller) {
	// 建立一個 indexer 作為local cache用
	clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers)
        //回傳 indexer 以及 實作 controller interface 的物件(之後會講到先不用管他)
	return clientState, newInformer(lw, objType, resyncPeriod, h, clientState)
}

    //基本上就是封裝成實作 controller interface 的物件(之後會講到先不用管他)
func newInformer(
	lw ListerWatcher,
	objType runtime.Object,
	resyncPeriod time.Duration,
	h ResourceEventHandler,
	clientState Store,
) Controller {

    ...
    return New(cfg)
}

我認為 client go 這個範例很好,很清楚明白地看出 informer 需要什麼物件分別是

  1. ListerWatcher
  2. runtime.Object
  3. ResourceEventHandler
  4. Store

像是 Store interface 我們已經看過了,就不再多提,我們先從 ListerWatcher 來看看這傢伙是什麼玩意。

ListerWatcher

從剛剛的範例來看在建立 Informer 的時候傳入了一個 podListWatcher 這是由

cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "pods", v1.NamespaceDefault, fields.Everything())這個 function 建立起來的,這個 function 回傳了實作 ListWatch interface 的物件,我們先從這個 interface 來看吧!

interface

這個 interface 很簡單就是列出(list)要觀測的 object 以及追蹤(watch)要觀測的 obejct 。
source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// ListerWatcher 組合了 Lister 跟 Watcher 接下去看 這兩個interface負責什麼
type ListerWatcher interface {
	Lister
	Watcher
}

// Lister is any object that knows how to perform an initial list.
type Lister interface {
	// 根據 ListOptions 來決定要列出哪些物件
	List(options metav1.ListOptions) (runtime.Object, error)
}

// Watcher is any object that knows how to start a watch on a resource.
type Watcher interface {
	// 根據 ListOptions 來決定要跟蹤哪些物件
	Watch(options metav1.ListOptions) (watch.Interface, error)
}

看完了 ListerWatcher 相關的 Interface 後我們來看一下實作 ListerWatcher 的資料結構吧!

Struct

先打預防針不是每一個 ListerWatcher 都是透過以下方式實作的,本章節只討論 client go 範例的調用鏈,我猜其他實作的方式也差不多吧?(有時間再來研究其他的 listwatch 怎麼做)
source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 定義實作 Lister 的 struct
type ListFunc func(options metav1.ListOptions) (runtime.Object, error)

// 定義實作 Watcher 的 struct
type WatchFunc func(options metav1.ListOptions) (watch.Interface, error)


// ListWatch knows how to list and watch a set of apiserver resources.  It satisfies the ListerWatcher interface.
// It is a convenience function for users of NewReflector, etc.
// ListFunc and WatchFunc must not be nil
type ListWatch struct {
	ListFunc  ListFunc
	WatchFunc WatchFunc
	// DisableChunking requests no chunking for this list watcher.
	DisableChunking bool
}

New Function

我們來看一下 cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "pods", v1.NamespaceDefault, fields.Everything())這個 function 做了什麼
source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// NewListWatchFromClient通過指定的 client , resource ,namespace
//  和 fieldsSelector 建立一個ListWatch。
func NewListWatchFromClient(c Getter, resource string, namespace string, fieldSelector fields.Selector) *ListWatch {
    //封裝 fieldSelector 為 func(options *metav1.ListOptions)
	optionsModifier := func(options *metav1.ListOptions) {
		options.FieldSelector = fieldSelector.String()
	}
    //建立一個ListWatch
	return NewFilteredListWatchFromClient(c, resource, namespace, optionsModifier)
}

// NewFilteredListWatchFromClient 跟上面那個很類似不過他是接收 optionsModifier 而不是 fieldsSelector
func NewFilteredListWatchFromClient(c Getter, resource string, namespace string, optionsModifier func(options *metav1.ListOptions)) *ListWatch {
    //建立一個實作 lister  的物件
	listFunc := func(options metav1.ListOptions) (runtime.Object, error) {
            //這個手法滿厲害的,就是在其他地方使用listFunc(ListOptions)時候
            //把傳入的 ListOptions 透過 optionsModifier function 處理
		optionsModifier(&options)
               //這裡就是 kubernetes client 請求 kubernetes api server 的方法
		return c.Get().
			Namespace(namespace).
			Resource(resource).
			VersionedParams(&options, metav1.ParameterCodec).
			Do(context.TODO()).
			Get()
	}
    //建立一個實作 Watcher  的物件
	watchFunc := func(options metav1.ListOptions) (watch.Interface, error) {
		options.Watch = true
            //這個手法滿厲害的,就是在其他地方使用watchFunc(ListOptions)時候
            //把傳入的 ListOptions 透過 optionsModifier function 處理
		optionsModifier(&options)
               //這裡就是 kubernetes client 請求 kubernetes api server 的方法
		return c.Get().
			Namespace(namespace).
			Resource(resource).
			VersionedParams(&options, metav1.ParameterCodec).
			Watch(context.TODO())
	}
    //建立實作 ListWatcher 的物件
	return &ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
}

impliment

List

source code

1
2
3
4
5
6
// 委託給剛剛的 object 列出物件的情況
func (lw *ListWatch) List(options metav1.ListOptions) (runtime.Object, error) {
	// ListWatch is used in Reflector, which already supports pagination.
	// Don't paginate here to avoid duplication.
	return lw.ListFunc(options)
}

Watch

source code

1
2
3
4
5

// 委託給剛剛的 object 監控物件的情況
func (lw *ListWatch) Watch(options metav1.ListOptions) (watch.Interface, error) {
	return lw.WatchFunc(options)
}

到這裡就是一個最基本的 listwatch ,接下來要看看要怎麼組合這個功能到 Reflector/Informer 內。

小結

kubernetes Reflector 中的 listwatch 的部分就先看到這裡,下一章節要接著看本篇最一開始提到的 controller interface 部分,kubernetes 底層設計的非常精美,透過閱讀程式碼的方式提升自己對 kubernetes 的了解,若文章有錯的部分希望大大們指出,謝謝!


Meng Ze Li
WRITTEN BY
Meng Ze Li
Kubernetes / DevOps / Backend

What's on this Page