使用动态工作负载调度器为批量工作负载部署 GPU


本页面介绍如何通过动态工作负载调度器优化 GPU 可获取性。我们建议将动态工作负载调度器用于可在非高峰时段运行且具有已定义的 GPU 容量管理条件的大规模批量工作负载。这些工作负载可能是深度学习模型训练,或者是需要大量具有原子预配模型的 GPU 的模拟,这意味着所有资源都是同时创建。

如需在不使用动态工作负载调度器的情况下在 Google Kubernetes Engine (GKE) 中运行 GPU 工作负载,请参阅在 GKE Standard 节点池中运行 GPU

何时使用动态工作负载调度器

如果您的工作负载满足以下所有条件,我们建议您使用动态工作负载调度器:

  • 您可以请求 GPU 来运行工作负载。
  • 您的预留 GPU 容量有限或完全没有,并且您希望提高 GPU 资源的可获取性。
  • 您的工作负载在时间上非常灵活,您的用例可以承受等待获取所有请求的容量,例如,GKE 在最繁忙时段以外分配 GPU 资源时。
  • 您的工作负载需要多个节点,并且只有在所有 GPU 节点均已预配并同时准备就绪之后(例如分布式机器学习训练)才能开始运行。

准备工作

在开始之前,请确保您已执行以下任务:

  • 启用 Google Kubernetes Engine API。
  • 启用 Google Kubernetes Engine API
  • 如果您要使用 Google Cloud CLI 执行此任务,请安装初始化 gcloud CLI。 如果您之前安装了 gcloud CLI,请运行 gcloud components update 以获取最新版本。

将节点池与动态工作负载调度器搭配使用

您可以使用以下三种方法中的任意一种来指定动态工作负载调度器可以使用集群中的特定节点池:

创建节点池

使用 gcloud CLI 创建启用了动态工作负载调度器的节点池:

gcloud beta container node-pools create NODEPOOL_NAME \
    --cluster=CLUSTER_NAME \
    --location=LOCATION \
     --enable-queued-provisioning \
    --accelerator type=GPU_TYPE,count=AMOUNT,gpu-driver-version=DRIVER_VERSION \
    --machine-type=MACHINE_TYPE \
    --enable-autoscaling  \
    --num-nodes=0   \
    --total-max-nodes TOTAL_MAX_NODES  \
    --location-policy=ANY  \
    --reservation-affinity=none  \
    --no-enable-autorepair

请替换以下内容:

  • NODEPOOL_NAME:您为节点池选择的名称。
  • CLUSTER_NAME:集群的名称。
  • LOCATION:集群的 Compute Engine 区域,例如 us-central1
  • GPU_TYPEGPU 类型
  • AMOUNT:要挂接到节点池中节点的 GPU 数量。
  • DRIVER_VERSION:要安装的 NVIDIA 驱动程序版本。可以是以下各项之一:
    • default:为您的 GKE 版本安装默认驱动程序版本。
    • latest:为您的 GKE 版本安装最新可用的驱动程序版本。仅适用于使用 Container-Optimized OS 的节点。
  • TOTAL_MAX_NODES:整个节点池自动扩缩的节点数上限。
  • MACHINE_TYPE:节点的 Compute Engine 机器类型。我们建议您选择加速器优化机器类型

您可以视需要使用以下标志:

  • --no-enable-autoupgrade:推荐。停用节点自动升级。只有未在发布渠道中注册的 GKE 集群支持此功能。如需了解详情,请参阅为现有节点池停用节点自动升级
  • --node-locations=COMPUTE_ZONES:GKE 在其中创建 GPU 节点的一个或多个可用区的英文逗号分隔列表。这些可用区必须与集群位于同一区域。选择具有可用 GPU 的可用区。
  • --enable-gvnic:此标志可启用 GPU 节点池上的 gVNIC 以提高网络流量速度。

此命令会创建一个具有以下配置的节点池:

  • GKE 会启用排队的预配和集群自动扩缩。
  • 节点池最初没有节点。
  • --enable-queued-provisioning 标志会启用动态工作负载调度器,并将 cloud.google.com/gke-queued 污点添加到节点池。
  • --no-enable-autorepair--no-enable-autoupgrade 标志会停用节点的自动修复和升级功能,这可能会中断在已修复或升级的节点上运行的工作负载。您只能在未在发布渠道中注册的集群上停用节点自动升级。

更新现有节点池并启用动态工作负载调度器

为现有节点池启用动态工作负载调度器。查看正确配置节点池的前提条件。

前提条件

  • 确保使用 --reservation-affinity=none 标志创建节点池。此标志稍后在启用动态工作负载调度器时需要,因为在创建节点池后便无法更改预留亲和性。

  • 请确保至少维护一个未启用动态工作负载调度器处理的节点池,以使集群正常运行。

  • 确保节点池为空。您可以调整节点池的大小,使其没有节点。

  • 确保已启用并正确配置自动扩缩

  • 确保已停用自动修复

为现有节点池启用动态工作负载调度器

您可以使用 gcloud CLI 为现有节点池启用动态工作负载调度器:

gcloud beta container node-pools update NODEPOOL_NAME \
    --cluster=CLUSTER_NAME \
    --location=LOCATION \
     --enable-queued-provisioning

请替换以下内容:

  • NODEPOOL_NAME:所选节点池的名称。
  • CLUSTER_NAME:集群的名称。
  • LOCATION:集群的 Compute Engine 区域,例如 us-central1

此节点池更新命令会导致以下配置更改:

  • --enable-queued-provisioning 标志会启用动态工作负载调度器,并将 cloud.google.com/gke-queued 污点添加到节点池。

(可选)您还可以更新以下节点池设置:

  • 停用节点自动升级:我们建议您停用节点自动升级,因为使用动态工作负载调度器时不支持节点池升级。如需停用节点自动升级,请确保您的 GKE 集群未在发布渠道中注册。
  • 在 GPU 节点池上启用 gVNIC:Google 虚拟 NIC (gVNIC) 可提高 GPU 节点的网络流量速度。

启用节点自动预配功能,以便为动态工作负载调度器创建节点池

您可以使用节点自动预配功能来管理运行 1.29.2-gke.1553000 版或更高版本的集群的动态工作负载调度器的节点池。启用节点自动预配并启用动态工作负载调度器后,GKE 会创建包含关联工作负载所需资源的节点池。

如需启用节点自动预配功能,请考虑以下设置并完成配置 GPU 限制中的步骤:

  • 在启用该功能时指定动态工作负载调度器所需的资源。如需列出可用的 resourceTypes,请运行 gcloud compute accelerator-types list
  • 我们建议您使用 --no-enable-autoprovisioning-autoupgrade--no-enable-autoprovisioning-autorepair 标志停用节点自动升级和节点自动修复功能。如需了解详情,请参阅为使用动态工作负载调度器的工作负载的节点池配置中断设置

使用动态工作负载调度器运行批处理工作负载

如需使用动态工作负载调度器,我们建议您使用 Kueue。Kueue 会根据配额和层次结构,在团队之间共享资源,从而实现 Job 排队,确定 Job 应等待的时间和应开始的时间。这简化了使用排入队列的虚拟机所需的设置。

当您使用自己的内部批量调度工具或平台时,可以在不使用 Kueue 的情况下使用动态工作负载调度器。如需为不使用 Kueue 的作业配置动态工作负载调度器,请参阅不使用 Kueue 的作业的动态工作负载调度器

使用 Kueue 的作业动态工作负载调度器

以下部分介绍了如何为使用 Kueue 的作业配置动态工作负载调度器。本部分使用 dws-examples 仓库中的示例。我们已根据 Apache2 许可在 dws-examples 仓库中发布示例。

准备环境

  1. 在 Cloud Shell 中,运行以下命令:

    git clone https://github.com/GoogleCloudPlatform/ai-on-gke
    cd ai-on-gke/tutorials-and-examples/workflow-orchestration/dws-examples
    
  2. 在集群中安装 Kueue 和必要的配置以启用预配请求集成:

    kubectl apply --server-side -f ./kueue-manifests.yaml
    

如需详细了解 Kueue 安装,请参阅安装

创建 Kueue 资源

使用以下清单,创建一个名为 dws-cluster-queue集群级队列和名为 dws-local-queueLocalQueue 命名空间。在此命名空间中引用 dws-cluster-queue 队列的作业使用动态工作负载调度器来获取 GPU 资源。

apiVersion: kueue.x-k8s.io/v1beta1
kind: ResourceFlavor
metadata:
  name: "default-flavor"
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: AdmissionCheck
metadata:
  name: dws-prov
spec:
  controllerName: kueue.x-k8s.io/provisioning-request
  parameters:
    apiGroup: kueue.x-k8s.io
    kind: ProvisioningRequestConfig
    name: dws-config
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ProvisioningRequestConfig
metadata:
  name: dws-config
spec:
  provisioningClassName: queued-provisioning.gke.io
  managedResources:
  - nvidia.com/gpu
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: ClusterQueue
metadata:
  name: "dws-cluster-queue"
spec:
  namespaceSelector: {} 
  resourceGroups:
  - coveredResources: ["cpu", "memory", "nvidia.com/gpu"]
    flavors:
    - name: "default-flavor"
      resources:
      - name: "cpu"
        nominalQuota: 10000  # Infinite quota.
      - name: "memory"
        nominalQuota: 10000Gi # Infinite quota.
      - name: "nvidia.com/gpu"
        nominalQuota: 10000  # Infinite quota.
  admissionChecks:
  - dws-prov
---
apiVersion: kueue.x-k8s.io/v1beta1
kind: LocalQueue
metadata:
  namespace: "default"
  name: "dws-local-queue"
spec:
  clusterQueue: "dws-cluster-queue"
---

部署 LocalQueue:

kubectl create -f ./dws-queues.yaml

输出类似于以下内容:

resourceflavor.kueue.x-k8s.io/default-flavor created
admissioncheck.kueue.x-k8s.io/dws-prov created
provisioningrequestconfig.kueue.x-k8s.io/dws-config created
clusterqueue.kueue.x-k8s.io/dws-cluster-queue created
localqueue.kueue.x-k8s.io/dws-local-queue created

如果您要运行在其他命名空间中使用动态工作负载调度器的 Job,您可以使用上述模板创建其他 LocalQueues

运行 Job

在以下清单中,示例作业使用动态工作负载调度器:

apiVersion: batch/v1
kind: Job
metadata:
  name: sample-job
  namespace: default
  labels:
    kueue.x-k8s.io/queue-name: dws-local-queue
  annotations:
    provreq.kueue.x-k8s.io/maxRunDurationSeconds: "600"
spec:
  parallelism: 1
  completions: 1
  suspend: true
  template:
    spec:
      nodeSelector:
        cloud.google.com/gke-nodepool: NODEPOOL_NAME
      tolerations:
      - key: "nvidia.com/gpu"
        operator: "Exists"
        effect: "NoSchedule"
      containers:
      - name: dummy-job
        image: gcr.io/k8s-staging-perf-tests/sleep:v0.0.3
        args: ["120s"]
        resources:
          requests:
            cpu: "100m"
            memory: "100Mi"
            nvidia.com/gpu: 1
          limits:
            cpu: "100m"
            memory: "100Mi"
            nvidia.com/gpu: 1
      restartPolicy: Never

此清单包含以下与动态工作负载调度器配置相关的字段:

  • kueue.x-k8s.io/queue-name: dws-local-queue 标签指示 GKE 由 Kueue 负责编排该 Job。此标签还定义了 Job 排入的队列。
  • suspend: true 标志指示 GKE 创建 Job 资源,但尚不调度 Pod。在节点准备好执行 Job 时,Kueue 会将此标志更改为 false
  • nodeSelector 指示 GKE 仅在指定的节点池上调度作业。该值应与 NODEPOOL_NAME(启用了队列预配的节点池的名称)匹配。
  1. 运行 Job:

    kubectl create -f ./job.yaml
    

    输出类似于以下内容:

    job.batch/sample-job created
    
  2. 检查作业的状态:

    kubectl describe job sample-job
    

    输出类似于以下内容:

    Events:
      Type    Reason            Age    From                        Message
      ----    ------            ----   ----                        -------
      Normal  Suspended         5m17s  job-controller              Job suspended
      Normal  CreatedWorkload   5m17s  batch/job-kueue-controller  Created Workload: default/job-sample-job-7f173
      Normal  Started           3m27s  batch/job-kueue-controller  Admitted by clusterQueue dws-cluster-queue
      Normal  SuccessfulCreate  3m27s  job-controller              Created pod: sample-job-9qsfd
      Normal  Resumed           3m27s  job-controller              Job resumed
      Normal  Completed         12s    job-controller              Job completed
    

动态工作负载调度器与 Kueue 的集成还支持开源生态系统中提供的其他工作负载类型,如下所示:

  • RayJob
  • JobSet
  • Kubeflow MPIJob、TFJob、PyTorchJob.
  • 工作流编排器常用的 Kubernetes Pod
  • Flux 迷你集群

如需详细了解此支持,请参阅 Kueue 的批量用户

不使用 Kueue 的作业的动态工作负载调度器

通过 ProvisioningRequest API 为每个作业创建请求。动态工作负载调度器不会启动 Pod,只会预配节点。

  1. 创建以下 provisioning-request.yaml 清单:

    apiVersion: v10
    kind: PodTemplate
    metadata:
      name: POD_TEMPLATE_NAME
      namespace: NAMESPACE_NAME
    template:
      spec:
        nodeSelector:
            cloud.google.com/gke-nodepool: NODEPOOL_NAME
        tolerations:
            - key: "nvidia.com/gpu"
              operator: "Exists"
              effect: "NoSchedule"
        containers:
            - name: pi
              image: perl
              command: ["/bin/sh"]
              resources:
                limits:
                  cpu: "700m"
                  nvidia.com/gpu: 1
                requests:
                  cpu: "700m"
                  nvidia.com/gpu: 1
        restartPolicy: Never
    ---
    apiVersion: autoscaling.x-k8s.io/v1beta1
    kind: ProvisioningRequest
    metadata:
      name: PROVISIONING_REQUEST_NAME
      namespace: NAMESPACE_NAME
    spec:
      provisioningClassName: queued-provisioning.gke.io
      parameters:
        maxRunDurationSeconds: "MAX_RUN_DURATION_SECONDS"
      podSets:
      - count: COUNT
        podTemplateRef:
          name: POD_TEMPLATE_NAME
    

    替换以下内容:

    • NAMESPACE_NAME:Kubernetes 命名空间的名称。命名空间必须与 Pod 的命名空间相同。
    • PROVISIONING_REQUEST_NAMEProvisioningRequest 的名称。您将在 Pod 注解中引用此名称。
    • MAX_RUN_DURATION_SECONDS:(可选)节点的最长运行时(以秒为单位),最长为 7 天(默认值)。如需了解详情,请参阅动态工作负载调度器的工作原理。创建请求后,您将无法更改此值。此字段在 GKE 1.28.5-gke.1355000 版或更高版本中为预览版
    • COUNT:请求的 Pod 数量。节点以原子方式调度到一个可用区中。
    • POD_TEMPLATE_NAME:Kubernetes 的标准名称。GKE 在预配请求 PodSet 中引用此值。
    • NODEPOOL_NAME:您为节点池选择的名称。
  2. 应用清单:

    kubectl apply -f provisioning-request.yaml
    

配置 Pod

作业规范中,使用以下注解将 Pod 关联到 ProvisioningRequest

apiVersion: batch/v1
kind: Job
spec:
  template:
    metadata:
      annotations:
        cluster-autoscaler.kubernetes.io/consume-provisioning-request: PROVISIONING_REQUEST_NAME
        cluster-autoscaler.kubernetes.io/provisioning-class-name: "queued-provisioning.gke.io"
    spec:
      ...

Pod 注解键 cluster-autoscaler.kubernetes.io/consume-provisioning-request 定义要使用的 ProvisioningRequest。GKE 使用 consume-provisioning-requestprovisioning-class-name 注解执行以下操作:

  • 仅在动态工作负载调度器预配的节点中调度 Pod。
  • 避免重复计算集群自动扩缩器中 Pod 和动态工作负载调度器之间的资源请求。
  • 注入 safe-to-evict: false 注解,以防止集群自动扩缩器在节点之间移动 Pod 和中断批量计算。您可以通过在 Pod 注解中指定 safe-to-evict: true 来更改此行为。

观察动态工作负载调度器的状态

动态工作负载调度器的状态定义是否可以安排 Pod。 您可以使用 Kubernetes 监视功能高效地观察更改,或您已用来跟踪 Kubernetes 对象状态的其他工具。下表介绍了动态工作负载调度器的可能状态以及每种可能的结果:

动态工作负载调度器状态 说明 可能的结果
等待中 该请求尚未被发现并且未被处理。 处理完成后,请求将转换为 AcceptedFailed 状态。
Accepted=true 请求被接受,正在等待资源变为可用状态。 如果找到资源并预配了节点,请求应转换为 Provisioned 状态;否则,请求应转换为 Failed 状态。
Provisioned=true 节点已准备就绪。 您可以在 10 分钟内启动 Pod 以使用预配的资源。此时间之后,集群自动扩缩器会将节点视为不需要的节点并将其移除。
Failed=true 由于存在错误,无法预配这些节点。Failed=true 是一种终端状态。 根据条件的 ReasonMessage 字段中的信息对条件进行问题排查。 创建并重试新的动态工作负载调度器请求。
Provisioned=false 尚未预配节点。

如果为 Reason=NotProvisioned,则表示这是所有资源都可用之前的临时状态。

如果为 Reason=QuotaExceeded,请根据此原因和条件的 Message 字段中的信息排查条件问题。您可能需要申请更多配额。如需了解详情,请参阅检查动态工作负载调度器是否受到配额的限制部分。此 Reason 仅适用于 GKE 1.29.2-gke.1181000 或更高版本。

启动 Pod

当动态工作负载调度器请求达到 Provisioned=true 状态时,您可以运行作业来启动 Pod。这样可以避免待处理或失败请求的无法安排的 Pod 数量激增,这可能会影响 kube-scheduler 和集群自动扩缩器的性能。

或者,如果您不在意 Pod 无法安排,则可以使用动态工作负载调度器并行创建 Pod。

取消动态工作负载调度器请求

如需在预配之前取消请求,您可以删除 ProvisioningRequest

kubectl delete provreq PROVISIONING_REQUEST_NAME -n NAMESPACE

在大多数情况下,删除 ProvisioningRequest 会停止创建节点。但是,根据时间(例如,如果已预配节点),则最终可能仍会创建节点。在这些情况下,如果未创建 Pod,集群自动扩缩器会在 10 分钟后移除节点。

动态工作负载调度器的工作原理

使用 ProvisioningRequest API 时,动态工作负载调度器会执行以下操作:

  1. 您会告诉 GKE,您的工作负载可以等待一段不确定的时间,直到所有必需的节点都准备好立即使用。
  2. 集群自动扩缩器接受您的请求并计算必要节点数,并将它们视为一个单元。
  3. 请求会等待单个可用区中的所有可用资源都可用。
  4. 集群自动扩缩器会在必要的节点可用时同时预配这些节点。
  5. 工作负载的所有 Pod 都可以在新预配的节点上一起运行。
  6. 预配的节点限制为运行七天或更早的时间(如果您设置 maxRunDurationSeconds 参数指示工作负载需要较少的运行时间)。如需了解详情,请参阅限制虚拟机的运行时(预览版)。GKE 1.28.5-gke.1355000 或更高版本提供此功能。此时间之后,节点及其上运行的 Pod 将被抢占。如果 Pod 更快地完成并且节点未被利用,则集群自动扩缩器会根据自动扩缩配置文件将其移除。
  7. 动态工作负载调度器之间不会重复使用节点。每个 ProvisioningRequest 将指示使用新 7 天运行时创建新节点。

配额

处于 Accepted 状态的 ProvisioningRequests 数量受专用配额的限制,该配额按项目在每个区域中独立配置。

在 Google Cloud 控制台中检查配额

如需在 Google Cloud 控制台中查看配额限制的名称和当前用量,请按照以下步骤操作:

  1. 转到 Google Cloud 控制台中的配额页面。

    转到“配额”

  2. 过滤条件框中,选择指标属性,输入 active_resize_requests 并按 Enter 键。

默认值为 100。如需增加配额,请按照申请更高配额限制指南中列出的步骤操作。

检查动态工作负载调度器是否受到配额的限制

如果您的动态工作负载调度器请求花费的时间超出预期,请检查该请求是否不受配额限制。您可能需要申请更多配额。

对于运行 1.29.2-gke.1181000 或更高版本的集群,请检查特定配额限制是否会阻止您的请求得到满足:

kubectl describe provreq PROVISIONING_REQUEST_NAME \
    --namespace NAMESPACE

输出类似于以下内容:

…
Last Transition Time:  2024-01-03T13:56:08Z
    Message:               Quota 'NVIDIA_P4_GPUS' exceeded. Limit: 1.0 in region europe-west4.
    Observed Generation:   1
    Reason:                QuotaExceeded
    Status:                False
    Type:                  Provisioned
…

在此示例中,GKE 无法部署节点,因为 europe-west4 区域没有足够的配额。

使用动态工作负载调度器为具有工作负载的节点池配置中断设置

要求节点池中所有节点或大多数节点的可用性的工作负载对逐出很敏感。不支持自动修复或升级使用 ProvisioningRequest API 预配的节点,因为这些操作会逐出该节点上运行的所有工作负载,并且无法安排工作负载。

为了最大限度地减少使用动态工作负载调度器正在运行的工作负载的中断,我们建议您采取以下措施:

  • 根据集群的发布渠道注册,请遵循以下最佳实践以防止节点自动升级中断您的工作负载:
  • 停用节点自动修复功能
  • 使用维护窗口和排除项来最大限度地减少正在运行的工作负载的中断,同时确保 GKE 可以在某个时间段内中断节点池以进行自动维护。如果您使用这些维护工具,则必须设置 GKE 可以中断节点池的特定时间段,因此我们建议您在没有正在运行的工作负载时设置此时间段。
  • 为确保您的节点池保持最新状态,我们建议您在没有活跃的动态工作负载调度器请求且节点池为空时手动升级节点池

限制

  • 不支持 Pod 之间的反亲和性。在节点预配期间,集群自动扩缩器不会考虑 pod 之间的反亲和性规则,这可能会导致工作负载无法调度。当在同一节点池中预配两个或更多动态工作负载调度器对象的节点时,可能会发生这种情况。
  • 仅支持 GPU 节点。
  • 动态工作负载调度器不支持预留。创建节点池时,您必须指定 --reservation-affinity=none。动态工作负载调度器要求并仅支持 ANY 位置政策用于集群自动扩缩。
  • 单个动态工作负载调度器请求最多可以创建 1000 个虚拟机,这是单个节点池的每个可用区的节点数上限。
  • GKE 使用 Compute Engine ACTIVE_RESIZE_REQUESTS 配额来控制队列中待处理的动态工作负载调度器请求数量。默认情况下,此配额在 Google Cloud 项目级限制为 100。如果您尝试创建大于此配额的动态工作负载调度器请求,新请求将失败。
  • 由于节点一起预配,因此使用动态工作负载调度器的节点池对中断很敏感。如需了解详情,请参阅为使用动态工作负载调度器的工作负载的节点池配置中断设置
  • 您可能会看到 Google Cloud 控制台中列出其他短期有效的虚拟机。这是预期行为,因为 Compute Engine 可能会创建虚拟机,并在预配所有必需机器的容量可用时立即移除这些虚拟机。
  • 动态工作负载调度器集成仅支持一个 PodSet。如果您想混合使用不同的 Pod 模板,请使用请求的资源最多的模板。不支持混合使用不同的机器类型,例如具有不同 GPU 类型的虚拟机。

后续步骤