全部產品
Search
文件中心

Container Service for Kubernetes:使用任務隊列ack-kube-queue

更新時間:Jun 19, 2024

任務隊列ack-kube-queue旨在管理Kubernetes中的AI/ML工作負載和批處理工作負載。它允許系統管理員使用自訂隊列的作業隊列管理,以提高隊列的靈活性。結合Quota系統,ack-kube-queue自動最佳化了工作負載和資源配額管理,以便最大化利用叢集資源。本文介紹如何安裝及使用任務隊列ack-kube-queue。

前提條件

已開通雲原生AI套件

使用限制

僅支援ACK Pro託管版叢集,且叢集版本為1.18.aliyun.1及以上。

安裝ack-kube-queue

分為以下兩個情境講解如何安裝ack-kube-queue。

情境一:未部署雲原生AI套件

  1. 登入Container Service管理主控台,在左側導覽列單擊叢集

  2. 叢集列表頁面,單擊目的地組群名稱,然後在左側導覽列,選擇應用 > 雲原生AI套件

  3. 雲原生AI套件頁面下方,單擊一鍵部署

  4. 調度地區,選中Kube Queue,在互動方式地區,選中Arena,然後在頁面下方單擊部署雲原生AI套件

情境二:已部署雲原生AI套件

  1. 登入Container Service管理主控台,在左側導覽列單擊叢集

  2. 叢集列表頁面,單擊目的地組群名稱,然後在左側導覽列,選擇應用 > 雲原生AI套件

  3. 安裝ack-arenaack-kube-queue

    • 雲原生AI套件頁面的操作列,單擊組件ack-arena對應的部署。在參數配置頁面,單擊確定

    • 雲原生AI套件頁面的操作列,單擊組件ack-kube-queue對應的部署。在彈出頁面,單擊確定

    ack-arenaack-kube-queue安裝完成後,組件列表地區的組件狀態已部署

支援排隊功能的任務類型

ack-kube-queue支援TfJob、PytorchJob、MpiJob、Argo Workflow、RayJob、SparkApplication以及原生Job的排隊功能。

使用限制

  • TfJob、PytorchJob、MpiJob需要使用ack-arena組件中提供的Operator。

  • 使用原生Job類型排隊功能需要叢集版本不低於1.22。

  • MpiJob當前僅支援通過Arena提交MpiJob。

  • Argo Workflow當前僅支援對整體進行排隊,可以通過在Annotation中申明如下內容,申明Workflow需要的資源。

    ```
     annotations:
       kube-queue/min-resources: |
         cpu: 5
         memory: 5G
    ```

如何開啟不同種類的任務排隊功能

預設情況下,kube-queue將會支援TfJob,Pytorchjob的排隊功能,您可以根據需要開啟或關閉任意類型任務的排隊功能。

v0.4.0之前

v0.4.0版本之前,每種任務類型的排隊功能由一個單獨的Deployment工作負載類型控制,您可以在kube-queue命名空間下將對應工作負載的Extension的副本數調整為0來關閉對應的負載類型的排隊功能。

v0.4.0及之後

v0.4.0版本及之後的版本中。除Argo Workflow之外,所有的任務類型的排隊功能均由Job-Extensions負責,您可以修改其Command中--enabled-extensions參數的值來開關特定任務類型。不同任務類型以逗號分隔,不同任務類型以及在參數中的表示如下表:

TfJob

tfjob

Pytorchjob

pytorchjob

Job

job

SparkApplication

sparkapp

RayJob

rayjob

RayJob(v1alpha1)

rayjobv1alpha1

MpiJob

mpiv1

如何提交TfJob、PytorchJob、MpiJob

您需要在Job的Annotation中添加scheduling.x-k8s.io/suspend="true"的標識,以下以TfJob為例進行說明:

apiVersion: "kubeflow.org/v1"
kind: "TFJob"
metadata:
  name: "job1"
  annotations:
    scheduling.x-k8s.io/suspend: "true"
spec:
  ...

如何提交原生Job

您需要將Job的Suspend欄位設定成True,以下是提交一個需要排隊的Job的例子:

apiVersion: batch/v1
kind: Job
metadata:
  generateName: pi-
spec:
  suspend: true
  completions: 1
  parallelism: 1
  template:
    spec:
      schedulerName: default-scheduler
      containers:
      - name: pi
        image: perl:5.34.0
        command: ["sleep",  "3s"]
        resources:
          requests:
            cpu: 100m
          limits:
            cpu: 100m
      restartPolicy: Never

以上的例子中,我們將產生一個需求100mCPU的排隊單元,當該排隊單元出隊後,將Job的Suspend改為false,此時該Job將會由叢集組件開始執行。

如何提交Argo Workflow

說明

請提前在應用市場中安裝ack-workflow組件。

您需要在Argo Workflow中增加名為kube-queue-suspend,類型為suspend的自訂Template,同時在提交時將Workflow設定為Suspend狀態,例子如下:

apiVersion: argoproj.io/v1alpha1
kind: Workflow
metadata:
  generateName: $example-name
spec:
  suspend: true # 需要將該項設定為true
  entrypoint: $example-entrypoint
  templates:
  # 需要添加名為kube-queue-suspend的suspend模板
  - name: kube-queue-suspend
    suspend: {}
  - name: $example-entrypoint
  ...

如何提交SparkApplication

說明

請提前在應用市場中安裝ack-spark-operator組件。

您需要在SparkApplication的Annotaion中scheduling.x-k8s.io/suspend="true"的標識。

apiVersion: sparkoperator.k8s.io/v1beta2
kind: SparkApplication
metadata:
  generateName: spark-pi-suspend-
  namespace: spark-operator
  annotations:
    scheduling.x-k8s.io/suspend: "true"
spec:
  type: Scala
  mode: cluster
  image: registry-cn-beijing.ack.aliyuncs.com/acs/spark:v3.1.1
  mainClass: org.apache.spark.examples.SparkPi
  mainApplicationFile: local:///opt/spark/examples/jars/spark-examples_2.12-3.1.1.jar
  sparkVersion: "3.1.1"
  driver:
    cores: 1
    coreLimit: "1200m"
    memory: "512m"
    serviceAccount: ack-spark-operator3.0-spark
  executor:
    cores: 1
    instances: 1
    memory: "512m"

如何提交RayJob

說明

請提前在應用市場中安裝ack-kuberay-operator組件。

您需要在提交RayJob時將RayJob的spec.suspend欄位設定成true。

apiVersion: ray.io/v1
kind: RayJob
metadata:
  name: rayjob-sample
spec:
  entrypoint: python /home/ray/samples/sample_code.py
  runtimeEnvYAML: |
    pip:
      - requests==2.26.0
      - pendulum==2.1.2
    env_vars:
      counter_name: "test_counter"

  # Suspend specifies whether the RayJob controller should create a RayCluster instance.
  # If a job is applied with the suspend field set to true, the RayCluster will not be created and we will wait for the transition to false.
  # If the RayCluster is already created, it will be deleted. In the case of transition to false, a new RayCluste rwill be created.
  suspend: true

  rayClusterSpec:
    rayVersion: '2.9.0' # should match the Ray version in the image of the containers
    headGroupSpec:
      rayStartParams:
        dashboard-host: '0.0.0.0'
      template:
        spec:
          containers:
            - name: ray-head
              image: rayproject/ray:2.9.0
              ports:
                - containerPort: 6379
                  name: gcs-server
                - containerPort: 8265 # Ray dashboard
                  name: dashboard
                - containerPort: 10001
                  name: client
              resources:
                limits:
                  cpu: "1"
                requests:
                  cpu: "200m"
              volumeMounts:
                - mountPath: /home/ray/samples
                  name: code-sample
          volumes:
            # You set volumes at the Pod level, then mount them into containers inside that Pod
            - name: code-sample
              configMap:
                # Provide the name of the ConfigMap you want to mount.
                name: ray-job-code-sample
                # An array of keys from the ConfigMap to create as files
                items:
                  - key: sample_code.py
                    path: sample_code.py
    workerGroupSpecs:
      - replicas: 1
        minReplicas: 1
        maxReplicas: 5
        groupName: small-group
        rayStartParams: {}
        template:
          spec:
            containers:
              - name: ray-worker # must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character (e.g. 'my-name',  or '123-abc'
                image: rayproject/ray:2.9.0
                lifecycle:
                  preStop:
                    exec:
                      command: [ "/bin/sh","-c","ray stop" ]
                resources:
                  limits:
                    cpu: "1"
                  requests:
                    cpu: "200m"

切換Quota資源類型

當叢集中有多個使用者時,為了保障使用者有足夠的資源使用,管理員會將叢集的資源固定分配給不同的使用者。傳統的方法是通過Kubernetes原生的ResourceQuota方式進行固定資源的分配。但由於小組之間資源忙閑不一,為了提升資源整體利用率,ack-kube-queue預設使用ElasticQuota。如果您想切換Quota資源類型為Kubernetes原生的ResourceQuota,可以按照如下步驟操作。關於ElasticQuota的更多資訊,請參見ElasticQuota

  1. 執行如下命令,切換ElasticQuota為Kubernetes的ResourceQuota。

    kubectl edit deploy kube-queue-controller -nkube-queue
  2. 修改環境變數elasticquotaresourcequota

    env:
    - name: QueueGroupPlugin
        value: resourcequota
  3. 切換完成後,儲存檔案。等待新的kube-queue-controller啟動,資源分派方式即可切換為ResourceQuota模式。

開啟阻塞隊列

預設情況下,ack-kube-queue處理任務時採用與kube-scheduler相同的任務輪轉機制,所有任務會在隊列中依次請求資源,請求失敗後進入Unschedulable隊列進行退避,直到下次調度。當叢集中存在大量資源需求量小的任務時,由於小任務會佔用大量隊列輪轉時間,資源需求量大的任務將難以獲得資源執行,存在長時間Pending的風險。為了避免大量小任務輪轉導致大任務無法執行,ack-kube-queue提供阻塞隊列功能,開啟後隊列將只調度隊列最前端的任務,從而使得大任務能夠有機會執行。

開啟方法

  1. 登入Container Service管理主控台,在左側導覽列選擇叢集

  2. 叢集列表頁面,單擊目的地組群名稱,然後在左側導覽列,選擇工作負載 > 無狀態

  3. 選擇命名空間kube-queue,在kube-queue-controller所在行,單擊操作列的編輯

  4. 單擊環境變數後面的新增,增加如下記錄。

    配置項

    取值

    類型

    自訂

    變數名稱

    StrictPriority

    變數/變數引用

    true

  5. 單擊頁面右側更新。在確認彈出框中單擊確定

開啟嚴格優先順序調度

預設情況下,請求失敗的任務進入Unschedulable隊列進行退避,直到下次調度。當叢集資源由於任務結束空閑時,高優先順序任務由於仍處於退避階段,不會被任務隊列調度,可能會導致叢集資源被低優先順序任務佔用。為了使得高優先順序任務在叢集獲得空閑資源時能夠被優先嘗試調度,ack-kube-queue提供嚴格優先順序調度功能。開啟後隊列將在正在啟動並執行任務結束後從高優先順序的最早提交的任務開始嘗試執行,從而使得高優先順序任務能夠在叢集獲得空閑資源時優先調度。

說明

當高優先順序任務未獲得充足資源時,低優先順序任務仍然可以獲得資源執行。

開啟方法

  1. 登入Container Service管理主控台,在左側導覽列選擇叢集

  2. 叢集列表頁面,單擊目的地組群名稱,然後在左側導覽列,選擇工作負載 > 無狀態

  3. 選擇命名空間kube-queue,在kube-queue-controller所在行,單擊操作列的編輯

  4. 單擊環境變數後面的新增,增加如下記錄。

    配置項

    取值

    類型

    自訂

    變數名稱

    StrictConsistency

    變數/變數引用

    true

  5. 單擊頁面右側更新。在確認彈出框中單擊確定

Quota資源使用樣本

ElasticQuota

  1. 使用如下YAML,建立一個ElasticQuotaTree。

    展開查看YAML檔案

    apiVersion: v1
    kind: List
    metadata:
      resourceVersion: ""
      selfLink: ""
    items:
      - apiVersion: scheduling.sigs.k8s.io/v1beta1
        kind: ElasticQuotaTree
        metadata:
          name: elasticquotatree
          namespace: kube-system
        spec:
          root: # root節點為總的資源數量,max的值不能小於Children的max之和。
            name: root
            namespaces: []
            max:
              cpu: "4"
              memory: 4Gi
              nvidia.com/gpu: "64"
              aliyun.com/gpu-mem: "32"
            min:
              cpu: "0"
              memory: 0M
              nvidia.com/gpu: "0"
              aliyun.com/gpu-mem: "0"
            children: # 可以有多個Child,一般每個Child對應一個Namespace。
              - name: root.defaultQuotaGroup
                namespaces:
                  - default
                max:
                  cpu: "4"
                  memory: 4Gi
                  nvidia.com/gpu: "64"
                  aliyun.com/gpu-mem: "16"
                min:
                  cpu: "0"
                  memory: 0M
                  nvidia.com/gpu: "0"
                  aliyun.com/gpu-mem: "0"
                children: null
  2. 執行如下命令,查看ElasticQuotaTree是否建立成功。

    kubectl get elasticquotatree -A

    預期輸出:

    NAMESPACE     NAME               AGE
    kube-system   elasticquotatree   7s

    表示ElasticQuotaTree建立成功。

  3. 建立測試工作。

    說明
    • 為了測試工作隊列效果,測試工作的資源配額應該小於執行所有任務需要的總資源。

    • 為了方便測試,TFJob用busybox鏡像替代真正的tensorflow鏡像。類比訓練過程,每個容器Sleep 30秒。

    1. 使用如下YAML,建立兩個TFJob進行測試。

      展開查看YAML檔案

      apiVersion: "kubeflow.org/v1"
      kind: "TFJob"
      metadata:
        name: "job1"
        annotations:
          scheduling.x-k8s.io/suspend: "true"
      spec:
        tfReplicaSpecs:
          PS:
            replicas: 1
            restartPolicy: Never
            template:
              spec:
                containers:
                  - name: tensorflow
                    image: busybox
                    command:
                      - /bin/sh
                      - -c
                      - --
                    args:
                      - "sleep 30s"
                    resources:
                      requests:
                        cpu: 1
                        memory: 1Gi
                      limits:
                        cpu: 1
                        memory: 1Gi
      
          Worker:
            replicas: 2
            restartPolicy: Never
            template:
              spec:
                containers:
                  - name: tensorflow
                    image: busybox
                    command:
                      - /bin/sh
                      - -c
                      - --
                    args:
                      - "sleep 30s"
                    resources:
                      requests:
                        cpu: 1
                        memory: 1Gi
                      limits:
                        cpu: 1
                        memory: 1Gi
      --
      apiVersion: "kubeflow.org/v1"
      kind: "TFJob"
      metadata:
        name: "job2"
        annotations:
          scheduling.x-k8s.io/suspend: "true"
      spec:
        tfReplicaSpecs:
          PS:
            replicas: 1
            restartPolicy: Never
            template:
              spec:
                containers:
                  - name: tensorflow
                    image: busybox
                    command:
                      - /bin/sh
                      - -c
                      - --
                    args:
                      - "sleep 30s"
                    resources:
                      requests:
                        cpu: 1
                        memory: 1Gi
                      limits:
                        cpu: 1
                        memory: 1Gi
      
          Worker:
            replicas: 2
            restartPolicy: Never
            template:
              spec:
                containers:
                  - name: tensorflow
                    image: busybox
                    command:
                      - /bin/sh
                      - -c
                      - --
                    args:
                      - "sleep 30s"
                    resources:
                      requests:
                        cpu: 1
                        memory: 1Gi
                      limits:
                        cpu: 1
                        memory: 1Gi
    2. 待任務提交後,查看任務狀態。

      1. 執行如下命令,查看任務狀態。

        kubectl get tfjob

        預期輸出:

        NAME   STATE     AGE
        job1   Running   3s
        job2   Queuing   2s

        預期輸出顯示job1處於Running狀態,job2處於Queuing狀態,符合預期。因為每個TFJob訓練任務需要的CPU資源為3 Core,ElasticQuotaTree給Default Namespace分配的最大CPU核心數為4 Core,所以同一時間段內只能運行一個TFJob。

      2. 稍等片刻後,再次執行如下命令。

        kubectl get tfjob

        預期輸出:

        NAME   STATE       AGE
        job1   Succeeded   77s
        job2   Running     77s

        預期輸出顯示job1執行成功。job1執行完成後,job2開始執行。表明ack-kube-queue可以正常工作。

ResourceQuota

  1. 使用如下YAML,建立一個ResourceQuota。

    apiVersion: v1
    kind: ResourceQuota
    metadata:
      name: default
    spec:
      hard:
        cpu: "4"
        memory: 4Gi
  2. 執行如下命令,查看ResourceQuota是否建立成功。

    kubectl get resourcequota default -o wide

    預期輸出:

    NAME      AGE   REQUEST                   LIMIT
    default   76s   cpu: 0/4, memory: 0/4Gi

    表示ResourceQuota建立成功。

  3. 使用如下YAML,建立兩個TFJob進行測試。

    展開查看YAML檔案

    apiVersion: "kubeflow.org/v1"
    kind: "TFJob"
    metadata:
      name: "job1"
      annotations:
        scheduling.x-k8s.io/suspend: "true"
    spec:
      tfReplicaSpecs:
        PS:
          replicas: 1
          restartPolicy: Never
          template:
            spec:
              containers:
                - name: tensorflow
                  image: busybox:stable
                  command:
                    - /bin/sh
                    - -c
                    - --
                  args:
                    - "sleep 30s"
                  resources:
                    requests:
                      cpu: 1
                      memory: 1Gi
                    limits:
                      cpu: 1
                      memory: 1Gi
    
        Worker:
          replicas: 2
          restartPolicy: Never
          template:
            spec:
              containers:
                - name: tensorflow
                  image: busybox:stable
                  command:
                    - /bin/sh
                    - -c
                    - --
                  args:
                    - "sleep 30s"
                  resources:
                    requests:
                      cpu: 1
                      memory: 1Gi
                    limits:
                      cpu: 1
                      memory: 1Gi
    --
    apiVersion: "kubeflow.org/v1"
    kind: "TFJob"
    metadata:
      name: "job2"
      annotations:
        scheduling.x-k8s.io/suspend: "true"
    spec:
      tfReplicaSpecs:
        PS:
          replicas: 1
          restartPolicy: Never
          template:
            spec:
              containers:
                - name: tensorflow
                  image: busybox:stable
                  command:
                    - /bin/sh
                    - -c
                    - --
                  args:
                    - "sleep 30s"
                  resources:
                    requests:
                      cpu: 1
                      memory: 1Gi
                    limits:
                      cpu: 1
                      memory: 1Gi
    
        Worker:
          replicas: 2
          restartPolicy: Never
          template:
            spec:
              containers:
                - name: tensorflow
                  image: busybox:stable
                  command:
                    - /bin/sh
                    - -c
                    - --
                  args:
                    - "sleep 30s"
                  resources:
                    requests:
                      cpu: 1
                      memory: 1Gi
                    limits:
                      cpu: 1
                      memory: 1Gi
  4. 待任務提交後,執行如下命令,查看任務狀態。

    kubectl get tfjob
    NAME   STATE     AGE
    job1   Running   5s
    job2   Queuing   5s
    
    kubectl get pods
    NAME            READY   STATUS    RESTARTS   AGE
    job1-ps-0       1/1     Running   0          8s
    job1-worker-0   1/1     Running   0          8s
    job1-worker-1   1/1     Running   0          8s

    可以看到job1處於Running狀態,job2處於Queuing狀態,這是符合預期的。因為每個TFJob訓練任務需要的CPU資源為3 Core(1個參數伺服器Pod,需要1 Core;和2個Worker Pod,均需要1 Core),而ResourceQuota給Default Namespace分配的最大CPU核心數為 4 Core,所以同一時間段內只能運行一個TFJob。

  5. 稍等片刻後,再次執行如下命令。

    kubectl get tfjob
    NAME   STATE       AGE
    job1   Succeeded   77s
    job2   Running     77s
    
    kubectl get pods
    NAME            READY   STATUS      RESTARTS   AGE
    job1-worker-0   0/1     Completed   0          54s
    job1-worker-1   0/1     Completed   0          54s
    job2-ps-0       1/1     Running     0          22s
    job2-worker-0   1/1     Running     0          22s
    job2-worker-1   1/1     Running     0          21s

    預期輸出表明,job1執行成功,執行成功後,job2開始執行,表明ack-kube-queue可以正常工作。

    限制同時出隊的任務數量

    在一些應用能夠自動進行伸縮的情境下,應用自身需要的資源量可能是無法估計的,這種情境下可以使用出隊的任務數量對隊列任務進行限制,聲明對任務數量的限制需要在ElasticQuotaTree中以kube-queue/max-jobs為資源進行限制,限制後,該Quota下能夠出隊的QueueUnit的數量將不會超過該值乘以超賣比例。如以下的例子:

    展開查看YAML檔案

    apiVersion: v1
    kind: List
    metadata:
      resourceVersion: ""
      selfLink: ""
    items:
      - apiVersion: scheduling.sigs.k8s.io/v1beta1
        kind: ElasticQuotaTree
        metadata:
          name: elasticquotatree
          namespace: kube-system
        spec:
          root: # root節點為總的資源數量,max的值不能小於Children的max之和。
            name: root
            namespaces: []
            max:
              kube-queue/max-jobs: 10
              cpu: "4"
              memory: 4Gi
              nvidia.com/gpu: "64"
              aliyun.com/gpu-mem: "32"
            min:
              cpu: "0"
              memory: 0M
              nvidia.com/gpu: "0"
              aliyun.com/gpu-mem: "0"
            children: # 可以有多個Child,一般每個Child對應一個Namespace。
              - name: root.defaultQuotaGroup
                namespaces:
                  - default
                max:
              		kube-queue/max-jobs: 10
                  cpu: "4"
                  memory: 4Gi
                  nvidia.com/gpu: "64"
                  aliyun.com/gpu-mem: "16"
                min:
                  cpu: "0"
                  memory: 0M
                  nvidia.com/gpu: "0"
                  aliyun.com/gpu-mem: "0"
                children: null