節點壓力驅逐
節點壓力驅逐是 kubelet 主動終止 Pod 以回收節點上的資源的過程。
kubelet 監視叢集節點上的記憶體、磁碟空間和檔案系統 inode 等資源。當一個或多個資源達到特定的消耗水平時,kubelet 可以主動使節點上的一個或多個 Pod 失敗,以回收資源並防止資源耗盡。
在節點壓力驅逐期間,kubelet 將選定 Pod 的階段設定為 Failed,並終止 Pod。
節點壓力驅逐與 API 發起的驅逐不同。
kubelet 不尊重你配置的 PodDisruptionBudget 或 Pod 的 terminationGracePeriodSeconds。如果你使用軟碟機逐閾值,kubelet 將遵循你配置的 eviction-max-pod-grace-period。如果你使用硬驅逐閾值,kubelet 將使用 0s 的寬限期(立即關機)進行終止。
自愈行為
kubelet 會在終止終端使用者 Pod 之前嘗試回收節點級資源。例如,當磁碟資源耗盡時,它會移除未使用的容器映象。
如果 Pod 由工作負載管理物件(例如 StatefulSet 或 Deployment)管理,並且這些物件會替換失敗的 Pod,則控制平面(kube-controller-manager)會建立新的 Pod 來替換被驅逐的 Pod。
靜態 Pod 的自愈
如果你在資源壓力下的節點上執行靜態 Pod,kubelet 可能會驅逐該靜態 Pod。然後 kubelet 會嘗試建立一個替代 Pod,因為靜態 Pod 始終表示在該節點上執行 Pod 的意圖。
kubelet 在建立替代 Pod 時會考慮靜態 Pod 的*優先順序*。如果靜態 Pod 的清單指定了較低的優先順序,並且叢集控制平面中定義了更高優先順序的 Pod,並且節點處於資源壓力下,kubelet 可能無法為該靜態 Pod 騰出空間。即使節點存在資源壓力,kubelet 仍會繼續嘗試執行所有靜態 Pod。
驅逐訊號和閾值
kubelet 使用各種引數來做出驅逐決策,例如以下各項:
- 驅逐訊號
- 驅逐閾值
- 監控間隔
驅逐訊號
驅逐訊號是特定時間點特定資源的當前狀態。kubelet 透過將訊號與驅逐閾值(即節點上應可用的最小資源量)進行比較來做出驅逐決策。
kubelet 使用以下驅逐訊號:
| 驅逐訊號 | 描述 | 僅限 Linux |
|---|---|---|
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 | • |
containerfs.available | containerfs.available := node.stats.runtime.containerfs.available | |
containerfs.inodesFree | containerfs.inodesFree := node.stats.runtime.containerfs.inodesFree | • |
pid.available | pid.available := node.stats.rlimit.maxpid - node.stats.rlimit.curproc | • |
在此表中,**描述**列顯示了 kubelet 如何獲取訊號的值。每個訊號都支援百分比或字面值。kubelet 根據與訊號關聯的總容量計算百分比值。
記憶體訊號
在 Linux 節點上,memory.available 的值來自 cgroupfs,而不是像 free -m 這樣的工具。這很重要,因為 free -m 在容器中不起作用,如果使用者使用節點可分配功能,則資源不足的決策是針對 cgroup 層次結構的終端使用者 Pod 部分以及根節點本地做出的。此指令碼或cgroupv2 指令碼重現了 kubelet 用於計算 memory.available 的相同步驟。kubelet 在計算中排除了 inactive_file(不活躍 LRU 列表中檔案支援記憶體的位元組數),因為它假定在壓力下記憶體是可回收的。
在 Windows 節點上,memory.available 的值是透過將節點的全域性 CommitTotal 從節點的 CommitLimit 中減去,從節點的全域性記憶體提交級別(透過 GetPerformanceInfo() 系統呼叫查詢)中派生出來的。請注意,如果節點的頁面檔案大小發生變化,CommitLimit 可能會發生變化!
檔案系統訊號
kubelet 識別三個特定的檔案系統識別符號,可與驅逐訊號一起使用(<identifier>.inodesFree 或 <identifier>.available):
nodefs:節點的主檔案系統,用於本地磁碟卷、非記憶體支援的 emptyDir 卷、日誌儲存、臨時儲存等。例如,nodefs包含/var/lib/kubelet。imagefs:容器執行時可用於儲存容器映象(只讀層)和容器可寫層的可選檔案系統。containerfs:容器執行時可用於儲存可寫層的可選檔案系統。與主檔案系統(見nodefs)類似,它用於儲存本地磁碟卷、非記憶體支援的 emptyDir 卷、日誌儲存和臨時儲存,但容器映象除外。當使用containerfs時,imagefs檔案系統可以拆分以僅儲存映象(只讀層)而不再儲存其他內容。
注意
Kubernetes v1.31 [beta] (預設啟用:true)“拆分映象檔案系統”功能支援 containerfs 檔案系統,它增加了幾個新的驅逐訊號、閾值和指標。要使用 containerfs,Kubernetes v1.34 版本需要啟用 KubeletSeparateDiskGC 功能門。目前,只有 CRI-O(v1.29 或更高版本)提供 containerfs 檔案系統支援。
因此,kubelet 通常允許三種容器檔案系統選項:
所有內容都在單個
nodefs上,也稱為“rootfs”或簡稱為“root”,並且沒有專用的映象檔案系統。容器儲存(見
nodefs)位於專用磁碟上,imagefs(可寫和只讀層)與根檔案系統分離。這通常稱為“拆分磁碟”(或“獨立磁碟”)檔案系統。容器檔案系統
containerfs(與nodefs加可寫層相同)位於根目錄,容器映象(只讀層)儲存在單獨的imagefs上。這通常稱為“拆分映象”檔案系統。
kubelet 將嘗試直接從底層容器執行時自動發現這些檔案系統及其當前配置,並忽略其他本地節點檔案系統。
kubelet 不支援其他容器檔案系統或儲存配置,目前也不支援映象和容器的多個檔案系統。
已棄用的 kubelet 垃圾回收功能
一些 kubelet 垃圾回收功能已棄用,轉而使用驅逐:
| 現有標誌 | 理由 |
|---|---|
--maximum-dead-containers | 舊日誌儲存在容器上下文之外後棄用 |
--maximum-dead-containers-per-container | 舊日誌儲存在容器上下文之外後棄用 |
--minimum-container-ttl-duration | 舊日誌儲存在容器上下文之外後棄用 |
驅逐閾值
你可以為 kubelet 指定自定義驅逐閾值,以便在做出驅逐決策時使用。你可以配置軟和硬驅逐閾值。
驅逐閾值的形式為 [驅逐訊號][運算子][數量],其中:
eviction-signal是要使用的驅逐訊號。operator是你想要的關係運算子,例如<(小於)。quantity是驅逐閾值數量,例如1Gi。quantity的值必須與 Kubernetes 使用的數量表示形式匹配。你可以使用字面值或百分比(%)。
例如,如果一個節點有 10GiB 的總記憶體,並且你希望在可用記憶體低於 1GiB 時觸發驅逐,你可以將驅逐閾值定義為 memory.available<10% 或 memory.available<1Gi(不能同時使用兩者)。
軟碟機逐閾值
軟碟機逐閾值將驅逐閾值與管理員指定的必需寬限期配對。在寬限期過期之前,kubelet 不會驅逐 Pod。如果你未指定寬限期,kubelet 會在啟動時返回錯誤。
你可以指定軟碟機逐閾值寬限期和驅逐期間 kubelet 使用的最長允許 Pod 終止寬限期。如果你指定了最長允許寬限期並且達到了軟碟機逐閾值,kubelet 會使用兩者中較短的寬限期。如果你未指定最長允許寬限期,kubelet 會立即殺死被驅逐的 Pod,而無需正常終止。
你可以使用以下標誌來配置軟碟機逐閾值:
eviction-soft:一組驅逐閾值,例如memory.available<1.5Gi,如果在指定的寬限期內保持不變,可以觸發 Pod 驅逐。eviction-soft-grace-period:一組驅逐寬限期,例如memory.available=1m30s,定義軟碟機逐閾值必須保持多長時間才能觸發 Pod 驅逐。eviction-max-pod-grace-period:在滿足軟碟機逐閾值時終止 Pod 所允許的最長寬限期(以秒為單位)。
硬驅逐閾值
硬驅逐閾值沒有寬限期。當達到硬驅逐閾值時,kubelet 會立即殺死 Pod,而無需正常終止,以回收耗盡的資源。
你可以使用 eviction-hard 標誌來配置一組硬驅逐閾值,例如 memory.available<1Gi。
kubelet 具有以下預設硬驅逐閾值:
memory.available<100Mi(Linux 節點)memory.available<500Mi(Windows 節點)nodefs.available<10%imagefs.available<15%nodefs.inodesFree<5%(Linux 節點)imagefs.inodesFree<5%(Linux 節點)
這些硬驅逐閾值的預設值只有在所有引數都未更改的情況下才會設定。如果你更改任何引數的值,則其他引數的值將不會作為預設值繼承,而是設定為零。為了提供自定義值,你應該分別提供所有閾值。你還可以在 kubelet 配置檔案中將 kubelet 配置 MergeDefaultEvictionSettings 設定為 true。如果設定為 true 並且任何引數被更改,則其他引數將繼承其預設值而不是 0。
containerfs.available 和 containerfs.inodesFree(Linux 節點)預設驅逐閾值將按如下方式設定:
如果所有內容都使用單個檔案系統,則
containerfs閾值將設定為與nodefs相同。如果為映象和容器都配置了單獨的檔案系統,則
containerfs閾值將設定為與imagefs相同。
目前不支援為與 containersfs 相關的閾值設定自定義覆蓋,如果嘗試這樣做將發出警告;因此,任何提供的自定義值都將被忽略。
驅逐監控間隔
kubelet 根據其配置的 housekeeping-interval 評估驅逐閾值,該值預設為 10s。
節點狀況
kubelet 報告節點狀況,以反映由於達到硬或軟碟機逐閾值而導致節點承受壓力,這與配置的寬限期無關。
kubelet 將驅逐訊號對映到節點狀況,如下所示:
| 節點狀況 | 驅逐訊號 | 描述 |
|---|---|---|
MemoryPressure | memory.available | 節點上可用記憶體已達到驅逐閾值 |
DiskPressure | nodefs.available、nodefs.inodesFree、imagefs.available、imagefs.inodesFree、containerfs.available 或 containerfs.inodesFree | 節點根檔案系統、映象檔案系統或容器檔案系統上可用磁碟空間和 inode 已達到驅逐閾值 |
PIDPressure | pid.available | (Linux) 節點上可用程序識別符號已低於驅逐閾值 |
控制平面還將這些節點狀況對映到汙點。
kubelet 根據配置的 --node-status-update-frequency 更新節點狀況,該值預設為 10s。
節點狀況振盪
在某些情況下,節點在軟碟機逐閾值上下波動,但未達到定義的寬限期。這會導致報告的節點狀況在 true 和 false 之間不斷切換,從而導致錯誤的驅逐決策。
為了防止振盪,你可以使用 eviction-pressure-transition-period 標誌,它控制 kubelet 必須等待多長時間才能將節點狀況轉換為不同的狀態。轉換期的預設值為 5m。
回收節點級資源
kubelet 會在驅逐終端使用者 Pod 之前嘗試回收節點級資源。
當報告 DiskPressure 節點狀況時,kubelet 會根據節點上的檔案系統回收節點級資源。
沒有 imagefs 或 containerfs
如果節點只有滿足驅逐閾值的 nodefs 檔案系統,kubelet 會按以下順序釋放磁碟空間:
- 垃圾回收死亡的 Pod 和容器。
- 刪除未使用的映象。
有 imagefs
如果節點有一個專用的 imagefs 檔案系統供容器執行時使用,kubelet 會執行以下操作:
如果
nodefs檔案系統滿足驅逐閾值,kubelet 會垃圾回收死亡的 Pod 和容器。如果
imagefs檔案系統滿足驅逐閾值,kubelet 會刪除所有未使用的映象。
有 imagefs 和 containerfs
如果節點有一個專用的 containerfs 檔案系統以及為容器執行時配置的 imagefs 檔案系統,則 kubelet 將嘗試按以下方式回收資源:
如果
containerfs檔案系統滿足驅逐閾值,kubelet 會垃圾回收死亡的 Pod 和容器。如果
imagefs檔案系統滿足驅逐閾值,kubelet 會刪除所有未使用的映象。
kubelet 驅逐的 Pod 選擇
如果 kubelet 嘗試回收節點級資源未能使驅逐訊號低於閾值,kubelet 將開始驅逐終端使用者 Pod。
kubelet 使用以下引數來確定 Pod 驅逐順序:
- Pod 的資源使用是否超過請求
- Pod 優先順序
- Pod 相對於請求的資源使用情況
因此,kubelet 按以下順序對 Pod 進行排名和驅逐:
使用量超過請求的
BestEffort或BurstablePod。這些 Pod 根據其優先順序,然後根據其使用量超出請求的程度進行驅逐。使用量小於請求的
GuaranteedPod 和BurstablePod 最後被驅逐,根據其優先順序。
注意
kubelet 不使用 Pod 的QoS 類來確定驅逐順序。你可以使用 QoS 類來估計在回收記憶體等資源時最可能的 Pod 驅逐順序。QoS 分類不適用於 EphemeralStorage 請求,因此如果節點處於DiskPressure 等情況下,上述場景將不適用。當所有容器都指定了請求和限制且它們相等時,Guaranteed Pod 才能得到保障。這些 Pod 永遠不會因為其他 Pod 的資源消耗而被驅逐。如果系統守護程序(如 kubelet 和 journald)消耗的資源超過透過 system-reserved 或 kube-reserved 分配預留的資源,並且節點只剩下使用資源少於請求的 Guaranteed 或 Burstable Pod,那麼 kubelet 必須選擇驅逐這些 Pod 中的一個,以維護節點穩定性並限制資源不足對其他 Pod 的影響。在這種情況下,它將首先選擇驅逐優先順序最低的 Pod。
如果你正在執行靜態 Pod 並希望避免在資源壓力下被驅逐,請直接為該 Pod 設定 priority 欄位。靜態 Pod 不支援 priorityClassName 欄位。
當 kubelet 響應 inode 或程序 ID 耗盡而驅逐 Pod 時,它使用 Pod 的相對優先順序來確定驅逐順序,因為 inode 和 PID 沒有請求。
kubelet 根據節點是否具有專用的 imagefs 或 containerfs 檔案系統來以不同方式對 Pod 進行排序:
沒有 imagefs 或 containerfs(nodefs 和 imagefs 使用相同的檔案系統)
- 如果
nodefs觸發驅逐,kubelet 會根據 Pod 的總磁碟使用量(本地卷 + 日誌和所有容器的可寫層)對 Pod 進行排序。
有 imagefs(nodefs 和 imagefs 檔案系統是分開的)
如果
nodefs觸發驅逐,kubelet 會根據nodefs使用量(本地卷 + 所有容器的日誌)對 Pod 進行排序。如果
imagefs觸發驅逐,kubelet 會根據所有容器的可寫層使用量對 Pod 進行排序。
有 imagesfs 和 containerfs(imagefs 和 containerfs 已拆分)
如果
containerfs觸發驅逐,kubelet 會根據containerfs使用量(本地卷 + 日誌和所有容器的可寫層)對 Pod 進行排序。如果
imagefs觸發驅逐,kubelet 會根據映象儲存排名對 Pod 進行排序,這表示給定映象的磁碟使用量。
最小驅逐回收量
注意
自 Kubernetes v1.34 起,你無法為containerfs.available 指標設定自定義值。此特定指標的配置將根據配置自動設定為反映 nodefs 或 imagefs 的值。在某些情況下,Pod 驅逐只回收少量耗盡的資源。這可能導致 kubelet 反覆達到配置的驅逐閾值並觸發多次驅逐。
你可以使用 --eviction-minimum-reclaim 標誌或kubelet 配置檔案來配置每個資源的最小回收量。當 kubelet 注意到資源耗盡時,它會繼續回收該資源,直到回收你指定的數量。
例如,以下配置設定了最小回收量:
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
evictionHard:
memory.available: "500Mi"
nodefs.available: "1Gi"
imagefs.available: "100Gi"
evictionMinimumReclaim:
memory.available: "0Mi"
nodefs.available: "500Mi"
imagefs.available: "2Gi"
在此示例中,如果 nodefs.available 訊號達到驅逐閾值,kubelet 會回收資源,直到訊號達到 1GiB 的閾值,然後繼續回收最小 500MiB,直到可用 nodefs 儲存值達到 1.5GiB。
同樣,kubelet 嘗試回收 imagefs 資源,直到 imagefs.available 值達到 102Gi,表示 102 GiB 的可用容器映象儲存。如果 kubelet 可以回收的儲存量小於 2GiB,kubelet 將不回收任何內容。
所有資源的預設 eviction-minimum-reclaim 均為 0。
節點記憶體不足行為
如果節點在 kubelet 能夠回收記憶體之前發生*記憶體不足*(OOM)事件,節點將依賴 oom_killer 來響應。
kubelet 根據 Pod 的 QoS 為每個容器設定 oom_score_adj 值。
| 服務質量 | oom_score_adj |
|---|---|
Guaranteed | -997 |
BestEffort | 1000 |
Burstable | min(max(2, 1000 - (1000 × memoryRequestBytes) / machineMemoryCapacityBytes), 999) |
如果 kubelet 在節點經歷 OOM 之前無法回收記憶體,oom_killer 會根據其在節點上使用的記憶體百分比計算 oom_score,然後將 oom_score_adj 新增到每個容器的有效 oom_score 中。然後它會殺死得分最高的容器。
這意味著低 QoS Pod 中消耗大量記憶體(相對於其排程請求)的容器將首先被殺死。
與 Pod 驅逐不同,如果容器被 OOM 殺死,kubelet 可以根據其 restartPolicy 重新啟動它。
良好實踐
以下部分描述了驅逐配置的良好實踐。
可排程資源和驅逐策略
當你使用驅逐策略配置 kubelet 時,你應該確保排程器不會排程會觸發驅逐的 Pod,因為它們會立即導致記憶體壓力。
考慮以下場景:
- 節點記憶體容量:10GiB
- 操作員希望為系統守護程序(核心、
kubelet等)保留 10% 的記憶體容量 - 操作員希望在記憶體利用率達到 95% 時驅逐 Pod,以減少系統 OOM 的發生。
為了實現這一點,kubelet 啟動如下:
--eviction-hard=memory.available<500Mi
--system-reserved=memory=1.5Gi
在此配置中,--system-reserved 標誌為系統預留了 1.5GiB 記憶體,即總記憶體的 10% + 驅逐閾值量。
如果 Pod 使用的記憶體超過其請求,或者系統使用的記憶體超過 1GiB,導致 memory.available 訊號低於 500MiB 並觸發閾值,則節點可能會達到驅逐閾值。
DaemonSet 和節點壓力驅逐
Pod 優先順序是做出驅逐決策的主要因素。如果你不希望 kubelet 驅逐屬於 DaemonSet 的 Pod,請透過在 Pod 規範中指定合適的 priorityClassName 為這些 Pod 提供足夠高的優先順序。你也可以使用較低的優先順序或預設優先順序,只允許該 DaemonSet 中的 Pod 在有足夠資源時執行。
已知問題
以下部分描述了與資源不足處理相關的已知問題。
kubelet 可能無法立即觀察到記憶體壓力
預設情況下,kubelet 以固定的時間間隔輪詢 cAdvisor 以收集記憶體使用統計資訊。如果記憶體使用量在此視窗內快速增加,kubelet 可能無法足夠快地觀察到 MemoryPressure,並且 OOM killer 仍將被呼叫。
你可以使用 --kernel-memcg-notification 標誌在 kubelet 上啟用 memcg 通知 API,以便在閾值被突破時立即收到通知。
如果你不追求極致的利用率,而是合理的超額承諾措施,解決此問題的一個可行方法是使用 --kube-reserved 和 --system-reserved 標誌為系統分配記憶體。
active_file 記憶體不被視為可用記憶體
在 Linux 上,核心將活動最近最少使用 (LRU) 列表上檔案支援記憶體的位元組數跟蹤為 active_file 統計資訊。kubelet 將 active_file 記憶體區域視為不可回收。對於大量使用塊支援的本地儲存(包括臨時本地儲存)的工作負載,檔案和塊資料的核心級快取意味著許多最近訪問的快取頁面很可能被計為 active_file。如果足夠的這些核心塊緩衝區在活動 LRU 列表中,kubelet 可能會將其視為高資源使用並汙染節點為經歷記憶體壓力 - 觸發 Pod 驅逐。
有關更多詳細資訊,請參閱 https://github.com/kubernetes/kubernetes/issues/43916
你可以透過為可能執行密集 I/O 活動的容器設定相同的記憶體限制和記憶體請求來解決此行為。你需要估計或測量該容器的最佳記憶體限制值。
下一步
- 瞭解API 啟動的驅逐
- 瞭解Pod 優先順序和搶佔
- 瞭解PodDisruptionBudgets
- 瞭解服務質量 (QoS)
- 檢視驅逐 API