在ACK集群中,企業可以通過AI套件中的任務管理工具Arena、隊列調度管理系統Kube Queue、配額管理工具ElasticQuotaTree以及Prometheus監控打造企業級任務調度系統。本文將從實際案例出發自底而上地介紹如何基于ACK集群構建一個任務調度系統。
背景介紹
批處理任務(Batch Job)常應用于數據處理、仿真計算、科學計算和人工智能等領域,主要用于執行一次數據處理或模型訓練任務。由于這類任務往往需要消耗大量計算資源,因此必須根據任務的優先級和提交者的可用資源情況進行合理排隊,才能最大化集群資源的利用效率。為了解決批處理任務的調度問題,企業需要自上而下地解決任務管理、配額管理、任務調度、用戶隔離、日志收集、集群監控、資源供給等多方面問題。
為解決以上問題,您可以使用ACK集群來管理大規模集群的管理系統,其較為完善的生態為企業打造大規模任務調度系統掃清了障礙,簡化了企業集群管理系統搭建的流程。
用戶角色
企業的任務調度系統中通常有兩類角色,分別為提交任務的開發人員以及任務調度系統的運維人員。開發人員和運維人員對任務調度系統的需求有所區別。
角色類型 | 說明 |
開發人員 | 專注于自身的業務領域,對任務調度系統使用的Kubernetes系統可能并不熟悉,日常對系統的操作僅為提交任務,因此只需能夠簡化提交任務流程的工具,并且在提交完任務后,需要能夠方便地查看任務的運行時日志以進行Bug修復。 |
運維人員 | 對Kubernetes系統較為熟悉,而對具體的業務領域并不熟悉,需要進行系統的維護并為用戶提供高效可靠的任務調度系統,因此需要對Kubernetes集群進行整體的監控。 |
實際使用場景中,開發人員很有可能來自于公司的不同部門,這些不同的部門內部有著自己的任務優先級定義規則,不同部門之間的任務優先級沒有關聯或關聯不大,因此需要將不同部門的任務單獨進行排隊。
同時,公司內部的可用資源是有限的,為了將資源傾斜給更重要的項目,這些部門的資源配額通常是互不相同。這些資源是受保障資源,只在這些部門的資源配額空閑時允許其他部門臨時借用資源運行自己的任務。當集群整體資源緊張時,保障資源的擁有者有權主動驅逐借用資源的部門的任務。
實現原理
如下圖所示,企業內部有兩個部門:Department-a和Department-b。Department-a有兩名員工(User-a和User-b),Department-b有一名員工(User-c)。企業在ACK上構建任務調度系統的過程為:
首先,運維人員需要創建一個ACK集群,并在ACK集群中安裝隊列調度系統Kube Queue、Prometheus監控以及Arena組件。
Kube Queue可以幫助企業有效地管理大量并發任務,確保資源的合理分配。Kube Queue通過監聽ElasticQuotaTree自動為配額生成對應的任務隊列,共享同一個配額的任務會一起進行排隊。
Prometheus監控提供了實時監控能力,使運維人員能夠快速識別并診斷集群中潛在的問題,保證任務調度系統的穩定性和資源利用率。
Arena組件可以簡化GPU資源的申請、任務提交、監控等流程。
然后,運維人員再對ElasticQuotaTree以及ResourcePolicy進行配置。
ElasticQuotaTree是一種彈性配額管理機制。通過對Department-a和Department-b分別設定合理的配額,可以確保各部門在共享集群資源時互不影響,避免資源爭搶或浪費。
ResourcePolicy定義了資源的使用規則和優先級,通過該方式實現當ECS資源耗盡時將Pod調度到Virtual Node上,觸發ECI實例的創建和運行,從而保持整體資源使用的均衡和效率。
最后,開發人員可以通過企業的任務提交平臺提交任務或通過ACK AI套件的開發控制臺提交任務。
步驟一:搭建環境
已創建ACK集群,且集群版本為1.20及以上。具體操作,請參見創建Kubernetes托管版集群。
ACK集群支持的GPU機型,請參見ACK支持的GPU機型。
為ACK集群部署云原生AI套件,并在部署云原生AI套件時安裝Kube Queue和Arena組件。具體操作,請參見安裝云原生AI套件。
Kube Queue支持管理TFJob、PyTorchJob以及MPIJob。若需要進一步使用SparkApplication、Argo Workflow、RayJob等工作負載,可以在應用市場頁面分別安裝ack-spark-operator、ack-workflow以及ack-kuberay等組件。這些組件會在集群中啟動各自任務類型的Operator,當集群中對應任務類型被提交時負責在集群中執行相應的任務。
關于應用市場組件的安裝,請參見應用市場。關于Kube Queue的詳細信息,請參見使用任務隊列ack-kube-queue。
為ACK集群安裝ACK Virtual Node組件。具體操作,請參見部署ack-virtual-node組件。
ACK Virtual Node組件安裝成功后會在ACK集群中生成一個代表ECI實例的虛擬節點,當有Pod需要使用ECI時,該組件會負責完成ECI實例的創建、實例與Pod的綁定,當Pod刪除時,該組件會自動清除ECI實例。
默認情況下,調度器不會將Pod調度到Virtual Node上(即不會使用到ECI實例)。如需實現快速擴容和避免消耗過多預算,您可以通過配置ResourcePolicy的方式實現當ECS資源耗盡時將Pod調度到Virtual Node上,從而觸發ECI實例的創建和運行,同時,這種配置方式也支持限制ECI實例中的Pod數量。
關于ResourcePolicy的具體用法,請參見自定義彈性資源優先級調度。
(可選)為ACK集群開啟阿里云Prometheus監控,并根據需要安裝日志組件。具體操作,請參見開啟阿里云Prometheus監控。
步驟二:設置集群配額
ElasticQuotaTree介紹
如下圖所示,ElasticQuotaTree利用樹狀結構清晰地展現了企業資源在不同層級(如部門級、用戶級)的分配與管理情況。通過為每個User分配獨立的Namespace,并將這些Namespace與樹中的Children(即資源配額)關聯起來,確保了User間資源使用的隔離性,同時允許資源在需要時進行共享。
ElasticQuotaTree中的Root代表整個企業的總資源限額,Children則代表不同的部門(如Department-a和Department-b),如果某個Children下包含多個User的Namespace,說明這些User共享這一部分資源。ElasticQuotaTree中的每個User都有自己單獨的Namespace,每個Children都與一個或多個User的Namespace相聯系。
操作步驟
創建3個命名空間,分別是user-a、user-b和user-c。
具體操作,請參見管理命名空間與配額。
配置ElasticQuotaTree。YAML文件樣例如下所示:
通過ElasticQuotaTree配置文件,給企業中的兩個不同部門規劃配額。ElasticQuotaTree可以實現部門間資源的動態共享與調整,確保在資源緊張時優先保障各部門的基本需求,同時允許在資源充裕時充分利用集群資源。
# ElasticQuotaTree中給整個企業限制了100核,100Gi以及4張顯卡的使用額度。 # 每個節點都設置了max和min屬性,分別表示節點允許使用的最大資源量和最小資源保障量,部門可以在限定范圍內動態調整資源使用。 apiVersion: scheduling.sigs.k8s.io/v1beta1 kind: ElasticQuotaTree metadata: name: elasticquotatree namespace: kube-system # 只在kube-system下才會生效。 spec: root: name: root # Root節點的max必須等于min,表示整個企業的總資源限額,且max的值不能小于Children的max之和。 max: cpu: 100 memory: 100Gi nvidia.com/gpu: 4 min: cpu: 100 memory: 100Gi nvidia.com/gpu: 4 children: # 配置了兩個子節點,分別為Department-a和Department-b。 # Department-a關聯了user-a和user-b兩個命名空間,而Department-b僅關聯了user-c命名空間。即各自命名空間中創建的Pod將受到相應部門資源配額的約束。 - name: Department-a max: cpu: 100 memory: 100Gi nvidia.com/gpu: 4 min: cpu: 60 memory: 60Gi nvidia.com/gpu: 3 namespaces: # 配置Department-a的Namespace。 - user-a - user-b - name: Department-b max: cpu: 100 memory: 100Gi nvidia.com/gpu: 4 min: cpu: 40 memory: 40Gi nvidia.com/gpu: 1 namespaces: # 配置Department-b的Namespace。 - user-c
當集群資源充足時,Department-a和Department-b的工作負載可以分別使用至各自Max部分所指定的資源上限。
當集群資源不足時,若某個部門的實際資源使用量低于其Min部分的保障性資源,該部門有權搶占另一個部門超出Min部分的資源,以確保自身的資源需求得到滿足。
配置完ElasticQuotaTree文件后,執行以下命令,可以看到kube-queue-controller組件會根據ElasticQuotaTree的定義自動為每個葉子Quota在kube-queue命名空間下創建一個Queue對象。
kubectl get queue -n kube-queue
預期輸出:
NAME AGE root-department-a-user-a 58s root-department-b-user-c 58s
設置隊列超賣比例。
為了嚴格按照Quota下發任務,需要您將隊列啟動參數中的
oversellrate
參數調整為1。登錄容器服務管理控制臺,在左側導航欄選擇集群。
在集群列表頁面,單擊目標集群名稱,然后在左側導航欄,選擇 。
在無狀態頁面的kube-queue命名空間下,單擊kube-queue-controller組件操作列下的編輯,將
oversellrate
參數由默認值2調整為1。單擊頁面右側的更新,在確認彈出框中單擊確定,保存參數配置。
步驟三:提交任務并分析結果
場景一:同Quota下,任務按照輪轉方式出隊
使用以下YAML樣例配置任務。
apiVersion: batch/v1 kind: Job metadata: generateName: pi- namespace: user-a # 指定了這個Job屬于user-a命名空間。 spec: suspend: true # 表明此Job處于暫停狀態,不會自動開始執行。 completions: 6 # Job需要完成的總任務數為6個。 parallelism: 6 # 同時運行的任務(Pod)的最大數量為6個。 template: spec: schedulerName: default-scheduler # 使用默認調度器調度Pod。 containers: - name: pi image: perl:5.34.0 command: ["sleep", "1d"] resources: requests: cpu: 10 memory: 10Gi limits: cpu: 10 memory: 10Gi restartPolicy: Never
Job配置完成后,將以下命令執行兩次,即同一個Quota下提交兩次任務。
kubectl create -f pi.yaml # pi.yaml為提交Job的名稱。Job的名稱可自定義。
預期輸出:
job.batch/pi-8k5pn created # 第一次提交任務的輸出結果。 job.batch/pi-2xtcj created # 第二次提交任務的輸出結果。
預期輸出表明,兩個任務均已成功提交。
執行以下命令,查看任務運行情況。
kubectl get pod -n user-a
預期輸出:
NAME READY STATUS RESTARTS AGE pi-8k5pn-8dn4k 1/1 Running 0 25s pi-8k5pn-lkdn5 1/1 Running 0 25s pi-8k5pn-s9cvm 1/1 Running 0 25s pi-8k5pn-tvw6c 1/1 Running 0 25s pi-8k5pn-wh9zv 1/1 Running 0 25s pi-8k5pn-zsdqs 1/1 Running 0 25s
預期輸出顯示僅第一個任務創建了6個Pod,而非12個Pod,表明僅有第一個任務在運行,第二個任務未啟動成功。即同一個Quota下的資源全部被的第一次提交的任務使用了。
執行以下命令,查看user-a命名空間下任務的排隊情況。
kubectl get queue -n kube-queue root-department-a-user-a -o yaml
apiVersion: scheduling.x-k8s.io/v1alpha1 kind: Queue metadata: annotations: kube-queue/queue-args: | {} kube-queue/quota-fullname: root/Department-a creationTimestamp: "2024-04-08T02:04:17Z" generation: 2 labels: create-by-kubequeue: "true" name: root-department-a-user-a namespace: kube-queue resourceVersion: "16544808" uid: bb1c4edf-f***-4***-a50a-45****62ce spec: queuePolicy: Round status: queueItemDetails: active: - name: pi-2xtcj namespace: user-a position: 1 backoff: []
預期輸出表明,root-department-a-user-a的Queue對象采用了輪轉(Round)調度策略,當前隊列中有1個活躍工作項。
執行以下命令,從ElasticQuotaTree的Status中查看集群中每個Quota的使用情況。
kubectl -n kube-system get eqtree -ojson elasticquotatree | jq 'status'
您將會從預期輸出中看到user-a分配到了Root的全部資源,而與user-a處于同一個Quota下的user-b并沒有分配到資源。
場景二:同Quota下,任務按照優先級進行阻塞式的出隊
本場景基于場景一進行配置。
ack-kube-queue處理任務時默認采用任務輪轉機制,如果需要集群中的任務按照優先級進行阻塞式的出隊,可以在kube-queue-controller組件中增加一個StrictPriority=true
的環境變量,就可以看到所有的隊列均已變為Block
狀態。
為任務開啟阻塞隊列。具體操作,請參見開啟阻塞隊列。
提交一個高優先級任務(其優先級需要高于場景一所提交的任務)。示例YAML如下所示:
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: annotations: meta.helm.sh/release-name: ack-kube-queue meta.helm.sh/release-namespace: kube-queue labels: app.kubernetes.io/managed-by: Helm name: priority-2 value: 2 --- apiVersion: batch/v1 kind: Job metadata: generateName: pi- namespace: user-a spec: suspend: true completions: 6 parallelism: 6 template: spec: schedulerName: default-scheduler priorityClassName: priority-2 priority: 2 containers: - name: pi image: perl:5.34.0 command: ["sleep", "1d"] resources: requests: cpu: 10 memory: 10Gi limits: cpu: 10 memory: 10Gi restartPolicy: Never
執行以下命令,查看Queue的狀態。
kubectl get queue -n kube-queue root-department-a-user-a -o yaml
apiVersion: scheduling.x-k8s.io/v1alpha1 kind: Queue metadata: annotations: kube-queue/queue-args: | {} kube-queue/quota-fullname: root/Department-a creationTimestamp: "2024-04-08T02:04:17Z" generation: 2 labels: create-by-kubequeue: "true" name: root-department-a-user-a namespace: kube-queue resourceVersion: "16549536" uid: bb1c4edf-f***-4***-a***-45d1****** spec: queuePolicy: Block status: queueItemDetails: active: - name: pi-6nsc7 namespace: user-a position: 1 priority: 2 - name: pi-2xtcj namespace: user-a position: 2 backoff: []
預期輸出表明,高優先級的任務排在了第一位。
場景三:不同Quota下,任務按照優先級進行阻塞式的出隊
本場景基于以上的場景二進行配置。
ack-kube-queue處理任務時默認采用任務輪轉機制,如果需要集群中的任務按照優先級進行阻塞式的出隊,可以在kube-queue-controller組件中增加一個StrictPriority=true
的環境變量,就可以看到所有的隊列均已變為Block
狀態。
在user-c命名空間下,提交一個更高優先級的任務,將集群中的資源全部利用。
apiVersion: scheduling.k8s.io/v1 kind: PriorityClass metadata: annotations: meta.helm.sh/release-name: ack-kube-queue meta.helm.sh/release-namespace: kube-queue labels: app.kubernetes.io/managed-by: Helm name: priority-3 value: 3 --- apiVersion: batch/v1 kind: Job metadata: generateName: pi- namespace: user-c spec: suspend: true completions: 1 parallelism: 1 template: spec: schedulerName: default-scheduler priorityClassName: priority-3 priority: 3 containers: - name: pi image: perl:5.34.0 command: ["sleep", "1d"] resources: requests: cpu: 10 memory: 10Gi limits: cpu: 10 memory: 10Gi restartPolicy: Never
執行以下命令,查看user-a命名空間下資源的使用情況。
kubectl get pod -o wide -n user-a
預期輸出:
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES pi-6nsc7-psz9k 1/1 Running 0 45s 192.XX.XX.XX cn-hangzhou.192.XX.XX.XX <none> <none> pi-6nsc7-qtkcc 1/1 Running 0 45s 192.XX.XX.XX cn-hangzhou.192.XX.XX.XX <none> <none> pi-6nsc7-rvklt 1/1 Running 0 45s 192.XX.XX.XX cn-hangzhou.192.XX.XX.XX <none> <none> pi-6nsc7-z4hhc 1/1 Running 0 45s 192.XX.XX.XX cn-hangzhou.192.XX.XX.XX <none> <none> pi-8k5pn-lkdn5 1/1 Running 0 26m 192.XX.XX.XX cn-hangzhou.192.XX.XX.XX <none> <none> pi-8k5pn-ql4fx 1/1 Running 0 25s 192.XX.XX.XX cn-hangzhou.192.XX.XX.XX <none> <none> pi-8k5pn-tcdlb 1/1 Running 0 25s 192.XX.XX.XX cn-hangzhou.192.XX.XX.XX <none> <none> pi-8k5pn-tvw6c 1/1 Running 0 26m 192.XX.XX.XX cn-hangzhou.192.XX.XX.XX <none> <none> pi-8k5pn-wh9zv 1/1 Running 0 26m 192.XX.XX.XX cn-hangzhou.192.XX.XX.XX <none> <none> pi-8k5pn-zsdqs 1/1 Running 0 26m 192.XX.XX.XX cn-hangzhou.192.XX.XX.XX <none> <none>
預期輸出表明,由于用戶user-c提交的任務未達到預設的資源下限(Min),系統允許user-a通過搶占其他任務資源的方式來滿足自身需求的過程。
相關文檔
關于任務隊列的更多信息,請參見使用任務隊列ack-kube-queue。