基于GPU指标实现弹性伸缩

Kubernetes提供了Custom Metrics机制,该机制可以对接阿里云Prometheus监控来采集GPU指标。本文介绍如何部署阿里云Prometheus监控,并结合示例说明如何通过阿里云Prometheus监控观测GPU指标,实现容器的弹性伸缩。

前提条件

您已创建GPU集群或已创建专有GPU集群

功能介绍

在高性能计算领域,例如深度学习模型训练、推理等场景,通常需要使用GPU来做计算加速。为了节省成本,您可以根据GPU指标(利用率、显存)来进行弹性伸缩。

Kubernetes默认提供CPU和内存作为HPA弹性伸缩的指标。如果有更复杂的场景需求,例如基于GPU指标进行自动扩缩容,您可以通过Prometheus Adapter适配Prometheus采集到的GPU指标,再利用Custom Metrics API来对HPA的指标进行扩展,从而根据GPU利用率、显存等指标进行弹性伸缩。GPU弹性伸缩原理如下图所示:

hpa

步骤一:部署阿里云Prometheus和Metrics Adapter

  1. 步骤一:开启阿里云Prometheus监控

    说明

    如果您在创建集群时,已选中安装Prometheus,则不需要重复安装。

  2. 安装并配置ack-alibaba-cloud-metrics-adapter。

    一、获取HTTP API地址

    1. 登录ARMS控制台

    2. 在左侧导航栏选择Prometheus监控 > 实例列表,进入可观测监控 Prometheus 版的实例列表页面。

    3. 在页面顶部选择容器服务K8s集群所在的地域,然后选择实例类型Prometheus for 容器服务的目标实例名称,进入对应实例页面。

    4. 在左侧导航栏单击设置,复制HTTP API地址下的内网地址。

    二、配置Prometheus Url

    1. 登录容器服务管理控制台,在左侧导航栏选择市场 > 应用市场

    2. 应用市场页面单击应用目录页签,搜索并单击ack-alibaba-cloud-metrics-adapter

    3. ack-alibaba-cloud-metrics-adapter页面,单击一键部署

    4. 基本信息配置向导中,选择集群和命名空间,然后单击下一步

    5. 参数配置配置向导中,选择Chart版本,在参数配置中将上述获取的HTTP API地址设置为Prometheus的url值,然后单击确定

步骤二:配置GPU指标Adapter Rules

一、查询GPU指标

查询GPU相关指标。详细信息,请参见监控指标说明

二、配置Adapter Rules

  1. 登录容器服务管理控制台,在左侧导航栏选择集群

  2. 集群列表页面,单击目标集群名称,然后在左侧导航栏,选择应用 > Helm

  3. 在Helm列表的操作列,单击ack-alibaba-cloud-metrics-adapter对应的更新。在custom字段下添加如下rules

    展开查看详细rules信息

    - metricsQuery: avg(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)
      resources:
        overrides:
          NodeName:
            resource: node
      seriesQuery: DCGM_FI_DEV_GPU_UTIL{} # GPU使用率
    - metricsQuery: avg(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)
      resources:
        overrides:
          NamespaceName:
            resource: namespace
          NodeName:
            resource: node
          PodName:
            resource: pod
      seriesQuery: DCGM_CUSTOM_PROCESS_SM_UTIL{} # 容器GPU使用率。
    - metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)
      resources:
        overrides:
          NodeName:
            resource: node
      seriesQuery: DCGM_FI_DEV_FB_USED{} # 显存使用量。
    - metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)
      resources:
        overrides:
          NamespaceName:
            resource: namespace
          NodeName:
            resource: node
          PodName:
            resource: pod
      seriesQuery: DCGM_CUSTOM_PROCESS_MEM_USED{} # 容器显存使用量。
    - metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>) / sum(DCGM_CUSTOM_CONTAINER_MEM_ALLOCATED{}) by (<<.GroupBy>>)
      name:
        as: ${1}_GPU_MEM_USED_RATIO
        matches: ^(.*)_MEM_USED
      resources:
        overrides:
          NamespaceName:
            resource: namespace
          PodName:
            resource: pod
      seriesQuery: DCGM_CUSTOM_PROCESS_MEM_USED{NamespaceName!="",PodName!=""}  # 容器GPU显存使用率。

    添加后效果如下。

    1690252651140-f693f03a-0f9e-4a6a-8772-b7abe9b2912a.png

    执行以下命令时,存在HPA可以识别的输出条目DCGM_FI_DEV_GPU_UTILDCGM_CUSTOM_PROCESS_SM_UTILDCGM_FI_DEV_FB_USEDDCGM_CUSTOM_PROCESS_MEM_USED指标,则说明配置成功生效。下面以DCGM_CUSTOM_PROCESS_SM_UTIL为例进行说明,实际以输出为准。

    展开查看输出示例

    kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1"
    {
    	[
        ...
    		{
          "name": "nodes/DCGM_CUSTOM_PROCESS_SM_UTIL",
          "singularName": "",
          "namespaced": false,
          "kind": "MetricValueList",
          "verbs": [
            "get"
          ]
        },
        ...
        {
          "name": "pods/DCGM_CUSTOM_PROCESS_SM_UTIL",
          "singularName": "",
          "namespaced": true,
          "kind": "MetricValueList",
          "verbs": [
            "get"
          ]
        },
        ...
        {
          "name": "namespaces/DCGM_CUSTOM_PROCESS_SM_UTIL",
          "singularName": "",
          "namespaced": false,
          "kind": "MetricValueList",
          "verbs": [
            "get"
          ]
        }
        ...
        {
          "name": "DCGM_CUSTOM_PROCESS_GPU_MEM_USED_RATIO",
          "singularName": "",
          "namespaced": false,
          "kind": "MetricValueList",
          "verbs": [
            "get"
          ]
        }
        ...
    	]
    }

步骤三:基于GPU指标实现弹性伸缩

本示例通过在GPU上部署一个模型推理服务,然后对其进行压测,根据GPU利用率测试弹性伸缩。

一、部署推理服务

  1. 执行以下命令部署推理服务。

    展开查看命令详情

    cat <<EOF | kubectl create -f -
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: bert-intent-detection
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: bert-intent-detection
      template:
        metadata:
          labels:
            app: bert-intent-detection
        spec:
          containers:
          - name: bert-container
            image: registry.cn-hangzhou.aliyuncs.com/ai-samples/bert-intent-detection:1.0.1
            ports:
            - containerPort: 80
            resources:
              limits:
                nvidia.com/gpu: 1
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: bert-intent-detection-svc
      labels:
        app: bert-intent-detection
    spec:
      selector:
        app: bert-intent-detection
      ports:
      - protocol: TCP
        name: http
        port: 80
        targetPort: 80
      type: LoadBalancer
    EOF
  2. 查看Pod和Service状态。

    • 执行以下命令查看Pod状态。

      kubectl get pods -o wide

      预期输出:

      NAME                                    READY   STATUS    RESTARTS   AGE     IP           NODE                        NOMINATED NODE   READINESS GATES
      bert-intent-detection-7b486f6bf-f****   1/1     Running   0          3m24s   10.15.1.17   cn-beijing.192.168.94.107   <none>           <none>

      预期输出表明,当前只有一个Pod部署在192.168.94.107这个GPU节点上。

    • 执行以下命令查看Service状态。

      kubectl get svc bert-intent-detection-svc

      预期输出:

      NAME                        TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
      bert-intent-detection-svc   LoadBalancer   172.16.186.159   47.95.XX.XX   80:30118/TCP   5m1s

      预期输出中显示服务名称,表示服务部署成功。

  3. 通过SSH登录GPU节点192.168.94.107后,执行以下命令查看GPU使用情况。

    nvidia-smi

    预期输出:

    Wed Feb 16 11:48:07 2022
    +-----------------------------------------------------------------------------+
    | NVIDIA-SMI 450.102.04   Driver Version: 450.102.04   CUDA Version: 11.0     |
    |-------------------------------+----------------------+----------------------+
    | GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
    | Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
    |                               |                      |               MIG M. |
    |===============================+======================+======================|
    |   0  Tesla V100-SXM2...  On   | 00000000:00:07.0 Off |                    0 |
    | N/A   32C    P0    55W / 300W |  15345MiB / 16160MiB |      0%      Default |
    |                               |                      |                  N/A |
    +-------------------------------+----------------------+----------------------+
    
    +-----------------------------------------------------------------------------+
    | Processes:                                                                  |
    |  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
    |        ID   ID                                                   Usage      |
    |=============================================================================|
    |    0   N/A  N/A   2305118      C   python                          15343MiB |
    +-----------------------------------------------------------------------------+

    预期输出表明,推理服务进程已运行在GPU上。由于尚未发起请求,当前GPU利用率为0。

  4. 执行以下命令调用推理服务,验证部署是否成功。

    curl -v  "http://47.95.XX.XX/predict?query=Music"

    预期输出:

    *   Trying 47.95.XX.XX...
    * TCP_NODELAY set
    * Connected to 47.95.XX.XX (47.95.XX.XX) port 80 (#0)
    > GET /predict?query=Music HTTP/1.1
    > Host: 47.95.XX.XX
    > User-Agent: curl/7.64.1
    > Accept: */*
    >
    * HTTP 1.0, assume close after body
    < HTTP/1.0 200 OK
    < Content-Type: text/html; charset=utf-8
    < Content-Length: 9
    < Server: Werkzeug/1.0.1 Python/3.6.9
    < Date: Wed, 16 Feb 2022 03:52:11 GMT
    <
    * Closing connection 0
    PlayMusic # 意图识别结果。

    预期输出中,HTTP请求返回状态码200并返回意图识别结果,表明推理服务部署成功。

二、配置HPA

本文以GPU利用率为例进行说明,当Pod的GPU利用率大于20%时,触发扩容。HPA目前支持的指标如下。

指标名称

说明

单位

DCGM_FI_DEV_GPU_UTIL

  • GPU卡的利用率。

  • 该指标仅在独占GPU调度时有效。

    重要

    在共享GPU的情况下,同一张GPU卡被分配给多个Pod,而NVIDIA目前仅提供卡级别的利用率,并未提供应用级别的利用率,所以在Pod中执行nvidia-smi看到的利用率为整张卡的利用率。

%

DCGM_FI_DEV_FB_USED

  • GPU卡显存使用量。

  • 该指标仅在独占GPU调度时有效。

MiB

DCGM_CUSTOM_PROCESS_SM_UTIL

容器的GPU利用率。

%

DCGM_CUSTOM_PROCESS_MEM_USED

容器的GPU显存使用量。

MiB

DCGM_CUSTOM_PROCESS_GPU_MEM_USED_RATIO

容器的GPU显存利用率。

容器的GPU显存利用率=当前Pod容器GPU实际显存使用量(Used) / 当前Pod容器GPU分配的显存使用量(Allocated)

%

  1. 执行以下命令部署HPA。

    集群版本≥1.23

    cat <<EOF | kubectl create -f -
    apiVersion: autoscaling/v2  # 使用autoscaling/v2版本的HPA配置。
    kind: HorizontalPodAutoscaler
    metadata:
      name: gpu-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: bert-intent-detection
      minReplicas: 1
      maxReplicas: 10
      metrics:
      - type: Pods
        pods:
          metric:
            name: DCGM_CUSTOM_PROCESS_SM_UTIL
          target:
            type: Utilization
            averageValue: 20 # 当容器的GPU利用率超过20%,触发扩容。
    EOF

    集群版本<1.23

    cat <<EOF | kubectl create -f -
    apiVersion: autoscaling/v2beta1  # 使用autoscaling/v2beta1版本的HPA配置。
    kind: HorizontalPodAutoscaler
    metadata:
      name: gpu-hpa
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: bert-intent-detection
      minReplicas: 1
      maxReplicas: 10
      metrics:
      - type: Pods
        pods:
          metricName: DCGM_CUSTOM_PROCESS_SM_UTIL # Pod的GPU利用率。
          targetAverageValue: 20 # 当容器的GPU利用率超过20%,触发扩容。
    EOF
  2. 执行以下命令查看HPA状态。

    kubectl get hpa

    预期输出:

    NAME      REFERENCE                          TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
    gpu-hpa   Deployment/bert-intent-detection   0/20      1         10        1          74s

    预期输出表明,TARGETS0/20,即当前GPU利用率为0,当GPU利用率超过20%时触发弹性扩容。

三、测试推理服务弹性伸缩

测试扩容效果

  1. 执行以下命令进行压测。

    hey -n 10000 -c 200 "http://47.95.XX.XX/predict?query=music"
    说明

    HPA弹性扩容的期望副本数的计算公式:期望副本数 = ceil[当前副本数 * (当前指标 / 期望指标)]。例如,当前副本数为1,当前指标为23,期望指标为20,由计算公式得到期望副本数为2。

  2. 压测过程中,观察HPA和Pod的状态。

    1. 执行以下命令查看HPA的状态。

      kubectl get hpa

      预期输出:

      NAME      REFERENCE                          TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
      gpu-hpa   Deployment/bert-intent-detection   23/20     1         10        2          7m56s

      预期输出表明,TARGETS值为23/20,当前GPU利用率超过20%时,触发弹性伸缩,此时集群开始扩容。

    2. 执行以下命令查看Pod的状态。

      kubectl get pods

      预期输出:

      NAME                                    READY   STATUS    RESTARTS   AGE
      bert-intent-detection-7b486f6bf-f****   1/1     Running   0          44m
      bert-intent-detection-7b486f6bf-m****   1/1     Running   0          14s

      预期输出表明,当前有2个Pod。由上述计算公式得到Pod总数应为2,公式计算值与实际输出一致。

    HPA和Pod的预期输出,Pod扩容成功。

测试缩容效果

当压测停止,GPU利用率降低且低于20%后,系统开始进行弹性缩容。

  1. 执行以下命令查看HPA的状态。

    kubectl get hpa

    预期输出:

    NAME      REFERENCE                          TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
    gpu-hpa   Deployment/bert-intent-detection   0/20      1         10        1          15m

    预期输出表明,TARGETS0/20 ,即当前GPU利用率为0,大约5分钟后,系统开始进行弹性缩容。

  2. 执行以下命令查看Pod的状态。

    kubectl get pods

    预期输出:

    NAME                                    READY   STATUS    RESTARTS   AGE
    bert-intent-detection-7b486f6bf-f****   1/1     Running   0          52m

    预期输出表明,当前Pod个数为1,即缩容成功。

常见问题

如何判断是否使用了GPU卡?

您可以通过GPU监控页签,观察GPU卡的使用率波动状况,以此判断GPU卡是否被使用。如果使用率上升,表明使用了GPU卡。如果使用率不变,表明未使用GPU卡。详细方法为:

  1. 登录容器服务管理控制台,在左侧导航栏中单击集群

  2. 集群列表页面中,单击目标集群名称,然后在左侧导航栏中,选择运维管理 > Prometheus监控

  3. Prometheus监控页面,单击GPU监控页签,然后观察GPU卡的使用率波动状况。