Kubelet 對資源緊缺狀況的應對

對於內存和磁盤這種不可壓縮的資源,緊缺就相當於不穩定。

驅逐策略

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 支持兩種文件系統分區。

  1. nodefs:保存 kubelet 的卷和守護進程日誌等。
  2. 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 會按照下面的順序來清理空間。

  1. 刪除死掉的 Pod/容器

如果 imagefs 文件系統到達了驅逐閾值,kubelet 會按照下面的順序來清理空間。

  1. 刪掉所有無用鏡像

沒有 Imagefs

如果 nodefs 文件系統到達了驅逐閾值,kubelet 會按照下面的順序來清理空間。

  1. 刪除死掉的 Pod/容器
  2. 刪掉所有無用鏡像

驅逐用戶 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-reclaim0

調度器

在節點資源緊缺的情況下,節點會報告這一狀況。調度器以此爲信號,不再繼續向此節點部署新的 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

本文轉自中文社區-Kubelet 對資源緊缺狀況的應對

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章