本文已超過一年。較舊的文章可能包含過時的內容。請檢查頁面中的資訊自發布以來是否已變得不正確。

Kubernetes 1.27:記憶體資源的服務品質 (alpha)

Kubernetes v1.27 在 2023 年 4 月發布,引入了 Memory QoS (alpha) 的變更,以改進 Linux 節點中的記憶體管理能力。

對 Memory QoS 的支援最初是在 Kubernetes v1.22 中新增的,後來在 限制 中發現了關於計算 memory.high 公式的一些限制。這些限制在 Kubernetes v1.27 中得到了解決。

背景

Kubernetes 允許您選擇性地指定 Pod 規格中容器所需的每種資源量。最常指定的資源是 CPU 和記憶體。

例如,定義容器資源需求的 Pod 資訊清單可能如下所示

apiVersion: v1
kind: Pod
metadata:
  name: example
spec:
  containers:
  - name: nginx
    resources:
      requests:
        memory: "64Mi"
        cpu: "250m"
      limits:
        memory: "64Mi"
        cpu: "500m"
  • spec.containers[].resources.requests

    當您在 Pod 中為容器指定資源請求時,Kubernetes 排程器 會使用此資訊來決定將 Pod 放置在哪個節點上。排程器確保對於每種資源類型,排程容器的資源請求總和小於節點上的總可分配資源。

  • spec.containers[].resources.limits

    當您在 Pod 中為容器指定資源限制時,kubelet 會強制執行這些限制,以便不允許執行中的容器使用超過您設定的限制的資源。

當 kubelet 啟動作為 Pod 一部分的容器時,kubelet 會將容器的 CPU 和記憶體請求和限制傳遞給容器執行期。容器執行期會為容器分配 CPU 請求和 CPU 限制。如果系統有空閒的 CPU 時間,則保證為容器分配與其請求一樣多的 CPU。容器使用的 CPU 不能超過設定的限制,即如果容器在給定的時間片段內使用的 CPU 超過指定的限制,則會受到 CPU 節流。

在 Memory QoS 功能之前,容器執行期僅使用記憶體限制並捨棄記憶體 request(請求過去和現在仍然用於影響排程)。如果容器使用的記憶體超過設定的限制,則會調用 Linux Out Of Memory (OOM) killer。

讓我們比較一下 Linux 上的容器執行期通常如何在 cgroups 中設定記憶體請求和限制,無論有無 Memory QoS 功能

  • 記憶體請求

    記憶體請求主要由 kube-scheduler 在(Kubernetes)Pod 排程期間使用。在 cgroups v1 中,沒有控制項可以指定 cgroups 必須始終保留的最小記憶體量。因此,容器執行期未使用 Pod 規格中設定的請求記憶體的值。

    cgroups v2 引入了 memory.min 設定,用於指定給定 cgroup 內的進程應始終保持可用的最小記憶體量。如果 cgroup 的記憶體使用量在其有效最小值邊界內,則在任何情況下都不會回收 cgroup 的記憶體。如果核心無法為 cgroup 內的進程維持至少 memory.min 位元組的記憶體,則核心會調用其 OOM killer。換句話說,核心保證至少有這麼多記憶體可用,或終止進程(可能在 cgroup 外部)以使更多記憶體可用。Memory QoS 將 memory.min 對應到 spec.containers[].resources.requests.memory,以確保 Kubernetes Pod 中容器的記憶體可用性。

  • 記憶體限制

    memory.limit 指定記憶體限制,如果容器嘗試分配更多記憶體超出此限制,Linux 核心將終止進程並顯示 OOM(記憶體不足)終止。如果終止的進程是容器內的主要(或唯一)進程,則容器可能會退出。

    在 cgroups v1 中,memory.limit_in_bytes 介面用於設定記憶體使用限制。但是,與 CPU 不同,無法應用記憶體節流:一旦容器超過記憶體限制,就會被 OOM 終止。

    在 cgroups v2 中,memory.max 類似於 cgroupv1 中的 memory.limit_in_bytes。Memory QoS 將 memory.max 對應到 spec.containers[].resources.limits.memory,以指定記憶體使用量的硬性限制。如果記憶體消耗超過此水平,核心將調用其 OOM Killer。

    cgroups v2 也新增了 memory.high 組態。Memory QoS 使用 memory.high 來設定記憶體使用節流限制。如果違反了 memory.high 限制,則會節流違規的 cgroup,並且核心會嘗試回收記憶體,這可能會避免 OOM 終止。

運作方式

Cgroups v2 記憶體控制器介面和 Kubernetes 容器資源對應

Memory QoS 使用 cgroups v2 的記憶體控制器來保證 Kubernetes 中的記憶體資源。此功能使用的 cgroupv2 介面為

  • memory.max
  • memory.min
  • memory.high.
Memory QoS Levels

Memory QoS 層級

memory.max 對應到 Pod 規格中指定的 limits.memory。kubelet 和容器執行期在各自的 cgroup 中設定限制。核心強制執行限制,以防止容器使用超過設定的資源限制。如果容器中的進程嘗試消耗超過指定限制的資源,核心會終止進程並顯示記憶體不足 (OOM) 錯誤。

memory.max maps to limits.memory

memory.max 對應到 limits.memory

memory.min 對應到 requests.memory,這會導致保留記憶體資源,核心永遠不應回收這些資源。這就是 Memory QoS 確保 Kubernetes pod 的記憶體可用性的方式。如果沒有未受保護的可回收記憶體可用,則會調用 OOM killer 以使更多記憶體可用。

memory.min maps to requests.memory

memory.min 對應到 requests.memory

對於記憶體保護,除了限制記憶體使用量的原始方式外,Memory QoS 還會節流接近其記憶體限制的工作負載,確保系統不會被記憶體使用量的零星增加所淹沒。啟用 MemoryQoS 功能時,KubeletConfiguration 中提供了一個新欄位 memoryThrottlingFactor。預設設定為 0.9。memory.high 對應到使用 memoryThrottlingFactorrequests.memorylimits.memory 以以下公式計算的節流限制,並將該值向下捨入到最接近的頁面大小

memory.high formula

memory.high 公式

摘要

檔案描述
memory.maxmemory.max 指定容器允許使用的最大記憶體限制。如果容器內的進程嘗試消耗超過設定限制的記憶體,核心將終止進程並顯示記憶體不足 (OOM) 錯誤。

它對應到 Pod 資訊清單中指定的容器記憶體限制。
memory.minmemory.min 指定 cgroups 必須始終保留的最小記憶體量,即系統永遠不應回收的記憶體。如果沒有未受保護的可回收記憶體可用,則會調用 OOM 終止。

它對應到 Pod 資訊清單中指定的容器記憶體請求。
memory.highmemory.high 指定記憶體使用節流限制。這是控制 cgroup 記憶體使用的主要機制。如果 cgroups 記憶體使用量超過此處指定的高邊界,則 cgroups 進程將被節流並承受沉重的回收壓力。

Kubernetes 使用公式來計算 memory.high,具體取決於容器的記憶體請求、記憶體限制或節點可分配記憶體(如果容器的記憶體限制為空)以及節流因子。有關公式的更多詳細資訊,請參閱 KEP

cgroups 層級結構的 memory.min 計算

當提出容器記憶體請求時,kubelet 在容器建立期間透過 CRI 中的 Unified 欄位將 memory.min 傳遞到後端 CRI 執行期(例如 containerd 或 CRI-O)。對於 pod 中的每個第 i 容器,容器層級 cgroup 中的 memory.min 將設定為

memory.min =  pod.spec.containers[i].resources.requests[memory]

由於 memory.min 介面要求已設定所有祖先 cgroup 目錄,因此 pod 和節點 cgroup 目錄需要正確設定。

對於 pod 中的每個第 i 容器,pod 層級 cgroup 中的 memory.min

memory.min = \sum_{i=0}^{no. of pods}pod.spec.containers[i].resources.requests[memory]

對於節點上每個第 i pod 中的每個第 j 容器,節點層級 cgroup 中的 memory.min

memory.min = \sum_{i}^{no. of nodes}\sum_{j}^{no. of pods}pod[i].spec.containers[j].resources.requests[memory]

Kubelet 將直接使用 libcontainer 程式庫(來自 runc 專案)管理 pod 層級和節點層級 cgroup 的 cgroup 層級結構,而容器 cgroup 限制由容器執行期管理。

Pod QoS 類別的支援

根據 Kubernetes v1.22 中 Alpha 功能的使用者回饋,一些使用者希望在每個 pod 的基礎上選擇退出 MemoryQoS,以確保沒有早期記憶體節流。因此,在 Kubernetes v1.27 中,Memory QOS 也支援根據 Pod 類別的服務品質 (QoS) 設定 memory.high。以下是根據 QOS 類別的 memory.high 的不同情況

  1. 根據 QoS 定義,Guaranteed pod 需要 memory requests=memory limits 並且不會過度承諾。因此,MemoryQoS 功能在這些 pod 上被停用,方法是不設定 memory.high。這確保了 Guaranteed pod 可以完全使用其記憶體請求,直至其設定的限制,並且不會受到任何節流。

  2. 根據 QoS 定義,Burstable pod 需要 Pod 中至少有一個容器設定了 CPU 或記憶體請求或限制。

    • 當設定了 requests.memory 和 limits.memory 時,將按原樣使用公式

      memory.high when requests and limits are set

      當設定了請求和限制時的 memory.high

    • 當設定了 requests.memory 而未設定 limits.memory 時,limits.memory 將替換為公式中的節點可分配記憶體

      memory.high when requests and limits are not set

      當未設定請求和限制時的 memory.high

  3. 根據 QoS 定義,BestEffort 不需要任何記憶體或 CPU 限制或請求。對於這種情況,kubernetes 設定 requests.memory = 0 並將 limits.memory 替換為公式中的節點可分配記憶體

    memory.high for BestEffort Pod

    BestEffort Pod 的 memory.high

摘要:只有 Burstable 和 BestEffort QoS 類別中的 Pod 才會設定 memory.high。Guaranteed QoS pod 不會設定 memory.high,因為它們的記憶體是有保證的。

我該如何使用它?

在您的 Linux 節點上啟用 Memory QoS 功能的先決條件是

  1. 驗證與 Kubernetes 對 cgroups v2 的支援 相關的需求是否已滿足。
  2. 確保 CRI 執行期支援 Memory QoS。在撰寫本文時,只有 containerd 和 CRI-O 提供與 Memory QoS (alpha) 相容的支援。這是在以下 PR 中實作的

Memory QoS 對於 Kubernetes v1.27 仍然是 alpha 功能。您可以透過在 kubelet 組態檔中設定 MemoryQoS=true 來啟用該功能

apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
featureGates:
  MemoryQoS: true

我該如何參與?

非常感謝所有協助此功能的設計、實作和審查的貢獻者

對於那些有興趣參與未來關於 Memory QoS 功能的討論的人,您可以透過以下幾種方式聯繫 SIG Node