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

前提条件

您已完成创建以下任一类型集群:

背景信息

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

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

图 1. GPU弹性伸缩原理图
hpa

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

  1. 开启阿里云Prometheus监控
    说明 如果您在创建集群时,已选中安装Prometheus,则不需要重复安装。
  2. 安装并配置ack-alibaba-cloud-metrics-adapter。
    一、获取HTTP API地址
    1. 登录ARMS控制台
    2. 在控制台左侧导航栏,选择Prometheus监控 > Prometheus实例列表
    3. Prometheus监控页面左上角选择容器服务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指标实现弹性伸缩

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

  1. 部署推理服务。
    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登录节点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并返回意图识别结果,说明推理服务部署成功。
  2. 配置HPA。本文以GPU利用率为例进行说明,当Pod的GPU利用率大于20%时,触发扩容。HPA目前支持的指标如下:
    指标名称说明单位
    duty_cycle_currentGPU利用率百分比
    memory_used_bytes_current显存使用量字节
    1. 执行以下命令部署HPA。
      cat <<EOF | kubectl create -f -
      apiVersion: autoscaling/v2beta1
      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: duty_cycle_current #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%时触发弹性扩容。

  3. 测试推理服务弹性伸缩。
    场景一:测试扩容效果
    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节点资源不足,HPA触发扩容后,Pod的状态会变为Pending,导致扩容失败。此时可以使用弹性节点池进行处理,当集群GPU不足时,自动扩容节点。更多信息,请参见配置自动弹性伸缩

一、配置自动弹性伸缩
  1. 登录容器服务管理控制台,在左侧导航栏中选择集群
  2. 在目标集群右侧的操作列下,选择更多 > 自动伸缩
  3. 自动弹性伸缩配置页面,完成弹性伸缩配置并提交。
二、创建节点池
  1. 自动弹性伸缩配置页面,单击创建节点池
  2. 创建节点池对话框,设置创建节点池的配置项。
    部分配置项说明如下所示。
    参数说明
    自动伸缩请选中开启自动开启弹性伸缩
    节点标签如果您有多个节点,可以在创建节点池对话框最下方单击显示高级选项,找到节点标签,为集群节点添加自定义标签。
    说明 部署应用时,您可以在YAML文件中通过nodeSelector配置节点标签,应用会自动部署到指定的节点池中,扩容的节点也会部署在指定节点池中。

    关于更多配置项说明,请参见创建Kubernetes专有版集群

  3. 配置完成后,在创建节点池对话框最下方单击确认配置

常见问题

  • 问:如果执行kubectl get hpa命令后,发现target一栏为UNKNOWN怎么办?

    答:请按照以下步骤进行验证:

    1. 确认HorizontalPodAutoscaler中指标名字是否正确。
    2. 执行kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1"命令,查看返回结果中是否有对应的指标。
    3. 检查ack-alibaba-cloud-metrics-adapter中配置的Prometheus url是否正确。
  • 问:如何根据GPU显存的使用情况进行弹性伸缩?

    答:您可以使用memory_used_bytes_current作为指标进行部署,此操作适用于GPU独占或GPU共享模式。
    apiVersion: autoscaling/v2beta1
    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: memory_used_bytes_current #Pod使用的显存,单位为字节。
          targetAverageValue: 4G #单个Pod显存超过4 GB触发扩容。
  • 问:如何禁止自动缩容?

    答:您可以在创建HPA时,通过配置behavior来禁止自动缩容。
    behavior:
      scaleDown:
        policies:
        - type: pods
          value: 0
    完整的HPA配置文件如下:
    apiVersion: autoscaling/v2beta1
    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: duty_cycle_current #Pod的GPU利用率。
          targetAverageValue: 20 #GPU利用率超过20%,触发扩容。
      behavior:
      scaleDown:
        policies:
        - type: pods
          value: 0
  • 问:如何延迟缩容时间窗口?

    答:缩容默认时间窗口(--horizontal-pod-autoscaler-downscale-stabilization-window)是5分钟 ,如果您需要延长时间窗口以避免一些流量毛刺造成的异常,可以指定缩容的时间窗口,behavior 参数配置示例如下:
    behavior:
      scaleDown:
        stabilizationWindowSeconds: 600 #等待10分钟后再开始缩容。
        policies:
        - type: pods
          value: 5 #每次只缩容5个Pod。
    上述示例表示当负载下降时,系统会等待600秒(10分钟)后再次进行缩容,每次只缩容5个Pod。