kubernetes中node心跳處理邏輯分析


最近在查看一個kubernetes集群中node not ready的奇怪現象,順便閱讀了一下kubernetes kube-controller-manager中管理node健康狀態的組件node lifecycle controller。我們知道kubernetes是典型的master-slave架構,master node負責整個集群元數據的管理,然后將具體的啟動執行pod的任務分發給各個salve node執行,各個salve node會定期與master通過心跳信息來告知自己的存活狀態。其中slave node上負責心跳的是kubelet程序, 他會定期更新apiserver中node lease或者node status數據,然后kube-controller-manager會監聽這些信息變化,如果一個node很長時間都沒有進行狀態更新,那么我們就可以認為該node發生了異常,需要進行一些容錯處理,將該node上面的pod進行安全的驅逐,使這些pod到其他node上面進行重建。這部分工作是由node lifecycel controller模塊負責。

在目前的版本(v1.16)中,默認開啟了TaintBasedEvictions, TaintNodesByCondition這兩個feature gate,則所有node生命周期管理都是通過condition + taint的方式進行管理。其主要邏輯由三部分組成:

  1. 不斷地檢查所有node狀態,設置對應的condition
  2. 不斷地根據node condition 設置對應的taint
  3. 不斷地根據taint驅逐node上面的pod

一. 檢查node狀態

檢查node狀態其實就是循環調用monitorNodeHealth函數,該函數首先調用tryUpdateNodeHealth檢查每個node是否還有心跳,然后判斷如果沒有心跳則設置對應的condtion。

node lifecycle controller內部會維護一個nodeHealthMap 數據結構來保存所有node的心跳信息,每次心跳之后都會更新這個結構體,其中最重要的信息就是每個node上次心跳時間probeTimestamp, 如果該timestamp很長時間都沒有更新(超過--node-monitor-grace-period參數指定的值),則認為該node可能已經掛了,設置node的所有condition為unknown狀態。

    gracePeriod, observedReadyCondition, currentReadyCondition, err = nc.tryUpdateNodeHealth(node)

tryUpdateNodeHealth傳入的參數為每個要檢查的node, 返回值中observedReadyCondition為當前從apiserver中獲取到的數據,也就是kubelet上報上來的最新的node信息, currentReadyCondition為修正過的數據。舉個例子,如果node很長時間沒有心跳的話,observedReadyCondition中nodeReadyCondion為true, 但是currentReadyCondion中所有的conditon已經被修正的實際狀態unknown了。

如果observedReadyCondition 狀態為true, 而currentReadyCondition狀態不為true, 則說明node狀態狀態發生變化,由ready變為not-ready。此時不光會更新node condition,還會將該node上所有的pod狀態設置為not ready,這樣的話,如果有對應的service資源選中該pod, 流量就可以從service上摘除了,但是此時並不會直接刪除pod。

node lifecycle controller會根據currentReadyCondition的狀態將該node加入到zoneNoExecuteTainter的隊列中,等待后面設置taint。如果此時已經有了taint的話則會直接更新。zoneNoExecuteTainter隊列的出隊速度是根據node所處zone狀態決定的,主要是為了防止出現集群級別的故障時,node lifecycle controller進行誤判,例如交換機,loadbalancer等故障時,防止node lifecycle controller錯誤地認為所有node都不健康而大規模的設置taint進而導致錯誤地驅逐很多pod,造成更大的故障。

設置出隊速率由handleDisruption函數中來處理,首先會選擇出來各個zone中不健康的node, 並確定當前zone所處的狀態。分為以下幾種情況:

  • Initial: zone剛加入到集群中,初始化完成。
  • Normal: zone處於正常狀態
  • FullDisruption: 該zone中所有的node都notReady了
  • PartialDisruption: 該zone中部分node notReady,此時已經超過了unhealthyZoneThreshold設置的閾值

對於上述不同狀態所設置不同的rate limiter, 從而決定出隊速度。該速率由函數setLimiterInZone決定具體數值, 具體規則是:

  1. 當所有zone都處於FullDisruption時,此時limiter為0
  2. 當只有部分zone處於FullDisruption時,此時limiter為正常速率: --node-eviction-rate
  3. 如果某個zone處於PartialDisruption時,則此時limiter為二級速率:--secondary-node-eviction-rate

二. 設置node taint

根據node condition設置taint主要由兩個循環來負責, 這兩個循環在程序啟動后會不斷執行:

  1. doNodeProcessingPassWorker中主要的邏輯就是: doNoScheduleTaintingPass, 該函數會根據node當前的condition設置unschedulable的taint,便於調度器根據該值進行調度決策,不再調度新pod至該node。
  2. doNoExecuteTaintingPass 會不斷地從上面提到的zoneNoExecuteTainter隊列中獲取元素進行處理,根據node condition設置對應的NotReadyUnreachable的taint, 如果NodeReadycondition為false則taint為NotReady, 如果為unknown,則taint為Unreachable, 這兩種狀態只能同時存在一種!

上面提到從zoneNoExecuteTainter隊列中出隊時是有一定的速率限制,防止大規模快速驅逐pod。該元素是由RateLimitedTimedQueue數據結構來實現:

// RateLimitedTimedQueue is a unique item priority queue ordered by
// the expected next time of execution. It is also rate limited.
type RateLimitedTimedQueue struct {
	queue       UniqueQueue
	limiterLock sync.Mutex
	limiter     flowcontrol.RateLimiter
}

從其定義就可以說明了這是一個 去重的優先級隊列, 對於每個加入到其中的node根據執行時間(此處即為加入時間)進行排序,優先級隊列肯定是通過heap數據結構來實現,而去重則通過set數據結構來實現。在每次doNoExecuteTaintingPass執行的時候,首先盡力從TokenBucketRateLimiter中獲取token,然后從隊頭獲取元素進行處理,這樣就能控制速度地依次處理最先加入的node了。

三. 驅逐pod

在node lifecycle controller啟動的時候,會啟動一個NoExecuteTaintManager。 該模塊負責不斷獲取node taint信息,然后刪除其上的pod。
首先會利用informer會監聽pod和node的各種事件,每個變化都會出發對應的update事件。分為兩類: 1.優先處理nodeUpdate事件; 2.然后是podUpdate事件

  • 對於nodeUpdate事件,會首先獲取該node的taint,然后獲取該node上面所有的pod,依次對每個pod調用processPodOnNode: 判斷是否有對應的toleration,如果沒有則將其加入到對應的taintEvictionQueue中,該queue是個定時器隊列,對於隊列中的每個元素會有一個定時器來來執行,該定時器執行時間由toleration中的tolerationSecond進行設置。對於一些在退出時需要進行清理的程序,toleration必不可少,可以保證給容器退出時留下足夠的時間進行清理或者恢復。 出隊時調用的是回調函數deletePodHandler來刪除pod。
  • 對於podUpdate事件則相對簡單,首先獲取所在的node,然后從taintNode map中獲取該node的taint, 最后調用processPodOnNode,后面的處理邏輯就同nodeUpdate事件一樣了。

為了加快處理速度,提高性能,上述處理會根據nodename hash之后交給多個worker進行處理。

上述就是controller-manager中心跳處理邏輯,三個模塊層層遞進,依次處理,最后將一個異常node上的pod安全地遷移。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM