Kubelet 對資源緊缺狀況的應對_Kubernetes中文社群
對於記憶體和磁碟這種不可壓縮的資源,緊缺就相當於不穩定。
驅逐策略
Kubelet 能夠監控資源消耗,來防止計算資源被耗盡。一旦出現資源緊缺的跡象,Kubelet 就會主動終止一或多個 Pod 的執行,以回收緊俏資源。當一個 Pod 被終止時,其中的容器會全部停止,Pod 狀態會被置為 Failed
。
驅逐訊號
下文中提到了一些訊號,kubelet 能夠利用這些訊號作為決策依據來觸發驅逐行為。描述列中的內容來自於 Kubelet summary API。
驅逐訊號 | 描述 |
---|---|
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 | imµagefs.inodesFree := node.stats.runtime.imagefs.inodesFree |
上面的每個訊號都支援整數值或者百分比。百分比的分母部分就是各個訊號的總量。kubelet 支援兩種檔案系統分割槽。
nodefs
:儲存 kubelet 的卷和守護程序日誌等。imagefs
:在容器執行時,用於儲存映象以及可寫入層。
imagefs 是可選的。Kubelet 能夠利用 cAdvisor 自動發現這些檔案系統。Kubelet 不關注其他的檔案系統。所有其他型別的配置,例如儲存在獨立檔案系統的卷和日誌,都不被支援。
因為磁碟壓力已經被驅逐策略接管,因此未來將會停止對現有 垃圾收集 方式的支援。
驅逐閾(yù,音同“預”)值:
一旦超出閾值,就會觸發 kubelet 進行資源回收的動作。閾值的定義方式如下:
- 上面的表格中列出了可用的
eviction-signal
. - 僅有一個
operator
quantity
需要符合 Kubernetes 中的描述方式。
例如如果一個 Node 有 10Gi 記憶體,我們希望在可用記憶體不足 1Gi 時進行驅逐,就可以選取下面的一種方式來定義驅逐閾值:
memory.available<10%
memory.available<1Gi
驅逐軟閾值
軟閾值需要和一個寬限期引數協同工作。當系統資源消耗達到軟閾值時,這一狀況的持續時間超過了寬限期之前,Kubelet 不會觸發任何動作。如果沒有定義寬限期,Kubelet 會拒絕啟動。
另外還可以定義一個 Pod 結束的寬限期。如果定義了這一寬限期,那麼 Kubelet 會使用 pod.Spec.TerminationGracePeriodSeconds
和最大寬限期這兩個值之間較小的那個(進行寬限),如果沒有指定的話,kubelet 會不留寬限立即殺死 Pod。
軟閾值的定義包括以下幾個引數:
eviction-soft
:描述一套驅逐閾值(例如memory.available<1.5Gi
),如果滿足這一條件的持續時間超過寬限期,就會觸發對 Pod 的驅逐動作。eviction-soft-grace-period
:包含一套驅逐寬限期(例如memory.available=1m30s
),用於定義達到軟閾值之後,持續時間超過多久才進行驅逐。eviction-max-pod-grace-period
:在因為達到軟閾值之後,到驅逐一個 Pod 之前的最大寬限時間(單位是秒),
驅逐硬閾值
硬閾值沒有寬限期,如果達到了硬閾值,kubelet 會立即殺掉 Pod 並進行資源回收。
硬閾值的定義:
eviction-hard
:描述一系列的驅逐閾值(比如說memory.available<1Gi
),一旦達到這一閾值,就會觸發對 Pod 的驅逐,預設的硬閾值定義是:
--eviction-hard=memory.available<100Mi
驅逐監控頻率
Housekeeping interval 引數定義一個時間間隔,Kubelet 每隔這一段就會對驅逐閾值進行評估。
housekeeping-interval
:容器檢查的時間間隔。
節點狀況
Kubelet 會把驅逐訊號跟節點狀況對應起來。
如果觸發了硬閾值,或者符合軟閾值的時間持續了與其對應的寬限期,Kubelet 就會認為當前節點壓力太大,下面的節點狀態定義描述了這種對應關係。
節點狀況 | 驅逐訊號 | 描述 |
---|---|---|
MemoryPressure | memory.available | 節點的可用記憶體達到了驅逐閾值 |
DiskPressure | nodefs.available, nodefs.inodesFree, imagefs.available, imagefs.inodesFree | 節點的 root 檔案系統或者映象檔案系統的可用空間達到了驅逐閾值 |
Kubelet 會持續報告節點狀態的更新過程,這一頻率由引數 —node-status-update-frequency 指定,預設情況下取值為 10s。
節點狀況的波動
如果一個節點的狀況在軟閾值的上下波動,但是又不會超過他的寬限期,將會導致該節點的狀態持續的在是否之間徘徊,最終會影響降低排程的決策過程。
要防止這種狀況,下面的標誌可以用來通知 Kubelet,在脫離壓力狀態之前,必須等待。
eviction-pressure-transition-period
定義了在跳出壓力狀態之前要等待的時間。
Kubelet 在把壓力狀態設定為 False 之前,會確認在週期之內,該節點沒有達到逐出閾值。
回收節點級別的資源
如果達到了驅逐閾值,並且超出了寬限期,那麼 Kubelet 會開始回收超出限量的資源,直到驅逐訊號量回到閾值以內。
Kubelet 在驅逐使用者 Pod 之前,會嘗試回收節點級別的資源。如果伺服器為容器定義了獨立的 imagefs,他的回收過程會有所不同。
有 Imagefs
如果 nodefs 檔案系統到達了驅逐閾值,kubelet 會按照下面的順序來清理空間。
- 刪除死掉的 Pod/容器
如果 imagefs 檔案系統到達了驅逐閾值,kubelet 會按照下面的順序來清理空間。
- 刪掉所有無用映象
沒有 Imagefs
如果 nodefs 檔案系統到達了驅逐閾值,kubelet 會按照下面的順序來清理空間。
- 刪除死掉的 Pod/容器
- 刪掉所有無用映象
驅逐使用者 Pod
如果 Kubelet 無法獲取到足夠的資源,就會開始驅逐 Pod。
Kubelet 會按照下面的標準對 Pod 的驅逐行為進行評判:
- 根據服務質量
- 根據 Pod 排程請求的被耗盡資源的消耗量
接下來,Pod 按照下面的順序進行驅逐:
BestEffort
:消耗最多緊缺資源的 Pod 最先失敗。Burstable
:相對請求(request)最多緊缺資源的 Pod 最先被驅逐,如果沒有 Pod 超出他們的請求,策略會瞄準緊缺資源消耗量最大的 Pod。Guaranteed
:相對請求(request)最多緊缺資源的 Pod 最先被驅逐,如果沒有 Pod 超出他們的請求,策略會瞄準緊缺資源消耗量最大的 Pod。
Guaranteed Pod
絕不會因為其他 Pod 的資源消費被驅逐。如果系統程序(例如 kubelet、docker、journald 等)消耗了超出 system-reserved
或者 kube-reserved
的資源,而且這一節點上只運行了 Guaranteed Pod,那麼為了保證節點的穩定性並降低異常消費對其他 Guaranteed Pod 的影響,必須選擇一個 Guaranteed Pod 進行驅逐。
本地磁碟是一個 BestEffort
資源。如有必要,kubelet 會在 DiskPressure
的情況下,kubelet 會按照 QoS 進行評估。如果 Kubelet 判定缺乏 inode 資源,就會通過驅逐最低 QoS 的 Pod 的方式來回收 inodes。如果 kubelet 判定缺乏磁碟空間,就會通過在相同 QoS 的 Pods 中,選擇消耗最多磁碟空間的 Pod 進行驅逐。
有 Imagefs
如果 nodefs 觸發了驅逐,Kubelet 會用 nodefs 的使用對 Pod 進行排序 – Pod 中所有容器的本地卷和日誌。
如果 imagefs 觸發了驅逐,Kubelet 會根據 Pod 中所有容器的消耗的可寫入層進行排序。
沒有 Imagefs
如果 nodefs 觸發了驅逐,Kubelet 會對各個 Pod 的所有容器的總體磁碟消耗進行排序 —— 本地卷 + 日誌 + 寫入層。
在某些場景下,驅逐 Pod 可能只回收了很少的資源。這就導致了 kubelet 反覆觸發驅逐閾值。另外回收資源例如磁碟資源,是需要消耗時間的。
要緩和這種狀況,Kubelet 能夠對每種資源定義 minimum-reclaim
。kubelet 一旦發現了資源壓力,就會試著回收至少 minimum-reclaim
的資源,使得資源消耗量回到期望範圍。
例如下面的配置:
--eviction-hard=memory.available<500Mi,nodefs.available<1Gi,imagefs.available<100Gi
--eviction-minimum-reclaim="memory.available=0Mi,nodefs.available=500Mi,imagefs.available=2Gi"`
- 如果
memory.available
被觸發,Kubelet 會啟動回收,讓memory.available
至少有 500Mi。 - 如果是
nodefs.available
,Kubelet 就要想法子讓nodefs.available
回到至少 1.5Gi。 - 而對於
imagefs.available
, kubelet 就要回收到最少 102Gi。
預設情況下,所有資源的 eviction-minimum-reclaim
為 0。
排程器
在節點資源緊缺的情況下,節點會報告這一狀況。排程器以此為訊號,不再繼續向此節點部署新的 Pod。
節點狀況 | 排程行為 |
---|---|
MemoryPressure |
不再分配新的 BestEffort Pod 到這個節點 |
DiskPressure |
不再向這一節點分配 Pod |
節點的 OOM 行為
如果節點在 Kubelet 能夠回收記憶體之前,遭遇到了系統的 OOM (記憶體不足),節點就依賴 oom_killer
進行響應了。
kubelet 根據 Pod 的 QoS 為每個容器設定了一個 oom_score_adj
值。
QoS | oom_score_adj |
---|---|
Guaranteed | -998 |
BestEffort | 1000 |
Burstable | min(max(2, 1000 - (1000 * memoryRequestBytes) / machineMemoryCapacityBytes), 999) |
如果 kubelet 無法在系統 OOM 之前回收足夠的記憶體,oom_killer
就會根據根據記憶體使用比率來計算 oom_score
,得出結果和 oom_score_adj
相加,最後得分最高的 Pod 會被首先驅逐。
這一行為的思路是,QoS 最低,相對於排程的 Reqeust 來說又消耗最多記憶體的 Pod 會被首先清除,來保障記憶體的回收。
跟 Pod 驅逐不同,如果一個 Pod 的容器被 OOM 殺掉,他是可能被 kubelet 根據 RestartPolicy
重啟的。
最佳時間
可排程的資源和驅逐策略
我們想象如下的場景:
- 節點記憶體容量:10Gi
- 保留 10% 的記憶體容量給系統服務(核心,kubelet 等)。
- 在 95% 記憶體使用率的時候驅逐 Pod,來降低系統 OOM 的發生率。
所以我們用這樣的引數啟動 Kubelet:
--eviction-hard=memory.available<500Mi
--system-reserved=memory=1.5Gi
這個配置中隱含了一個設定就是,系統保留涵蓋了驅逐標準。
要達到這一容量,可能是有的 Pod 使用了超出其請求的數量,或者系統佔用了超過 500Mi。
這樣的配置保證了排程器不會向即將發生記憶體壓力的節點分配 Pod,避免觸發驅逐。
DaemonSet
因為 DaemonSet 中的 Pod 會立即重建到同一個節點,所以 Kubelet 不應驅逐 DaemonSet 中的 Pod。
但是目前 Kubelet 無法分辨一個 Pod 是否由 DaemonSet 建立。如果/當 Kubelet 能夠識別這一點,那麼就可以先從驅逐候選列表中過濾掉 DaemonSet 的 Pod。
一般來說,強烈建議 DaemonSet 不要建立 BestEffort Pod,而是使用 Guaranteed Pod,來避免進入驅逐候選列表。
棄用的現存回收磁碟的選項
為了保證節點的穩定性,Kubelet 已經嘗試來釋放磁碟空間了。
因為基於磁碟的驅逐方式已經成熟,下列的 Kubelet 引數會被標記為棄用。
現有引數 | 新引數 |
---|---|
—image-gc-high-threshold | —eviction-hard or eviction-soft |
—image-gc-low-threshold | —eviction-minimum-reclaim |
—maximum-dead-containers | 棄用 |
—maximum-dead-containers-per-container | 棄用 |
—minimum-container-ttl-duration | 棄用 |
—low-diskspace-threshold-mb | —eviction-hard or eviction-soft |
—outofdisk-transition-frequency | —eviction-pressure-transition-period |
已知問題
Kubelet 無法及時觀測到記憶體壓力
Kubelet 目前從 cAdvisor 定時獲取記憶體使用狀況統計。如果記憶體使用在這個時間段內發生了快速增長,Kubelet 就無法觀察到 MemoryPressure,可能會觸發 OOMKiller。我們正在嘗試將這一過程整合到 memcg 通知 API 中,來降低這一延遲,而不是讓核心首先發現這一情況。
如果使用者不是希望獲得終極使用率,而是作為一個過量使用的衡量方式,對付這一個問題的較為可靠的方式就是設定驅逐閾值為 75% 容量。這樣就提高了避開 OOM 的能力,提高了驅逐的標準,有助於叢集狀態的平衡。
Kubelet 可能驅逐超出需要的更多 Pod
這也是因為狀態蒐集的時間差導致的。未來會加入功能,讓根容器的統計頻率和其他容器分別開來(https://github.com/google/cadvisor/issues/1247)。
Kubelet 如何在 inode 耗盡的時候評價 Pod 的驅逐
目前不可能知道一個容器消耗了多少 inode。如果 Kubelet 覺察到了 inode 耗盡,他會利用 QoS 對 Pod 進行驅逐評估。在 cadvisor 中有一個 issue,來跟蹤容器的 inode 消耗,這樣我們就能利用 inode 進行評估了。例如如果我們知道一個容器建立了大量的 0 位元組檔案,就會優先驅逐這一 Pod