ACK集群上实现Slurm HPC & Kubernetes负载混合调度

本文介绍了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负载混合调

imageimage

Slurm HPC + Kubernetes负载混合调度方案的运行原理如下图所示。

yuque_diagram (2)

核心组件

描述

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在同一批物理机器上混合部署。

  1. 登录容器服务管理控制台。单击目标集群名称,进入集群详情页面,如下图所示,按照序号依次单击,为目标集群安装ack-slurm-operator组件。

    您无需为组件配置应用名命名空间,单击④下一步后会出现一个请确认弹框,单击,即可使用默认的应用名(ack-slurm-operator)和命名空间(ack-slurm-operator)。

    image

  2. 然后选择Chart 版本为最新版本,并将参数enableCopilot设置为true,并设置watchNamespacedefault(您也可以根据需要自主设置命名空间),单击确定即可完成ack-slurm-operator组件安装。image

  3. (可选)更新ack-slurm-operator组件操作步骤。

    登录容器服务管理控制台。在集群信息页面,单击应用 > Helm页签,在应用页面找到ack-slurm-operator组件,然后点击更新。image

1.2 安装配置ack-slurm-cluster组件

如需快速安装、管理SlurmCluster,以及灵活调整集群配置,您可以使用Helm软件包管理器来部署阿里云提供的SlurmClusterart。从charts-incubator中下载由阿里云包装好的SlurmCluster的Helm,设置好相应的参数后,Helm会帮助您创建出RBAC、Configmap、Secret以及SlurmCuster等资源。

展开查看Helm Chart资源及参数说明

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

nodeSelector:
  example: example

渲染为Slurmctld的Pod的NodeSelector。

headNodeConfig.tolerations

slurmdbdConfigs.tolerations

slurmrestdConfigs.tolerations

workerNodesConfig.workerGroups[].tolerations

tolerations:
- key:
  value:
  operator:

渲染为Slurmctld的Pod的Toleration。

headNodeConfig.affinity

slurmdbdConfigs.affinity

slurmrestdConfigs.affinity

workerNodesConfig.workerGroups[].affinity

affinity:
  nodeAffinity:
    requiredDuringSchedulingIgnoredDuringExecution:
      nodeSelectorTerms:
      - matchExpressions:
        - key: topology.kubernetes.io/zone
          operator: In
          values:
          - zone-a
    preferredDuringSchedulingIgnoredDuringExecution:
    - weight: 1
      preference:
        matchExpressions:
        - key: another-node-label-key
          operator: In
          values:
          - another-node-label-value

渲染为Slurmctld的Pod的Affinity。

headNodeConfig.resources

slurmdbdConfigs.resources

slurmrestdConfigs.resources

workerNodesConfig.workerGroups[].resources

resources:
  requests:
    cpu: 1
  limits:
    cpu: 1

渲染为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

imagePullSecrets:
- name: example

渲染为Slurmctld的镜像拉取密钥。

headNodeConfig.podSecurityContext

slurmdbdConfigs.podSecurityContext

slurmrestdConfigs.podSecurityContext

workerNodesConfig.workerGroups[].podSecurityContext

podSecurityContext:
  runAsUser: 1000
  runAsGroup: 3000
  fsGroup: 2000
  supplementalGroups: [4000]

渲染为Slurmctld的SecurityContext。

headNodeConfig.securityContext

slurmdbdConfigs.securityContext

slurmrestdConfigs.securityContext

workerNodesConfig.workerGroups[].securityContext

securityContext:
  allowPrivilegeEscalation: false

渲染为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应用新的配置文件,故请提前确认配置文件中的内容。

具体操作如下所示:

  1. 执行以下命令,将阿里云Helm仓库添加到您的Helm客户端。该操作将允许您访问阿里云提供的各种Charts,包括ack-slurm-cluster组件。

    helm repo add aliyun https://aliacs-app-catalog.oss-cn-hangzhou.aliyuncs.com/charts-incubator/
  2. 执行以下命令,拉取并解压ack-slurm-cluster组件。该操作将会在当前目录下创建一个名为ack-slurm-cluster的目录,其中包含了Chart的所有文件和模板。

    helm pull aliyun/ack-slurm-cluster --untar=true
  3. 执行以下命令,在名为values.yaml的文件中修改Chart参数。

    values.yaml文件包含了Chart的默认配置。您可以根据您的实际需求通过编辑这个文件来修改参数。例如Slurm的配置、资源请求与限制、存储等。

    cd ack-slurm-cluster
    vi values.yaml

    展开查看如何生成JWT并提交到集群中

    生成JWT插件需要的key,并将生成的key通过以下命令导入集群中。具体操作,请参见JWT认证插件

    1. 获取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一致

    2. 将获取的JWK导入集群中。

      kubectl create configmap jwt --from-literal=jwt_hs256.key={{ .jwtkey }}

    展开查看如何声明数据库地址和配置gres

    开启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相关配置

    设置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
  4. 使用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可以根据实际情况进行更改。
  5. 通过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      
  6. 验证Slurmrestd & Slurmdbd正常启动

    1. 通过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
    2. 执行以下命令查看日志信息,了解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通用镜像步骤

准备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资源检查,保障了集群资源的正常使用。

展开查看示例Dockerfile

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 验证负载混合调度功能

  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"}]
  2. 提交一个任务到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"}]
  3. 此时再提交一个任务到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任务脚本示例解析

提交Slurm任务时需要在Job上计算gres相关资源请求数量,以下是srun、sbatch提交任务时可使用的参数,以及Slurm任务资源计算示例。

参数名

说明

--tres-per-task

指定任务中的每个task需要的tres资源。

--gres

指定整个任务需要的gres资源。

展开查看Slurm任务资源计算示例

计算单个任务所需的gres资源量时,需要计算任务在单个节点上需要的CPU数量以及内存数量,这两种资源可以通过以下方法进行计算。

  1. 计算总共需要的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核心。

  2. 计算总共需要的内存数量。

    在使用Slurm作业调度时,计算任务在每个节点上所需的总内存量,是确保资源分配合理,避免资源浪费或任务因内存不足等问题的关键。和CPU核数量的计算相似,总内存量的计算也基于每个节点的任务数量以及每个任务需要的内存量等参数。

    节点需要的总内存量可以通过以下公式计算:节点总内存量 = Tasks per Node × Cores per Task × Memory per Core。

  3. 提交任务时自动填充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之外,需要手动创建的资源如下。

  1. 为每个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
  2. 为每个SlurmCluster创建DNS解析。

    为了能访问到对应的Slurmrestd进程,需要在SlurmCopilot中创建对${.metadata.name}.${.metadata.namespace}.svc.cluster.local:${.spec.ports[0].port}的地址解析,解析结果为Slurmrestd的进程地址。

  3. 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容器工作负载整合到同一个集群中,更有效地利用硬件资源。