混部之殤-論雲原生資源隔離技術之CPU隔離(一)

騰訊雲原生發表於2021-05-13

作者

蔣彪,騰訊雲高階工程師,10+年專注於作業系統相關技術,Linux核心資深發燒友。目前負責騰訊雲原生OS的研發,以及OS/虛擬化的效能優化工作。

導語

混部,通常指在離線混部(也有離線上混部之說),意指通過將線上業務(通常為延遲敏感型高優先順序任務)和離線任務(通常為 CPU 消耗型低優先順序任務)同時混合部署在同一個節點上,以期提升節點的資源利用率。其中的關鍵難點在於底層資源隔離技術,嚴重依賴於 OS 核心,而現有的原生 Linux kernel 提供的資源隔離能力在面對混部需求時,再次顯得有些捉襟見肘(或至少說不夠完美),仍需深度 Hack,方能滿足生產級別的需求。

(雲原生)資源隔離技術主要包括 CPU、memory、IO 和網路,4個方面。本文聚焦於 CPU 隔離技術和相關背景,後續(系列)再循序漸進,逐步展開到其他方面。

背景

無論在 IDC,還是在雲場景中,資源利用率低都絕對是大部分使用者/廠商面臨的共同難題。一方面,硬體成本很高(大家都是靠買,而且絕大部分硬體(核心技術)掌握於別人手中,沒有定價權,議價能力也通常很弱),生命週期還很短(過幾年還得換新);另一方面,極度尷尬的是這麼貴的東西無法充分利用,拿 CPU 佔用率來說,絕大部分場景的平均佔用率都很低(如果我拍不超過20%(這裡指日均值,或周均值),相信大部分同學都不會有意見,這就意味著,賊貴的東西實際只用了不到五分之一,如果你還想正經的居家過日子,想必都會覺得心疼。

因此,提升主機(節點)的資源利用率是一項非常值得探索,同時收益非常明顯的任務。解決思路也非常直接,

常規的思維模式:多跑點業務。說起來容易,誰沒試過呢。核心困難在於:通常的業務都有明顯的峰谷特徵

你希望的樣子可能是這樣的:

但現實的樣子多半是這樣的:

而在為業務做容量規劃是,需要按 Worst Case 做(假設所有業務的優先順序都一樣),具體來說,從 CPU 層面的話,就需要按 CPU 峰值(可能是周峰值、甚至月/年峰值)的來規劃容量(通常還得留一定的餘量,應對突發),

而現實中大部分情況是:峰值很高,但實際的均值很低。因此導致了絕大部分場景中的 CPU 均值都很低,實際 CPU 利用率很低。

前面做了個假設:“所有業務的優先順序都一樣”,業務的 Worst Case 決定了整機的最終表現(資源利用率低)。如果換種思路,但業務區分優先順序時,就有更多的發揮空間了,可以通過犧牲低優先順序業務的服務質量(通常可以容忍)來保證高優先順序業務的服務質量,如此能部署在適量高優先順序業務的同時,部署更多的業務(低優先順序),從而整體上提升資源利用率。

混部(混合部署)因此應運而生。這裡的“混”,本質上就是“區分優先順序”。狹義上,可以簡單的理解為“線上+離線”(在離線)混部,廣義上,可以擴充套件到更廣的應用範圍:多優先順序業務混合部署。

其中涉及的核心技術包括兩個層面:

  1. 底層資源隔離技術。(通常)由作業系統(核心)提供,這是本(系列)文章的核心關注點。
  2. 上層的資源排程技術。(通常)由上層的資源編排/排程框架(典型如 K8s)提供,打算另做系列文章展開,仍請期待。

混部也是業界非常熱門的話題和技術方向,當前主流的頭部大廠都在持續投入,價值顯而易見,也有較高的技術門檻(壁壘)。相關技術起源甚早,頗有淵源,大名鼎鼎的 K8s(前身 Borg)其實源於 Google 的混部場景,而從混部的歷史和效果看,Google 算是行業內的標杆,號稱 CPU 佔用率(均值)能做到60%,具體可參考其經典論文:

https://dl.acm.org/doi/pdf/10.1145/3342195.3387517

https://storage.googleapis.com/pub-tools-public-publication-data/pdf/43438.pdf

當然,騰訊(雲)在混部方向的探索也很早,也經歷了幾次大的技術/方案迭代,至今已有不錯的落地規模和成果,詳情又需起另外的話題,不在本文探討。

技術挑戰

如前面所說,混部場景中,底層資源隔離技術至關重要,其中的“資源”,整體上分為4個大類:

  • CPU
  • Memory
  • IO
  • 網路

本文聚焦於 CPU 隔離技術,主要分析在 CPU 隔離層面的技術難點、現狀和方案。

CPU隔離

前面說的4類資源中,CPU 資源隔離可以說是最基礎的隔離技術。一方面,CPU 是可壓縮(可複用)資源,複用難度相對較低,Upstream的解決方案可用性相對較好;另一方面,CPU 資源與其他資源關聯性較強,其他資源的使用(申請/釋放)往往依賴於程式上下文,間接依賴於 CPU 資源。舉例來說,當 CPU 被隔離(壓制)後,其他如 IO、網路的請求可能(大部分情況)因為 CPU 被壓制(得不到排程),從而也隨之被壓制。

因此,CPU 隔離的效果也會間接影響其他資源的隔離效果,CPU 隔離是最核心的隔離技術。

核心排程器

具體來說,落地到 OS 中,CPU 隔離本質上完全依賴於核心排程器實現,核心排程器是負載分配 CPU 資源的核心基本功能單元(很官方的說法),具體來說(狹義說),可以對應到我們接觸最多的 Linux 核心的預設排程器:CFS 排程器(本質上是一個排程類,一套排程策略)。

核心排程器決定了何時、選取什麼任務(程式)到 CPU 上執行,因此決定了混部場景中線上和離線任務的 CPU 執行時間,從而決定了 CPU 隔離效果。

Upstream kernel隔離效果

Linux 核心排程器預設提供了5個排程類,實際業務能用的基本上只有兩種:

  • CFS
  • 實時排程器(rt/deadline)

混部場景中,CPU 隔離的本質在於需要:

  • 當線上任務需要執行時,盡力壓制離線任務
  • 當線上任務不執行時,離線任務利用空閒CPU執行

對於“壓制”,基於 Upstream kernel(基於 CFS),有如下幾種思路(方案):

優先順序

可以降低離線任務的優先順序,或提升線上任務的優先順序實現。在不修改排程類的情況下(基於預設的 CFS),可以動態調整的優先順序範圍為:[-20, 20)

時間片的具體表現為單個排程週期內可分得的時間片,具體來說:

  • 普通優先順序0與最低優先順序19之間的時間片分配權重比為:1024/15,約為:68:1
  • 最高優先順序-20與普通優先順序0之間的時間片分配權重比為:88761/1024,約為:87:1
  • 最高優先順序-20和最低優先順序19之間的時間片分配權重比為:88761/15,約為:5917:1

看起來壓制比還比較高,加入通過設定離線任務的優先順序為20,線上保持預設0(通常的做法),此時線上和離線的時間片分配權重為68:1。

假設單個排程週期長度為24ms(大部分系統的預設配置),看起來(粗略估算),單個排程週期中離線能分配到的時間片約為24ms/69=348us,可佔用約1/69=1.4%的CPU。

實際的執行邏輯還有點差異:CFS 考慮吞吐,設定了單次執行的最小時間粒度保護(程式單次執行的最小時間):sched_min_granularity_ns,多數情況設定為10ms,意味著離線一旦發生搶佔後,可以持續執行10ms的時間,也就意味著線上任務的排程延遲(RR切換延遲)可能達10ms。

Wakeup 時也有最小時間粒度保護(Wakeup時,被搶佔任務的最小執行時間保證):sched_wakeup_granularity_ns,多數情況設定為4ms。意味著離線一旦執行後,線上任務的 wakeup latency(另一種典型的排程延遲)也可能達4ms。

此外,調整優先順序並不能優化搶佔邏輯,具體來說,在實施搶佔時(wakeup 和週期性),並不會參考優先順序,並不會因為優先順序不同,而實時不同的搶佔策略(不會因為離線任務優先順序低,而壓制其搶佔,減少搶佔時機),因此有可能導致離線產生不必要的搶佔,從而導致干擾。

Cgroup(CPU share)

Linux核心提供了 CPU Cgroup(對應於容器pod),可以通過設定 Cgroup 的 share 值來控制容器的優先順序,也就是說可以通過調低離線 Cgroup 的 share 值來實現“壓制"目的。對於 Cgroup v1 來說,Cgroup 的預設 share 值為1024,Cgruop v2 的預設 share(weight) 值為100(當然還可以調),如果設定離線 Cgroup 的 share/weight 值為1(最低值),那麼,在CFS中,相應的時間片分配權重比分別為:1024:1和100:1,對應的CPU佔用分別約為0.1%和1%。

實際執行邏輯仍然受限於 sched_min_granularity_ns 和 sched_wakeup_granularity_ns。邏輯跟優先順序場景類似。

與優先順序方案類似,搶佔邏輯未根據 share 值優化,可能存在額外的干擾。

特殊 policy

CFS中還提供了一個特殊的排程 policy:SCHED_IDLE,專用於執行優先順序極低的任務,看起來是專為”離線任務“設計的。SCHED_IDLE 類任務本質上是有一個權重為3的 CFS 任務,其與普通任務的時間片分配權重比為:1024:3,約為334:1,此時離線任務的 CPU 佔用率約為0.3%。時間片分配如:

實際執行邏輯仍然受限於 sched_min_granularity_ns 和 sched_wakeup_granularity_ns。邏輯跟優先順序場景類似。

CFS 中對於 SCHED_IDLE 任務做了特殊的搶佔邏輯優化(壓制 SCHED_IDLE 任務對其他任務的搶佔,減少搶佔時機),因此,從這個角度看,SCHED_IDLE 為”適配“(雖然 Upstream 本意並非如此)混部場景邁進了一小步。

此外,由於 SCHED_IDLE 是 per-task 的標記,並無 Cgroup 級別的 SCHED_IDLE 標記能力,而 CFS 排程時,需要先選 (task)group,然後再從 group 中選 task ,因此對於雲原生場景(容器)混部來說,單純使用 SCHED_IDLE 並不能發揮實際作用。

整體看,雖然 CFS 提供了優先順序(share/SCHED_IDLE 原理上類似,本質都是優先順序),並可根據優先順序對低優先順序任務進行一定程度的壓制,但是,CFS 的核心設計在於”公平“,本質上無法做到對離線的”絕對壓制“,即使設定”優先順序“(權重)最低,離線任務仍能獲得固定的時間片,而獲得的時間片不是空閒的 CPU 時間片,而是從線上任務的時間片中搶到的。也就是說,CFS 的”公平設計“,決定了無法完全避免離線任務對線上的干擾,無法達到完美的隔離效果。

除此之外,通過(極限)降低離線任務的優先順序(上述幾種方案本質都是如此)的方式,本質上,還壓縮了離線任務的優先順序空間,換句話說,如果還想進一步在離線任務之間區分優先順序(離線任務之間也可能有 QoS 區分,實際可能有這樣的需求),那就無能為力了。

另,從底層實現的角度看,由於線上和離線均使用 CFS 排程類,實際執行時,線上和離線共用執行佇列(rq),疊加計算 load,共用 load balance 機制,一方面,離線在做共用資源(比如執行佇列)操作時需要做同步操作(加鎖),而鎖原語本身是不區分優先順序的,不能排除離線干擾;另一方面,load balance 時也無法區分離線任務,對其做特殊處理(比如激進 balance 防止飢餓、提升 CPU 利用率等),對於離線任務的 balance 效果無法控制。

實時優先順序

此時,你可能會想,如果需要絕對搶佔(壓制離線),為何不用實時排程類(RT/deadline)呢?實時排程類相比於 CFS,剛好達到”絕對壓制“的效果。

確實如此。但是,這種思路下,只能將線上業務設定為實時,離線任務保持為 CFS,如此,線上能絕對搶佔離線,同時如果擔心離線被餓死的話,還有 rt_throttle 機制來保證離線不被餓死。

看起來”完美“,其實不然。這種做法的本質,會壓縮線上任務的優先順序空間和生存空間(與之前調低離線任務優先順序的結果相反),結果是線上業務只能用實時排程類(儘管大部分線上業務並不滿足實時型別的特徵),再無法利用 CFS 的原生能力(比如公平排程、Cgroup 等,而這恰恰是線上任務的剛需)。

簡單來看,問題在於:實時型別並不能滿足線上任務自身執行的需要,本質上看線上業務自身並不是實時任務,如此強扭為實時後,會有比較嚴重的副作用,比如系統任務(OS 自帶的任務,比如各種核心執行緒和系統服務)會出現飢餓等。

總結一下,對於實時優先順序的方案:

  1. 認可實時型別對於 CFS 型別的”絕對壓制“能力(這正是我們想要的)
  2. 但當前 Upstream kernel 實現中,只能將線上任務設定為比 CFS 優先順序更高的實時型別,這是實際應用場景中無法接受的。

優先順序反轉

說到這,你心裡可能還有一個巨大的問號:”絕對壓制“後,會有優先順序反轉問題吧?怎麼辦?

答案是:的確存在優先順序反轉問題

解釋下這種場景下的優先順序反轉的邏輯:如果線上任務和離線任務之間有共享資源(比如核心中的一些公共資料,如 /proc 檔案系統之類),當離線任務因訪問共享資源而拿到鎖(抽象一下,不一定是鎖)後,如果被”絕對壓制“,一直無法執行,當線上任務也需要訪問該共享資源,而等待相應的鎖時,優先順序反轉出現,導致死鎖(長時間阻塞也可能)。優先順序反轉是排程模型中需要考慮的一個經典問題。

粗略總結下優先順序反轉發生的條件:

  • 在離線存在共享資源。
  • 存在共享資源的併發訪問,且使用了睡眠鎖保護。
  • 離線拿到鎖後,被完全絕對壓制,沒有執行的機會。這句話可以這樣理解:所有的 CPU 都被線上任務100%佔用,導致離線沒有任何執行機會。(理論上,只要有空閒 CPU,離線任務就可能通過 load balance 機制利用上)

在雲原生混部場景中,對於優先順序反轉問題的處理方法(思路),取決於看待該問題的角度,我們從如下幾個不同的角度來看,

  1. 優先順序反轉發生可能性有多大?這取決於實際的應用場景,理論上如果線上業務和離線業務之間不存在共享資源,其實就不會發生優先順序反轉。在雲原生的場景中,大體上分兩種情況:

(1)安全容器場景。此場景中,業務實際執行於”虛擬機器“(抽象理解)中,而虛擬機器自身保證了絕大部分資源的隔離性,這種場景中,基本可以避免發生優先順序反轉(如果確實存在,可以特事特辦,單獨處理)

(2)普通容器場景。此場景中,業務執行於容器中,存在一些共享資源,比如核心的公共資源,共享檔案系統等。如前面分析,在存在共享資源的前提下,出現優先順序反轉的條件還是比較嚴苛的,其中最關鍵的條件是:所有 CPU 都被線上任務100%佔用,這種情況在現實的場景中,是非常少見的,算是非常極端的場景,現實中可以單獨處理這樣的”極端場景“

因此,在(絕大部分)真實雲原生場景中,我們可以認為,在排程器優化/hack 足夠好的前提下,可以規避。

  1. 優先順序反轉如何處理?雖然優先順序反轉僅在極端場景出現,但如果一定要處理的話(Upstream 一定會考慮),該怎麼處理?

(1)Upstream 的想法。原生 Linux kernel 的 CFS 實現中,為最低優先順序(可以認為是 SCHED_IDLE )也保留了一定的權重,也就意味著,最低優先順序任務也能得到一定的時間片,因此可以(基本)避免優先順序反轉問題。這也是社群一直的態度:通用,即使是極度極端的場景,也需要完美cover。這樣的設計也恰恰是不能實現”絕對壓制“的原因。從設計的角度看,這樣的設計並無不妥,但對於雲原生混部場景來說,這樣的設計並不完美:並不感知離線的飢餓程度,也就是說,在離線並不飢餓的情況下,也可能對線上搶佔,導致不必要的干擾。

(2)另一種想法。針對雲原生場景的優化設計:感知離線的飢餓和出現優先順序反轉的可能性,但離線出現飢餓並可能導致優先順序反轉時(也就是迫不得已時),才進行搶佔。如此一方面能避免不一樣的搶佔(干擾),同時還能避免優先順序反轉問題。達到(相對)完美的效果。當然,不得不承認,這樣的設計不那麼 Generic,不那麼 Graceful,因此 Upstream 也基本不太可能接受。

超執行緒干擾

至此,還漏了另一個關鍵問題:超執行緒干擾。這也是混部場景的頑疾,業界也一直沒有針對性的解決方案。

具體的問題是,由於同一個物理 CPU 上的超執行緒共享核心的硬體資源,比如 Cache 和計算單元。當線上任務和離線任務同時執行在一對超執行緒上時,相互之間會因為硬體資源爭搶,而出現相互干擾的情況。而 CFS 在設計時也完全沒有考慮這個問題

導致結果是,在混部場景中,線上業務的效能受損。實際測試使用 CPU 密集型 benchmark,因超執行緒導致的效能干擾可達40%+。

注:Intel 官方的資料:物理 core 效能差不多隻能1.2倍左右的單核效能。

超執行緒干擾問題是混部場景中的關鍵問題,而 CFS 在最初設計時是(幾乎)完全沒有考慮過的,不能說是設計缺失,只能說是 CFS 並不是為混部場景而設計的,而是為更通用的、更巨集觀的場景而生。

Core scheduling

說到這,專業(搞核心排程)的同學可能又會冒出一個疑問:難道沒聽說過 Core scheduling 麼,不能解決超執行緒干擾問題麼?

聽到這,不得不說這位同學確實很專業,Core Scheduling 是核心排程器模組 Maintainer Perter 在2019年提交的一個新 feature(基於更早之前的社群中曾提出的 coscheduling 概念),主要的目標在於解決(應該是 mitigation 或者是 workaround) L1TF 漏洞(由於超執行緒之間共享 cache 導致資料洩露),主要應用場景為:雲主機場景中,避免不同的虛擬機器程式執行於同一對超執行緒上,導致資料洩露。

其核心思想是:避免不同標記的程式執行於同一對超執行緒上。

現狀是:Core scheduling patchset 在經過長達v10的版本的迭代,近2年的討論和 improve/rework 之後,終於,就在最近(2021.4.22),Perter 發出了看似可能進入(何時能進入還不好說) master 的版本(還不太完整):

https://lkml.org/lkml/2021/4/22/501

關於這個話題,值得一個單獨的深入的分享,不在這裡展開。也請期待...

這裡直接拋(個人)觀點(輕拍):

  • Core scheduling 確實能用來解決超執行緒干擾問題。
  • Core scheduling 設計初衷是解決安全漏洞(L1TF),並非為混部超執行緒干擾而設計。由於需要保障安全,需要實現絕對隔離,需要複雜(開銷大)的同步原語(比如 core 級別的 rq lock),重量級的 feature 實現,如 core 範圍的 pick task,過重的 force idle。另外,還有配套的中斷上下文的併發隔離等。
  • Core scheduling 的設計和實現太重、開銷太大,開啟後效能 regression 嚴重,並不能區分線上和離線。不太適合(雲原生)混部場景。

本質還是:Core scheduling 亦非為雲原生混部場景而設計。

結論

綜合前面的分析,可以抽象的總結下當前現有的各種方案的優點和問題。

基於 CFS 中的優先順序(share/SCHED_IDLE 類似)方案,優點:

  • 通用。能力強,能全面hold住大部分的應用場景
  • 能(基本)避免優先順序反轉問題

問題:

  • 隔離效果不完美(沒有絕對壓制效果)
  • 其他各種小毛病(不完美)

基於實時任務型別的方案,優點:

  • 絕對壓制,隔離效果完美
  • 有機制避免優先順序反轉(rt_throttle)

問題:

  • 不適用。線上任務不能(大部分情況)用實時任務型別。
  • 有機制(rt_throttle)避免優先順序反轉,但開啟後,隔離效果就不完美了。

基於 Core scheduling 解決超執行緒干擾隔離,優點:

  • 完美超執行緒干擾隔離效果

問題:

  • 設計太重,開銷太大

結語

Upstream Linux kernel 為考慮通用性,設計的優雅,難以滿足特定場景(雲原生混部)中的極致需求,若想追求卓越和極致,還需要深度 Hack,而 TencentOS Server 一直在路上。(聽著耳熟?確實以前也這麼說過!

關於 Linux kernel 的核心排程器的具體實現和程式碼分析(基於5.4核心(Tkernel4)),我們後續會陸續推出相應的解析系列文章,在探討雲原生場景的痛點的同時,結合相應的程式碼分析,以期降低 Linux 核心神祕感,探討更廣闊的 Hack 空間。敬請期待。

思考

  1. 如果想要讓線上業務使用 CFS (利用 CFS 的強大能力),同時又想具備”絕對壓制“的能力,理想的做法應該怎麼辦?(感覺答案就要呼之欲出了!

  2. 如果不需要完美隔離效果(絕對壓制),同時需要處理優先順序反轉,還需要”接近完美“的隔離效果,還想盡量利用現有機制(不想太大的排程器 Hack,風險更小),那又該怎麼辦?(仔細看看前面的各種現有方案的分析總結,感覺也快接近答案了)

相關文章