1. 程式人生 > >kubelet版本升級引起的容器重啟機制與參考解決方案_Kubernetes中文社群

kubelet版本升級引起的容器重啟機制與參考解決方案_Kubernetes中文社群

背景

k8s能夠幫助我們的服務實現服務高可用,其提供的副本機制能夠有效的保證執行例項的副本數,從而當某個例項異常後服務可以重新被自動喚起,但在我們的生產環境中,某些特殊的服務(如廣告資金服務或計費服務)因服務重啟期間而導致的業務中斷,對業務請求的延時響應也是不可忽略的問題;而在kubelet的部分版本升級中,也可能會因版本的升級進而導致已經執行的容器服務發生重啟;而在特殊的生產環境中類似的操作直接引起的服務重啟是需要我們儘可能去規避的;

此篇內容即是對kubelet版本升級與container自動重啟這一關聯過程原因分析以及供參考方案的簡單介紹。

環境基本資訊:

作業系統: CentOS 7

kubelet當前版本:1.7.4

目標kubelet版本:1.9.1

現象

當前kubelet版本1.7.4中已持續執行一些容器服務,在對kubelet版本升級1.9.1後,所升級機器上已持續執行多天的container服務出現自動重啟,重啟之後繼續提供服務

原因

因為container的自動重啟發生在kubelet版本升級後,所以需要沿kubelet啟動後的介面函式呼叫鏈進行定位,以確認container重啟的根本原因.

總體呼叫鏈關係圖:(右下角computePodActions() 函式中containerChanged()函式即為決定container服務是否重啟的判斷邏輯)

鏈路跟蹤過程:(整體

kubelet呼叫關係比較複雜,後面僅針對問題的場景關鍵鏈路進行分析)

@kubernetes-1.9.1/cmd/kubelet/kubelet.go @表示所處的原始碼檔案,後面意義相同)

啟動kubelet main()入口,風格上保持主入口程式碼最小化的設計風格

功能上僅是讀取config初始化配置結構後呼叫真正的server中的Run()

@kubernetes-1.9.1/cmd/kubelet/app/server.go

判斷啟動模式,同時初始化證書相關和心跳等,以及進行健康度的檢查

RunKubelet中啟動協程,進入真正的更新處理流程kubelet中的Run主體

@kubernetes-1.9.1/pkg/kubelet­/kubelet.go

與場景現象相關的邏輯處理主入口,依次進行非依賴模組初始化initializeModules(“internal modules that do not require the container runtime to be up”),以及使用kubeClient與APIserver 同步node狀態資訊和本地網路狀態及容器網路狀態的檢查

statusprobepleg作用如下圖示,可參照如上整體的呼叫鏈路簡圖

@kubernetes-1.9.1/pkg/kubelet/kubelet.go

syncLoop(),處理變化的主迴圈邏輯,監聽3種來源的管道:file, apiserver,  http;

只要有訊息就會進行處理(監聽的也是updates訊息佇列)

在syncLoopIteration迭代過程中,會根據不同的型別(config\pleg\sync…)進行相應的處理,處理是通過呼叫對應的Handle*()函式實現的

在各自的處理函式中,針對所有的類別,本質都是呼叫dispatchWork,只是傳入的引數分別為不同的類別而已;如果pod已是終止狀態了,則需要同時呼叫statusManager.TerminatePod(pod) 來通知apiserver狀態的更新;

在dispatchWork函式中,會呼叫具體執行單元worker中UpdatePod函式,此時已將變化封裝的資訊交由執行單元worker。

@kubernetes-1.9.1/pkg/kubelet/pod_workers.go

在UpdatePod中,主要是啟動持續處理邏輯managePodLoop(),此函式只執行一次,執行後會啟動一個迴圈來處理syncPodFn指向函式。如果希望退出迴圈則如下2個函式被呼叫時會中止這個迴圈:ForgetWorker()與 ForgetNonExistingPodWorkers();控制這2個函式是否被呼叫則依賴於housekeepingCh 訊息佇列的處理。

managePodLoop會呼叫syncPodFn函式

syncPodFn指向kubelet.go中的syncPod處理函式。

@kubernetes-1.9.1/pkg/kubelet/kubelet.go

對於status, mkdir與volume簡述如下

最後呼叫容器執行時的SyncPod函式,以便對POD中容器進行操作。

@ kubernetes-1.9.1/pkg/kubelet/container/runtime.go

containerRuntime.SyncPod進而呼叫

@ kubernetes-1.9.1/pkg/kubelet/kuberuntime/kuberuntime_manager.go

呼叫computePodActions()判斷哪些container發生了變化,以便後面對變化的container進行處理(如重啟等)

在computePodAction()中會對POD中執行的container進行判斷,以此為基礎來決定後面是否對這個執行的container進行重啟(即決定是否重啟container的入口判斷邏輯)

檢查執行時container與pod.spec中是否Hash一致,如果發生變化,則在changes結構中ContainersToStart 加入這個container,以便統一對ContainersToStart陣列中的容器進行重啟

@ kubernetes-1.9.1/pkg/kubelet/container/helpers.go

container進行深層次HASH計算

@ kubernetes-1.9.1/pkg/util/hash/hash.go

使用spewcontainer物件元素進行字元拼接(github.com/davecgh/go-spew/spew

由此可見,在版本升級啟動後,定時觸發的containerChanged()函式對container結構元素進行了內容上的HASH比對校驗,從而將變化的container加入到ContainersToStart列表。

syncPodFnDeepHashObject鏈路如下所示:

由於DeepHashObject是對container結構的序列化進行HASH計算,所以對比升級前後版本Container結構不難發現,成員增了VolumeDevices等新成員。

所以在對container進行HASH計算時出現了新舊版本計算HASH值的不一致進而引起重啟container服務。

方案

由於重啟container是基於container HASH值的比較來判定的,所以可以將kubelet版本資訊與啟動時間記錄並持久化,當container變化後計算HASH的時候,參考持久化的資訊輔助決定是否忽略重啟的條件。基本參考方案示例流圖如下

更新原始碼的補丁檔案@ https://github.com/cloudusers/UpdateKubeletVersionIgnoreContainerRestart

適用場景及注意事項

適用於在kubelet版本升級情況下,container服務持續執行不希望重啟的業務場景;同時因對原始碼的改動也引入瞭如下的注意事項或複雜點:

1)版本升級前對比不同版本container結構的變化情況以便了解是否升級對容器HASH計算產生影響;

2)目前採用的記錄並快取kubelet版本及啟動時間資訊至checkpoint持久化本地檔案,所以版本升級前檔案的損壞可能使得容器服務依舊會重新啟動(因checkpoint內容較少,可以臨時手動生成);

3)在每次版本升級前需要對新版本進行原始碼更新重新編譯生成二進位制執行檔案,過程稍顯複雜。

綜上所述,簡單介紹了自kubelet啟動至container服務重啟的鏈路呼叫關係,同時確認版本升級後引起容器重啟的判斷條件及處理邏輯,以及提供可參考的避免因版本升級引起重啟容器的解決方案;

另一方面簡單說明了原始碼修改後所適用的場景及注意事項。因目前還處在研發階段,但是我們可以發現,通過規避容器服務因版本升級導致的重啟可以解決生產環境中的關鍵業務無服務中斷的問題。