進擊的Kubernetes調度系統(三):支持批任務的Binpack Scheduling

作者:

王慶璨:阿里雲技術專家,專注於大規模集群資源管理和調度。Kubernetes社區成員,主要參與Kube-scheduler社區開發。目前負責阿里雲容器服務ACK資源調度和雲原生AI相關工作。

張凱:阿里雲高級技術專家,從事容器服務ACK 和雲原生AI解決方案的研發和客戶支持。擁有10餘年大規模深度學習平臺,雲計算,SOA等領域經驗。

往期回顧:

進擊的Kubernetes調度系統(一):Scheduling Framework

進擊的Kubernetes調度系統(二):支持批任務的Coscheduling/Gang scheduling

前言

本系列的前兩篇《進擊的Kubernetes調度系統 (一):Scheduling Framework》進擊的 Kubernetes 調度系統(二):支持批任務的 Coscheduling/Gang scheduling 分別介紹了Kubernetes Scheduling Framework和如何通過擴展Scheduling Framework實現Coscheduling/Gang scheduling調度策略。當我們的批任務作業在集群裡邊運行起來之後,隨後要關注的就是資源的利用率。特別是對於GPU卡的價格昂貴,不希望有資源的浪費。本文將介紹在批任務的調度過程中如何通過Binpack的方式,減少資源碎片,提升GPU的利用率。

為什麼需要Binpack功能?

Kubernetes默認開啟的資源調度策略是LeastRequestedPriority,消耗的資源最少的節點會優先被調度,使得整體集群的資源使用在所有節點之間分配地相對均勻。但是這種調度策略往往也會在單個節點上產生較多資源碎片。

下面拿一個簡單的例子來說明這種問題。如下圖所示,資源在節點之間平均使用,所以每個節點使用3個GPU卡,則兩個節點各剩餘1GPU的資源。這是有申請2GPU的新作業,提交到調度器,則因為無法提供足夠的資源,導致調度失敗。


1587092661680-55bf016c-e041-47b8-8bc4-2b49db8f90ff.png

如上這種情況情況,每個節點都有1個GPU卡空閒,可是又無法被利用,導致資源GPU這種昂貴的資源被浪費。如果使用的資源調度策略是Binpack,優先將節點資源填滿之後,再調度下一個節點,則上圖所出現的資源碎片問題得到解決。申請2GPU的作業被正常調度到節點上,提升了集群的資源使用率。


1587092697318-72d49b1c-2a8d-4268-a5e4-808649f416f0.png

實現方案


1587104875349-e8cd886d-3d66-4349-9ce3-2f1e7cdbf138.png

Binpack實現已經抽象成Kubernetes Scheduler Framework的Score插件RequestedToCapacityRatio,用於優選階段給節點打分。將節點根據自己定義的配置進行打分。具體的實現可以分為兩個部分,構建打分函數和打分.

構建打分函數

構建打分函數的過程比較容易理解,就是用戶可以自己定義不同的利用率所對應的分值大小,以便影響調度的決策過程。
如果用戶設定的對應方式如下所示,即如果資源利用率為0的時候,得分為0分,當資源利用率為100時,得分為10分,所以得到的資源利用率越高,得分越高,則這個行為是Binpack的資源分配方式。
undefinedundefined

用戶也可以設置成利用率為0時,得分為10分,利用率為100時,得分為0分。這樣意味著資源利用率越低,則得分越高,這種行為是spreading的資源分配方式。
undefinedundefined

用戶除了2個點之外也可以新增更多的點,對應關係可以不是線性的關係,例如可以標識資源利用率為50時,得分為8,則會將打分分割為兩個區間: 0-50和50-100。
undefinedundefined

打分

用戶可以自己定義在Binpack計算中所要參考的資源以及權重值,例如可以只是設定GPU和CPU的值和權重。

resourcetoweightmap: 
    "cpu": 1
    "nvidia.com/gpu": 1

然後在打分過程總,會通過計算(pod.Request + node.Allocated)/node.Total的結果得到對應資源的利用率,並且將利用率帶入上文中所述的打分函數中,得到相應的分數。最後將所有的資源根據weight值,加權得到最終的分數。

Score = line(resource1_utilization) * weight1 + line(resource2_utilization) * weight2 ....) / (weight1 + weight2 ....)

Binpack使用

配置方法

  1. 新建/etc/kubernetes/scheduler-config.yaml, 用戶可以自行配置其他的priorities策略。
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

Demo演示

接下來我們通過運行Tensorflow的分佈式作業來進行演示,展示Binpack的效果,當前測試集群有2臺4卡的GPU機器

  1. 通過Kubeflow的arena在已有的Kubernetes集群中部署tf-operator

Arena是基於Kubernetes的機器學習系統開源社區Kubeflow中的子項目之一。Arena用命令行和SDK的形式支持了機器學習任務的主要生命週期管理(包括環境安裝,數據準備,到模型開發,模型訓練,模型預測等),有效提升了數據科學家工作效率。

git clone https://github.com/kubeflow/arena.git
kubectl create ns arena-system
kubectl create -f arena/kubernetes-artifacts/jobmon/jobmon-role.yaml
kubectl create -f arena/kubernetes-artifacts/tf-operator/tf-crd.yaml
kubectl create -f arena/kubernetes-artifacts/tf-operator/tf-operator.yaml

檢查是否部署成功

$ kubectl  get pods -n arena-system
NAME                                READY   STATUS    RESTARTS   AGE
tf-job-dashboard-56cf48874f-gwlhv   1/1     Running   0          54s
tf-job-operator-66494d88fd-snm9m    1/1     Running   0          54s
  1. 用戶向集群中提交Tensorflow分佈式,作業含有1個PS和4個Worker,每個Worker需要1個GPU
apiVersion: "kubeflow.org/v1"
kind: "TFJob"
metadata:
  name: "tf-smoke-gpu"
spec:
  tfReplicaSpecs:
    PS:
      replicas: 1
      template:
        metadata:
          creationTimestamp: null
          labels:
            pod-group.scheduling.sigs.k8s.io/name: tf-smoke-gpu
            pod-group.scheduling.sigs.k8s.io/min-available: "5"
        spec:
          containers:
          - args:
            - python
            - tf_cnn_benchmarks.py
            - --batch_size=32
            - --model=resnet50
            - --variable_update=parameter_server
            - --flush_stdout=true
            - --num_gpus=1
            - --local_parameter_device=cpu
            - --device=cpu
            - --data_format=NHWC
            image: registry.cn-hangzhou.aliyuncs.com/kubeflow-images-public/tf-benchmarks-cpu:v20171202-bdab599-dirty-284af3
            name: tensorflow
            ports:
            - containerPort: 2222
              name: tfjob-port
            resources:
              limits:
                cpu: '1'
            workingDir: /opt/tf-benchmarks/scripts/tf_cnn_benchmarks
          restartPolicy: OnFailure
    Worker:
      replicas: 4
      template:
        metadata:
          creationTimestamp: null
          labels:
            pod-group.scheduling.sigs.k8s.io/name: tf-smoke-gpu
            pod-group.scheduling.sigs.k8s.io/min-available: "5"
        spec:
          containers:
          - args:
            - python
            - tf_cnn_benchmarks.py
            - --batch_size=32
            - --model=resnet50
            - --variable_update=parameter_server
            - --flush_stdout=true
            - --num_gpus=1
            - --local_parameter_device=cpu
            - --device=gpu
            - --data_format=NHWC
            image: registry.cn-hangzhou.aliyuncs.com/kubeflow-images-public/tf-benchmarks-gpu:v20171202-bdab599-dirty-284af3
            name: tensorflow
            ports:
            - containerPort: 2222
              name: tfjob-port
            resources:
              limits:
                nvidia.com/gpu: 1
            workingDir: /opt/tf-benchmarks/scripts/tf_cnn_benchmarks
          restartPolicy: OnFailure
  1. 當用戶使用Binpack功能時,用戶提交任務後,4個Worker被調度到同一個GPU節點cn-shanghai.192.168.0.129
$ kubectl get pods -o wide
NAME                    READY   STATUS    AGE   IP             NODE
tf-smoke-gpu-ps-0       1/1     Running    15s   172.20.0.210   cn-shanghai.192.168.0.129  
tf-smoke-gpu-worker-0   1/1     Running    17s   172.20.0.206   cn-shanghai.192.168.0.129   
tf-smoke-gpu-worker-1   1/1     Running    17s   172.20.0.207   cn-shanghai.192.168.0.129      
tf-smoke-gpu-worker-2   1/1     Running    17s   172.20.0.209   cn-shanghai.192.168.0.129
tf-smoke-gpu-worker-3   1/1     Running    17s   172.20.0.208   cn-shanghai.192.168.0.129     
  1. 當用戶不使用Binpack功能時,用戶提交任務後,4個Worker被分配到cn-shanghai.192.168.0.129和cn-shanghai.192.168.0.130兩個節點上,產生資源碎片
$ kubectl get pods -o wide
NAME                    READY   STATUS AGE   IP             NODE
tf-smoke-gpu-ps-0       1/1     Running    7s    172.20.1.72    cn-shanghai.192.168.0.130
tf-smoke-gpu-worker-0   1/1     Running    8s    172.20.0.214   cn-shanghai.192.168.0.129
tf-smoke-gpu-worker-1   1/1     Running    8s    172.20.1.70    cn-shanghai.192.168.0.130
tf-smoke-gpu-worker-2   1/1     Running    8s    172.20.0.215   cn-shanghai.192.168.0.129
tf-smoke-gpu-worker-3   1/1     Running    8s    172.20.1.71    cn-shanghai.192.168.0.130

後記

上文中我們介紹瞭如何利用Kubernetes原生的調度策略RequestedToCapacityRatio來支持Binpack Scheduling的功能,減少資源碎片,提升GPU的利用率。使用起來很簡單,但是效果很明顯。針對GPU的資源利用率的提升的課題,我們將在本系列接下來的文章中介紹如何在推理服務下,通過GPU共享調度的方法大大的提升GPU利用率。

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top