基於kubernetes排程框架的自定義排程器實現
簡介
排程框架為kubernetes的排程器二次開發提供了豐富的擴充套件介面,基本上只需要實現特點擴充套件點的邏輯,並配合預設擴充套件點外掛,進行組合,即可實現各種豐富的排程策略。關於排程器和排程框架,可以參考本部落格前幾篇介紹。
本文介紹基於kubernetes排程框架,實現一個排程外掛的主要流程,並對其中的細節進行說明。該外掛最終可以實現按照節點可用記憶體量作為唯一依據對pod進行排程。依賴的kubernetes版本以及開發測試叢集的版本均為v1.21.6。
依賴問題
因為外掛最終的執行方式是獨立於預設的scheduler的,所以需要一個main函式作為排程器的入口。正如參考資料裡大佬所介紹的那樣,我們不需要自己完整地山寨一個scheduler,只需要引用kubernetes scheduler裡的部分函式作為入口即可。
但在動手時發現一個問題,kubernetes在設計上會獨立釋出各個模組,並對外遮蔽部分不穩定的程式碼,避免主倉庫作為一個整體被外部引用,所以將部分模組的依賴版本寫成了v0.0.0,然後再用replace替換成了程式碼倉庫裡的相對路徑,即staging目錄裡的獨立模組。如此一來,直接使用kubernetes程式碼是沒問題的,但是使用go get或者go mod去獲取kubernetes主倉庫作為依賴時會遇到諸如此類的錯誤:k8s.io/[email protected]: reading k8s.io/api/go.mod at revision v0.0.0: unknown revision
解決方法是用shell指令碼,指定一個kubernetes版本,一方面直接下載官方倉庫裡被寫成v0.0.0版本的各個模組的該版本的程式碼到本地作為依賴,另一方面修改go mod,將依賴replace成指定的版本,這樣版本的需求和供給即實現了匹配。
該shell指令碼倉庫路徑為:hack/get-k8s-as-dep.sh
核心實現
ScorePlugin介面實現
為了完成依據節點記憶體剩餘量進行排程的功能,本外掛主要修改排程器的打分階段邏輯,即我們的邏輯會寫在Score擴充套件點。因此我們的外掛需要實現相應的介面
// pkg/scheduler/framework/interface.go type ScorePlugin interface { Plugin // Score is called on each filtered node. It must return success and an integer // indicating the rank of the node. All scoring plugins must return success or // the pod will be rejected. Score(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (int64, *Status) // ScoreExtensions returns a ScoreExtensions interface if it implements one, or nil if does not. ScoreExtensions() ScoreExtensions } type Plugin interface { Name() string }
Name() string
方法簡單返回外掛名稱即可Score(ctx context.Context, state *CycleState, p *v1.Pod, nodeName string) (int64, *Status)
方法負責為每個待排程的pod+node進行打分ScoreExtensions() ScoreExtensions
方法本文暫未用到,直接返回nil即可
打分邏輯
排程器打分時要求打分值為[0,100]範圍內的整數,而節點的實際可用記憶體是以byte為單位的整數。因此打分的核心邏輯是要實現記憶體可用量到分數的單調遞增對映。
- 從prometheus api獲取節點的實際剩餘記憶體(此處需要一個外部配置:prometheus的endpoint)
- 將記憶體值進行歸一化處理(此處需要一個外部配置:歸一化時的記憶體最大值)
- 將歸一化後的值作為引數進行sigmoid函式運算
- 將sigmoid計算的結果轉化為[0,100]範圍內的整數,作為打分結果返回
引數解析
上面的打分邏輯裡,prometheus的endpoint和記憶體最大值需要由使用者配置輸入。
在plugin的New方法中,使用k8s.io/kubernetes/pkg/scheduler/framework/runtime
包裡的DecodeInto
方法,將runtime.Object
轉為自定義的Arg結構體,然後便可以在擴充套件點打分時使用。
本地除錯
程式碼編寫完成後,可以在本地連線一個叢集,實際測試一下排程器的效果。
排程器的配置檔案對排程器的行為至關重要,在除錯前需要先編寫合適的配置檔案,程式碼倉庫有一個示例配置檔案hack/config-sample.yml
供參考。
在以前的文章中,曾經觀察過排程器的預設配置檔案。我們僅需要在配置檔案中寫明需要修改的部分即可,未寫的部分,會自動使用預設值。
scheduler名稱
schedulerName: mem-scheduler
這裡我們指定一個排程器名稱,後面為pod指定排程器時指定這個名稱即可
plugin組合
對於Score以外的擴充套件點,我們全部儲存預設值,因此不需要在配置檔案中體現
對於Score擴充套件點,為了簡化打分邏輯,以本外掛的打分邏輯作為唯一打分外掛,可以在配置檔案中禁用k8s預設的score擴充套件點外掛,然後僅啟用本外掛,並設定權重為1
plugins:
score:
disabled:
- name: NodeResourcesBalancedAllocation
- name: ImageLocality
- name: InterPodAffinity
- name: NodeResourcesLeastAllocated
- name: NodeAffinity
- name: NodePreferAvoidPods
- name: PodTopologySpread
- name: TaintToleration
enabled:
- name: NodeAvailableMemory
weight: 1
選舉行為
因為scheduler預設開啟了leader election以支援高可用,預設配置下,選舉過程中多個排程器副本會同時搶佔kube-system名稱空間下的lease物件。
對於我們的自定義排程器,需要指定一個不同於預設排程器的租約物件。如果不指定額外的租約物件,我們的排程器將成為預設排程器的後備軍,會遲遲因為搶不到leader租約而不生效。
leaderElection:
resourceName: lwabish-scheduler
叢集部署
- 倉庫makefile可自動構建二進位制,映象,並將helm chart部署到叢集。
- 叢集部署不需要在排程器配置裡指定kubeconfig。排程器會自動發現叢集配置。
- 由於偷懶,本文直接給排程器的sa綁定了叢集最高許可權cluster-role:cluster-admin,不夠安全。
hack/scheduler-test.yml
該deployment指定了我們的自定義排程器,用作測試。
參考
How to import 'any' Kubernetes package into your project? - Suraj Deshmukh
kubernetes 程式碼中的 k8s.io 是怎麼回事?By李佶澳 (lijiaocn.com)
自定義 Kubernetes 排程器-陽明的部落格|Kubernetes|Istio|Prometheus|Python|Golang|雲原生 (qikqiak.com)