利用 NUMA 感知記憶體管理器
Kubernetes v1.32 [stable]
(預設啟用:true)Kubernetes 記憶體管理器 實現了在 Guaranteed
QoS 類 中為 Pod 保證記憶體(和巨頁)分配的功能。
記憶體管理器採用提示生成協議來為 Pod 生成最合適的 NUMA 親和性。記憶體管理器將這些親和性提示提供給中央管理器(拓撲管理器)。根據提示和拓撲管理器策略,Pod 被拒絕或被接納到節點。
此外,記憶體管理器確保 Pod 請求的記憶體是從最少數量的 NUMA 節點分配的。
記憶體管理器僅適用於基於 Linux 的主機。
準備工作
你需要擁有一個 Kubernetes 叢集,並且 kubectl 命令列工具已配置為與你的叢集通訊。建議在至少有兩個不是控制平面主機的節點的叢集上執行本教程。如果你還沒有叢集,你可以使用 minikube 建立一個,或者你可以使用這些 Kubernetes 遊樂場之一
你的 Kubernetes 伺服器必須是 v1.32 或更高版本。要檢查版本,請輸入 kubectl version
。
為了使記憶體資源與 Pod 規範中請求的其他資源對齊
- 應在節點上啟用 CPU 管理器並配置適當的 CPU 管理器策略。請參閱 控制 CPU 管理策略;
- 應在節點上啟用拓撲管理器並配置適當的拓撲管理器策略。請參閱 控制拓撲管理策略。
從 v1.22 開始,記憶體管理器透過 MemoryManager
特性門控 預設啟用。
在 v1.22 之前,必須使用以下標誌啟動 kubelet
--feature-gates=MemoryManager=true
以啟用記憶體管理器功能。
記憶體管理器如何運作?
記憶體管理器目前為 Guaranteed QoS 類中的 Pod 提供有保證的記憶體(和巨頁)分配。要立即啟用記憶體管理器,請遵循 記憶體管理器配置 部分中的指南,然後,按照 將 Pod 放入 Guaranteed QoS 類 部分的說明準備和部署一個 Guaranteed
Pod。
記憶體管理器是一個提示提供者,它為拓撲管理器提供拓撲提示,然後拓撲管理器根據這些拓撲提示對齊請求的資源。在 Linux 上,它還為 Pod 強制執行 cgroups
(即 cpuset.mems
)。Pod 准入和部署過程的完整流程圖在 記憶體管理器 KEP:設計概述 和下方展示
在此過程中,記憶體管理器會更新儲存在 節點對映和記憶體對映 中的內部計數器,以管理有保證的記憶體分配。
記憶體管理器在啟動和執行時按如下方式更新節點對映。
啟動
這發生在節點管理員使用 --reserved-memory
(保留記憶體標誌 部分)時。在這種情況下,節點對映會更新以反映此保留,如 記憶體管理器 KEP:啟動時的記憶體對映(帶示例) 所示。
配置 Static
策略時,管理員必須提供 --reserved-memory
標誌。
執行時
參考 記憶體管理器 KEP:執行時的記憶體對映(帶示例) 說明了成功的 Pod 部署如何影響節點對映,它還涉及 Kubernetes 或作業系統如何進一步處理潛在的記憶體不足 (OOM) 情況。
記憶體管理器操作中一個重要主題是 NUMA 組的管理。每當 Pod 的記憶體請求超過單個 NUMA 節點容量時,記憶體管理器會嘗試建立一個包含多個 NUMA 節點並具有擴充套件記憶體容量的組。這個問題已在 記憶體管理器 KEP:如何啟用跨多個 NUMA 節點的有保證記憶體分配? 中詳細闡述。此外,參考 記憶體管理器 KEP:模擬 - 記憶體管理器如何工作?(帶示例) 說明了組的管理如何進行。
Windows 支援
Kubernetes v1.32 [alpha]
(預設停用)Windows 支援可以透過 WindowsCPUAndMemoryAffinity
特性門控啟用,並且需要容器執行時的支援。Windows 上僅支援 BestEffort 策略。
記憶體管理器配置
其他管理器應首先預配置。接下來,應啟用記憶體管理器功能並使用 Static
策略執行(靜態策略 部分)。可選地,可以為系統或 kubelet 程序保留一定量的記憶體,以提高節點穩定性(保留記憶體標誌 部分)。
策略
記憶體管理器支援兩種策略。你可以透過 kubelet
標誌 --memory-manager-policy
選擇策略
None
(預設)Static
(僅限 Linux)BestEffort
(僅限 Windows)
None 策略
這是預設策略,不以任何方式影響記憶體分配。它的作用就像記憶體管理器根本不存在一樣。
None
策略返回預設拓撲提示。此特殊提示表示提示提供者(在本例中為記憶體管理器)對任何資源的 NUMA 親和性沒有偏好。
Static 策略
對於 Guaranteed
Pod,Static
記憶體管理器策略返回與可以保證記憶體的 NUMA 節點集相關的拓撲提示,並透過更新內部 NodeMap 物件來保留記憶體。
對於 BestEffort
或 Burstable
Pod,Static
記憶體管理器策略傳送回預設拓撲提示,因為沒有請求保證記憶體,並且不在內部 NodeMap 物件中保留記憶體。
此策略僅在 Linux 上受支援。
BestEffort 策略
Kubernetes v1.32 [alpha]
(預設停用)此策略僅在 Windows 上受支援。
在 Windows 上,NUMA 節點分配的工作方式與 Linux 不同。沒有機制可以確保記憶體訪問僅來自特定的 NUMA 節點。相反,Windows 排程程式將根據 CPU 分配選擇最最佳化 NUMA 節點。如果 Windows 排程程式認為最佳,Windows 可能會使用其他 NUMA 節點。
該策略透過內部 NodeMap 跟蹤可用和請求的記憶體量。記憶體管理器將在分配之前盡力確保 NUMA 節點上有足夠的記憶體可用。
這意味著在大多數情況下,記憶體分配應按預期執行。
保留記憶體標誌
節點可分配 機制通常由節點管理員用於為 kubelet 或作業系統程序保留 K8S 節點系統資源,以增強節點穩定性。為此目的可以使用一組專用標誌來設定節點保留記憶體的總量。此預配置值隨後用於計算可供 Pod 使用的節點“可分配”記憶體的實際量。
Kubernetes 排程程式包含“可分配”以最佳化 Pod 排程過程。上述標誌包括 --kube-reserved
、--system-reserved
和 --eviction-threshold
。它們的總和將構成保留記憶體的總量。
記憶體管理器中添加了一個新的 --reserved-memory
標誌,以允許此總保留記憶體由節點管理員拆分並在多個 NUMA 節點上相應保留。
該標誌指定了每 NUMA 節點不同記憶體型別的逗號分隔記憶體保留列表。跨多個 NUMA 節點的記憶體保留可以使用分號作為分隔符指定。此引數僅在記憶體管理器功能的上下文中有效。記憶體管理器不會將此保留記憶體用於容器工作負載的分配。
例如,如果你有一個可用記憶體為 10Gi
的 NUMA 節點“NUMA0”,並且指定了 --reserved-memory
以在“NUMA0”保留 1Gi
記憶體,則記憶體管理器假定只有 9Gi
可用於容器。
你可以省略此引數,但是,你應該注意所有 NUMA 節點的保留記憶體量應等於 節點可分配功能 指定的記憶體量。如果至少一個節點可分配引數不為零,則你需要為至少一個 NUMA 節點指定 --reserved-memory
。實際上,eviction-hard
閾值預設為 100Mi
,因此如果使用 Static
策略,則 --reserved-memory
是強制性的。
此外,避免以下配置
- 重複項,即相同的 NUMA 節點或記憶體型別,但值不同;
- 將任何記憶體型別的限制設定為零;
- 機器硬體中不存在的 NUMA 節點 ID;
- 記憶體型別名稱不同於
memory
或hugepages-<size>
(特定<size>
的巨頁也應該存在)。
語法
--reserved-memory N:memory-type1=value1,memory-type2=value2,...
N
(整數)- NUMA 節點索引,例如0
memory-type
(字串)- 表示記憶體型別memory
- 常規記憶體hugepages-2Mi
或hugepages-1Gi
- 巨頁
value
(字串)- 保留記憶體的數量,例如1Gi
示例用法
--reserved-memory 0:memory=1Gi,hugepages-1Gi=2Gi
或者
--reserved-memory 0:memory=1Gi --reserved-memory 1:memory=2Gi
或者
--reserved-memory '0:memory=1Gi;1:memory=2Gi'
當你為 --reserved-memory
標誌指定值時,你必須遵守你之前透過節點可分配功能標誌提供的設定。也就是說,對於每種記憶體型別都必須遵守以下規則
sum(reserved-memory(i)) = kube-reserved + system-reserved + eviction-threshold
,
其中 i
是 NUMA 節點的索引。
如果你不遵循上述公式,記憶體管理器將在啟動時顯示錯誤。
換句話說,上面的示例說明,對於常規記憶體 (type=memory
),我們總共保留 3Gi
,即
sum(reserved-memory(i)) = reserved-memory(0) + reserved-memory(1) = 1Gi + 2Gi = 3Gi
kubelet 命令列引數與節點可分配配置相關的示例
--kube-reserved=cpu=500m,memory=50Mi
--system-reserved=cpu=123m,memory=333Mi
--eviction-hard=memory.available<500Mi
注意
預設的硬驅逐閾值為 100MiB,而**不是**零。請記住透過設定--reserved-memory
將你保留的記憶體量增加該硬驅逐閾值。否則,kubelet 將不會啟動記憶體管理器並顯示錯誤。這是一個正確配置的示例
--kube-reserved=cpu=4,memory=4Gi
--system-reserved=cpu=1,memory=1Gi
--memory-manager-policy=Static
--reserved-memory '0:memory=3Gi;1:memory=2148Mi'
在 Kubernetes 1.32 之前,你還需要新增
--feature-gates=MemoryManager=true
讓我們驗證上面的配置
kube-reserved + system-reserved + eviction-hard(default) = reserved-memory(0) + reserved-memory(1)
4GiB + 1GiB + 100MiB = 3GiB + 2148MiB
5120MiB + 100MiB = 3072MiB + 2148MiB
5220MiB = 5220MiB
(正確)
將 Pod 放入 Guaranteed QoS 類
如果選擇的策略不是 None
,記憶體管理器會識別屬於 Guaranteed
QoS 類的 Pod。記憶體管理器為每個 Guaranteed
Pod 提供特定的拓撲提示給拓撲管理器。對於不屬於 Guaranteed
QoS 類的 Pod,記憶體管理器向拓撲管理器提供預設拓撲提示。
以下 Pod 清單摘錄將 Pod 分配到 Guaranteed
QoS 類。
當 requests
等於 limits
時,具有整數 CPU 的 Pod 在 Guaranteed
QoS 類中執行
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "2"
example.com/device: "1"
requests:
memory: "200Mi"
cpu: "2"
example.com/device: "1"
同樣,當 requests
等於 limits
時,共享 CPU 的 Pod 在 Guaranteed
QoS 類中執行。
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "300m"
example.com/device: "1"
requests:
memory: "200Mi"
cpu: "300m"
example.com/device: "1"
請注意,Pod 必須同時指定 CPU 和記憶體請求才能將其置於 Guaranteed QoS 類中。
故障排除
可以使用以下方法來排查 Pod 未能部署或在節點上被拒絕的原因
- Pod 狀態 - 指示拓撲親和性錯誤
- 系統日誌 - 包含有價值的除錯資訊,例如有關生成的提示
- 狀態檔案 - 記憶體管理器內部狀態的轉儲(包括 節點對映和記憶體對映)
- 從 v1.22 開始,裝置外掛資源 API 可用於檢索有關為容器保留的記憶體的資訊
Pod 狀態 (TopologyAffinityError)
此錯誤通常發生在以下情況
- 節點沒有足夠的可用資源來滿足 Pod 的請求
- 由於特定的拓撲管理器策略限制,Pod 的請求被拒絕
錯誤出現在 Pod 的狀態中
kubectl get pods
NAME READY STATUS RESTARTS AGE
guaranteed 0/1 TopologyAffinityError 0 113s
使用 kubectl describe pod <id>
或 kubectl get events
獲取詳細錯誤訊息
Warning TopologyAffinityError 10m kubelet, dell8 Resources cannot be allocated with Topology locality
系統日誌
搜尋與特定 Pod 相關的系統日誌。
記憶體管理器為 Pod 生成的提示集可以在日誌中找到。此外,CPU 管理器生成的提示集也應該出現在日誌中。
拓撲管理器合併這些提示以計算出最佳提示。最佳提示也應該出現在日誌中。
最佳提示指示在哪裡分配所有資源。拓撲管理器根據其當前策略測試此提示,並根據結果決定是將 Pod 接納到節點還是拒絕它。
此外,搜尋日誌中與記憶體管理器相關的事件,例如,查詢有關 cgroups
和 cpuset.mems
更新的資訊。
檢查節點上的記憶體管理器狀態
讓我們首先部署一個示例 Guaranteed
Pod,其規範如下
apiVersion: v1
kind: Pod
metadata:
name: guaranteed
spec:
containers:
- name: guaranteed
image: consumer
imagePullPolicy: Never
resources:
limits:
cpu: "2"
memory: 150Gi
requests:
cpu: "2"
memory: 150Gi
command: ["sleep","infinity"]
接下來,讓我們登入到部署它的節點並檢查 /var/lib/kubelet/memory_manager_state
中的狀態檔案
{
"policyName":"Static",
"machineState":{
"0":{
"numberOfAssignments":1,
"memoryMap":{
"hugepages-1Gi":{
"total":0,
"systemReserved":0,
"allocatable":0,
"reserved":0,
"free":0
},
"memory":{
"total":134987354112,
"systemReserved":3221225472,
"allocatable":131766128640,
"reserved":131766128640,
"free":0
}
},
"nodes":[
0,
1
]
},
"1":{
"numberOfAssignments":1,
"memoryMap":{
"hugepages-1Gi":{
"total":0,
"systemReserved":0,
"allocatable":0,
"reserved":0,
"free":0
},
"memory":{
"total":135286722560,
"systemReserved":2252341248,
"allocatable":133034381312,
"reserved":29295144960,
"free":103739236352
}
},
"nodes":[
0,
1
]
}
},
"entries":{
"fa9bdd38-6df9-4cf9-aa67-8c4814da37a8":{
"guaranteed":[
{
"numaAffinity":[
0,
1
],
"type":"memory",
"size":161061273600
}
]
}
},
"checksum":4142013182
}
從狀態檔案中可以推斷,該 Pod 已固定到兩個 NUMA 節點,即
"numaAffinity":[
0,
1
],
“固定”一詞表示 Pod 的記憶體消耗受限於(透過 cgroups
配置)這些 NUMA 節點。
這自動意味著記憶體管理器例項化了一個新組,該組包含這兩個 NUMA 節點,即索引為 0
和 1
的 NUMA 節點。
請注意,組的管理以相對複雜的方式處理,更多詳細說明請參見記憶體管理器 KEP 的 此 和 此 部分。
為了分析組中可用的記憶體資源,必須將屬於該組的 NUMA 節點中的相應條目相加。
例如,組中可用“常規”記憶體的總量可以透過將組中每個 NUMA 節點(即 NUMA 節點 0
的 "memory"
部分("free":0
)和 NUMA 節點 1
的 "memory"
部分("free":103739236352
))的可用空閒記憶體相加來計算。因此,該組中可用“常規”記憶體的總量等於 0 + 103739236352
位元組。
行 "systemReserved":3221225472
表示此節點的管理員已使用 --reserved-memory
標誌保留了 3221225472
位元組(即 3Gi
)以在 NUMA 節點 0
上為 kubelet 和系統程序提供服務。
裝置外掛資源 API
kubelet 提供了一個 PodResourceLister
gRPC 服務,以實現資源和關聯元資料的發現。透過使用其 List gRPC 端點,可以檢索每個容器保留記憶體的資訊,該資訊包含在 protobuf ContainerMemory
訊息中。此資訊只能針對 Guaranteed QoS 類中的 Pod 檢索。