本文介紹了Slurm HPC與Kubernetes融合的負載調度策略,旨在通過優化資源配置與作業調度機制,不僅能提升計算資源的利用率,還增強了系統整體的穩定性和運行效能。此方案確保在滿足多種計算場景需求的同時,為您構建一個更為高效且靈活的計算平臺。
方案概述
為什么要提出在ACK集群上實現Slurm HPC & Kubernetes負載混合調度呢?
原因分析:目前ACK會提供靜態分配+分離調度的方案,但是由于每個Slurm Pod的規格固定,且Slurm Pod屬于提前占用集群資源,Slurm集群中資源空閑時Kubernetes無法使用這些已占用的集群資源,從而導致集群資源碎片。此外,修改Slurm Pod的資源規格需要刪除Pod重建,因此在Slurm與Kubernetes資源占用變化較大的場景中,節點遷移的難度較大。
改進方案:考慮到現有方案的弊端,ACK Slurm Operator提供一種Slurm & Kubernetes混合調度方案,通過配置運行在Kubernetes集群中的協調器以及Slurm集群的擴展資源插件,使得Kubernetes與Slurm可以共享集群資源,并且避免在分配資源時出現重復分配的情況。
目前任務共享資源方案可以分為以下兩種。
靜態分配 + 分離調度 | Slurm HPC + Kubernetes負載混合調度 |
Slurm HPC + Kubernetes負載混合調度方案的運行原理如下圖所示。
核心組件 | 描述 |
SlurmOperator | 負責在集群中以容器化形式拉起Slurm集群。集群會以容器化的方式運行,運行Slurm的Worker Pod會以互斥的方式運行在不同集群節點上,其他的Slurm系統組件會隨機運行在集群節點上。 |
SlurmCopilot | 使用集群Token(默認啟動Slurmctld時會自動生成Token并通過kubectl更新到secret中,可通過自定義啟動腳本或取消更新secret權限修改此行為,修改后需要手動更新Toekn至ack-slurm-operator空間下的ack-slurm-jwt-token, Data中以ClusterName為Key,以Token base64 --wrap=0后的結果為value)與Slurmctld進行資源協調通信。負責在AdmissionCheck被添加到GenericNode上后,修改Slurmctld中對應節點的可用資源量,成功修改可用資源量后將狀態寫回GenericNode,通知ACK Scheduler完成調度。 |
Slurmctld | slurm的中心管理器,負責監測集群的資源和作業,以及進行作業的調度和分配。為了提高可用性,還可以配置一個備份的slurmctld。 |
GenericNodes | 是一種自定義資源,作為Kubernetes和Slurm的中間賬本。ACK Scheduler調度一個Pod到節點上之前,會在GenericNode上新增AdmissionCheck,請求Slurm系統確認資源。 |
Slurmd | slurm的節點守護進程,運行在每個計算節點上,負責執行作業,以及向slurmctld匯報節點和作業的狀態。 |
Slurmdbd | slurm的數據庫守護進程,負責存儲和管理作業的記賬信息,以及提供查詢和統計的接口。slurmdbd是可選的,也可以將記賬信息存儲在文件中。 |
Slurmrested | slurm的REST API守護進程,提供了一種通過REST API與slurm進行交互的方式,可以實現slurm的所有功能。slurmrestd是可選的,也可以通過命令行工具與slurm進行交互。 |
1. 環境準備
1.1 安裝ack-slurm-operator組件
確認已安裝的ACK集群版本為v1.26及以上。具體操作,請參見創建GPU集群、升級集群。
安裝ack-slurm-operator組件并開啟Copilot功能,實現Slurm任務與Kubernetes Pod在同一批物理機器上混合部署。
登錄容器服務管理控制臺。單擊目標集群名稱,進入集群詳情頁面,如下圖所示,按照序號依次單擊,為目標集群安裝ack-slurm-operator組件。
您無需為組件配置應用名和命名空間,單擊④下一步后會出現一個請確認彈框,單擊是,即可使用默認的應用名(ack-slurm-operator)和命名空間(ack-slurm-operator)。
然后選擇Chart 版本為最新版本,并將②參數
enableCopilot
設置為true
,并設置③watchNamespace
為default
(您也可以根據需要自主設置命名空間),單擊確定即可完成ack-slurm-operator組件安裝。(可選)更新ack-slurm-operator組件操作步驟。
登錄容器服務管理控制臺。在集群信息頁面,單擊應用 > Helm頁簽,在應用頁面找到ack-slurm-operator組件,然后點擊更新。
1.2 安裝配置ack-slurm-cluster組件
如需快速安裝、管理SlurmCluster,以及靈活調整集群配置,您可以使用Helm軟件包管理器來部署阿里云提供的SlurmClusterart。從charts-incubator中下載由阿里云包裝好的SlurmCluster的Helm,設置好相應的參數后,Helm會幫助您創建出RBAC、Configmap、Secret以及SlurmCuster等資源。
Helm Chart中包含以下資源:
資源類型 | 資源名稱 | 功能及用途 |
ConfigMap | {{ .Values.slurmConfigs.configMapName }} | 當.Values.slurmConfigs.createConfigsByConfigMap為True時創建該ConfigMap,用于存儲用戶定義的Slurm配置文件。該配置文件會被掛載到.Values.slurmConfigs.slurmConfigPathInPod的路徑中(該路徑會被渲染到SlurmCluster的.Spec.SlurmConfPath中,最終被渲染到Pod的啟動命令中),在Pod啟動時被復制到/etc/slurm/路徑下并設置訪問權限。 |
ServiceAccount | {{ .Release.Namespace }}/{{ .Values.clusterName }} | 允許SlurmCtld所在的Pod修改SlurmCluster,用于SlurmCluster使用CloudNode功能自動擴縮容的場景中。 |
Role | {{ .Release.Namespace }}/{{ .Values.clusterName }} | 允許SlurmCtld所在的Pod修改SlurmCluster,用于SlurmCluster使用CloudNode功能自動擴縮容的場景中。 |
RoleBinding | {{ .Release.Namespace }}/{{ .Values.clusterName }} | 允許SlurmCtld所在的Pod修改SlurmCluster,用于SlurmCluster使用CloudNode功能自動擴縮容的場景中。 |
Role | {{ .Values.slurmOperatorNamespace }}/{{ .Values.clusterName }} | 允許SlurmCtld所在的Pod修改SlurmOperator命名空間下的Secrets,用于SlurmCluster與Kubernetes混合部署場景下SlurmCluster更新Token使用。 |
RoleBinding | {{ .Values.slurmOperatorNamespace }}/{{ .Values.clusterName }} | 允許SlurmCtld所在的Pod修改SlurmOperator命名空間下的Secrets,用于SlurmCluster與Kubernetes混合部署場景下SlurmCluster更新Token使用。 |
Secret | {{ .Values.mungeConfigs.secretName }} | 用于Slurm組件之間的認證,當.Values.mungeConfigs.createConfigsBySecret為True時會自動創建,內容即為"munge.key"={{ .Values.mungeConfigs.content }}。.Values.mungeConfigs.createConfigsBySecret為True時.Values.mungeConfigs.createConfigsBySecret會被渲染為.Spec.MungeConfPath,最終被渲染為Pod的掛載路徑。Pod的啟動命令中會根據該路徑初始化/etc/munge/munge.key。 |
SlurmCluster | 自定義 | 渲染出的SlurmCluster |
相關的參數以及說明可參考下表:
參數 | 參考值 | 用途 |
clusterName | "" | 集群名稱,用于Secret、Role等資源生成,需要與后續Slurm配置文件中的ClusterName對應。 |
headNodeConfig | 無 | 必須存在。聲明Slurmctld的Pod的相關配置。 |
workerNodesConfig | 無 | 申明Slurmd的Pod的相關配置。 |
workerNodesConfig.deleteSelfBeforeSuspend | true | 該值為true時,為workerPod自動添加preStopHook,用于節點下線前自動排水并將節點標記為下線狀態。 |
slurmdbdConfigs | 無 | 申明Slurmdbd的Pod的相關配置,不存在該值時,將不會創建Slurmdbd的對應Pod。 |
slurmrestdConfigs | 無 | 申明Slurmrestd的Pod的相關配置,不存在該值時,將不會創建Slurmrestd的對應Pod。 |
headNodeConfig.hostNetwork slurmdbdConfigs.hostNetwork slurmrestdConfigs.hostNetwork workerNodesConfig.workerGroups[].hostNetwork | false | 渲染為Slurmctld的Pod的hostNetwork。 |
headNodeConfig.setHostnameAsFQDN slurmdbdConfigs.setHostnameAsFQDN slurmrestdConfigs.setHostnameAsFQDN workerNodesConfig.workerGroups[].setHostnameAsFQDN | false | 渲染為Slurmctld的Pod的setHostnameAsFQDN。 |
headNodeConfig.nodeSelector slurmdbdConfigs.nodeSelector slurmrestdConfigs.nodeSelector workerNodesConfig.workerGroups[].nodeSelector |
| 渲染為Slurmctld的Pod的NodeSelector。 |
headNodeConfig.tolerations slurmdbdConfigs.tolerations slurmrestdConfigs.tolerations workerNodesConfig.workerGroups[].tolerations |
| 渲染為Slurmctld的Pod的Toleration。 |
headNodeConfig.affinity slurmdbdConfigs.affinity slurmrestdConfigs.affinity workerNodesConfig.workerGroups[].affinity |
| 渲染為Slurmctld的Pod的Affinity。 |
headNodeConfig.resources slurmdbdConfigs.resources slurmrestdConfigs.resources workerNodesConfig.workerGroups[].resources |
| 渲染為Slurmctld的主容器的資源。WorkerPod的主容器的資源限制會被渲染成為slurm節點的資源上限。 |
headNodeConfig.image slurmdbdConfigs.image slurmrestdConfigs.image workerNodesConfig.workerGroups[].image | "registry-cn-hangzhou.ack.aliyuncs.com/acs/slurm:23.06-1.6-aliyun-49259f59" | 渲染為Slurmctld的鏡像。如需使用自定義鏡像請參考ai-models-on-ack/framework/slurm/building-slurm-image at main · AliyunContainerService/ai-models-on-ack (github.com)。 |
headNodeConfig.imagePullSecrets slurmdbdConfigs.imagePullSecrets slurmrestdConfigs.imagePullSecrets workerNodesConfig.workerGroups[].imagePullSecrets |
| 渲染為Slurmctld的鏡像拉取密鑰。 |
headNodeConfig.podSecurityContext slurmdbdConfigs.podSecurityContext slurmrestdConfigs.podSecurityContext workerNodesConfig.workerGroups[].podSecurityContext |
| 渲染為Slurmctld的SecurityContext。 |
headNodeConfig.securityContext slurmdbdConfigs.securityContext slurmrestdConfigs.securityContext workerNodesConfig.workerGroups[].securityContext |
| 渲染為Slurmctld的主容器的SecurityContext。 |
headNodeConfig.volumeMounts slurmdbdConfigs.volumeMounts slurmrestdConfigs.volumeMounts workerNodesConfig.workerGroups[].volumeMounts | 無 | 渲染為Slurmctld的主容器的卷掛載。 |
headNodeConfig.volumes slurmdbdConfigs.volumes slurmrestdConfigs.volumes workerNodesConfig.workerGroups[].volumes | 無 | 渲染為Slurmctld的Pod的卷。 |
slurmConfigs.slurmConfigPathInPod | "" | slurm相關配置在Pod中的掛載位置。當Slurm的相關配置文件是通過Volume掛載進入Pod時。需要通過該項聲明slurm.conf的位置。Pod的啟動命令中會將該路徑下的文件復制到/etc/slurm/下并設置對應權限。 |
slurmConfigs.createConfigsByConfigMap | true | 是否自動創建存儲Slurm配置文件的Configmap。 |
slurmConfigs.configMapName | "" | 存儲Slurm配置文件的Configmap的資源名稱。 |
slurmConfigs.filesInConfigMap | "" | 自動創建存儲Slurm配置文件的Configmap時配置文件的內容。 |
mungeConfigs.mungeConfigPathInPod | 無 | munge相關配置在Pod中的掛載位置。當munge的相關配置文件是通過Volume掛載進入Pod時。需要通過該項聲明munge.key的位置。Pod的啟動命令中會將該路徑下的文件復制到/etc/munge/下并設置對應權限。 |
mungeConfigs.createConfigsBySecret | 無 | 是否自動創建存儲munge配置文件的Secret。 |
mungeConfigs.secretName | 無 | 自動創建存儲munge配置文件的Secret時的資源名稱。 |
mungeConfigs.content | 無 | 自動創建存儲munge配置文件的Secret時配置文件的內容。 |
其中關于slurmConfigs.filesInConfigMap詳細信息,請參見Slurm System Configuration Tool (schedmd.com)。
Pod啟動后修改slurmConfigs.filesInConfigMap的情況下,需要重建Pod應用新的配置文件,故請提前確認配置文件中的內容。
具體操作如下所示:
執行以下命令,將阿里云Helm倉庫添加到您的Helm客戶端。該操作將允許您訪問阿里云提供的各種Charts,包括ack-slurm-cluster組件。
helm repo add aliyun https://aliacs-app-catalog.oss-cn-hangzhou.aliyuncs.com/charts-incubator/
執行以下命令,拉取并解壓ack-slurm-cluster組件。該操作將會在當前目錄下創建一個名為
ack-slurm-cluster
的目錄,其中包含了Chart的所有文件和模板。helm pull aliyun/ack-slurm-cluster --untar=true
執行以下命令,在名為values.yaml的文件中修改Chart參數。
values.yaml文件包含了Chart的默認配置。您可以根據您的實際需求通過編輯這個文件來修改參數。例如Slurm的配置、資源請求與限制、存儲等。
cd ack-slurm-cluster vi values.yaml
生成
JWT
插件需要的key,并將生成的key通過以下命令導入集群中。具體操作,請參見JWT認證插件。獲取JWK,實現JWT插件的簽名與認證。
JWT認證插件通過Json Web Key(RFC7517),實現JWT的簽名與認證,配置
JWT認證插件
首先需要生成一個有效的Json Web Key
,您可以通過自行生成,或搜索Json Web Key Generator
尋找可用的在線生成工具,如mkjwk.org,一個可用的Json Web Key
大概如下所示,其中私鑰用于對Token進行簽名,公鑰需要配置在JWT認證
插件中用于對Token進行驗證,一個合法的JWK大概格式如下:{ "kty": "RSA", "e": "AQAB", "kid": "O9fpdhrViq2zaaaBEWZITz", "use": "sig", "alg": "RS256", "n": "qSVxcknOm0uCq5vGsOmaorPDzHUubBmZZ4UXj-9do7w9X1uKFXAnqfto4TepSNuYU2bA_-tzSLAGBsR-BqvT6w9SjxakeiyQpVmexxnDw5WZwpWenUAcYrfSPEoNU-0hAQwFYgqZwJQMN8ptxkd0170PFauwACOx4Hfr-9FPGy8NCoIO4MfLXzJ3mJ7xqgIZp3NIOGXz-GIAbCf13ii7kSStpYqN3L_zzpvXUAos1FJ9IPXRV84tIZpFVh2lmRh0h8ImK-vI42dwlD_hOIzayL1Xno2R0T-d5AwTSdnep7g-Fwu8-sj4cCRWq3bd61Zs2QOJ8iustH0vSRMYdP5oYQ" }
這里展示的是JSON格式,當使用YAML格式配置插件,需要轉換*
JWT認證插件
只需要配置Public Key
, 請妥善保存好您的Private Key
,目前JWT認證插件支持以下算法:
簽名算法
支持的
alg
取值RSASSA-PKCS1-V1_5 with SHA-2
RS256, RS384, RS512
Elliptic Curve (ECDSA) with SHA-2
ES256, ES384, ES512
HMAC using SHA-2
HS256, HS384, HS512
重要當配置HS256,HS384,HS512類型的Key時,密鑰需要為Base64 UrlEncode后的值,如遇到Invalid Signature問題,請檢查您的Key的格式是否與生成Token的Key一致
將獲取的JWK導入集群中。
kubectl create configmap jwt --from-literal=jwt_hs256.key={{ .jwtkey }}
開啟Slurmrestd和Slurmdbd。修改values.yaml的
.Values.slurmConfigs.filesInConfigMap
字段聲明數據庫地址以及gres配置,配置文件中的數據庫地址可以使用阿里云RDS地址,也可以使用自建數據庫地址。slurmConfigs: ... filesInConfigMap: gres.conf: | # 用于Copilot將Kubernetes已分配資源同步至Slurm Name=k8scpu Flags=CountOnly Name=k8smemory Flags=CountOnly slurmdbd.conf: | # 日志路徑,需要與下面驗證時的路徑相同 LogFile=/var/log/slurmdbd.log # 使用slurmrestd時必須指定jwt認證 AuthAltTypes=auth/jwt # Slurmdbd需要使用該路徑中的Key認證token。需要配合下文將Key掛載到Pod中 AuthAltParameters=jwt_key=/var/jwt/jwt_hs256.key AuthType=auth/munge SlurmUser=slurm # 設置mysql數據庫賬號信息 StoragePass= StorageHost= StorageType=accounting_storage/mysql StorageUser=root StoragePort=3306 slurm.conf: | # 用于在節點加入Slurm集群中設置k8scpu,k8smemory擴展資源屬性,防止節點被設置為DOWN狀態 NodeFeaturesPlugins=node_features/k8s_resources # 用于Slurm中提交任務時自動添加k8scpu,k8smemory兩種擴展資源 JobSubmitPlugins=k8s_resource_completion AccountingStorageHost=slurm-test-slurmdbd # 使用slurmrestd時必須指定jwt認證 AuthAltTypes=auth/jwt # Slurmctld需要使用該路徑中的Key生成token。需要配合下文將Key掛載到Pod中 AuthAltParameters=jwt_key=/var/jwt/jwt_hs256.key # 用于Copilot將Kubernetes已分配資源同步至Slurm GresTypes=k8scpu,k8smemory # 填寫${slurmClusterName}-slurmdbd,slurmOperator將會自動創建對應的slurmdbd服務 AccuntingStorageHost= AccountingStoragePort=6819 AccountingStorageType=accounting_storage/slurmdbd # 設置JobComp插件使用mysql數據庫時的信息 JobCompHost= JobCompLoc=/var/log/slurm/slurm_jobcomp.log JobCompPass= JobCompPort=3306 JobCompType=jobcomp/mysql JobCompUser=root # 高可用配置 SlurmctldHost=
設置Slurmrestd Pod以及Slurmdbd Pod相關配置。
... headNodeConfig: image: "registry-cn-hangzhou.ack.aliyuncs.com/acs/slurm:23.06-1.6-aliyun-49259f59" # 將創建出的JWT Key掛載到Slurm中,與上文配置文件中的路徑對應 volumes: - configMap: defaultMode: 444 name: jwt name: config-jwt volumeMounts: - mountPath: /var/jwt name: config-jwt slurmdbdConfigs: nodeSelector: {} tolerations: [] affinity: {} resources: {} image: "registry-cn-hangzhou.ack.aliyuncs.com/acs/slurm:23.06-1.6-aliyun-49259f59" imagePullSecrets: [] # if .slurmConfigs.createConfigsByConfigMap is true, slurmConfPath and volume and volumeMounts will be auto set as: # volumeMounts: # - name: config-{{ .Values.slurmConfigs.configMapName }} # mountPath: {{ .Values.slurmConfigs.slurmConfigPathInPod }} # volumes: # - name: config-{{ .Values.slurmConfigs.configMapName }} # configMap: # name: {{ .Values.slurmConfigs.configMapName }} # also for mungeConfigs.createConfigsBySecret # 將創建出的JWT Key掛載到Slurm中,與上文配置文件中的路徑對應 volumes: - configMap: defaultMode: 444 name: jwt name: config-jwt volumeMounts: - mountPath: /var/jwt name: config-jwt slurmrestdConfigs: nodeSelector: {} tolerations: [] affinity: {} resources: {} image: "registry-cn-hangzhou.ack.aliyuncs.com/acs/slurm:23.06-1.6-aliyun-49259f59" imagePullSecrets: [] # if .slurmConfigs.createConfigsByConfigMap is true, slurmConfPath and volume and volumeMounts will be auto set as: # volumeMounts: # - name: config-{{ .Values.slurmConfigs.configMapName }} # mountPath: {{ .Values.slurmConfigs.slurmConfigPathInPod }} # volumes: # - name: config-{{ .Values.slurmConfigs.configMapName }} # configMap: # name: {{ .Values.slurmConfigs.configMapName }} # also for mungeConfigs.createConfigsBySecret # 將創建出的JWT Key掛載到Slurm中,與上文配置文件中的路徑對應 volumes: - configMap: defaultMode: 444 name: jwt name: config-jwt volumeMounts: - mountPath: /var/jwt name: config-jwt
使用Helm安裝Chart,執行以下命令將會部署ack-slurm-cluster組件。(如果已經安裝了ack-slurm-cluster,可以使用helm upgrade命令更新helm chart。更新后需要手動清理已有Pod以及Slurmctld的StatefulSet完成配置更新。)
cd .. helm install my-slurm-cluster ack-slurm-cluster # my-slurm-cluster可以根據實際情況進行更改。
通過Helm安裝之后可以通過Helm list查看當前的ack-slurm-cluster是否完成安裝。
Helm list
預期輸出結果如下。
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION ack-slurm-cluster default 1 2024-07-19 14:47:58.126357 +0800 CST deployed ack-slurm-cluster-2.0.0 2.0.0
驗證Slurmrestd & Slurmdbd正常啟動
通過kubectl連接集群查看slurmdbd Pod是否正常啟動。
kubectl get pod
預期輸出結果如下,可以看到此時集群中有1個Worker節點和3個控制面組件的Pod。
NAME READY STATUS RESTARTS AGE slurm-test-slurmctld-dlncz 1/1 Running 0 3h49m slurm-test-slurmdbd-8f75r 1/1 Running 0 3h49m slurm-test-slurmrestd-mjdzt 1/1 Running 0 3h49m slurm-test-worker-cpu-0 1/1 Running 0 166m
執行以下命令查看日志信息,了解Slurmdbd是否已經正常啟動。
kubectl exec slurm-test-slurmdbd-8f75r cat /var/log/slurmdbd.log | head
預期輸出結果如下。
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. [2024-07-22T19:52:55.727] accounting_storage/as_mysql: _check_mysql_concat_is_sane: MySQL server version is: 8.0.34 [2024-07-22T19:52:55.737] error: Database settings not recommended values: innodb_lock_wait_timeout [2024-07-22T19:52:56.089] slurmdbd version 23.02.7 started
如果您需要在Slurm中擴展安裝其他依賴軟件,您可以展開查看下述內容。
準備Slurm鏡像(registry-cn-beijing.ack.aliyuncs.com/acs/slurm:23.06-1.6-aliyun-49259f59中已內置了完成本文檔示例的所有需要軟件包),您可使用以下Dockerfile示例,也可以自主添加環境依賴,但需要注意以下插件是否配備,以下插件以及Dockerfile源碼均可以在阿里云開源倉庫找到。
是否包含kubectl、node_features/k8s_resources。
是否包含job_submit/k8s_resource_completion插件(可選),使用自動填充gres資源時需要。
默認情況下,Slurmctld會在Slurmd發送
_slurm_rpc_node_registration
請求時檢查節點的Gres資源使用情況,并在發現Gres資源發生變化時認為節點錯誤,將節點標記為INVAL狀態。INVAL狀態的節點無法調度新的任務,需要重新加入集群,影響集群的正常使用。k8s_resources插件在節點的ActivateFeature被更新時將k8s cpu以及k8s memory資源置為0,并將二者的node_feature標志位設置為true,從而跳過節點的Gres資源檢查,保障了集群資源的正常使用。
FROM nvidia/cuda:11.4.3-cudnn8-devel-ubuntu20.04 as exporterBuilder
ENV TZ=Asia/Shanghai
ENV DEBIAN_FRONTEND=noninteractive
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt-get update && apt install -y golang git munge libhttp-parser-dev libjson-c-dev libyaml-dev libjwt-dev libgtk2.0-dev libreadline-dev libpmix-dev libmysqlclient-dev libhwloc-dev openmpi-bin openmpi-common libopenmpi-dev rpm libmunge-dev libmunge2 libpam-dev perl python3 systemd lua5.3 libnvidia-ml-dev libhdf5-dev
# Download the source code before building the image
COPY ./slurm-23.02.7.tar.bz2 ./slurm-23.02.7.tar.bz2
RUN tar -xaf slurm-23.02.7.tar.bz2
COPY ../node_features/k8s_resources ./slurm-23.02.7/src/plugins/node_features/k8s_resources
RUN sed -i '/"src\/plugins\/node_features\/Makefile") CONFIG_FILES="\$CONFIG_FILES src\/plugins\/node_features\/Makefile" ;;/ a " src/plugins/node_features/k8s_resources/Makefile") CONFIG_FILES="\$CONFIG_FILES src/plugins/node_features/k8s_resources/Makefile" ;;' ./slurm-23.02.7/configure
RUN awk '/^ac_config_files="\$ac_config_files/ && !found { print; print "ac_config_files=\"$ac_config_files src/plugins/node_features/k8s_resources/Makefile\""; found=1; next } { print }' ./slurm-23.02.7/configure > ./slurm-23.02.7/configure.new && mv ./slurm-23.02.7/configure.new ./slurm-23.02.7/configure && chmod +x ./slurm-23.02.7/configure
RUN cat ./slurm-23.02.7/configure
RUN sed -i '/^SUBDIRS =/ s/$/ k8s_resources/' ./slurm-23.02.7/src/plugins/node_features/Makefile & \
sed -i '/^SUBDIRS =/ s/$/ k8s_resources/' ./slurm-23.02.7/src/plugins/node_features/Makefile.in & \
sed -i '/^SUBDIRS =/ s/$/ k8s_resources/' ./slurm-23.02.7/src/plugins/node_features/Makefile.am
RUN cd slurm-23.02.7 && ./configure --prefix=/usr/ --sysconfdir=/etc/slurm && make
FROM nvidia/cuda:11.4.3-cudnn8-runtime-ubuntu20.04
ENV TZ=Asia/Shanghai
ENV DEBIAN_FRONTEND=noninteractive
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt update
RUN apt install -y munge libhttp-parser-dev libjson-c-dev libyaml-dev libjwt-dev libgtk2.0-dev libreadline-dev libpmix-dev libmysqlclient-dev libhwloc-dev openmpi-bin openmpi-common libopenmpi-dev rpm libmunge-dev libmunge2 libpam-dev perl python3 systemd lua5.3 inotify-tools openssh-server pip libnvidia-ml-dev libhdf5-dev
COPY --from=0 /slurm-23.02.7 /slurm-23.02.7
RUN cd slurm-23.02.7 && make install && cd ../ && rm -rf /slurm-23.02.7
RUN apt remove libnvidia-ml-dev libnvidia-compute-545 -y; apt autoremove -y ; ln -s /usr/lib/x86_64-linux-gnu/libnvidia-ml.so.1 /usr/lib/x86_64-linux-gnu/libnvidia-ml.so
COPY ./sh ./
RUN mkdir /etc/slurm
RUN chmod +x create-users.sh munge-inisitalization.sh slurm-initialization.sh slurm-suspend.sh slurm-resume.sh slurmd slurmctld slurmdbd slurmrestd
RUN touch /var/log/slurm-resume.log /var/log/slurm-suspend.log ; chmod 777 /var/log/slurm-resume.log /var/log/slurm-suspend.log
RUN mv slurmd /etc/init.d/slurmd && mv slurmdbd /etc/init.d/slurmdbd && mv slurmctld /etc/init.d/slurmctld
RUN ./create-users.sh && ./munge-inisitalization.sh && ./slurm-initialization.sh
RUN rm ./create-users.sh ./munge-inisitalization.sh ./slurm-initialization.sh
ENV NVIDIA_VISIBLE_DEVICES=
RUN apt-get update && apt-get upgrade -y && rm -rf /var/cache/apt/
2. 驗證拓展負載混合調度功能
2.1 驗證負載混合調度功能
查看genericnode狀態,可以看到Slurm與Kubernetes的負載狀態。
kubectl get genericnode
預期輸出結果如下。
NAME CLUSTERNAME ALIAS TYPE ALLOCATEDRESOURCES cn-hongkong.10.1.0.19 slurm-test-worker-cpu-0 Slurm [{"allocated":{"cpu":"0","memory":"0"},"type":"Slurm"},{"allocated":{"cpu":"1735m","memory":"2393Mi"},"type":"Kubernetes"}]
提交一個任務到Slurm集群中,相關命令和預期輸出結果如下,可以看到Kubernetes的任務和Slurm的任務資源使用量都反映在了GenericNode上。
root@iZj6c1wf3c25dbynbna3qgZ ~]# kubectl exec slurm-test-slurmctld-dlncz -- nohup srun --cpus-per-task=3 --mem=4000 --gres=k8scpu:3,k8smemory:4000 sleep inf & [1] 4132674 [root@iZj6c1wf3c25dbynbna3qgZ ~]# kubectl scale deployment nginx-deployment-basic --replicas 2 deployment.apps/nginx-deployment-basic scaled [root@iZj6c1wf3c25dbynbna3qgZ ~]# kubectl get genericnode NAME CLUSTERNAME ALIAS TYPE ALLOCATEDRESOURCES cn-hongkong.10.1.0.19 slurm-test-worker-cpu-0 Slurm [{"allocated":{"cpu":"3","memory":"4000Mi"},"type":"Slurm"},{"allocated":{"cpu":"2735m","memory":"3417Mi"},"type":"Kubernetes"}]
此時再提交一個任務到Slurm集群中,可以看到第2個提交的任務進入了PD(Pending)狀態。
[root@iZj6c1wf3c25dbynbna3qgZ ~]# kubectl exec slurm-test-slurmctld-dlncz -- nohup srun --cpus-per-task=3 --mem=4000 sleep inf & [2] 4133454 [root@iZj6c1wf3c25dbynbna3qgZ ~]# srun: job 2 queued and waiting for resources [root@iZj6c1wf3c25dbynbna3qgZ ~]# kubectl exec slurm-test-slurmctld-dlncz -- squeue JOBID PARTITION NAME USER ST TIME NODES NODELIST(REASON) 2 debug sleep root PD 0:00 1 (Resources) 1 debug sleep root R 2:34 1 slurm-test-worker-cpu-0
在上述的srun的示例中,我們并沒有指定gres擴展資源,是由于在slurm集群啟動時已經加載了job_resource_completion插件,該插件會自動根據cpu和mem的請求量生成對應的gres資源量。如果沒有開啟該插件,您需要手動指定,本例中需要指定的額外參數為--gres=k8scpu:3,k8smemory:4000。如果您需要了解關于Slurm任務腳本參數是如何設置的,您可以展開查看下述內容。
提交Slurm任務時需要在Job上計算gres相關資源請求數量,以下是srun、sbatch提交任務時可使用的參數,以及Slurm任務資源計算示例。
參數名 | 說明 |
--tres-per-task | 指定任務中的每個task需要的tres資源。 |
--gres | 指定整個任務需要的gres資源。 |
計算單個任務所需的gres資源量時,需要計算任務在單個節點上需要的CPU數量以及內存數量,這兩種資源可以通過以下方法進行計算。
計算總共需要的CPU數量。
在使用Slurm做作業調度時,合理計算一個任務在單個節點上所需的總CPU核心數量,是確保資源分配和作業調度效率的關鍵。
計算基于作業的以下幾個參數:
Nodes:作業需要的計算節點數量。
Tasks per Node:每個節點所需的任務數量。
CPUs per Task:每個任務所需的CPU核心數量。
這些參數可以在Slurm腳本或命令行中通過對應的選項指定。
計算公式
總的CPU核心數量可以通過以下公式計算:節點總CPU核心數 = (Tasks per Node) * (CPUs per Task)
示例
假設你有以下參數:
Nodes: 2
Tasks per Node: 4
CPUs per Task: 2
那么總的CPU核心數量通過以下公式計算:節點總CPU核心數 = 4 任務/節點 * 2CPU/任務 = 16核心。
計算總共需要的內存數量。
在使用Slurm作業調度時,計算任務在每個節點上所需的總內存量,是確保資源分配合理,避免資源浪費或任務因內存不足等問題的關鍵。和CPU核數量的計算相似,總內存量的計算也基于每個節點的任務數量以及每個任務需要的內存量等參數。
節點需要的總內存量可以通過以下公式計算:節點總內存量 = Tasks per Node × Cores per Task × Memory per Core。
提交任務時自動填充gres。
提交任務時手動添加--gres時,需要您自行計算任務在每個節點上需要的cpu以及mem的資源量,且不能阻止惡意用戶提交不帶有--gres申明的任務。
您可以通過擴展slurm的job_submit插件來實現自動填充--gres。我們提供了一個相關的代碼示例。通過該示例編譯出的插件,提交Job時可以正常提交,但是提交Job時必須使用-n或--ntasks指定Task數量,否則任務會提交失敗。此外,不支持通過--gpus以及--gpus-per-socket的方式聲明總GPU數,否則任務會提交失敗,需要使用--gpus-per-task等方式申請GPU資源。
Slurm任務腳本示例
#!/bin/bash
#SBATCH --job-name=test_job # 任務名字
#SBATCH --nodes=2 # 需要的節點數量
#SBATCH --ntasks-per-node=4 # 每個節點的任務數量
#SBATCH --cpus-per-task=2 # 每個任務的CPU核心數量
#SBATCH --time=01:00:00 # 任務運行的最長時間
#SBATCH --output=job_output_%j.txt # 標準輸出文件名稱
#SBATCH --error=job_error_%j.txt # 錯誤輸出文件名稱
# 用戶的作業命令
srun my_program
您也可以在命令行中指定這些參數。
sbatch --nodes=2 --ntasks-per-node=4 --cpus-per-task=2 --time=01:00:00 --job-name=test_job my_job_script.sh
Slurm命令中的參數解釋:
--nodes
(-N
): 指定需要分配的節點數量。--ntasks-per-node
(--tasks-per-node
): 指定每個節點運行的任務數量。--cpus-per-task
: 指定每個任務需要的 CPU 核心數量。--time
(-t
): 指定作業運行的最長時間。--job-name
(-J
): 指定作業的名稱。
(可選)2.2 拓展混合調度功能-非容器化Slurm集群
由于SlurmCopilot通過Slurm的OpenAPI與Slurm進行交互,所以在非容器化場景中,SlurmCopilot同樣可以使用。
針對非容器化場景,Kubernetes中的部分資源需要手動進行創建,除上文中可能需要手動創建的Token之外,需要手動創建的資源如下。
為每個SlurmCluster創建SVC。
SlurmCopilot會從集群中獲取Service信息,并向
${.metadata.name}.${.metadata.namespace}.svc.cluster.local:${.spec.ports[0].port}
發出OpenAPI請求,在非容器化場景中,需要為每個SlurmCluster創建對應的SVC,示例如下,必須要注意的是SVC的Name必須是${slurmCluster}-slurmrestd,該${slurmCluster}需要能夠與GenericNode中相對應。apiVersion: v1 kind: Service metadata: name: slurm-slurmrestd namespace: default spec: ports: - name: slurmrestd port: 8080 protocol: TCP targetPort: 8080
為每個SlurmCluster創建DNS解析。
為了能訪問到對應的Slurmrestd進程,需要在SlurmCopilot中創建對
${.metadata.name}.${.metadata.namespace}.svc.cluster.local:${.spec.ports[0].port}
的地址解析,解析結果為Slurmrestd的進程地址。Slurm節點對應的GenericNode資源。
GenericNode用于給SlurmCopilot提供節點在Slurm集群內的別名,否則SlurmCopilot將無法獲取到Slurm中該節點的具體信息。其中GenericNode的Name必須與Kubernetes的節點名對應,
.spec.alias
必須與Slurm中該節點的命名對應,而標簽中的kai.alibabacloud.com/cluster-name
以及kai.alibabacloud.com/cluster-namespace
需要與SVC的信息對應。apiVersion: kai.alibabacloud.com/v1alpha1 kind: GenericNode metadata: labels: kai.alibabacloud.com/cluster-name: slurm-test kai.alibabacloud.com/cluster-namespace: default name: cn-hongkong.10.1.0.19 spec: alias: slurm-test-worker-cpu-0 type: Slurm
總結
在Slurm HPC和容器化工作負載的混合調度環境中,使用Slurm作為HPC調度程序和Kubernetes作為容器編排工具,您可以利用Kubernetes的大量生態系統和服務,例如Helm Charts、CI/CD流水線、監控工具,以及相同的作業調度和管理界面提交HPC作業和容器化工作負載。實現將HPC作業和Kubernetes容器工作負載整合到同一個集群中,更有效地利用硬件資源。