基于阿里云Prometheus指标的容器水平伸缩

HPA默认支持基于CPU和内存指标实现自动伸缩,可能无法满足更为复杂的运维场景。如果您需要将Prometheus收集的Custom Metrics与External Metrics转换为HPA可用的弹性伸缩指标,您可以参见本文获取监控数据并实现对应的伸缩配置,为应用提供更灵活、便捷的扩缩机制。

前提条件

功能介绍

默认HPA只支持基于CPU和内存的自动伸缩,并不能满足日常的运维需求。阿里云Prometheus监控全面对接开源Prometheus生态,支持类型丰富的组件监控,提供多种开箱即用的预置监控大盘,且提供全面托管的Prometheus服务。此功能主要分为三个步骤:

  1. 在ACK集群中使用Prometheus监控透出监控指标。

  2. 依托alibaba-cloud-metrics-adapter组件,负责转换Prometheus监控指标为HPA可消费的Kubernetes聚合指标。更多信息,请参见Autoscaling on multiple metrics and custom metrics

  3. 配置并部署HPA,根据上一步的指标进行弹性扩缩。

    指标类型根据场景分为两种:

下文介绍如何配置alibaba-cloud-metrics-adapter,实现将阿里云Prometheus指标转换为HPA可用指标,并实现该指标自动伸缩。

步骤一:获取Prometheus监控数据

示例一:使用ACK默认容器监控指标

您可以直接使用ACK默认安装的阿里云Prometheus中的默认指标进行HPA弹性扩缩。支持的指标包括容器监控cAdvisor指标、节点基础监控Node-Exporter、GPU-Exporter指标,以及您当前已接入到阿里云Prometheus中的所有指标。查看已接入阿里云Prometheus的指标的步骤如下:

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

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

  3. 单击右上角跳转到Prometheus服务

  4. 在Prometheus监控服务控制台的左侧导航栏,单击设置,查看所有已接入阿里云Prometheus的指标。

示例二:通过Pod自身上报的Prometheus指标进行HPA扩缩

部署测试应用,并通过Prometheus标准方式暴露指标。更多信息,请参见metric_type。下文介绍如何部署sample-app应用,并自身透出http_requests_total的指标用来标识访问次数。

  1. 部署应用的工作负载。

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

    2. 集群列表页面,单击目标集群名称,在左侧导航栏,单击工作负载 > 无状态

    3. 无状态页面右侧,单击使用YAML创建资源,然后在创建页面,示例模板选择自定义,配置以下YAML,单击创建

      展开查看YAML详细信息

      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: sample-app
        labels:
          app: sample-app
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: sample-app
        template:
          metadata:
            labels:
              app: sample-app
          spec:
            containers:
            - image: luxas/autoscale-demo:v0.1.2
              name: metrics-provider
              ports:
              - name: http
                containerPort: 8080
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: sample-app
        namespace: default
        labels:
          app: sample-app
      spec:
        ports:
          - port: 8080
            name: http
            protocol: TCP
            targetPort: 8080
        selector:
          app: sample-app
        type: ClusterIP
      说明

      此容器暴露出http_requests_total的指标用来标识访问次数。

  2. 添加ServiceMonitor

    1. 登录ARMS控制台

    2. 在页面顶部选择目标集群所在地域,然后单击与集群同名的实例名称。

    3. 在左侧导航栏,单击接入管理,在右侧页面的已接入环境容器环境页签下,单击对应Prometheus服务实例的名称。在容器环境页面,单击指标采集页签。

    4. 在当前页面的左侧导航栏,单击ServiceMonitor,然后在ServiceMonitor页面单击新增,最后在新增ServiceMonitor配置页面,单击YAML编辑添加ServiceMonitor,完成后单击创建

      apiVersion: monitoring.coreos.com/v1
      kind: ServiceMonitor
      metadata:
        name: sample-app
        namespace: default
      spec:
        endpoints:
        - interval: 30s
          port: http
          path: /metrics
        namespaceSelector:
          any: true
        selector:
          matchLabels:
            app: sample-app
  3. 确认监控状态。

    自监控页签下,单击Targets页签,如果看到default/sample-app/0(1/1 up),则说明您已成功在阿里云Prometheus监控到了部署的应用。

  4. 通过在Prometheus大盘中,查询最近时间范围的http_requests_total数值,确定监控数据已经正确获取。

步骤二:修改alibaba-cloud-metrics-adapter组件配置

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

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

  3. Helm页面的操作列,单击ack-alibaba-cloud-metrics-adapter对应的更新

  4. 更新发布面板,配置如下YAML,然后单击确定

    展开查看YAML详细信息

      AlibabaCloudMetricsAdapter:
      ......
        prometheus:
          enabled: true    # 这里设置为true,打开整体Prometheus adapter功能。
          # 填写阿里云Prometheus监控的地址。
          url: https://cn-beijing.arms.aliyuncs.com:9443/api/v1/prometheus/xxxx/xxxx/xxxx/cn-beijing
        	# 阿里云Prometheus开启鉴权Token后,请配置prometheusHeader Authorization。
          prometheusHeader:
          - Authorization: xxxxxxx
          	
          metricsRelistInterval: 1m # 重新获取指标列表的时间周期,推荐保持默认1min。
        	logLevel: 5								# 组件调试日志级别,推荐保持默认。
        
          adapter:
            rules:
              default: false  			# 默认指标获取配置,推荐保持false。
              custom:
              
              # 示例1:this is an example of custom metric config.
              # this config will convert prometheus metric: container_memory_working_set_bytes to a custom metric container_memory_working_set_bytes_per_second
              # and cpu metric container_cpu_usage_seconds_total convert to container_cpu_usage_core_per_second
              # you can run command to check the memory/cpu value:
              # kubectl get --raw  "/apis/custom.metrics.k8s.io/v1beta1/namespaces/kube-system/pods/*/container_memory_working_set_bytes_per_second"
              # kubectl get --raw  "/apis/custom.metrics.k8s.io/v1beta1/namespaces/kube-system/pods/*/container_cpu_usage_core_per_second"
              # refer to doc: https://help.aliyun.com/document_detail/184519.html
      
              - seriesQuery: 'container_memory_working_set_bytes{namespace!="",pod!=""}'
                resources:
                  overrides:
                    namespace: { resource: "namespace" }
                    pod: { resource: "pod" }
                name:
                  matches: "^(.*)_bytes"
                  as: "${1}_bytes_per_second"
                metricsQuery: 'sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)' # metricsQuery配置中的labelSelector不会继承series_Query的过滤标签。
              - seriesQuery: 'container_cpu_usage_seconds_total{namespace!="",pod!=""}'
                resources:
                  overrides:
                    namespace: { resource: "namespace" }
                    pod: { resource: "pod" }
                name:
                  matches: "^(.*)_seconds_total"
                  as: "${1}_core_per_second"
                metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)'  # metricsQuery配置中的labelSelector不会继承series_Query的过滤标签。
      
              # 示例2:this is an example of external metric config.
              
              # refer to doc: https://help.aliyun.com/document_detail/608768.html
      
              # 添加新的转换规则,请确保阿里云Prometheus中指标标签和此处一致,如果不一致,请参见ARMS Prometheus中指标标签修改。
              
              #- seriesQuery: http_requests_total{namespace!="",pod!=""}
              #  resources:
              #    overrides:
              #      # 此处resource为Kubernetes的API Resource,可通过kubectl api-resources -o wide查看。
              #      # 此处key对应Prometheus数据中的LabelName,请确认Prometheus指标数据中有此LabelName。
              #      namespace: {resource: "namespace"}
              #      pod: {resource: "pod"}
              #  name:
              #    matches: ^(.*)_total
              #   as: ${1}_per_second
              #  metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)
      
      
              # this is an example of external metric config.
              
              # refer to doc: https://help.aliyun.com/document_detail/608768.html
      
              #- seriesQuery: arms_app_requests_count
              #  metricsQuery: sum by (rpc) (sum_over_time(<<.Series>>{rpc="/demo/queryUser/{id}",service="arms-demo:arms-k8s-demo",prpc="__all__",ppid="__all__",endpoint="__all__",destId="__all__",<<.LabelMatchers>>}[1m]))
              #  name:
              #    as: ${1}_per_second_queryuser
              #    matches: ^(.*)_count
              #  resources:
              #    namespaced: false
      
      
              # this is an example of custom metric from user define prometheus metric: http_requests_total
              # refer to doc: https://help.aliyun.com/document_detail/184519.html
      
              #- seriesQuery: 'http_requests_total{namespace!="",pod!=""}'
              #  resources:
              #    overrides:
              #      namespace: {resource: "namespace"}
              #      pod: {resource: "pod"}
              #  name:
              #    matches: "^(.*)_total"
              #    as: "${1}_per_second"
              #  metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'
              # - seriesQuery: '{__name__=~"^some_metric_count$"}'
              #   resources:
              #     template: <<.Resource>>
              #   name:
              #     matches: ""
              #     as: "my_custom_metric"
              #   metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>}) by (<<.GroupBy>>)
      
        ......

    部分字段说明如下。关于ack-alibaba-cloud-adapter配置文件的详细说明,请参见ack-alibaba-cloud-adapter配置文件详解

    字段

    说明

    AlibabaCloudMetricsAdapter. prometheus.adapter.rules.custom

    该字段内容请修改为示例YAML中对应的内容。

    alibabaCloudMetricsAdapter. prometheus.url

    填写阿里云Prometheus监控的地址。关于如何获取Prometheus数据请求URL,请参见如何获取Prometheus数据请求URL

    AlibabaCloudMetricsAdapter. prometheus.prometheusHeader[].Authorization

    填写Token。关于如何获取Token,请参见如何获取Prometheus数据请求URL

    AlibabaCloudMetricsAdapter. prometheus.adapter.rules.default

    默认创建预置指标,推荐关闭,配置为false

配置Metrics-adapter组件参数,并成功部署Metrics-adapter组件后,可通过如下命令查看K8s聚合API是否已经成功接入数据。

  1. 通过Custom Metrics进行容器伸缩。

    1. 执行以下命令,通过Custom Metrics指标查询方式,查看HPA可用指标的详情和列表。

      kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/" | jq .
    2. 执行以下命令,查询http_requests_per_second指标在default命名空间下的当前数值。

      # 通过查询container_memory_working_set_bytes_per_second查看kube-system namespace中Pod的工作内存当前每秒大小。
      kubectl get --raw  "/apis/custom.metrics.k8s.io/v1beta1/namespaces/kube-system/pods/*/container_memory_working_set_bytes_per_second"
      
      # 通过查询container_cpu_usage_core_per_second查看kube-system namespace中Pod的CPU使用核数每秒大小。
      kubectl get --raw  "/apis/custom.metrics.k8s.io/v1beta1/namespaces/kube-system/pods/*/container_cpu_usage_core_per_second"

      指标查询结果示例:

      {
        "kind": "MetricValueList",
        "apiVersion": "custom.metrics.k8s.io/v1beta1",
        "metadata": {
          "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/kube-system/pods/%2A/container_memory_working_set_bytes_per_second"
        },
        "items": [
          {
            "describedObject": {
              "kind": "Pod",
              "namespace": "kube-system",
              "name": "ack-alibaba-cloud-metrics-adapter-7cf8dcb845-h****",
              "apiVersion": "/v1"
            },
            "metricName": "container_memory_working_set_bytes_per_second",
            "timestamp": "2023-08-09T06:30:19Z",
            "value": "24576k",
            "selector": null
          }
        ]
      }
  2. 通过External Metrics进行容器伸缩。

    1. 执行以下命令,通过External Metrics指标查询方式,查看HPA可用的External指标详情和列表。

      kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/" | jq .
    2. 执行以下命令,查询http_requests_per_second指标在default命名空间下的当前数值。

      kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/default/http_requests_per_second"

      示例输出:

      {
        "kind": "ExternalMetricValueList",
        "apiVersion": "external.metrics.k8s.io/v1beta1",
        "metadata": {},
        "items": [
          {
            "metricName": "http_requests_per_second",
            "metricLabels": {},
            "timestamp": "2022-01-28T08:40:20Z",
            "value": "33m"
          }
        ]
      }
      

步骤三:配置并部署HPA,根据获得的指标进行弹性扩缩

部署HPA

当前版本已支持Prometheus Metrics同时透出Custom Metrics与External Metrics。您可以根据需求任选以下方式通过HPA进行容器伸缩。

类型

说明

Custom Metric

根据与要进行扩缩的Kubernetes目标对象(例如Pod)相关的指标进行扩缩,例如Pod自身维度的指标。更多信息,请参见autoscaling-on-multiple-metrics-and-custom-metrics

External Metric

根据与要进行扩缩的Kubernetes目标对象(例如Pod)不相关的指标进行扩缩,例如通过整体的业务QPS指标来扩缩某一个Workload的Pod。更多信息,请参见autoscaling-on-metrics-not-related-to-kubernetes-objects

通过Custom Metrics进行容器伸缩

  1. 使用以下内容,创建hpa.yaml文件。

    kind: HorizontalPodAutoscaler
    apiVersion: autoscaling/v2
    metadata:
      name: sample-app-memory-high
    spec:
    # HPA的伸缩对象描述,HPA会动态修改该对象的Pod数量。
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: sample-app
    # HPA的最小Pod数量和最大Pod数量。
      minReplicas: 1
      maxReplicas: 10
    # 监控的指标数组,支持多种类型的指标共存。
      metrics:
      - type: Pods
        pods:
          # 使用指标:pods/container_memory_working_set_bytes_per_second。
          metric: 
            name: container_memory_working_set_bytes_per_second
     # AverageValue类型的目标值,Pods指标类型下只支持AverageValue类型的目标值。
          target:
            type: AverageValue
            averageValue: 1024000m       # 此处1024000m代表1 KB内存阈值,当前指标单位为byte/per second,m为K8s转换精度单位,当出现了小数点,K8s又需要高精度时,会使用单位m或k。例如1001m=1.001,1k=1000。
    
  2. 执行以下命令,创建HPA应用。

    kubectl apply -f hpa.yaml
  3. 执行以下命令,查看HPA是否生效。

    kubectl get hpa sample-app-memory-high

    预期输出:

    NAME                     REFERENCE               TARGETS           MINPODS   MAXPODS   REPLICAS   AGE
    sample-app-memory-high   Deployment/sample-app   24576k/1024000m   3         10        1          7m

通过External Metrics进行容器伸缩

  1. 使用以下内容,创建hpa.yaml文件。

    apiVersion: autoscaling/v2
    kind: HorizontalPodAutoscaler
    metadata:
      name: sample-app
    spec:
      scaleTargetRef:
        apiVersion: apps/v1
        kind: Deployment
        name: sample-app
      minReplicas: 1
      maxReplicas: 10
      metrics:
        - type: External
          external:
            metric:
              name: http_requests_per_second
              selector:
                matchLabels:
                  job: "sample-app"
    # External指标类型下只支持Value和AverageValue类型的目标值。
            target:
              type: AverageValue
              averageValue: 500m
  2. 执行以下命令,创建HPA应用。

    kubectl apply -f hpa.yaml
  3. 在Service中开启负载均衡后,执行以下命令,进行压测实验。

    ab -c 50 -n 2000 LoadBalancer(sample-app):8080/
  4. 执行以下命令,查看HPA详情。

    kubectl get hpa sample-app

    预期输出:

    NAME         REFERENCE               TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
    sample-app   Deployment/sample-app   33m/500m   1         10        1          7m

ack-alibaba-cloud-adapter配置文件详解

ack-alibaba-cloud-adapter通过以下步骤将Prometheus中的指标转换成HPA可用的指标:

  1. Discovery:ack-alibaba-cloud-adapter会从Prometheus发现可用的指标。

  2. Association:将指标与Kubernetes资源(Pod、Node、Namespace)相关联。

  3. Naming:定义转换后的HPA可用指标名称。

  4. Querying:定义查询Prometheus语句。

以上文中sample-app容器中暴露出来的http_requests_total指标转换成HPA中的http_requests_per_second为例,完整的ack-alibaba-cloud-adapter配置文件如下。

- seriesQuery: http_requests_total{namespace!="",pod!=""}
  resources:
    overrides:
      namespace: {resource: "namespace"}
      pod: {resource: "pod"}
  name:
    matches: ^(.*)_total
    as: ${1}_per_second
  metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)

字段

说明

seriesQuery

PromQL请求数据。

metricsQuery

对seriesQuery中PromQL请求的数据做聚合操作。

说明

metricsQuery配置中的labelSelector不会继承series_Query的过滤标签。

resources

是PromQL里的数据Label,与resource进行匹配。此处的resource是指集群内的api-resource,例如Pod、Namespace和Node。您可以通过kubectl api-resources -o wide命令查看。此处Key对应Prometheus数据中的LabelName,请确认Prometheus指标数据中有此LabelName。

name

指根据正则匹配把Prometheus指标名转为比较可读的指标名,此处将http_requests_total转为http_requests_per_second

  1. Discovery

    指定待转换的Prometheus指标,您可以通过seriesFilters精确过滤指标。seriesQuery可以根据标签进行查找,示例代码如下。

    seriesQuery: http_requests_total{namespace!="",pod!=""}
    seriesFilters:
        - isNot: "^container_.*_seconds_total"

    seriesFilters为非必填项,用于过滤指标:

    • is:<regex>:匹配包含该正则表达式的指标。

    • isNot:<regex>:匹配不包含该正则表达式的指标。

  2. Association

    设置Prometheus指标标签与Kubernetes中的资源映射关系。http_requests_total指标的标签包括namespace!=""pod!=""

    - seriesQuery: http_requests_total{namespace!="",pod!=""}
      resources:
        overrides:
          namespace: {resource: "namespace"}
          pod: {resource: "pod"}
  3. Naming

    用于将Prometheus指标名称转换成HPA的指标名称,但不会改变Prometheus本身的指标名称。如果使用Prometheus原来的指标,可以不设置。

    您可以通过执行命令kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1"查看HPA可用的所有指标。

    - seriesQuery: http_requests_total{namespace!="",pod!=""}
      resources:
        overrides:
          namespace: {resource: "namespace"}
          pod: {resource: "pod"}
      name:
        matches: "^(.*)_total"
        as: "${1}_per_second"
  4. Querying

    查询Prometheus API的模板。ack-alibaba-cloud-adapter会根据HPA中的参数,填充参数到此模板中,然后发送给Prometheus API请求,并将获得的值最终提供给HPA进行弹性扩缩。

    - seriesQuery: http_requests_total{namespace!="",pod!=""}
      resources:
        overrides:
          namespace: {resource: "namespace"}
          pod: {resource: "pod"}
      name:
        matches: ^(.*)_total
        as: ${1}_per_second
      metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)

如何获取Prometheus数据请求URL

场景一:阿里云Prometheus监控

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

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

  3. 单击右上角跳转到Prometheus服务

  4. 在Prometheus监控服务控制台的左侧导航栏,单击设置,然后单击设置页签,获取HTTP API地址(Grafana 读取地址)

    推荐使用内网,如无法使用内网时,可使用公网。

    3.png

场景二:开源Prometheus监控

针对开源自建Prometheus方案,您需要通过Service暴露Prometheus的标准访问API,然后将其配置在metrics-adapter组件的Prometheus数据源URL参数中,即可完成基于开源Prometheus数据的HPA数据源配置。

下文以ACK应用市场提供的ack-prometheus-operator社区版应用Helm Chart为例。更多信息,请参见开源Prometheus监控

  1. 部署Prometheus监控方案,并暴露标准Prometheus API。

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

    • 应用市场页面,搜索并单击ack-prometheus-operator,然后在页面右侧,单击一键部署

    • 在创建页面,选择集群命名空间,按需修改发布名称,然后单击下一步,按需修改参数,然后单击确定

    • 查看部署结果。

      1. 通过Service暴露Prometheus的标准API,当前以ack-prometheus-operator的Service:ack-prometheus-operator-prometheus为例。

      2. 在浏览器中访问ServiceIP:9090,如需为Service开通公网访问SLB,查看Prometheus控制台。

      3. 在页面上方菜单栏,单击Status > Targets,查看所有采集任务。image.png

        如果所有任务的状态为UP,表示所有采集任务均已正常运行。

        image.png

    • 查看Labels中对应的servicenamespace

      本示例以ServiceName为ack-prometheus-operator-prometheus,ServiceNamespace为monitoring为例说明该开源Prometheus数据请求的URL。

      http://ack-prometheus-operator-prometheus.monitoring.svc.cluster.local:9090
  2. 配置组件的Prometheus数据源URL参数中,以确保组件与Prometheus之间的通信正常。

    如果选择通过公网访问Prometheus的标准API,可参见以下示例进行配置。

      AlibabaCloudMetricsAdapter:
      ......
        prometheus:
          enabled: true
          url: http://your_domain.com:9090   # 请将your_domain.com替换为您的公网IP

    以ack-prometheus-operator方案为例,此时url值为http://ack-prometheus-operator-prometheus.monitoring.svc.cluster.local:9090

相关文档