k8s調度器擴展機制


在kube-scheduler有四種擴展機制:

一、Multiple Scheduler

若要部署第二調度器,可以直接修改kubernetes的源碼
git clone https://github.com/kubernetes/kubernetes.git
cd kubernetes
make
使用如下Dockerfile構建成鏡像:
FROM busybox
ADD ./_output/local/bin/linux/amd64/kube-scheduler /usr/local/bin/kibe-scheduler
創建ServiceAccount並將其綁定到ClusterRole,使其與kube-scheduler具有相同的權限:
apiVersion: v1
kind: ServiceAccount
metadata:
  name: my-scheduler
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: my-scheduler-as-kube-scheduler
subjects:
- kind: ServiceAccount
  name: my-scheduler
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: system:kube-scheduler
  apiGroup: rbac.authorization.k8s.io
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: my-scheduler-as-volume-scheduler
subjects:
- kind: ServiceAccount
  name: my-scheduler
  namespace: kube-system
roleRef:
  kind: ClusterRole
  name: system:volume-scheduler
  apiGroup: rbac.authorization.k8s.io
部署該調度器:
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    component: scheduler
    tier: control-plane
  name: my-scheduler
  namespace: kube-system
spec:
  selector:
    matchLabels:
      component: scheduler
      tier: control-plane
  replicas: 1
  template:
    metadata:
      labels:
        component: scheduler
        tier: control-plane
        version: second
    spec:
      serviceAccountName: my-scheduler
      containers:
      - command:
        - /usr/local/bin/kube-scheduler
        - --address=0.0.0.0
        - --leader-elect=false
        - --scheduler-name=my-scheduler
        image: xxxxxx
        livenessProbe:
          httpGet:
            path: /healthz
            port: 10251
          initialDelaySeconds: 15
        name: kube-second-scheduler
        readinessProbe:
          httpGet:
            path: /healthz
            port: 10251
        resources:
          requests:
            cpu: '0.1'
        securityContext:
          privileged: false
        volumeMounts: []
      hostNetwork: false
      hostPID: false
      volumes: []
啟動時通過參數scheduler-name指定了第二調度器的名字
Pod通過spec.schedulerName指定使用的調度器(默認調度器是default-scheduler)
 

二、Multiple profiles

無需部署第二調度器,1.18+的kube-scheduler默認支持多profile:
apiVersion: kubescheduler.config.k8s.io/v1beta1
kind: KubeSchedulerConfiguration
profiles:  
  - schedulerName: default-scheduler  
  - schedulerName: no-scoring-scheduler    
    plugins:      
      preScore:        
        disabled:        
        - name: '*'      
      score:        
        disabled:        
        - name: '*'

三、Schduler Extender

在啟動官方調度器之后,可以再啟動一個擴展調度器Schedule Extender。
Scheduler Extender實際上是一個額外的調度進程,用戶需要實現自己的Filter方法、Prioritize方法、Bind方法
策略文件的配置內容:
apiVersion: v1
kind: Policy
alwaysCheckALLPredicates: false  # 不管每個prediacate的結果,所有的predicate都要走一遍
predicates:['GeneralPredicates','...']  # 不配的話使用默認的predicates;為空列表則跳過所有配置
hardPodAffinitySymmetricWeight:
# 配置打分算法插件;不配的話使用默認的打分插件;為空列表則跳過所有配置
priorities:
- name: LeastRequestedPriority
  weight: 1
extenders:
- urlPrxfix: "http://xxx:xxx/scheduler-gpu-extender"
  filterVerb: filter
  exableHttps: false
  nodeCacheCapable: true  # 服務是否已經有NodeCache,為true則調度器傳遞nodeName列表而不是整個nodeinfo完整結構
  ignorable: false   # 調用擴展調度器不成功時(如報錯或網絡問題),是否可忽略擴展調度器
  managedResources:
  - name: "example/gpu-mem"    # 官方調度器在遇到這個Resource時會用擴展調度器
    ignoredByScheduler: false  # resourceFit階段是否忽略這個資源的校驗
這里可以看到配置的過濾器predicates、配置的打分器 priorities、配置的擴展調度器。
 
以Filter階段舉例,執行過程會經過2個階段:
(1)scheduler會先執行內置的Filter策略。如果執行失敗,會直接標識Pod調度失敗;
(2)如果內置的Filter策略執行成功,scheduler通過http調用Extender注冊的webhook,將調度所需要的Pod和Node的信息發送到Extender,根據返回Filter結果作為最終結果。
Schedule Extender存在以下問題:
(1)調用Extender的接口是HTTP請求,性能遠低於本地的函數調用。同時每次調用都需要將Pod和Node的信息進行marshaling和unmarshalling的操作,會進一步降低性能;
(2)用戶可以擴展的點比較有限,位置比較固定,無法支持靈活的擴展,例如只能在執行完默認的Filter策略后才能調用。
因此,Extender在集群規模較小,調度效率要求不高的情況下,是一個靈活可用的擴展方案。但是在正常生產環境的大型集群中,Extender無法支持高吞吐量,性能較差。
 

四、Scheduling Framework

基於scheduler framework進行out-of-tree的scheduler plugins開發示例:
kubernetes-sigs團隊實現了兩個插件示例

1、Qos的插件

調度過程中如果Pod的優先級相同,根據Pod的Qos來決定調度順序
(1)插件構造
定義插件的對象和構造函數:
type Sort struct {}
// 新初始化一個插件,返回它
func New(_ *runtime.Unknown, _ framework.FrameworkHandle) (framework.Plugin, error){
    return &Sort{}, nil
}
然后,實現QueueSort接口的Less函數,改為在優先級相同的情況下,通過比較Qos來決定優先級。
func (*Sort) Less(pInfo1, pInfo2 *framework.PodInfo) bool{
    p1 := pod.GetPodPriority(pInfo1.Pod)
    p2 := pod.GetPodPriority(pInfo2.Pod)
    return (p1 > p2) || (p1 == p2 && compQOS(pInfo1.Pod, pInfo2.Pod))
}
func compQOS(p1, p2 *v1.Pod) bool{
    p1QOS, p2QOS := v1qos.GetPodQOS(p1), v1qos.GetPodQOS(p2)
    if p1QOS == v1.PodQOSGuaranteed {
        return true
    } else if p1QOS == v1.PodQOSBurstable {
        return p2QOS != v1.PodQOSGuaranteed
    } else{
        return p2QOS == v1.PodQOSBestEffort
    }
}
(2)插件注冊在main函數中注冊自定義插件和相應的構造函數:
func main() {    
    rand.Seed(time.Now().UnixNano())    // 向scheduler framework注冊用戶自定義插件    
    command := app.NewSchedulerCommand(        
        app.WithPlugin(qos.Name, qos.New),        
        app.WithPlugin(qos.Name, qos.New),    
    )    
    if err := command.Execute(); 
    err != nil {        
        os.Exit(1)    
    }
}
(3)通過make命令進行代碼編譯
(4)配置scheduler-config.yaml
apiVersion: kubescheduler.config.k8s.io/v1alpha2
kind: KubeSchedulerConfiguration
leaderElection:
  leaderElect: false
clientConnection:
  kubeconfig: "REPLACE_ME_WITH_KUBE_CONFIG_PATH"
profiles:
- schedulerName: default-scheduler
  plugins:
    queueSort:
      enabled:
      - name: QOSSort
      disabled:
      - name: "*"
啟動kube-scheduler時傳入集群的kubeconfig文件以及插件的配置文件即可
$ kube-scheduler --kubeconfig=scheduler.conf --config=scheduler-config.yaml

2、Coscheduling插件

在並發系統中將多個相關聯的進程調度到不同處理器上同時運行,需要保證所有相關聯的進程能夠同時啟動。
部分進程的異常,可能導致整個關聯進程組的阻塞。這種導致阻塞的部分異常進程,稱之為碎片(fragement)。
Coscheduling的具體實現過程中,根據是否允許碎片存在,可以細分為完全不允許有碎片存在的Explicit Coscheduling(Gang Scheduling)、Local Coscheduling和Implicit Coscheduling。
對應到Kubernetes中,Coscheduling的含義就是:一個批任務(關聯進程組)包括了N個Pod(進程),Kubernetes調度器負責將這N個Pod組成的PodGroup調度到M個節點(處理器)上同時運行。
如果這個批任務需要部分Pod同時啟動即可運行,稱需啟動Pod的最小數量為min-available。
特別地,當min-available=N時,批任務要求滿足Gang Scheduling。
 
插件通過給Pod打label的形式來定義PodGroup:
    pod-group.scheduling.sigs.k8s.io/name:xxx用於表示PodGroup的Name;
    pod-group.scheduling.sigs.k8s.io/min-available:"2" 表示PodGroup能夠運行所需要的最小副本數
備注:要求屬於同一個PodGroup的Pod必須保持相同的優先級
 
Permit階段使用了延遲綁定功能,當屬於同一個PodGruop的Pod數量不夠時,進行等待;積累的Pod數目足夠時,才會將所有Pod全部綁定並創建。
如果某個Pod在Permit階段等待超時了,則會進入到UnReserve階段。
QueueSort階段,由於默認的Scheduler隊列並不能感知 PodGroup 的信息,同一個PodGroup中的Pod在隊列中是混亂無序的。
一旦某個PodGroup在 Permit 階段處於等待狀態,其它的PodGroup也會因此處於等待狀態,從而導致死鎖。
此處自定義的Less方法繼承了默認的基於優先級的比較方式,高優先級的Pod會排在低優先級的Pod之前。如果兩個Pod的優先級相同,則采用新的排隊邏輯,以保證隊列中屬於同一個PodGroup的Pod排列在一起:  
  • 如果兩個Pod 都是普通的Pod,則誰先創建誰在隊列里邊排在前邊;
  • 如果兩個Pod 一個是普通的Pod,另一個是屬於某個PodGroup的Pod,則比較的是前者的創建時間和后者所屬PodGroup的創建時間;
  • 如果兩個Pod 都是屬於某個PodGroup的Pod,比較兩個 PodGroup 的創建時間,則誰先創建誰在隊列里排在前邊;
  • 如果兩個PodGroup 的創建時間相同,誰的PodGroup 的自增Id誰小誰在隊列里排在前邊
Prefilter 階段增加一個過濾條件,當一個Pod調度時,會計算該Pod所屬PodGroup的Pod的Sum(包括Running狀態的),如果Sum小於min-available時,則肯定無法滿足min-available要求,則直接在該階段拒絕掉,不再進入調度的主流程。
UnReserve 階段會直接拒絕掉所有跟Pod屬於同一個PodGroup的Pod,避免剩余的Pod進行長時間的無效等待。
 
Coscheduling插件和原生調度器代碼已經統一構建成一個容器鏡像。可以通過helm chart包ack-coscheduling來自動安裝。它會啟動一個任務,自動用Coscheduling scheduler替換原生的kube-scheduler,並且會修改相關Config文件,使scheduling framework正確地加載Coscheduling 插件。
 
下載 helm chart包,執行命令安裝:
$  wget http://kubeflow.oss-cn-beijing.aliyuncs.com/ack-coscheduling.tar.gz
$  tar zxvf ack-coscheduling.tar.gz
$  helm install ack-coscheduling -n kube-system ./ack-coscheduling
NAME: ack-coscheduling
LAST DEPLOYED: Mon Apr 13 16:03:57 2020
NAMESPACE: kube-system
STATUS: deployed
REVISION: 1
TEST SUITE: None
在 Master 節點上,使用helm命令驗證是否安裝成功。
$ helm get manifest ack-coscheduling -n kube-system | kubectl get -n kube-system -f -
NAME                           COMPLETIONS   DURATION   AGE
scheduler-update-clusterrole   1/1           8s         35s
scheduler-update               3/1 of 3      8s         35s
使用Coscheduling時,只需要在創建任務的yaml描述中配置兩個描述任務狀態的label即可。
通過 helm 卸載,將kube-scheduler的版本及配置回滾到集群默認的狀態:
$ helm uninstall ack-coscheduling -n kube-system
 
 
阿里雲實現的插件:

3、RequestedToCapacityRatio插件

用戶自己定義的資源利用率與得分間的線性關系、根據哪些資源進行打分、每種資源的權重:
apiVersion: kubescheduler.config.k8s.io/v1alpha1
kind: KubeSchedulerConfiguration
leaderElection:
  leaderElect: false
clientConnection:
  kubeconfig: "REPLACE_ME_WITH_KUBE_CONFIG_PATH"
plugins:
  score:
    enabled:
    - name: RequestedToCapacityRatio
      weight: 100
    disabled:
    - name: LeastRequestedPriority
pluginConfig:
- name: RequestedToCapacityRatio
  args:
    functionshape:
      - utilization: 0
        score: 0
      - utilization: 100
        score: 100
    resourcetoweightmap: # 定義具體根據哪種資源類型進行binpack操作,多種資源時可以設置weight來進行比重設置
      "cpu": 1
      "nvidia.com/gpu": 1
在打分過程中,會通過計算(pod.Request + node.Allocated)/node.Total的結果得到對應資源的利用率,並且將利用率帶入上文中所述的打分函數中,得到相應的分數。
最后將所有的資源根據weight值,加權得到最終的分數。
 

參考資料:

[1] https://kubernetes.io/docs/home/

[2] https://edu.aliyun.com/roadmap/cloudnative

[3] 鄭東旭《Kubernetes源碼剖析》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM