Kubernetes Eviction Manager工作機制分析
摘要:為了極限的壓榨資源,很多時候Kubernetes叢集會執行一些Best-Effort Task,這樣就會存在資源超配的情況,Kubernetes是如何控制Node上資源的使用,在壓榨資源使用的同時又能保證Node的穩定性?本文就為你介紹其背後執行機制。我的下一篇博文,會對Kubelet Eviction Manager進行原始碼分析,感興趣的同學可以關注。
研究過Kubernetes Resource QoS的同學,肯定會有一個疑問:QoS中會通過Pod QoS和OOM Killer進行資源的回收,當發生資源緊缺的時候。那為什麼Kubernetes會再搞一個Kubelet Eviction機制,來做幾乎同樣的事呢?
首先,我們來談一下kubelet通過OOM Killer來回收資源的缺點:
- System OOM events本來就是對資源敏感的,它會stall這個Node直到完成了OOM Killing Process。
- 當OOM Killer幹掉某些containers之後,kubernetes Scheduler可能很快又會排程一個新的Pod到該Node上或者container 直接在node上restart,馬上又會觸發該Node上的OOM Killer啟動OOM Killing Process,事情可能會沒完沒了的進行,這可不妙啊。
我們再來看看Kubelet Eviction有何不同:
- Kubelet通過pro-actively監控並阻止Node上資源的耗盡,一旦觸發Eviction Signals,就會直接Fail一個或者多個Pod以回收資源,而不是通過Linux OOM Killer這樣本身耗資源的元件進行回收。
- 這樣的Eviction Signals的可配置的,可以做到Pro-actively。
- 另外,被Evicted Pods會在其他Node上重新排程,而不會再次觸發本Node上的再次Eviction。
下面,我們具體來研究一下Kubelet Eviction Policy的工作機制。
- kubelet預先監控本節點的資源使用,並且阻止資源被耗盡,這樣保證node的穩定性。
- kubelet會預先Fail N(>= 1)個Pod以回收出現緊缺的資源。
- kubelet會Fail一個Node時,會將Pod內所有Containners都kill掉,並把PodPhase設為Failed。
- kubelet通過事先人為設定Eviction Thresholds來觸發Eviction動作以回收資源。
Eviction Signals
支援如下Eviction Signals:
Eviction Signal | Description |
---|---|
memory.available | memory.available := node.status.capacity[memory] - node.stats.memory.workingSet |
nodefs.available | nodefs.available := node.stats.fs.available |
nodefs.inodesFree | nodefs.inodesFree := node.stats.fs.inodesFree |
imagefs.available | imagefs.available := node.stats.runtime.imagefs.available |
imagefs.inodesFree | imagefs.inodesFree := node.stats.runtime.imagefs.inodesFree |
- kubelet目前支援一下兩種filesystem,其中imagefs為可選的。Kubelet通過cAdvisor來自動發現這些filesystem。
- nodefs - Kubelet用來儲存volume,logs等資料。
- imagefs - 容器執行時(dockerd/rkt等)用來存放映象和容器的Writable Layer。
Eviction Thresholds
前面也提到,kubelet通過事先人為設定Eviction Thresholds來觸發Eviction動作以回收資源。
- Eviction Thresholds的形式為:
<eviction-signal><operator><quantity>
quantity
支援絕對值和相對百分比兩種形式,比如:
- memory.available<10%
- memory.available<1Gi
Soft Eviction Thresholds
Soft Eviction Thresholds
是什麼意思?
它指的是,當Eviction Signal中值達到Soft Eviction Thresholds
配置的值時,並不會馬上觸發Kubelet去Evict Pods,而是會等待一個使用者配置的grace period之後,再觸發。相關的配置有三個,如下:
eviction-soft
- (e.g. memory.available<1.5Gi) 觸發Soft Eviction的Evication Signal閾值。eviction-soft-grace-period
- (e.g. memory.available=1m30s) 當Eviction Signal的值達到配置eviction-soft
值後,需要等待grace period,注意這期間,每10s會重新獲取監控資料並維護Threshold的值。如果grace period最後一次監控資料仍然觸發了閾值,才會再觸發Evict Pods。這個引數就是配置這個grace period的。eviction-max-pod-grace-period
- (e.g. memory.available=30s) 這個是配置Evict Pods時,Pod Termination的Max Grace Period。如果待Evict的Pod指定了pod.Spec.TerminationGracePeriodSeconds
,則取min(eviction-max-pod-grace-period, pod.Spec.TerminationGracePeriodSeconds)
作為Pod Termination真正的Grace Period。
因此,從kubelet監控到的Eviction Signal達到指定的Soft Eviction Thresholds
開始,到Pod真正被Kill,總共所需要的時間為:
sum(eviction-soft-grace-period + min(eviction-max-pod-grace-period, pod.Spec.TerminationGracePeriodSeconds))
Hard Eviction Thresholds
理解了Soft Eviction Thresholds
,那麼Hard Eviction Thresholds
就很簡單了,它是指:當Eviction Signal中值達到Hard Eviction Thresholds
配置的值時,會立刻觸發Kubelet去Evict Pods,並且也不會有Pod Termination Grace Period,而是立刻kill Pods,即使待Evict的Pod指定了pod.Spec.TerminationGracePeriodSeconds
。
總之,Hard Eviction Thresholds
就是來硬的,一旦觸發,kubelet立刻馬上kill相關的pods。
因此,kubelet關於Hard Eviction Thresholds
的配置也只有一個:
eviction-hard
- (e.g. memory.available<1Gi) 這個值,要設定的比eviction-soft
更低才有意義。
Eviction Monitoring Interval
kubelet會通過監控Eviction Signal的值,當達到配置的閾值時,就會觸發Evict Pods。
kubelet對應的監控週期,就通過cAdvisor的housekeeping-interval
配置的,預設10s。
Node Conditions
當Hard Eviction Thresholds
或Soft Eviction Thresholds
被觸及後,Kubelet會將對應的Eviction Signals對映到對應的Node Conditions,其對映關係如下:
Node Condition | Eviction Signal | Description |
---|---|---|
MemoryPressure | memory.available | Available memory on the node has satisfied an eviction threshold |
DiskPressure | nodefs.available, nodefs.inodesFree, imagefs.available, or imagefs.inodesFree | Available disk space and inodes on either the node’s root filesystem or image filesystem has satisfied an eviction threshold |
kubelet映射了Node Condition之後,會繼續按照--node-status-update-frequency
(default 10s)配置的時間間隔,週期性的與kube-apiserver進行node status updates。
Oscillation of node conditions
想象一下,如果一個Node上監控到的Soft Eviction Signals
的值,一直在eviction-soft
水平線上下波動,那麼Kubelet就會將該Node對應的Node Condition在true和false頻繁切換。這可不是什麼好事,它可能會帶來kube-scheduler做出錯誤的排程決定。kubelet是怎麼處理這種情況的呢?
很簡單,Kubelet通過新增引數eviction-pressure-transition-period
(default 5m0s)配置,使Kubelet在解除由Evicion Signal對映的Node Pressure之前,必須等待這麼長的時間。
因此,邏輯就變成這樣了:
Soft Evction Singal
高於Soft Eviction Thresholds
時,Kubelet還是會立刻設定對應的MemoryPressure Or DiskPressure為True。- 當MemoryPressure Or DiskPressure為True的前提下,發生了
Soft Evction Singal
低於Soft Eviction Thresholds
的情況,則需要等待eviction-pressure-transition-period
(default 5m0s)配置的這麼長時間,才會將condition pressure切換回False。
一句話總結:Node Condition Pressure成為True容易,切換回False則要等
eviction-pressure-transition-period
。
Eviction of Pods
Kubelet的Eviction流程概括如下:
- 在每一個監控週期內,如果Eviction Thresholds被觸及,則:
- 獲取候選Pod
- Fail the Pod
- 等待該Pod被Terminated
如果該Pod由於種種原因沒有被成功Terminated,Kubelet將會再選一個Pod進行Fail Operation。其中,Fail Pod的時候,Kubelet是通過呼叫容器執行時的KillPod介面,如果介面返回True,則認為Fail Pod成功,否則視為失敗。
Eviction Strategy
kubelet根據Pod的QoS Class實現了一套預設的Evication策略,內容見我的另外一篇博文Kubernetes Resource QoS機制解讀中介紹的“如何根據不同的QoS回收Resource”,這裡不再贅述。
下面給出Eviction Strategy的圖解:
Minimum eviction reclaim
有些情況下,eviction pods可能只能回收一小部分的資源就能使得Evication Signal
的值低於Thresholds。但是,可能隨著資源使用的波動或者新的排程Pod使得在該Node上很快又會觸發evict pods的動作,eviction畢竟是耗時的動作,所以應該儘量避免這種情況的發生。
Kubelet是通過--eviction-minimum-reclaim
(e.g. memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi)來控制每次Evict Pods後,Node上對應的Resource不僅要比Eviction Thresholds低,還要保證最少比Eviction Thresholds低--eviction-minimum-reclaim
中配置的數量。
Node OOM Behavior
正常情況下,但Node上資源利用率很高時,Node的資源回收是會通過Kubelet Eviction觸發完成。但是存在這麼一種情況,Kubelet配置的Soft/Hard memory.available還沒觸發,卻先觸發了Node上linux kernel oom_killer,這時回收記憶體資源的請求就被kernel oom_killer處理了,而不會經過Kubelet Eviction去完成。
我的博文Kubernetes Resource QoS機制解讀中介紹過,Kubelet根據Pod QoS給每個container都設定了oom_score_adj,整理如下:
Quality of Service |
oom_score_adj |
---|---|
Guaranteed | -998 |
BestEffort | 1000 |
Burstable | min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999) |
oom_killer
根據container使用的記憶體佔Node總記憶體的百分比計算得到該container的oom_score,然後再將該oom_sore和前面對應的oom_score_adj相加作為最終的oom_score,Node上所有containers的最終oom_score進行排名,將oom_score得分最高的container kill掉。通過如此方式進行資源回收。
oom_killer這樣做的目標就是幹掉QoS低的又消耗最多記憶體(相對request值)的容器首先被kill掉,如此回收記憶體。
不同於Kubelet Evict Pods的是,Node OOM Behavior存在一個缺點:如果Pod中某個容器被oom_killer幹掉之後,會根據該容器的RestartPolicy決定是否restart這個容器。如果這是個有問題的容器,restart之後,可能又很快消耗大量記憶體進而觸發了再次Node OOM Behavior,如此迴圈反覆,該Node沒有真正的回收到記憶體資源。
Scheduler
前面提到,Kubelet會定期的將Node Condition傳給kube-apiserver並存於etcd。kube-scheduler watch到Node Condition Pressure之後,會根據以下策略,阻止更多Pods Bind到該Node。
Node Condition | Scheduler Behavior |
---|---|
MemoryPressure | No new BestEffort pods are scheduled to the node. |
DiskPressure | No new pods are scheduled to the node. |
總結
- Kubelet通過Eviction Signal來記錄監控到的Node節點使用情況。
- Eviction Signal支援:memory.available, nodefs.available, nodefs.inodesFree, imagefs.available, imagefs.inodesFree。
- 通過設定
Hard Eviction Thresholds
和Soft Eviction Thresholds
相關引數來觸發Kubelet進行Evict Pods的操作。 - Evict Pods的時候根據Pod QoS和資源使用情況挑選Pods進行Kill。
- Kubelet通過
eviction-pressure-transition-period
防止Node Condition來回切換引起scheduler做出錯誤的排程決定。 - Kubelet通過
--eviction-minimum-reclaim
來保證每次進行資源回收後,Node的最少可用資源,以避免頻繁被觸發Evict Pods操作。 - 當Node Condition為MemoryPressure時,Scheduler不會排程新的QoS Class為BestEffort的Pods到該Node。
- 當Node Condition為DiskPressure時,Scheduler不會排程任何新的Pods到該Node。