Kubernetes叢集排程器原理剖析及思考

CCE_huawei發表於2019-04-01

導讀

雲環境或者計算倉庫級別(將整個資料中心當做單個計算池)的叢集管理系統通常會定義出工作負載的規範,並使用排程器將工作負載放置到叢集恰當的位置。好的排程器可以讓叢集的工作處理更高效,同時提高資源利用率,節省能源開銷。

通用排程器,如Kubernetes原生排程器Scheduler實現了根據特定的排程演算法和策略將pod排程到指定的計算節點(Node)上。但實際上設計大規模共享叢集的排程器並不是一件容易的事情。排程器不僅要了解叢集資源的使用和分佈情況,還要兼顧任務分配速度和執行效率。過度設計的排程器遮蔽了太多的技術實現,以至於無法按照預期完成排程任務,或導致異常情況的發生,不恰當的排程器的選擇同樣會降低工作效率,或導致排程任務無法完成。

本文主要從設計原理、程式碼實現兩個層面介紹Kubernetes的排程器以及社群對其的補充加強,同時對業界常用排程器的設計實現進行比較分析。透過本文,讀者可瞭解排程器的來龍去脈,從而為選擇甚至設計實現適合實際場景的排程器打下基礎。

註明:本文中程式碼基於v1.11版本Kubernetes進行分析,如有不當之處,歡迎指正!

排程器的基本知識

1.1 排程器的定義

通用排程的定義是指基於某種方法將某項任務分配到特定資源以完成相關工作,其中任務可以是虛擬計算元素,如執行緒、程式或資料流,特定資源一般是指處理器、網路、磁碟等,排程器則是完成這些排程行為的具體實現。使用排程器的目的是實現使用者共享系統資源的同時,降低等待時間,提高吞吐率以及資源利用率。

本文中我們討論的排程器是指大規模叢集下排程任務的實現,比較典型的有Mesos/Yarn(Apache)、Borg/Omega(Google)、Quincy(Microsoft)等。構建大規模叢集(如資料中心規模)的成本非常之高,因此精心設計排程器就顯得尤為重要。

常見型別的排程器的對比分析如下表1所示:

1.2 排程器的考量標準

我們首先思考一下排程器是根據哪些資訊來進行排程工作的,以及哪些指標可以用來衡量排程工作質量。

排程器的主要工作是將資源需求與資源提供方做全域性最優的匹配。所以一方面排程器的設計需要了解不同型別的資源拓撲,另一方面還需要對工作負載有充分的認識。

瞭解不同型別的資源拓撲,充分掌握環境拓撲資訊能夠使排程工作更充分的利用資源(如經常訪問資料的任務如果距資料近可以顯著減少執行時間),並且可以基於資源拓撲資訊定義更加複雜的策略。但全域性資源資訊的維護消耗會限制叢集的整體規模和排程執行時間,這也讓排程器難以擴充套件,從而限制叢集規模。

另一方面,由於不同型別的工作負載會有不同的甚至截然相反的特性,排程器還需要對工作負載有充分的認識,例如服務類任務,資源需求少,執行時間長,對排程時間並不敏感;而批處理類任務,資源需求大,執行時間短,任務可能相關,對排程時間要求較高。 同時,排程器也要滿足使用方的特殊要求。如任務儘量集中或者分散,保證多個任務同時進行等。

總的來說,好的排程器需要平衡好單次排程(排程時間,質量),同時要考慮到環境變化對排程結果的影響,保持結果最優(必要時重新排程),保證叢集規模,同時還要能夠支援使用者無感知的升級和擴充套件。排程的結果需要滿足但不限於下列條件,並最大可能滿足儘可能優先順序較高的條件:

資源使用率最大化

滿足使用者指定的排程需求

滿足自定義優先順序要求

排程效率高,能夠根據資源情況快速做出決策

能夠根據負載的變化調整排程策略

充分考慮各種層級的公平性

1.3 鎖對排程器設計的影響

對於資源的排程,一定會涉及到鎖的應用,不同型別鎖的選擇將直接決定排程器的使用場景。類似Mesos等兩層排程器,一般採用悲觀鎖的設計實現方式,當資源全部滿足任務需要時啟動任務,否則將增量繼續申請更多的資源直到排程條件滿足;而共享狀態的排程器,會考慮使用樂觀鎖的實現方式,Kubernetes預設排程器是基於樂觀鎖進行設計的。

我們首先透過一個簡單的例子,比較下悲觀鎖和樂觀鎖處理邏輯的不同,假設有如下的一個場景:

作業A讀取物件O

作業B讀取物件O

作業A在記憶體中更新物件O

作業B在記憶體中更新物件O

作業A寫入物件O實現持久化

作業B寫入物件O實現持久化

悲觀鎖的設計是對物件O實現獨佔鎖,直到作業A完成對物件O的更新並寫入持久化資料之前,阻斷其他讀取請求。樂觀鎖的設計是對物件O實現共享鎖,假設所有的工作都能夠正常完成,直到有衝突產生,記錄衝突的發生並拒絕衝突的請求。

樂觀鎖一般會結合資源版本實現,同樣是上述中的例子,當前物件O的版本為v1,作業A首先完成對物件O的寫入持久化操作,並標記物件O的版本為v2,作業B在更新時發現物件版本已經變化,則會取消更改。

Kubernetes排程器剖析

Kubernetes中的計算任務大多透過pod來承載執行。pod是使用者定義的一個或多個共享儲存、網路和名稱空間資源的容器的組合,是排程器可排程的最小單元。Kubernetes的排程器是控制平面的一部分,它主要監聽APIServer提供的pod任務列表,獲取待排程pod,根據預選和優選策略,為這些pod分配執行的節點。概括來說,排程器主要依據資源消耗的描述得到一個排程結果。

2.1 Kubernetes排程器的設計

Kubernetes的排程設計參考了Omega的實現,主要採用兩層排程架構,基於全域性狀態進行排程,透過樂觀鎖控制資源歸屬,同時支援多排程器的設計。

兩層架構幫助排程器遮蔽了很多底層實現細節,將策略和限制分別實現,同時過濾可用資源,讓排程器能夠更靈活適應資源變化,滿足使用者個性化的排程需求。相比單體架構而言,不僅更容易新增自定義規則、支援叢集動態伸縮,同時對大規模叢集有更好的支援(支援多排程器)。

相比於使用悲觀鎖和部分環境檢視的架構(如Mesos),基於全域性狀態和樂觀鎖實現的好處是排程器可以看到叢集所有可以支配的資源,然後搶佔低優先順序任務的資源,以達到策略要求的狀態。它的資源分配更符合策略要求,避免了作業囤積資源導致叢集死鎖的問題。當然這會有搶佔任務的開銷以及衝突導致的重試,但總體來看資源的使用率更高了。

Kubernetes中預設只有一個排程器,而Omega的設計本身支援資源分配管理器共享資源環境資訊給多個排程器。所以從設計上來說,Kubernetes可以支援多個排程器。

2.2 Kubernetes排程器的實現

Kubernetes排程器的工作流程如下圖所示。排程器的工作本質是透過監聽pod的建立、更新、刪除等事件,迴圈遍歷地完成每個pod的排程流程。如排程過程順利,則基於預選和優選策略,完成pod和主機節點的繫結,最終通知kubelet完成pod啟動的過程。如遇到錯誤的排程過程,透過優先順序搶佔的方式,獲取優先排程的能力,進而重新進入排程迴圈的過程,等待成功排程。

2.2.1 排程迴圈的完整邏輯

Kubernetes排程器完成排程的整體流程如下圖1所示。下面就每個步驟的實現邏輯進行說明。

(1)基於事件驅動啟動迴圈過程

Kubernetes排程器維護sharedIndexInformer,來完成informer物件的初始化工作。也就是排程器會監聽pod建立、更新、刪除的操作事件,主動更新事件快取,並持久化到記憶體佇列,發起排程迴圈。

該過程的函式入口在

(2)將沒有排程的pod加到排程器快取並更新排程器佇列

Informer物件負責監聽pod的事件,主要的事件型別有:針對已排程pod的addPodToCache、updatePodInCache、deletePodFromCache和針對未被排程pod的addPodToSchedulingQueue、updatePodInSchedulingQueue、deletePodFromSchedulingQueue六種事件。該過程的函式入口在:

各類事件的含義如下表2所示:

(3)對排程器佇列中的每個pod執行排程

這裡需要指出的是,在單個pod排程的過程中,對於主機節點的排程演算法是順序執行的。也就是說,pod在排程的過程中會嚴格的順序執行Kubernetes內建的策略和優先順序,然後選擇最合適的節點。

單個pod的排程過程分為預選和優選兩個階段。預選階段排程器根據一組規則過濾掉不符合要求的主機,選擇出合適的節點;優選階段透過節點優先順序打分的方式(依據整體最佳化策略等),選擇出分值最高的節點進行排程。

單個pod的排程過程由以下函式作為入口:

當然,排程的過程可能由於沒有滿足pod執行條件的節點而排程失敗,此時當pod有優先順序指定的時候,將觸發競爭機制。具有高優先順序的pod將嘗試搶佔低優先順序的pod資源。相關部分程式碼實現如下:

如果資源搶佔成功,將在下一次排程迴圈時標記可排程過程。如果搶佔失敗,排程程式退出。排程結果不儲存意味著pod仍然會出現在未分配列表中。

(4)接下來檢查使用者提供的外掛的條件是否滿足

Reserve外掛是Kubernets留給使用者進行擴充套件的介面,基於reserver外掛使用者在這個階段可以設定自定義條件,從而滿足期望的排程過程。外掛的入口函式在:

可以在檢視外掛擴充套件reserver介面進行自定義排程的示例。

(5)找到滿足的節點後,更新Pod物件的標籤,儲存被排程節點的結果

該過程的函式入口在。

(6)完成pod到節點的繫結

pod到節點的繫結需要首先完成儲存卷的掛載,最後透過pod物件的更新,完成最後的繫結。具體程式碼的邏輯可以參考: 。

(7)排程完成後,主協程返回,執行下一個排程

至此排程的完整流程就完成了,下面重點介紹下,在單個pod排程過程中Kubernetes主要是如何對節點進行選擇的,主要包括預選和優選兩種策略。

2.2.2 單個pod的排程流程

單個pod的排程過程如下圖2所示。主要包括由pre-filter、filter、post-filter的預選過程和scoring的優選過程。

圖2:單個Pod的排程過程

(1)pod進入排程階段,首先進入預選環節

透過規則過濾找到滿足pod排程條件的節點。

k8s內建了許多過濾規則,排程器會按照事先定義好的順序進行過濾。內建的過濾規則主要包括檢查節點是否有足夠資源(例如CPU、記憶體與GPU等)滿足pod的執行需求,檢查pod容器所需的HostPort是否已被節點上其它容器或服務佔用,檢查節點標籤(label)是否匹配pod的nodeSelector屬性要求,根據 taints 和 toleration 的關係判斷pod是否可以排程到節點上pod是否滿足節點容忍的一些條件,還有檢查是否滿足csi最大可掛載卷限制等。

(2)經過預選策略對節點過濾後,進入優選階段

排程器根據預置的預設規則進行打分(優先順序函式得分*權重的和),然後選擇分數最高的節點實現pod到節點的繫結。

Kubernetes內建的優先順序函式如下,主要包括平均分佈優先順序(SelectorSpreadPriority)、最少訪問優先順序(LeastRequestedPriority)、平衡資源分佈優先順序(BalancedResourceAllocation)等。

SelectorSpreadPriority:為了更好的高可用,對同屬於一個service、replication controller或者replica的多個Pod副本,儘量排程到多個不同的節點上。

InterPodAffinityPriority:透過迭代 weightedPodAffinityTerm的元素計算和,如果對該節點滿足相應的PodAffinityTerm,則將 “weight” 加到和中,具有最高和的節點是最優選的。

LeastRequestedPriority:由節點空閒資源與節點總容量的比值,即由(總容量-節點上Pod的容量總和-新Pod的容量)/總容量)來決定節點的優先順序。CPU和memory具有相同權重,比值越大的節點得分越高。

BalancedResourceAllocation:CPU和記憶體使用率越接近的節點優先順序越高,該策略不能單獨使用,必須和LeastRequestedPriority同時使用,也就是說盡量選擇在部署Pod後各項資源更均衡的機器。

NodePreferAvoidPodsPriority(權重1w): 如果節點的 Anotation 沒有設定 key-value:scheduler. alpha.kubernetes.io/ preferAvoidPods = “…”,則該 節點對該 policy 的得分就是10分,加上權重10000,那麼該節點對該policy的得分至少10W分。如果節點的Anotation設定了scheduler.alpha.kubernetes.io/preferAvoidPods = “…” ,如果該 pod 對應的 Controller 是 ReplicationController 或 ReplicaSet,則該節點對該 policy 的得分就是0分。

NodeAffinityPriority:實現Kubernetes排程中的親和性機制。

TaintTolerationPriority : 使用 Pod 中 tolerationList 與 節點 Taint 進行匹配,配對成功的項越多,則得分越低。

Kubernetes排程器的不足和解決思路

3.1典型的幾個問題和解決思路

(1)排程器只根據當前資源環境情況進行一次排程,一旦完成排程就沒有機制實現調整

雖然pod只有在自己退出、使用者刪除以及叢集資源不足等情況下才會有變化。但資源拓撲的變化是隨時都有可能發生的,如批處理任務會結束,節點會新增或崩潰。這些情況導致排程的結果可能在排程時是最優的,但在拓撲變化後排程質量由於以上情況的發生而下降。

經過社群討論,認為需要重新找出不滿足排程策略的pod,刪除並建立替代者來重新排程,據此設計啟動了專案descheduler。

(2)排程以單個pod進行的,因而排程互相關聯的工作負載會難以實現

如大資料分析、機器學習等計算多依賴於批處理任務,這類工作負載相關性大,互相之間有依賴關係。為了解決這個問題,社群經過討論,提出了coscheduling 一次排程一組pod的專案,以此來最佳化這類排程任務的執行。

(3)目前排程器的實現只關心是否能將pod與節點繫結,資源使用情況的資料未被充分利用

目前,叢集的使用量只能透過監控資料間接推導。如果k8s叢集剩餘資源不足時,並沒有直觀資料可以用來觸發擴容或者告警。

根據上述情況,社群啟動了cluster-capacity framework專案 ,提供叢集的容量資料,方便叢集的維護程式或者管理員基於這些資料做叢集擴容等。也有專案抓取監控資料自己計算叢集的整體負載情況給排程演算法參考,如poseidon。

3.2 Kubernetes排程器的定製擴充套件

如上節所述,通用排程器在某些場景下並不能滿足使用者個性化需求,實際環境下執行的叢集的排程器,往往需要根據實際的需求做定製與二次開發。

kubernetes的排程器以外掛化的形式實現的, 方便使用者對排程的定製與二次開發。定製排程器有如下幾種方式的選擇:

更改Kubernetes內建策略,透過更改預設的策略檔案或者重新編譯排程器來實現。

擴充套件排程器在pre-filter、filter、post-filter、reserve、prebind、bind和post-bind各個階段的介面,更改排程器過濾、打分、搶佔、預留的具體實現邏輯。

更改排程器排程演算法,從頭實現排程器邏輯。

企業場景應用的案例

4.1 通用計算場景

Kubernetes default-scheduler滿足通用計算的需求,主要服務於以快速開發測試為目標的持續整合和持續部署平臺(DevOps平臺)、以標準三層架構應用為特點的容器應用執行與運維平臺(容器平臺)、PaaS平臺和雲原生應用的核心基礎架構平臺(aPaaS平臺)幾種場景。

通常情況下,標準Kubernetes排程器能夠滿足大多數透過計算場景的訴求,主要解決應用上雲過程中不同異構雲資源之間的排程問題,應用上雲後彈性伸縮、故障自愈等的動態排程響應,標準中介軟體服務和資料庫服務基於日常運維規範的排程問題以及雲原生應用在服務治理、配置管理、狀態反饋、事件鏈路跟蹤上的綜合排程過程。

4.2 批處理場景

大資料分析和機器學習類任務執行時需要大量資源,多個任務同時進行時,資源很快會用盡,部分任務會需要等待資源釋放。這型別任務的步驟往往互相關聯,單獨執行步驟可能會影響最終結果。使用預設的排程器在叢集資源緊張時,甚至會出現佔用資源的pod都在等待依賴的pod執行完畢,而叢集沒有空閒資源去執行依賴任務,導致死鎖。所以在排程這類任務時,支援群組排程(在排程作業所需的資源都收集完成後才進行排程),減少了pod數量,因而降低排程器的負載,同時避免了很多資源緊張帶來的問題。

與預設排程器一次排程一個pod不同,kube-batch定義了PodGroup 定義一組相關的pod資源,並實現了一個全新的排程器。排程器的流程基本與預設排程器相同。Podgroup保證一組pod可以同時被排程。是Kubernetes社群在大資料分析場景中的一種實現。

4.3 特定領域業務場景

特定的業務場景需要排程器能夠快速生成排程的策略,並儘可能避免排程超時。Poseidon是大規模叢集中基於圖應用資料區域性性減少任務執行時間同時混合多種排程演算法提升排程速度的一種排程器。

Poseidon是基於Firmament演算法的排程器,它透過接收heapster資料來構建資源使用資訊。呼叫Firmament實現進行排程。Firmament演算法受Quincy[11]啟發,構建一個從任務到節點的圖,但作者為減少排程時間,將兩種計算最短路徑的演算法合併,將全量環境資訊同步改為增量同步。讓Firmament處理短時間批次任務時快於Quincy,在資源短缺時沒有Kubernetes預設排程器超時的問題。

總結

本文主要從設計原理、程式碼實現等層面介紹Kubernetes的排程器以及社群對其的補充加強,總結了Kubernetes排程器的設計原理以及在何種場景如何增強Kubernetes來滿足業務需求,提供技術選型的依據和評價標準。

相關服務請訪問:  https://support.huaweicloud.com/cce/index.html?utm_content=cce_helpcenter_2019


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69908804/viewspace-2639995/,如需轉載,請註明出處,否則將追究法律責任。

相關文章