This page looks best with JavaScript enabled

Kubernetes kubelet 探測 pod 的生命症狀探針得如何出生-1

 ·  ☕ 7 min read

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

本篇文章基於Kubernetes kubelet 探測 pod 的生命症狀 Http GetKubernetes kubelet 探測 pod 的生命症狀 tcp socket以及Kubernetes kubelet 探測 pod 的生命症狀 Exec繼續往上蓋的違建(X),如果對於前幾個章節有興趣的小夥伴可以先到前三個章節了解一下 kubernetes 中的 kubelet 是如何分別透過三種手段去完成監測的。

前幾個章節內容比較著重於 probe 是如何做檢測 container 的關於物件本身是如何產生的比較沒有著墨,因此本篇文章會將重點聚焦在 probe 物件是如何產生的以及相關的調用鍊。

probe 在哪裏絕對難不倒你

我們可以先從 kubelet 的資料結構中找到 probeManager 、 livenessManager 、 readinessManager 、 startupManager Kubernetes 。 kubelet 主要是靠以上這幾種 manager 去管理 probe 的相關物件。
source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// Kubelet is the main kubelet implementation.
type Kubelet struct 
	//用來控制哪些 pod 要加入到 probe manager
	// Handles container probing.
	probeManager prober.Manager

	// 用來探測 container liveness 、 readiness 、  startup prob 結果儲存
	// Manages container health check results.
	livenessManager  proberesults.Manager
    readinessManager proberesults.Manager
	startupManager   proberesults.Manager
	...
}

看完了 kubelet 其中跟 probe 相關的資料結構後我們接著來看看這些資料結構是如何生成與作用的。
等等上面那個 prober.Manager 跟 proberesults.Manager 到底是什麼?別急我們後續會看到!
source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,...)(*Kubelet, error) {

	...
	//在這裡透過 proberesults.NewManager() 生成 並且設定 kubelet 的 livenessManager readinessManager startupManager,用來儲存 container 探測 liveness 、 readiness 、  startup prob 的結果
	//後面會繼續解析 proberesults.NewManager 得實作
	klet.livenessManager = proberesults.NewManager()
	klet.readinessManager = proberesults.NewManager()
	klet.startupManager = proberesults.NewManager()
    
	...
	// kubelet 的 probemanager 則是需要組合上面提到的三種 manager 以及 runner 與 recorder 透過 prober.NewManager 新增對應的物件,用以用來控制哪些 pod 要加入到 prob manager。
	//後面會繼續解析 prober.NewManager 得實作    
	klet.probeManager = prober.NewManager(
		klet.statusManager,
		klet.livenessManager,
		klet.readinessManager,
		klet.startupManager,
		klet.runner,
		kubeDeps.Recorder)
	...
} 

了解完 livenessManager readinessManager startupManager 以及 probeManager 的物件是從哪裡生成的後,我們就要來看看這四個物件作用在什麼地方,以及作用在哪裡。

 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

//kubelet 會透過 syncLoopIteration 去"得到" container 的狀態並且作出相應的行為,
//至於怎麼跑到 syncLoopIteration 這一段的,我之後再做一篇整理,以減少文章的複雜xD
func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
	syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
    
	select {
    
	...    

	//接收 livenessManager channel 傳來的訊號,判斷是否探測成功。若是失敗透過 handleProbeSync function 處理。
	case update := <-kl.livenessManager.Updates():
		if update.Result == proberesults.Failure {
			handleProbeSync(kl, update, handler, "liveness", "unhealthy")
		}
	...
    
	//接收 readinessManager channel 傳來的訊號,判斷是否探測成功。若是失敗透過 statusManager SetContainerReadiness function 與 handleProbeSync function 處理。
	case update := <-kl.readinessManager.Updates():
		ready := update.Result == proberesults.Success
		kl.statusManager.SetContainerReadiness(update.PodUID, update.ContainerID, ready)
		handleProbeSync(kl, update, handler, "readiness", map[bool]string{true: "ready", false: ""}[ready])    
        
	...
    
//接收 startupManager channel 傳來的訊號,判斷是否探測成功。若是失敗透過 statusManager SetContainerReadiness function 與 handleProbeSync function 處理。
	case update := <-kl.startupManager.Updates():
		started := update.Result == proberesults.Success
		kl.statusManager.SetContainerStartup(update.PodUID, update.ContainerID, started)
		handleProbeSync(kl, update, handler, "startup", map[bool]string{true: "started", false: "unhealthy"}[started])
	...
}    

proberesults.Manager

我們首先來看 剛剛出現在 kubelet struct 裡面 livenessManager 、 readinessManager 以及 startupManager 共同的型態 proberesults.Manager 到底是什麼吧!

Interface

source code

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// Manager interface 定義了一些行為,讓實作的物件可以透過 container id 將 pod 的結果儲存起來,並且透過 channel 獲取探測的結果。
type Manager interface {
	// 透過 container id 從 實作者身上得到 result 結果
	Get(kubecontainer.ContainerID) (Result, bool)
	// 透過 container id 設定 pod 探測的結果。實作者需要把結果儲存起來。
	Set(kubecontainer.ContainerID, Result, *v1.Pod)
	// 透過 container id 移除時實作者身上的對應的資料
	Remove(kubecontainer.ContainerID)
	// 透過 channel 接受 pod 探測的結果。
	// NOTE: The current implementation only supports a single updates channel.
	Updates() <-chan Update
}

從以上的程式碼我們可以看到 prober.Manager 是一個 interface 他定義了實作物件必須要可以透過 container id 將 pod 的結果儲存起來,並且透過 channel 獲取探測的結果。

Struct

我們接著來了解實作 prober.Manager interface 的物件 manager
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
// 實作 manager interface 的物件
type manager struct {
	// 用來保證 map 的一致性的鎖
	sync.RWMutex
	// 使用 map 儲存 container ID 與 probe Result
	cache map[kubecontainer.ContainerID]Result
	// 透過 channel 回傳探針執行結果,透過 Update 物件包起來。
	updates chan Update
}

// 透過 channel 要回傳給 kubelet 的 probe 結果之資料結構以 update 物件將結果包起來。
type Update struct {
	//哪一個 container id 的 探測結果
	ContainerID kubecontainer.ContainerID
	//探測結果
	Result      Result
	//哪一個 pod id 的探測結果
	PodUID      types.UID
}

// 探測得結果,以 int 表示
type Result int

const (
	// golang 處理這種 enum 通常會 iota - 1 表示 Unknown 狀態,避免零值產生的錯誤
	Unknown Result = iota - 1

	// prob 成功回傳以 0 做為代表
	Success

	// prob 成功回傳以 1 做為代表
	Failure
)

//編譯時期確保 manager 物件有實作 manger interface
var _ Manager = &manager{}

好,看完了 manager struct 結構以及相關的物件長怎麼樣後,我們還需要了解這個物件是怎麼生成的。

New function

1
2
3
4
5
6
7
8
// Manager 的 new function 回傳一個空的 manger 物件
func NewManager() Manager {
	//回傳初始化過後的 manager 物件
	return &manager{
		cache:   make(map[kubecontainer.ContainerID]Result),        
		updates: make(chan Update, 20),
	}
}

impliment

manger 物件實作了 prober.Manager interface ,我們來看每一個 function 實作的內容吧。

 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
43
44
45
46
47
48
49
50
//傳入 container 從 map 中取的儲存的 prob result 
func (m *manager) Get(id kubecontainer.ContainerID) (Result, bool) {
	//防止競爭加入 rw 鎖
	m.RLock()
	defer m.RUnlock()
	//從 map 中透過 container id 取得對應的 probe 結果
	result, found := m.cache[id]
	return result, found
}

//設定哪一個 container id 的 prob 探測結果(由外部傳入 probe 結果),manager 單純作為 cache 用。
func (m *manager) Set(id kubecontainer.ContainerID, result Result, pod *v1.Pod) {
	//判斷本次 prob 結果是否與上次相符,若是有不一樣的地方存入 map 。並且包裝成 update 物件傳入 update channel 
	if m.setInternal(id, result) {
		m.updates <- Update{id, result, pod.UID}
	}
}

// 判斷本次 probe 結果是否與上次相符,若是有不一樣的地方存入 map ,並且回傳 true。告知使用者本次 prob 的結果跟上次不一樣。
func (m *manager) setInternal(id kubecontainer.ContainerID, result Result) bool {
	//防止競爭加入 rw 鎖
	m.Lock()
	defer m.Unlock()
	//需要判定本次處理 result 的跟上次 map 中儲存的處理結果是否一致。    
	//若是一致就不進行任何動作,並且回傳 false
    
	//若是不一致就需要將本次處理的結過存到 map 中,並且回傳 true
	prev, exists := m.cache[id]
	if !exists || prev != result {
		m.cache[id] = result
		return true
	}
	return false
}


//透過 container id 將 map 中對應的資料刪除
func (m *manager) Remove(id kubecontainer.ContainerID) {
	//防止競爭加入 rw 鎖
	m.Lock()
	defer m.Unlock()
	//刪除 map 對應資料
	delete(m.cache, id)
}

//回傳update channel
func (m *manager) Updates() <-chan Update {
	return m.updates
}

到此簡單的分析完實作了 proberesults.Manager interface 的 manger 物件,接著我們要來看看 manger 物件 在 kubelet 中怎麼被使用的的。

等等!不是還有一個沒有分析嗎?probeManager 他的型態不都是 prober.Manager 嗎?怎麼不一起說說呢?這個部分我認為先了解livenessManager readinessManager startupManager 怎麼用之後我們再來看

proberesults.Manager 觸發的時機

這邊雖然我們開始在 syncLoopIteration 小節已經看過,我想想再提一次,原來 livenessManager readinessManager startupManager 三個物件在 syncLoopIteration 時候會用到。

 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
func (kl *Kubelet) syncLoopIteration(configCh <-chan kubetypes.PodUpdate, handler SyncHandler,
	syncCh <-chan time.Time, housekeepingCh <-chan time.Time, plegCh <-chan *pleg.PodLifecycleEvent) bool {
    
	select {
    
	...    

	//接收 livenessManager channel 傳來的訊號,判斷是否探測成功。若是失敗透過 handleProbeSync function 處理。
	case update := <-kl.livenessManager.Updates():
		if update.Result == proberesults.Failure {
			handleProbeSync(kl, update, handler, "liveness", "unhealthy")
		}
	...
    
	//接收 readinessManager channel 傳來的訊號,判斷是否探測成功。若是失敗透過 statusManager SetContainerReadiness function 與 handleProbeSync function 處理。
	case update := <-kl.readinessManager.Updates():
		ready := update.Result == proberesults.Success
		kl.statusManager.SetContainerReadiness(update.PodUID, update.ContainerID, ready)
		handleProbeSync(kl, update, handler, "readiness", map[bool]string{true: "ready", false: ""}[ready])    
        
	...
    
//接收 startupManager channel 傳來的訊號,判斷是否探測成功。若是失敗透過 statusManager SetContainerReadiness function 與 handleProbeSync function 處理。
	case update := <-kl.startupManager.Updates():
		started := update.Result == proberesults.Success
		kl.statusManager.SetContainerStartup(update.PodUID, update.ContainerID, started)
		handleProbeSync(kl, update, handler, "startup", map[bool]string{true: "started", false: "unhealthy"}[started])
	...
}    

只有 Updates() function 被用到這樣嗎?剛剛看到 interface 定義了一堆東西,怎麼都沒用到呢?
還記得 probeManager 怎麼生成的嗎?

prob 在哪裏絕對難不倒你 章節有看過 probeManager 是由其他三種 manager 組合再一起生成的,那魔鬼一定就藏在這裡面。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
	...
	// kubelet 的 probemanager 則是需要組合上面提到的三種 manager 以及 runner 與 recorder 透過 prober.NewManager 新增對應的物件,用以用來控制哪些 pod 要加入到 prob manager。
	//後面會繼續解析 prober.NewManager 實作    
	klet.probeManager = prober.NewManager(
		klet.statusManager,
		klet.livenessManager,
		klet.readinessManager,
		klet.startupManager,
		klet.runner,
		kubeDeps.Recorder)
	...

所以說其他 function 會在 probeManager 裡面被調用囉?是沒錯!所以我們需要進一步的來分析 probeManager 是什麼。

小結

前幾個章節內容比較著重於 probe 是如何做檢測 container 的關於物件本身是如何產生的比較沒有著墨,因此本篇文章會將重點聚焦在 probe 物件是如何產生的以及相關的調用鍊。

由上文分析所得知 kubernetes 的 kubelet 資料結構中包含了 livenessManager、readinessManager 以及 startupManager 他們的型態皆為 proberesults.Manager 主要負責儲存 liveness 、 readiness 以及 startup 的 probe 結果。
目前只看到 kubelet 在 syncLoopIteration 的階段會透過 proberesults.Manager的 Updates function 取出 probe 的變化,並且依照便會進行不同行為。

這裡我保留了一個伏筆在下篇文章做揭曉,proberesults.Manager 的其他 function 去哪了?
kubelet 資料結構中型態為 probeManager 的 probeManager 到底是什麼跟 livenessManager、readinessManager 以及 startupManager 又有什麼關西呢?


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

What's on this Page