任務隊列ack-kube-queue旨在管理Kubernetes中的AI/ML工作負載和批處理工作負載。它允許系統管理員使用自定義隊列的作業隊列管理,以提高隊列的靈活性。結合Quota系統,ack-kube-queue自動優化了工作負載和資源配額管理,以便最大化利用集群資源。本文介紹如何安裝及使用任務隊列ack-kube-queue。
前提條件
使用限制
僅支持ACK Pro托管版集群,且集群版本為1.18.aliyun.1及以上。
安裝ack-kube-queue
分為以下兩個場景講解如何安裝ack-kube-queue。
場景一:未部署云原生AI套件
登錄容器服務管理控制臺,在左側導航欄單擊集群。
在集群列表頁面,單擊目標集群名稱,然后在左側導航欄,選擇
。在云原生AI套件頁面下方,單擊一鍵部署。
在調度區域,選中Kube Queue,在交互方式區域,選中Arena,然后在頁面下方單擊部署云原生AI套件。
場景二:已部署云原生AI套件
登錄容器服務管理控制臺,在左側導航欄單擊集群。
在集群列表頁面,單擊目標集群名稱,然后在左側導航欄,選擇
。安裝ack-arena和ack-kube-queue。
在云原生AI套件頁面的操作列,單擊組件ack-arena對應的部署。在參數配置頁面,單擊確定。
在云原生AI套件頁面的操作列,單擊組件ack-kube-queue對應的部署。在彈出頁面,單擊確定。
ack-arena和ack-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
以上的例子中,我們將生成一個需求100m
CPU的排隊單元,當該排隊單元出隊后,將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。
執行如下命令,切換ElasticQuota為Kubernetes的ResourceQuota。
kubectl edit deploy kube-queue-controller -nkube-queue
修改環境變量
elasticquota
為resourcequota
。env: - name: QueueGroupPlugin value: resourcequota
切換完成后,保存文件。等待新的kube-queue-controller啟動,資源分配方式即可切換為ResourceQuota模式。
開啟阻塞隊列
默認情況下,ack-kube-queue處理任務時采用與kube-scheduler相同的任務輪轉機制,所有任務會在隊列中依次請求資源,請求失敗后進入Unschedulable隊列進行退避,直到下次調度。當集群中存在大量資源需求量小的任務時,由于小任務會占用大量隊列輪轉時間,資源需求量大的任務將難以獲得資源執行,存在長時間Pending的風險。為了避免大量小任務輪轉導致大任務無法執行,ack-kube-queue提供阻塞隊列功能,開啟后隊列將只調度隊列最前端的任務,從而使得大任務能夠有機會執行。
開啟方法
登錄容器服務管理控制臺,在左側導航欄選擇集群。
在集群列表頁面,單擊目標集群名稱,然后在左側導航欄,選擇 。
選擇命名空間為kube-queue,在kube-queue-controller所在行,單擊操作列的編輯。
單擊環境變量后面的新增,增加如下記錄。
配置項
取值
類型
自定義
變量名稱
StrictPriority
變量/變量引用
true
單擊頁面右側更新。在確認彈出框中單擊確定。
開啟嚴格優先級調度
默認情況下,請求失敗的任務進入Unschedulable隊列進行退避,直到下次調度。當集群資源由于任務結束空閑時,高優先級任務由于仍處于退避階段,不會被任務隊列調度,可能會導致集群資源被低優先級任務占用。為了使得高優先級任務在集群獲得空閑資源時能夠被優先嘗試調度,ack-kube-queue提供嚴格優先級調度功能。開啟后隊列將在正在運行的任務結束后從高優先級的最早提交的任務開始嘗試執行,從而使得高優先級任務能夠在集群獲得空閑資源時優先調度。
當高優先級任務未獲得充足資源時,低優先級任務仍然可以獲得資源執行。
開啟方法
登錄容器服務管理控制臺,在左側導航欄選擇集群。
在集群列表頁面,單擊目標集群名稱,然后在左側導航欄,選擇 。
選擇命名空間為kube-queue,在kube-queue-controller所在行,單擊操作列的編輯。
單擊環境變量后面的新增,增加如下記錄。
配置項
取值
類型
自定義
變量名稱
StrictConsistency
變量/變量引用
true
單擊頁面右側更新。在確認彈出框中單擊確定。
Quota資源使用示例
ElasticQuota
使用如下YAML,創建一個ElasticQuotaTree。
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
執行如下命令,查看ElasticQuotaTree是否創建成功。
kubectl get elasticquotatree -A
預期輸出:
NAMESPACE NAME AGE kube-system elasticquotatree 7s
表示ElasticQuotaTree創建成功。
創建測試任務。
說明為了測試任務隊列效果,測試任務的資源配額應該小于執行所有任務需要的總資源。
為了方便測試,TFJob用busybox鏡像替代真正的tensorflow鏡像。模擬訓練過程,每個容器Sleep 30秒。
使用如下YAML,創建兩個TFJob進行測試。
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
待任務提交后,查看任務狀態。
執行如下命令,查看任務狀態。
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。稍等片刻后,再次執行如下命令。
kubectl get tfjob
預期輸出:
NAME STATE AGE job1 Succeeded 77s job2 Running 77s
預期輸出顯示
job1
執行成功。job1
執行完成后,job2
開始執行。表明ack-kube-queue可以正常工作。
ResourceQuota
使用如下YAML,創建一個ResourceQuota。
apiVersion: v1 kind: ResourceQuota metadata: name: default spec: hard: cpu: "4" memory: 4Gi
執行如下命令,查看ResourceQuota是否創建成功。
kubectl get resourcequota default -o wide
預期輸出:
NAME AGE REQUEST LIMIT default 76s cpu: 0/4, memory: 0/4Gi
表示ResourceQuota創建成功。
使用如下YAML,創建兩個TFJob進行測試。
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
待任務提交后,執行如下命令,查看任務狀態。
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。
稍等片刻后,再次執行如下命令。
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的數量將不會超過該值乘以超賣比例。如以下的例子: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