1. 程式人生 > >kubernetes中node心跳處理邏輯分析

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安全地遷移