Kubernetes 中 CPU 調(diào)度管理的現(xiàn)狀與限制
原文鏈接:https://edwardesire.com/posts/the-trending-of-cpu-management-in-k8s

K8s 的 cpuManager 完成節(jié)點側(cè)的 CPU 資源分配和隔離(core pinning and isolation,如何做到隔離)。
發(fā)現(xiàn)機器上的 CPU 拓撲 上報給 K8s 層機器的可用資源(包含 kubelet 側(cè)的調(diào)度) 分配資源供 workload 執(zhí)行 追蹤 pod 的資源分配情況
本文大致介紹 K8s 中 CPU 管理的現(xiàn)狀與限制,結(jié)合社區(qū)文檔分析當前社區(qū)動態(tài)。
CPU 管理的現(xiàn)狀與限制 相關(guān)的 issues 社區(qū)提案
CPU 管理的現(xiàn)狀與限制
kubelet 將系統(tǒng)的 CPU 分為 2 個資源池:
獨占池(exclusive pool):同時只有一個任務(wù)能夠分配到 CPU 共享池(shared pool):多個進程分配到 CPU
原生的 K8s cpuManager 目前只提供靜態(tài)的 CPU 分配策略。當 K8s 創(chuàng)建一個 pod 后,pod 會被分類為一個 QoS:
Guaranteed Burstable BestEffort
并且 kubelet 允許管理員通過 –reserved-cpus 指定保留的 CPU 提供給系統(tǒng)進程或者 kube 守護進程(kubelet, npd)。保留的這部分資源主要提供給系統(tǒng)進程使用??梢宰鳛楣蚕沓胤峙浣o非 Guaranteed 的 pod 容器。但是 Guaranteed 類 pod 無法分配這些 cpus。
目前 K8s 的節(jié)點側(cè)依據(jù) cpuManager 的分配策略來分配 numa node 的 cpuset,能夠做到:
容器被分配到一個 numa node 上。 容器被分配到一組共享的 numa node 上。
cpuManager 當前的限制:
最大 numa node 數(shù)不能大于 8,防止狀態(tài)爆炸(state explosion)。 策略只支持靜態(tài)分配 cpuset,未來會支持在容器生命周期內(nèi)動態(tài)調(diào)整 cpuset。 調(diào)度器不感知節(jié)點上的拓撲信息。下文會介紹相應(yīng)的提案。 對于線程布局(thread placement)的應(yīng)用,防止物理核的共享和鄰居干擾。CPU manager 當前不支持。下文有介紹相應(yīng)的提案。
相關(guān) issues
1、針對處理器的異構(gòu)特征,用戶可以指定服務(wù)所需要的硬件類別[1]。
異構(gòu)計算的異構(gòu)資源有著不同額性能和特征和多級。比如 Intel 11th gen,性能內(nèi)核(Performance-cores, P-cores)是高性能內(nèi)核,效率內(nèi)核(Efficiency-cores,)是性能功耗比更優(yōu)的內(nèi)核。
ref:https://www.intel.cn/content/www/cn/zh/gaming/resources/how-hybrid-design-works.html
這個 issue 描述的用戶場景是,可以將 E-cores 分配給守護進程或者后臺任務(wù),將 P-cores 分配給性能要求更高的應(yīng)用服務(wù)。支持這種場景需要對 CPU 進行分組分配。但是 issue 具體的方案討論。因為底層硬件差異,目前無法做到通用。目前 K8s 層需要設(shè)計重構(gòu)方案。
當前相關(guān)需求的落地方案都是在 K8s 上使用擴展資源的方式來標識不同的異構(gòu)資源。這種方法會產(chǎn)生對于原生 CPU/ 內(nèi)存資源的重復統(tǒng)計。
2、topologyManager 的 best-effort 策略優(yōu)化[2]。
issue 提到 best-effort 的策略,迭代每個 provider hint,依據(jù)位與運算聚合結(jié)果。如果最后的結(jié)果為 not preferred,topologyManager 應(yīng)該盡力依據(jù)資源的傾向做到 preferred 的選擇。這個想法的初衷是因為 CPU 資源相比其他外設(shè)的 numa 親和更重要。當多個 provider hint 相互沖突時,如果 CPU 有 preferred 的單 numa node 分配結(jié)果,應(yīng)該先滿足 CPU 的分配結(jié)果。比如 CPU 返回的結(jié)果為 [‘10’ preferred, ‘11’ non-preferred]),一個設(shè)備返回的結(jié)果 [‘01’, preferred]。topologyManager 應(yīng)該使用 '10’ preferred 作為最后的結(jié)果,而不是合并之后的 '01’ not preferred。
而社區(qū)對于這種的調(diào)度邏輯的改變,建議是創(chuàng)建新的 policy 以提供類似調(diào)度器優(yōu)選(scoring)的算法系統(tǒng)。
3、嚴格的 kubelet 預留資源[3]。
希望提供新的參數(shù) StrictCPUReservation,表示嚴格的預留資源,DefaultCPUSet 列表會移除 ReservedSystemCPUs.
4、bug:釋放 init container 的資源時,釋放了重新分配給 main container 的資源[4]。
這個 issue 已經(jīng)修復:在 RemoveContainer 階段,排除還在使用的容器的 cpuset。剩下的 cpuset 才可以釋放回 DefaultCPUSet。
5、支持原地垂直擴展:針對已經(jīng)部署到節(jié)點的 pod 實例,通過 resize 請求,修改 pod 的資源量[5]。
Docker+K8s+Jenkins 主流技術(shù)全解視頻資料【干貨免費分享】
原地垂直擴展的意思是:當業(yè)務(wù)調(diào)整服務(wù)的資源時,不需要重啟容器。
原地垂直擴容是個復雜的功能,這里大致介紹設(shè)計思路。詳細實現(xiàn)可以看 PR: https://github.com/kubernetes/kubernetes/pull/102884。
kube-scheduler 依然使用 pod 的 Spec…Resources.Requests 來進行調(diào)度。依據(jù) pod 的 Status.Resize 狀態(tài),判斷緩存中 node 已經(jīng)分配的資源量。
Status.Resize = “InProgress” or “Infeasible”,依據(jù) Status…ResourcesAllocated(已經(jīng)分配的值)統(tǒng)計資源量。 Status.Resize = “Proposed”,依據(jù) Spec…Resources.Requests(新修改的值) 和 Status…ResourcesAllocated(已經(jīng)分配的值,如果 resize 合適,kubelet 也會將新 requests 更新這個屬性),取兩者的最大值。
kubelet 側(cè)的核心在 admit 階段來判斷剩余資源是否滿足 resize。而具體 resize 是否需要容器重啟,需要依據(jù) container runtime 來判斷。所以這個 resize 功能其實是盡力型。通過 ResizePolicy 字段來判斷:

還值得注意點是當前 PR 主要是在 kata、docker 上支持原地重啟,windows 容器還未支持。
有趣的社區(qū)提案
調(diào)度器拓撲感知調(diào)度
Redhat 將他們實現(xiàn)的一套拓撲調(diào)度[6]的方案貢獻到社區(qū):https://github.com/kubernetes/enhancements/pull/2787
擴展 cpuManager 防止理核不在容器間共享:
kep:https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2625-cpumanager-policies-thread-placement
防止同一個物理核的虛擬分配帶來的干擾。
設(shè)計文檔里引入新參數(shù) cpumanager-policy-options:full-pcpus-only,期望分配獨占一個物理 CPU。當指定了 full-pcpus-only 參數(shù)以及 static 策略時,cpuManager 會在分配 cpusets 會額外檢查,確保分配 CPU 的時候是分配整個物理核。從而確保容器在物理核上的競爭。
具體例子比如,一個容器申請了 5 個獨占核(虛擬核),CPU 0-4 都分配個了服務(wù)容器。CPU 5 也被鎖住不能再分配給容器。因為 CPU 5 和 CPU 4 同在一個物理核上。

增加 cpuMananger 跨 numa 分散策略:distribute-cpus-across-numa[7]
full-pcpus-only:上面已經(jīng)描述:full-pcpus-only確保容器分配的 CPU 物理核獨占 。distribute-cpus-across-numa:跨 numa node 均勻分配容器。
開啟 distribute-cpus-across-numa 時,當容器需要分配跨 numa node 時,statie policy 會跨 numa node 平均分配 CPU。非開啟的默認邏輯是優(yōu)選填滿一個 numa node。防止跨 numa node 分配時,在一個余量最小的 numa node 上分配。從整個應(yīng)用性能考慮,性能瓶頸收到落在剩余資源較少的 numa node 上性能最差的 worker(process?)。這個選項能夠提供整體性能。
接下來介紹幾個社區(qū) slack 里討論的幾個提案:
CPU Manager Plugin Model Node Resource Interface Dynamic resource allocation
CPU Manager Plugin Model
CPU Manager Plugin Model:kubelet cpuManager 的插件框架。在不改動資源管理主流程前提下,支持不同的 CPU 分配場景。依據(jù)業(yè)務(wù)需求,實現(xiàn)更細粒度的控制 cpuset。
kubelet 在 pod 綁定成功之后,會將 pod 壓入本地調(diào)度隊列里,依次執(zhí)行 pod 的 cpuset 的調(diào)度流程。調(diào)度流程本質(zhì)上借鑒了 kube-scheduler 的調(diào)度框架。
插件可擴展點:
一個插件可以實現(xiàn) 1 個或多個可擴展點:
Sort:將調(diào)度到節(jié)點上的 pod 排序處理。例如依據(jù) pod QoS 判定的優(yōu)先順序。 Filter:過濾無法分配給 pod 的 CPU。 PostFilter:當沒有合適的 CPU 時,可以通過 PostFilter 進行預處理,然后將 pod 重新進行處理。 PreScore:對于單個 CPU 評分,提供給后面流程來判定分配組合的優(yōu)先級。 Select:依據(jù) PreScore 的結(jié)果選擇一個 CPU 組合的最優(yōu)解,最優(yōu)解的結(jié)構(gòu)是一組 CPU。 Score:依據(jù) Select 的結(jié)果——CPU 分配組合評分。 Allocate:在分配 cpuset 之后,調(diào)用該插件。 Deallocate:在 PostFilter 之后,釋放 CPU 的分配。
三個評分插件的區(qū)別:
PreScore:返回以 CPU 為 key,value 為單個 CPU 對于 pod 容器的親和程度。 Select:依據(jù)插件的領(lǐng)域知識(比如同一個 numa 的 CPU 分配結(jié)構(gòu)聚合),將 CPU 組合的分數(shù)聚合。返回是一組最佳 CPU。 Score:依據(jù)所有的 CPU 組合,評分分配組合依據(jù)插件強約束邏輯。
方案提出了兩種擴展插件的方案。當前在 kubelet 的容器管理中,topologyManager 主要完成下列事項:
調(diào)度用 hintProvider,獲得各個子管理域的可分配情況 編排整體的拓撲分配決策 提供“scopes”和 policies 參數(shù)來影響整體策略
其他子管理域的子 manager(如 cpuManager)作為 hintProvider 提供單個分配策略。在 CPU Manager Plugin Model 中,子 manager 作為模型插件接口提供原有功能。
方案 1:擴展子 manager,讓 topologyManager 感知 cpuset
通過當前的值回去 numa node 的分配擴展到能夠針對單個 cpuset 的分配傾向。擴展插件以 hint providers 的形式執(zhí)行,主流程不需要修改。
缺點:其他 hintProvider(其他資源的分配)并不感知 CPU 信息,導致 hintProvider 的結(jié)果未參考 CPU 分配。最終聚合的結(jié)果不一定是最優(yōu)解。
方案 2:擴展 cpuManager 為插件模型
topologyManager 依然通過 GetTopologyHints() 和 Allocate() 調(diào)用 cpuManager,cpuManager 內(nèi)部進行擴展調(diào)度流程。具體的擴展方式可以通過引入新的 policy 配置,或者通過調(diào)度框架的方式直接擴展。
缺點:cpuManager 的結(jié)果并不決定性的,topologyManager 會結(jié)合其他 hints 來分配。
可以看到 CPU Manager Plugin Model 當前提案還出于非常原始的階段,主要是 Red Hat 的人在推。并未在社區(qū)充分討論。
Node Resource Interface
該方案來自 containerd。主要是在 CRI 中擴展 NRI 插件。
containerd
containerd 主要工作在平臺和更底層的 runtime 之間。平臺是指 docker、k8s 這類容器平臺,runtime 是指 runc, kata 等更底層的運行時。containerd 在中間提供容器進程的管理,鏡像的管理,文件系統(tǒng)快照以及元數(shù)據(jù)和依賴管理。下圖是 containerd 架構(gòu)總覽圖:

client 是用戶交互的第一層,提供接口給調(diào)用方。 core 定義了核心功能接口。所有的數(shù)據(jù)都通過 core 管理存儲(metadata store),所有其他組件 / 插件不需要存儲數(shù)據(jù)。 backend 中的 runtime 負責通過不同 shim 與底層 runtime 打交道。 api 層主要提供兩大類 gRPC 服務(wù):image,runtime。提供了多種插件擴展。
在 CRI 這一層,包含了 CRI、CNI、NRI 類型的插件接口:

CRI plugin:容器運行時接口插件,通過共享 namespace、cgroups 給 pod 下所有的容器,負責定義 pod。 CNI plugin:容器網(wǎng)絡(luò)接口插件,配置容器網(wǎng)絡(luò)。當 containerd 創(chuàng)建第一個容器之后,通過 namespace 配置網(wǎng)絡(luò)。 NRI plugin:節(jié)點資源接口插件,管理 cgroups 和拓撲。
NRI
NRI 位于 containerd 架構(gòu)中的 CRI 插件,提供一個在容器運行時級別來管理節(jié)點資源的插件框架。
cni 可以用來解決批量計算,延遲敏感性服務(wù)的性能問題,以及滿足服務(wù) SLA/SLO、優(yōu)先級等用戶需求。例如性能需求通過將容器的 CPU 分配同一個 numa node,來保證 numa 內(nèi)的內(nèi)存調(diào)用。當然除了 numa,還有 CPU、L3 cache 等資源拓撲親和性。
當前 kubelet 的實現(xiàn)是通過 cpuManager 的處理對象只能是 guaranteed 類的 pod, topologyManager 通過 cpuManager 提供的 hints 實現(xiàn)資源分配。
kubelet 當前也不適合處理多種需求的擴展,因為在 kubelet 增加細粒度的資源分配會導致 kubelet 和 CRI 的界限越來越模糊。而上述 CRI 內(nèi)的插件,則是在 CRI 容器生命周期期間調(diào)用,適合做 resoruce pinning 和節(jié)點的拓撲的感知。并且在 CRI 內(nèi)部做插件定義和迭代,可以做到上層 kubernetes 以最小代價來適配變化。
在容器生命周期中,CNI/NRI 插件能夠注入到容器初始化進程的 Create 和 Start 之間:
Create->NRI->Start
以官方例子clearcfs[8]:在啟動容器前,依據(jù) qos 類型調(diào)用 cgroup 命令,cpu.cfs_quota_us 為-1 表示不設(shè)上限。
可以分析出 NRI 直接控制 cgroup,所以能有更底層的資源分配方式。不過越接近底層,處理邏輯的復雜度也越高。
Dynamic resource allocation
KEP 里翻到了這個動態(tài)資源分配,方案提供了一套新的 K8s 管理資源和設(shè)備資源的模型。核心思想和存儲類型(storageclass)類似,通過掛載來實現(xiàn)具體設(shè)備資源的聲明和消費,而不是通過 request/limit 來分配一定數(shù)量的設(shè)備資源。
用例:
設(shè)備初始化:為 workload 配置設(shè)備?;谌萜餍枨蟮呐渲?,但是這部分配配置不應(yīng)該直接暴露給容器。 設(shè)備清理:容器結(jié)束后清理設(shè)備參數(shù) / 數(shù)據(jù)等信息 . Partial allocation:支持部分分配,一個設(shè)備共享多個容器。 optional allocation:支持容器聲明軟性 (可選的) 資源請求。例如:GPU and crypto-offload engines 設(shè)備的應(yīng)用場景。 Over the Fabric devices:支持容器使用網(wǎng)絡(luò)上的設(shè)備資源。
動態(tài)資源分配的設(shè)計目的是提供更靈活控制、用戶友好的 api,資源管理插件化不需要重新構(gòu)建 K8s 組件。
通過定義動態(tài)資源分配的資源分配協(xié)議和 gRPC 接口來管理新定義 K8s 資源 ResourceClass 和 ResourceClaim:
ResourceClass 指定資源的驅(qū)動和驅(qū)動參數(shù) ResourceClaim 指定業(yè)務(wù)使用資源的實例
立即分配和延遲分配:
立即分配:ResourceClaim 創(chuàng)建時就分配。對于稀缺資源的分配能夠有效使用(allocating a resource is expensive)。但是沒有保障由于其他資源(CPU,內(nèi)存)導致節(jié)點無法調(diào)度。 延遲分配:調(diào)度成功才分配。能夠處理立即分配帶來的問題。
調(diào)用流程

用戶創(chuàng)建 帶有 resourceClaimTemplate 配置的 pod。 資源聲明 controller 創(chuàng)建 resourceClaim。 依據(jù) resourceClaim 的 spec 中,立即分配(immediate allocation)和延遲分配(delayed allocation)處理。 立即分配:資源驅(qū)動 controller 發(fā)現(xiàn) resourceClaim 的創(chuàng)建時并 claim。 延遲分配:調(diào)度器首先處理,過濾不滿足條件的節(jié)點,獲得候選節(jié)點集。資源驅(qū)動再過濾一次候選節(jié)點集不符合要求的節(jié)點。 當資源驅(qū)動完成資源分配之后,調(diào)度器預留資源并綁定節(jié)點。 節(jié)點上的 kubelet 負責 pod 的執(zhí)行和資源管理(調(diào)用驅(qū)動插件)。 當 pod 刪除時,kubelet 負責停止 pod 的容器,并回收資源(調(diào)用驅(qū)動插件)。 pod 刪除之后,gc 會負責相應(yīng)的 resourceClaim 刪除。
這塊文檔沒有具體描述:在立即分配的場景中,如果沒有調(diào)度器工作,resoruce driver controller 來節(jié)點選擇機制是怎么樣的。
總結(jié)
可以看到未來社區(qū)會對 kubelet 容器管理做一次重構(gòu),來支持更復雜的業(yè)務(wù)場景。近期在 CPU 資源管理上會落地的調(diào)度器拓撲感知調(diào)度,和定制化的 kubelet CPU 分配策略。在上述的一些 case 中,有發(fā)展?jié)摿Φ氖?NRI 方案。
支持定制化擴展,kubelet 可以直接載入擴展配置無需修改自身代碼。 通過與 CRI 交互,kubelet 將部分復雜的 CPU 分配需求下放到 runtime 來處理。
- END -
推薦閱讀 31天拿下K8s含金量最高的雙認證!【本周開課】 K8s 選 cgroupfs 還是 systemd?這是一個問題 Docker 暴重大安全漏洞:外部網(wǎng)絡(luò)可直接訪問本地服務(wù) 在 Kubernetes 容器集群,微服務(wù)項目最佳實踐 2022 年要考慮的 7 種 Docker 替代方案 Linux 運維工程師的 6 類好習慣和 23 個教訓 頂級 DevOps 工具鏈大盤點 某外企從 0 建設(shè) SRE 運維體系經(jīng)驗分享 Nginx+Redis:高性能緩存利器 主流監(jiān)控系統(tǒng) Prometheus 學習指南 基于 eBPF 的 Kubernetes 問題排查全景圖發(fā)布 一文掌握 Ansible 自動化運維 Linux的10個最危險命令 24 個 Docker 常見問題處理技巧 這篇文章帶你全面掌握 Nginx ! 一文搞懂 Kubernetes 網(wǎng)絡(luò)通信原理 搭建一套完整的企業(yè)級 K8s 集群(v1.22,二進制方式) 點亮,服務(wù)器三年不宕機


