本文發表於一年多前。舊文章可能包含過時內容。請檢查頁面中的資訊自發布以來是否已變得不正確。
Kubernetes Topology Manager 進入 Beta 階段 - 對齊吧!
這篇博文介紹了 TopologyManager
,這是 Kubernetes 1.18 版本中的一個 Beta 功能。TopologyManager
功能支援 CPU 和外圍裝置(如 SR-IOV VFs 和 GPU)的 NUMA 對齊,使您的工作負載能夠在針對低延遲最佳化的環境中執行。
在引入 TopologyManager
之前,CPU 管理器和裝置管理器會獨立做出資源分配決策。這可能導致在多路 CPU 系統上進行不理想的分配,從而降低延遲關鍵應用程式的效能。透過引入 TopologyManager
,我們現在有了一種避免這種情況的方法。
這篇博文涵蓋了
- NUMA 簡介及其重要性
- 為終端使用者提供的確保 CPU 和裝置 NUMA 對齊的策略
TopologyManager
如何工作的內部細節TopologyManager
的當前限制TopologyManager
的未來方向
那麼,什麼是 NUMA,我為什麼應該關心?
NUMA 是非統一記憶體訪問(Non-Uniform Memory Access)的縮寫。它是多 CPU 系統上的一項技術,允許不同的 CPU 以不同的速度訪問記憶體的不同部分。直接連線到 CPU 的任何記憶體都被認為是該 CPU 的“本地”記憶體,可以非常快速地訪問。任何未直接連線到 CPU 的記憶體都被視為“非本地”記憶體,其訪問時間將取決於必須透過多少個互連才能到達它。在現代系統中,“本地”與“非本地”記憶體的概念也可以擴充套件到 NIC 或 GPU 等外圍裝置。為了獲得高效能,CPU 和裝置應該進行分配,以便它們可以訪問相同的本地記憶體。
NUMA 系統上的所有記憶體都分為一組“NUMA 節點”,每個節點代表一組 CPU 或裝置的本地記憶體。當我們說某個 CPU 是 NUMA 節點的一部分時,是指它的本地記憶體與該 NUMA 節點相關聯。
當我們說某個外圍裝置是 NUMA 節點的一部分時,是根據到達它所需的互連數量最短來確定的。
例如,在圖 1 中,CPU 0-3 被認為是 NUMA 節點 0 的一部分,而 CPU 4-7 是 NUMA 節點 1 的一部分。同樣,GPU 0 和 NIC 0 被認為是 NUMA 節點 0 的一部分,因為它們連線到 Socket 0,而 Socket 0 的所有 CPU 都是 NUMA 節點 0 的一部分。對於 NUMA 節點 1 上的 GPU 1 和 NIC 1 也是如此。
圖 1:一個包含 2 個 NUMA 節點、2 個包含 4 個 CPU 的套接字、2 個 GPU 和 2 個 NIC 的系統示例。Socket 0 上的 CPU、GPU 0 和 NIC 0 都屬於 NUMA 節點 0。Socket 1 上的 CPU、GPU 1 和 NIC 1 都屬於 NUMA 節點 1。
儘管上面的示例顯示了 NUMA 節點與套接字的一對一對映,但這在一般情況下並非如此。單個 NUMA 節點上可能有多個套接字,或者單個套接字上的各個 CPU 可能連線到不同的 NUMA 節點。此外,新興技術,如子 NUMA 叢集(在最新的 Intel CPU 上可用),允許單個 CPU 與多個 NUMA 節點相關聯,只要它們對兩個節點的記憶體訪問時間相同(或差異可忽略)。
TopologyManager
已構建用於處理所有這些場景。
團結一致!這是一個團隊的努力!
如前所述,TopologyManager
允許使用者按 NUMA 節點對齊 CPU 和外圍裝置分配。有幾種策略可供選擇:
none:
此策略不會嘗試進行任何資源對齊。它的行為與根本不存在TopologyManager
時相同。這是預設策略。best-effort:
使用此策略,TopologyManager
將盡力嘗試在 NUMA 節點上對齊分配,但即使某些分配的資源未對齊在同一 NUMA 節點上,它也會始終允許 pod 啟動。restricted:
此策略與best-effort
策略相同,不同之處在於,如果分配的資源無法正確對齊,它將拒絕 pod 准入。與single-numa-node
策略不同,如果無法*在單個 NUMA 節點上滿足*分配請求(例如,請求了 2 個裝置,而系統中僅有的 2 個裝置位於不同的 NUMA 節點上),則某些分配可能來自多個 NUMA 節點。single-numa-node:
此策略最嚴格,僅允許在*所有*請求的 CPU 和裝置都可以從恰好一個 NUMA 節點分配時才准入 pod。
需要注意的是,所選策略是單獨應用於 pod spec 中的每個容器,而不是將所有容器的資源一起對齊。
此外,一個策略是透過全域性 kubelet
標誌應用於節點上的*所有* pod,而不是允許使用者逐個 pod(或逐個容器)選擇不同的策略。我們希望將來能放寬此限制。
下面可以看到用於設定這些策略之一的 kubelet
標誌:
--topology-manager-policy=
[none | best-effort | restricted | single-numa-node]
此外,TopologyManager
受一個功能門控(feature gate)保護。此功能門控自 Kubernetes 1.16 起可用,但直到 1.18 版本才預設啟用。
功能門控可以啟用或停用,如下所示(此處有更詳細的描述):
--feature-gates="...,TopologyManager=<true|false>"
為了根據所選策略觸發對齊,使用者必須在其 pod spec 中請求 CPU 和外圍裝置,並滿足某些要求。
對於外圍裝置,這意味著請求來自裝置外掛(例如 intel.com/sriov
、nvidia.com/gpu
等)提供的可用資源。這僅在裝置外掛已擴充套件以與 TopologyManager
正確整合時才有效。目前,已知具有此擴充套件的外掛是 Nvidia GPU 裝置外掛 和 Intel SRIOV 網路裝置外掛。有關如何擴充套件裝置外掛以與 TopologyManager
整合的詳細資訊,請參見此處。
對於 CPU,這要求 CPUManager
已配置其 --static
策略已啟用,並且 pod 執行在 Guaranteed QoS 級別(即所有 CPU 和記憶體 limits
等於其各自的 CPU 和記憶體 requests
)。CPU 還必須以整數值請求(例如 1
、2
、1000m
等)。有關如何設定 CPUManager
策略的詳細資訊,請參見此處。
例如,假設 CPUManager
以其 --static
策略執行,並且 gpu-vendor.com
和 nic-vendor.com
的裝置外掛已擴充套件以與 TopologyManager
正確整合,則以下 pod spec 足以觸發 TopologyManager
執行其選定的策略:
spec:
containers:
- name: numa-aligned-container
image: alpine
resources:
limits:
cpu: 2
memory: 200Mi
gpu-vendor.com/gpu: 1
nic-vendor.com/nic: 1
遵循上一節的圖 1,這將導致以下對齊分配之一:
{cpu: {0, 1}, gpu: 0, nic: 0}
{cpu: {0, 2}, gpu: 0, nic: 0}
{cpu: {0, 3}, gpu: 0, nic: 0}
{cpu: {1, 2}, gpu: 0, nic: 0}
{cpu: {1, 3}, gpu: 0, nic: 0}
{cpu: {2, 3}, gpu: 0, nic: 0}
{cpu: {4, 5}, gpu: 1, nic: 1}
{cpu: {4, 6}, gpu: 1, nic: 1}
{cpu: {4, 7}, gpu: 1, nic: 1}
{cpu: {5, 6}, gpu: 1, nic: 1}
{cpu: {5, 7}, gpu: 1, nic: 1}
{cpu: {6, 7}, gpu: 1, nic: 1}
就是這樣!只需遵循此模式,TopologyManager
即可確保跨請求拓撲感知裝置和獨佔 CPU 的容器實現 NUMA 對齊。
注意:如果 pod 被 TopologyManager
策略拒絕,它將被置於 Terminated
(已終止)狀態,並帶有 pod 准入錯誤,原因為“TopologyAffinityError
”。一旦 pod 處於此狀態,Kubernetes 排程程式將不會嘗試重新排程它。因此,建議使用具有副本的 Deployment
以在發生此類故障時觸發 pod 的重新部署。還可以實現一個 外部控制器迴圈 來觸發具有 TopologyAffinityError
的 pod 的重新部署。
這太棒了,那麼它是如何工作的呢?
TopologyManager
執行的主要邏輯的虛擬碼如下所示:
for container := range append(InitContainers, Containers...) {
for provider := range HintProviders {
hints += provider.GetTopologyHints(container)
}
bestHint := policy.Merge(hints)
for provider := range HintProviders {
provider.Allocate(container, bestHint)
}
}
下圖總結了在此迴圈中執行的步驟:
步驟本身是:
- 迴圈遍歷 pod 中的所有容器。
- 對於每個容器,從一組“
HintProviders
”中收集容器請求的每個拓撲感知資源型別(例如gpu-vendor.com/gpu
、nic-vendor.com/nic
、cpu
等)的“TopologyHints
”。 - 使用所選策略,合併收集到的
TopologyHints
,以找到“最佳”提示,從而對所有資源型別的資源分配進行對齊。 - 再次迴圈遍歷提示提供程式集,指示它們使用合併後的提示作為指導來分配它們控制的資源。
- 此迴圈在 pod 准入時執行,如果任何步驟失敗或根據所選策略無法滿足對齊,則 pod 准入將失敗。在失敗之前分配的任何資源都將按原樣清理。
以下各節將更詳細地介紹 TopologyHints
和 HintProviders
的確切結構,以及每個策略使用的合併策略的一些詳細資訊。
TopologyHints
TopologyHint
編碼了一組約束,資源請求可以從中得到滿足。目前,我們考慮的唯一約束是 NUMA 對齊。定義如下:
type TopologyHint struct {
NUMANodeAffinity bitmask.BitMask
Preferred bool
}
NUMANodeAffinity
欄位包含一個位掩碼,表示資源請求可以滿足的 NUMA 節點。例如,具有 2 個 NUMA 節點的系統上的可能掩碼包括:
{00}, {01}, {10}, {11}
Preferred
欄位包含一個布林值,用於編碼該提示是否為“首選”提示。在 best-effort
策略下,在生成“最佳”提示時,首選提示將優先於非首選提示。在 restricted
和 single-numa-node
策略下,將拒絕非首選提示。
通常,HintProviders
透過檢視可滿足資源請求的當前可用資源集來生成 TopologyHints
。更具體地說,它們為資源請求可以滿足的每個 NUMA 節點掩碼生成一個 TopologyHint
。如果某個掩碼無法滿足請求,則將其省略。例如,當被要求分配 2 個資源時,在具有 2 個 NUMA 節點的系統上,HintProvider
可能會提供以下提示。這些提示編碼了兩個資源都可以來自單個 NUMA 節點(0 或 1),或者它們可以分別來自不同的 NUMA 節點(但我們希望它們僅來自一個)。
{01: True}, {10: True}, {11: False}
目前,只有當 NUMANodeAffinity
編碼了一個*最小*集,可以滿足資源請求的 NUMA 節點時,所有 HintProviders
才將 Preferred
欄位設定為 True
。通常,這僅對 TopologyHints
的位掩碼中設定了單個 NUMA 節點的有效。然而,如果*只能*透過跨越多個 NUMA 節點來滿足資源請求(例如,請求了 2 個裝置,而系統中僅有的 2 個裝置位於不同的 NUMA 節點上),它也可能為 True
。
{0011: True}, {0111: False}, {1011: False}, {1111: False}
注意:此處 Preferred
欄位的設定*不是*基於當前可用資源的集合。它是基於在最小 NUMA 節點集上物理分配所需資源數量的能力。
這樣,HintProvider
就可能返回一個列表,其中所有 Preferred
欄位都設定為 False
,因為在其他容器釋放其資源之前,無法滿足實際的首選分配。例如,考慮圖 1 系統中的以下場景:
- 除 2 個 CPU 外,所有 CPU 都已分配給容器
- 剩餘的 2 個 CPU 位於不同的 NUMA 節點上
- 一個新的容器來了,要求 2 個 CPU
在這種情況下,唯一生成的提示將是 {11: False}
,而不是 {11: True}
。發生這種情況是因為在此係統上*可以*從同一 NUMA 節點分配 2 個 CPU(只是現在不行,因為當前的分配狀態)。其想法是,最好是拒絕 pod 准入並重試部署,而不是允許 pod 以次優的對齊方式進行排程。
HintProviders
HintProvider
是 kubelet
內部的一個元件,它與 TopologyManager
協調對齊的資源分配。目前,Kubernetes 中唯一的 HintProviders
是 CPUManager
和 DeviceManager
。我們計劃很快新增對 HugePages
的支援。
如前所述,TopologyManager
既從 HintProviders
收集 TopologyHints
,也使用合併後的“最佳”提示觸發它們上的對齊資源分配。因此,HintProviders
實現以下介面:
type HintProvider interface {
GetTopologyHints(*v1.Pod, *v1.Container) map[string][]TopologyHint
Allocate(*v1.Pod, *v1.Container) error
}
請注意,對 GetTopologyHints()
的呼叫返回一個 map[string][]TopologyHint
。這允許單個 HintProvider
為多種資源型別提供提示,而不是僅一種。例如,DeviceManager
需要這樣做才能將其外掛註冊的所有資源型別的提示傳遞回來。
當 HintProviders
生成其提示時,它們僅考慮*當前*系統上可用資源的對齊方式。不考慮已分配給其他容器的任何資源。
例如,考慮圖 1 系統,其中包含以下兩個請求資源的容器:
Container0 | Container1 |
spec: containers: - name: numa-aligned-container0 image: alpine resources: limits: cpu: 2 memory: 200Mi gpu-vendor.com/gpu: 1 nic-vendor.com/nic: 1 | spec: containers: - name: numa-aligned-container1 image: alpine resources: limits: cpu: 2 memory: 200Mi gpu-vendor.com/gpu: 1 nic-vendor.com/nic: 1 |
如果 Container0
是系統中第一個被考慮分配的容器,則將為 spec 中三個拓撲感知資源型別生成以下提示集:
cpu: {{01: True}, {10: True}, {11: False}}
gpu-vendor.com/gpu: {{01: True}, {10: True}}
nic-vendor.com/nic: {{01: True}, {10: True}}
最終對齊分配為:
{cpu: {0, 1}, gpu: 0, nic: 0}
當考慮 Container1
時,這些資源被假定為不可用,因此只會生成以下提示集:
cpu: {{01: True}, {10: True}, {11: False}}
gpu-vendor.com/gpu: {{10: True}}
nic-vendor.com/nic: {{10: True}}
最終對齊分配為:
{cpu: {4, 5}, gpu: 1, nic: 1}
注意:與本節開頭提供的虛擬碼不同,對 Allocate()
的呼叫實際上不接受直接合並後的“最佳”提示的引數。相反,TopologyManager
實現以下 Store
介面,HintProviders
可以查詢該介面以檢索為特定容器生成的提示(一旦生成):
type Store interface {
GetAffinity(podUID string, containerName string) TopologyHint
}
將此分離到自己的 API 呼叫中,允許在 pod 准入迴圈之外訪問此提示。這對於除錯以及在 kubectl
(尚未提供)等工具中報告生成的提示非常有用。
Policy.Merge
給定策略定義的合併策略決定了它如何將所有 HintProviders
生成的 TopologyHints
集合併為一個單一的 TopologyHint
,該提示可用於告知對齊的資源分配。
所有支援的策略的通用合併策略都開始於相同:
- 取為每種資源型別生成的
TopologyHints
的笛卡爾積。 - 對於笛卡爾積中的每個條目,將每個
TopologyHint
的 NUMA 親和性進行*按位與*運算。將其設定為結果“合併”提示中的 NUMA 親和性。 - 如果條目中的所有提示都將
Preferred
設定為True
,則將結果“合併”提示中的Preferred
設定為True
。 - 如果條目中的任何一個提示將
Preferred
設定為False
,則將結果“合併”提示中的Preferred
設定為False
。如果其 NUMA 親和性包含全零,也將“合併”提示中的Preferred
設定為False
。
遵循上一節的示例,為 Container0
生成的提示如下:
cpu: {{01: True}, {10: True}, {11: False}}
gpu-vendor.com/gpu: {{01: True}, {10: True}}
nic-vendor.com/nic: {{01: True}, {10: True}}
上述演算法導致以下笛卡爾積條目和“合併”提示集:
笛卡爾積條目
| “合併”提示 |
{{01: True}, {01: True}, {01: True}} | {01: True} |
{{01: True}, {01: True}, {10: True}} | {00: False} |
{{01: True}, {10: True}, {01: True}} | {00: False} |
{{01: True}, {10: True}, {10: True}} | {00: False} |
{{10: True}, {01: True}, {01: True}} | {00: False} |
{{10: True}, {01: True}, {10: True}} | {00: False} |
{{10: True}, {10: True}, {01: True}} | {00: False} |
{{10: True}, {10: True}, {10: True}} | {01: True} |
{{11: False}, {01: True}, {01: True}} | {01: False} |
{{11: False}, {01: True}, {10: True}} | {00: False} |
{{11: False}, {10: True}, {01: True}} | {00: False} |
{{11: False}, {10: True}, {10: True}} | {10: False} |
一旦生成了此“合併”提示列表,就由正在使用的特定 TopologyManager
策略來決定哪個提示被視為“最佳”提示。
通常,這涉及:
- 按“狹窄度”對合並後的提示進行排序。狹窄度定義為提示的 NUMA 親和性掩碼中設定的位數。設定的位數越少,提示越窄。對於 NUMA 親和性掩碼中設定位數相同的提示,設定了最高低位順序的提示被認為更窄。
- 按
Preferred
欄位對合並後的提示進行排序。Preferred
設定為True
的提示被視為比Preferred
設定為False
的提示更有可能是候選。 - 選擇最窄的提示,並具有最佳的
Preferred
設定。
對於 best-effort
策略,此演算法將始終導致*某個*提示被選為“最佳”提示,並准入 pod。這個“最佳”提示隨後可供 HintProviders
使用,以便它們可以基於此進行資源分配。
然而,對於 restricted
和 single-numa-node
策略,任何被選中的 Preferred
設定為 False
的提示都將被立即拒絕,導致 pod 准入失敗且不分配任何資源。此外,single-numa-node
還會拒絕選中的提示,因為其親和性掩碼中設定了多個 NUMA 節點。
在上面的示例中,pod 將被所有策略以提示 {01: True}
准入。
即將進行的增強
雖然 1.18 版本釋出並晉升為 Beta 版帶來了一些很棒的增強和修復,但仍然存在一些限制,此處有描述。我們已經著手解決這些限制以及更多問題。
本節將介紹我們計劃在不久的將來為 TopologyManager
實現的一系列增強。此列表並非詳盡無遺,但它能讓你大致瞭解我們的發展方向。它按我們預計每個增強功能完成的時間順序排列。
如果您想參與幫助實現任何這些增強功能,請加入每週的 Kubernetes SIG-node 會議以瞭解更多資訊併成為社群努力的一部分!
支援裝置特定約束
目前,NUMA 親和性是 TopologyManager
為資源對齊考慮的唯一約束。此外,可以對 TopologyHint
進行的可擴充套件的唯一擴充套件涉及*節點級*約束,例如跨裝置型別的 PCIe 匯流排對齊。嘗試將任何*裝置特定*約束新增到此結構(例如,一組 GPU 裝置之間的內部 NVLINK 拓撲)將是不可行的。
因此,我們建議對裝置外掛介面進行擴充套件,允許外掛宣告其拓撲感知的分配偏好,而無需將任何裝置特定的拓撲資訊暴露給 kubelet。透過這種方式,TopologyManager
可以僅限於處理通用的節點級拓撲約束,同時仍然有一種方法可以將其裝置特定的拓撲約束納入其分配決策。
此提案的詳細資訊可以在此處找到,並且應該很快就可以在 Kubernetes 1.19 中使用了。
Hugepages 的 NUMA 對齊
如前所述,目前 TopologyManager
可用的兩個 HintProviders
是 CPUManager
和 DeviceManager
。然而,目前正在努力新增對 hugepages 的支援。完成這項工作後,TopologyManager
將最終能夠將記憶體、hugepages、CPU 和 PCI 裝置全部分配在同一個 NUMA 節點上。
此工作的KEP目前正在審查中,並且一個原型正在進行中,以儘快實現此功能。
排程器感知
目前,TopologyManager
作為 Pod 准入控制器。它不直接參與 pod 的放置位置的排程決策。相反,當 Kubernetes 排程程式(或部署中執行的任何排程程式)將 pod 放置在節點上執行時,TopologyManager
將決定 pod 是否應被“准入”或“拒絕”。如果 pod 因缺乏可用的 NUMA 對齊資源而被拒絕,情況可能會變得有些棘手。這個 Kubernetes issue 很好地突出了並討論了這種情況。
那麼我們如何解決這個限制呢?我們有 Kubernetes 排程框架 來拯救!該框架提供了一組新的外掛 API,與現有的 Kubernetes 排程程式整合,並允許實現排程功能(例如 NUMA 對齊),而無需訴諸其他可能不那麼理想的替代方案,包括編寫自己的排程程式,甚至更糟的是,建立分叉以新增自己的排程程式秘密武器。
這些擴充套件以與 TopologyManager
整合的細節尚未制定。我們仍然需要回答以下問題:
- 我們將需要重複邏輯來確定
TopologyManager
和排程程式中的裝置親和性嗎? - 我們需要一個新的 API 將
TopologyHints
從TopologyManager
傳遞到排程程式外掛嗎?
這項功能的工作應該在未來幾個月內開始,所以請繼續關注!
每個 pod 的對齊策略
如前所述,一個策略是透過全域性 kubelet
標誌應用於節點上的*所有* pod,而不是允許使用者逐個 pod(或逐個容器)選擇不同的策略。
雖然我們同意這是一個很棒的功能,但在實現它之前需要克服許多障礙。最大的障礙是,此增強功能需要進行 API 更改,以便能夠將所需的對齊策略表達在 Pod spec 或其關聯的 RuntimeClass
中。
我們才剛剛開始認真討論這個功能,並且最多還需要幾個版本才能可用。
結論
隨著 TopologyManager
在 1.18 版本中晉升為 Beta 版,我們鼓勵大家嘗試一下,並期待您的反饋。在過去幾個版本中,已進行了許多修復和增強,極大地提高了 TopologyManager
及其 HintProviders
的功能性和可靠性。儘管仍存在一些限制,但我們已計劃進行一系列增強來解決這些限制,並期待在即將釋出的新版本中為您提供許多新功能。
如果您對其他增強功能有想法或希望使用某些功能,請不要猶豫告訴我們。該團隊始終樂於接受有關增強和改進 TopologyManager
的建議。
希望這篇博文內容豐富且有用!如果您有任何問題或評論,請告訴我們。祝您部署順利……團結一致!