[译] kubernetes:kube-scheduler 调度器代码结构概述
本文翻譯自 https://github.com/kubernetes/community/blob/master/contributors/devel/sig-scheduling/scheduling_code_hierarchy_overview.md
譯者:胡云 Troy
調度器代碼層次結構概述
介紹
調度器監視新創建的還沒有分配節點的 Pod。當發現這樣的 Pod 后,調度器將 Pod 調度到最適合它的節點。一般來說,調度是計算機科學中一個相當廣泛的領域,它考慮了各種各樣的約束和限制。調度器的每個工作負載可能需要不同的方法來實現最佳調度結果。Kubernetes 項目提供的 kube-scheduler 調度器的目標是以簡單為代價提供高吞吐量。為了幫助構建調度器(默認或者定制化)和共享調度邏輯,kube-scheduler 實現了 調度框架。該框架沒有提供構建新調度器的所有部分。組裝一個功能齊全的單元仍然需要隊列、緩存、調度算法和其他構建元素。本文檔旨在描述所有單獨的部分是如何組合在一起,以及它們在整個體系結構中的作用,以便開發人員能夠快速了解調度器代碼。
調度 Pod
默認的調度器實例運行無限期的循環,該循環(每次有 Pod 時)負責調用調度邏輯,確保 Pod 分配或重新排隊以供后續處理。每個循環由一個阻塞調度周期和一個非阻塞綁定周期組成。調度周期負責運行調度算法,選擇最合適的節點分配給 Pod。綁定周期確保 kube-apiserver 及時接收分配給 Pod 的節點。一個 Pod 可以立即綁定,或者在群調度中,等所有同級 Pod 分配節點之后再綁定。
圖片來源 調度框架
調度周期
每個周期遵循以下步驟:
- 獲取下一個調度的 Pod
- 根據提供的調度算法調度 Pod
- 如果調度 Pod 時出現
FitError錯誤,調度器將運行PostFilterPlugin搶占插件(如果該插件已注冊),該插件將指定一個可以運行 Pod 的節點。如果搶占成功,讓當前 Pod 知道分配的節點。調度器將處理錯誤,獲取下一個 Pod 并重新開始調度。 - 如果調度算法找到了合適的節點,則將 Pod 存儲到調度器緩存中(
AssumePod操作),然后按順序運行Reserve和Permit擴展點插件。任何插件運行失敗將結束當前調度周期,增加相關的指標,調度器的Error handler將處理調度錯誤。 - 成功運行所有擴展點后,繼續綁定循環。在執行綁定循環的同時,調度周期開始處理下一個調度的 Pod(如果有的話)。
綁定周期
按相同順序運行以下四個步驟:
- 從
Permit擴展點調用插件的 WaitOnPermit (內部 API)。擴展點的一些插件會發送操作請求去等待相應的條件(例如,等待額外的可用資源或者一組中的所有 Pod 被分配)。WaitOnPermit等待條件滿足直到超時。 - 調用 PreBind 擴展點的插件
- 調用 Bind 擴展點的插件
- 調用 PostBind 擴展點的插件
任何擴展點執行失敗將調用所有 Reserve 插件的 Unreserve 操作(例如,為一群 Pod 分配空閑資源)。
配置和組裝調度器
調度器代碼庫分散在不同的地方:
- cmd/kube-scheduler/app:控制器代碼的位置以及 CLI 參數的定義(遵守所有 Kubernetes 控制器的標準設定)
- pkg/scheduler:默認調度器代碼庫的根目錄
- pkg/scheduler/core:默認調度算法的位置
- pkg/scheduler/framework:調度框架和插件
- pkg/scheduler/internal:緩存,隊列和其它元素的實現
- staging/src/k8s.io/kube-scheduler:ComponentConfig API 類型的位置
- test/e2e/scheduling:調度 e2e
- test/integration/scheduler:調度集成測試
- test/integration/scheduler_perf:調度性能基準
初始啟動配置
cmd/kube-scheduler/app 下的代碼負責收集調度器配置和調度器初始化邏輯,它是 kube-scheduler 作為 Kubernetes 控制面運行的一部分。代碼包括:
- 初始化 命令行選項(以及默認的
ComponentConfig) 和 驗證 - 初始化 指標,健康檢查 和 其它 handlers
- KubeSchedulerConfiguration 的讀取和默認配置
- 通過插件構建
registry(in-tree, out-of-tree) - 多種選項初始化調度器,例如 profiles,算法源,pod back off,等等。
- 調用 LogOrWriteConfig,用于記錄最終調度程序配置以進行調試
- 運行之前,
/configz已注冊,事件廣播程序已啟動,*選舉已啟動,server(包含所有配置的 handlers 和 informers)已啟動。
初始化之后,調度器開始運行。
更詳細地說,Setup 函數完成了調度器核心流程的初始化。首先,Setup 驗證傳遞的選項(NewSchedulerCommand() 中添加的 flags 直接設置在此選項結構的字段上)。如果傳遞的選項沒有引發任何錯誤,那么它將調用 opts.Config(),用于設置最終的內部配置,包括安全服務、*選舉、客戶端,并開始解析與算法源相關的選項(比如,加載配置文件和初始化空 profiles,以及處理不推薦使用的選項像策略配置)。接下來,調用 c.Complete() 填充配置 Config 中的空值。此時,創建一個空 registry 注冊 out-of-tree 插件,在 registry 中為每個插件的 New 函數添加條目。Registry 只是插件名稱到插件工廠函數的映射。對于默認調度器,注冊 registry 這一步什么都不做(因為 cmd/kube-chuler/scheduler.go 中的 main 函數不向 NewSchedulerCommand() 傳遞任何信息)。這意味著默認的插件在 scheduler.New() 中初始化。
初始化是在調度框架之外執行的,使用框架的用戶可以以不同的方式初始化環境來滿足自身的需求。例如,模擬器可以通過 informer 注入自身需要的對象。或者自定義的插件可以替換默認的插件。調度框架的已知使用者:
- cluster-autoscaler
- cluster-capacity
組裝調度器
默認調度器實現的目錄在 pkg/scheduler,調度器的各種元素在這里初始化并組合在一起:
- 默認調度選項,例如
node percentage, 初始化和最大backoff,profiles - 調度器緩存和隊列
- 實例化調度的
profiles以定制框架,每個profile可以更好的安置 Pod(每個 profile 定義自身使用的插件集合) -
Handler函數用于獲取下一個調度的 Pod(NextPod)和處理錯誤(Error)
在創建調度器實例的過程中,將執行以下步驟:
- 初始化調度器 緩存
-
合并 帶插件的
in-tree和out-of-tree注冊表 -
Metrics已注冊 - 配置器 構建調度器實例(連接緩存,插件注冊表,調度算法和其它元素)
-
注冊
Event handlers以允許調度器對 PV、PVC、服務和其它與調度相關的資源的更新做出反應(最終,每個插件都將定義一組事件,并對其作出反應,更詳細的可參考 kubernetes/kubernetes#100347)。
下圖表明了初始化后各個元素是如何連接在一起的。Event handlers 確保 Pod 在 調度隊列中排隊,緩存隨 Pod 和節點的更新而更新(提供最新的快照 snapshot)。調度框架有對應的調度算法和綁定周期(每個框架實例有自己的 profile)。
調度框架
調度器的框架代碼目前位于 pkg/scheduler/framework 下。它包含 各種插件,負責過濾和評分節點(以及其他)。常常用作調度算法的構建模塊。
當 插件初始化 后,它會傳遞一個 框架 handler,該框架 handler 提供訪問和/或操作 pod、節點、clientset、事件記錄器和每個插件實現其功能所需的其他 handler 的接口。
調度緩存
緩存負責記錄集群的最新狀態。保存節點和 assumed Pod 以及 Pod 和 images 的狀態。緩存提供了協調 Pod 和節點對象(調用 event handlers)的方法,使集群的狀態保持最新。允許在每個調度周期開始時使用最新狀態(在運行調度算法時固定集群狀態)更新集群的快照。
緩存還允許運行假定的操作,該操作將 Pod 臨時存儲在緩存中,使得 Pod 看起來像已經在快照的所有消費者的指定節點上運行那樣。假定操作忽視了 kube-apiserver 和 Pod 實際更新的時間,從而增加調度器的吞吐量。
以下操作使用假定的 Pod 進行操作:
-
AssumePod:用于通知調度算法找到可行的節點,以便在當前 Pod 進入綁定周期時可以調度下一個 Pod -
FinishBinding:用于發出綁定完成的信號,以便可以將 Pod 從假定 Pod 列表中刪除 -
ForgetPod:從假定的 Pod 列表中刪除 Pod,用于綁定周期中未能成功處理 Pod 的情況(例如,Reserve,Permit,PreBind或者Bind評估)
緩存跟蹤以下三個指標:
-
scheduler_cache_size_assumed_pods:在假定 Pod 列表中的 Pod 數量 -
scheduler_cache_size_pods:在緩存中的 Pod 數量 -
scheduler_cache_size_nodes:在緩存中的節點數量
快照
快照 捕獲集群的狀態,其中包含集群中所有節點和每個節點上對象的信息。即節點對象、分配在每個節點上的 Pod、每個節點上所有 Pod 的請求資源、節點的可分配資源、拉取的鏡像以及做出調度決策所需的其他信息。每次調度 Pod 時,都會捕獲集群當前狀態的快照。這樣是為了避免在處理插件時更改 Pod 或節點時導致的數據不一致,因為一些插件可能會獲得不同的集群狀態。
配置器
配置器 通過將插件、緩存、隊列、handlers 和其他元素連接在一起來構建調度器實例。每個 profile 都使用自己的框架(所有框架共享 informers,event recorders 等)進行 初始化。
也可以讓配置器根據 策略文件 創建實例。不過,這種方法已被棄用,最終將從配置中刪除。只保留調度器配置作為提供給配置器配置的唯一方式。
默認調度算法
代碼庫定義了 ScheduleAlgorithm 接口。任何該接口的實現都可以用作調度算法。這里有兩種方法:
-
Schedule:負責使用從PreFilter到NormalizeScore擴展點的插件來調度 Pod,提供包含調度決策(最合適的節點)和附帶信息的 ScheduleResult,其中附帶信息包括評估了多少節點以及發現有多少節點可用于調度。 -
Extenders: 當前僅用于測試
默認算法實現的每個周期包括:
- 從調度緩存中獲取 當前快照
-
過濾掉所有無法調度 Pod 的節點
- 運行 PreFilter 插件(預處理階段,例如計算 Pod 親和性關系)
- 并行運算
Filter 插件:過濾掉不滿足 Pod 限制條件(例如資源,節點親和性等)的節點,包括運行Filter 擴展器 - 運行
PostFilter 插件如果沒有節點滿足要調度的 Pod
- 在 Pod 至少有兩個可行節點可以調度的情況下,運行 scoring 插件:
- 運行 PreScore 插件(預處理階段)
- 并行運行 Score 插件:每個節點都有一個分數向量(每個坐標對應一個插件)
- 運行 NormalizeScore 插件:給所有插件打分,間隔為 [0, 100]
- 計算每個節點的 權重分數 (每個分數插件都可以分配一個權重,指示其分數在多大程度上優于其他插件)
- 運行 打分擴展器,并且將分數計入每個節點的總分
-
選擇 并 返回 得分最高的節點。如果只有一個可供調度的節點則跳過
Prescore,Score和NormalizeScore擴展點,并且立即返回調度的節點。如果沒有可供調度的節點,將結果返回給調度器。
值得注意的是:
- 如果插件提供
score normalization,當調用 ScoreExtensions() 時,插件需要返回非 nil
總結
以上是生活随笔為你收集整理的[译] kubernetes:kube-scheduler 调度器代码结构概述的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 洛谷 P9683 A Certain F
- 下一篇: Feign源码解析6:如何集成disco