1. 程式人生 > >Kubelet 對資源緊缺狀況的應對_Kubernetes中文社群

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 支援兩種檔案系統分割槽。

  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