
# 基于ELB监控指标的弹性伸缩实践
#### 应用现状
在使用工作负载弹性伸缩时，Kubernetes默认提供基于CPU/内存等资源使用率指标进行伸缩。但是在流量突发的场景下，基于CPU/内存使用率资源使用率数据会滞后于ELB流量指标，无法及时反映应用实际需求。因此，对于某些需要快速弹性扩缩容的业务（例如抢购和社交媒体），仅依靠资源使用率进行扩缩容可能存在伸缩不及时的问题，无法及时满足业务的实际需求。在这种情况下，通过基于ELB的QPS数据进行弹性伸缩可以更加及时地响应业务需求。
#### 解决方案
本文介绍一种基于ELB监控指标的弹性伸缩方法，相比CPU/内存使用率进行弹性伸缩，基于ELB的QPS数据弹性伸缩更有针对性，更加及时。
本方案的关键点是获取ELB的指标数据并上报到Prometheus，再将Prometheus中的数据转换成HPA能够识别的metric数据，然后HPA根据metric数据进行弹性伸缩。
基于ELB监控指标的弹性伸缩具体实施方案如下所示：
1. 开发一个Prometheus exporter，获取ELB的指标，并转化成Prometheus需要的格式，上报到Prometheus。本文使用[cloudeye-exporter](https://github.com/huaweicloud/cloudeye-exporter)作为示例。
2. 将Prometheus的数据转换成Kubernetes metric api提供给HPA controller使用。
3. 设置HPA规则，使用ELB的监控数据作为弹性伸缩指标。
图1ELB流量与监控数据示意图   
![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000001160642449.png "点击放大")
![](https://support.huaweicloud.com/bestpractice-cce/public_sys-resources/note_3.0-zh-cn.png)
本文介绍的方法不限于ELB指标，其他指标可按照类似方法操作。
#### 前提条件
- 本实践需要您熟悉Prometheus。
- 在集群中安装3.10.1以上版本的云原生监控插件（kube-prometheus-stack）。
  ![](https://support.huaweicloud.com/bestpractice-cce/public_sys-resources/note_3.0-zh-cn.png)
  插件"数据存储配置"需开启"本地数据存储"。
  
 
#### 构建exporter镜像
本文使用[cloudeye-exporter](https://github.com/huaweicloud/cloudeye-exporter)实现ELB指标监控，如您需要自行开发exporter，请参见[附录：自行开发一个exporter]。
1. 登录一台可访问公网且安装Docker的虚拟机，编写Dockerfile。 
   ```
   vi Dockerfile
   ```
   Dockerfile内容如下：
   ```
   RUN apt-get update \
     && apt-get install -y wget ca-certificates \
       && update-ca-certificates \
         && wget https://github.com/huaweicloud/cloudeye-exporter/releases/download/v2.0.16/cloudeye-exporter-v2.0.16.tar.gz \
           && tar -xzvf cloudeye-exporter-v2.0.16.tar.gz
   CMD ["./cloudeye-exporter -config=/tmp/clouds.yml"]
   ```
   本文使用[v2.0.16版本](https://github.com/huaweicloud/cloudeye-exporter/releases/download/v2.0.16/cloudeye-exporter-v2.0.16.tar.gz)的cloudeye-exporter，更多版本请参见[Releases](https://github.com/huaweicloud/cloudeye-exporter/)。
   
   
2. 构建镜像，镜像名称为cloudeye-exporter，版本为2.0.16。 
   ```
   docker build --network host . -t cloudeye-exporter:2.0.16
   ```
   
   
3. 上传镜像至SWR镜像仓库。 
   1. （可选）登录SWR管理控制台，选择左侧导航栏的"组织管理"，单击页面右上角的"创建组织"，创建一个组织。
      如已有组织可跳过此步骤。
      
   
   2. 在左侧导航栏选择"我的镜像"，单击右侧"客户端上传"，在弹出的页面中单击"生成临时登录指令"，单击![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000001380832974.png)复制登录指令。
   
   3. 在集群节点上执行上一步复制的登录指令，登录成功会显示"Login Succeeded"。
   
   4. 为cloudeye-exporter镜像打标签。 **docker tag \[镜像名称1:版本名称1\] \[镜像仓库地址\]/\[组织名称\]/\[镜像名称2:版本名称2\]**
      - **\[镜像名称1:版本名称1\]**：请替换为您本地所要上传的实际镜像的名称和版本名称。
      
      - **\[镜像仓库地址\]** ：可在SWR控制台上查询，[b]中登录指令末尾的域名即为镜像仓库地址。
      
      - **\[组织名称\]** ：请替换为[a]中创建的组织。
      
      - **\[镜像名称2:版本名称2\]**：请替换为SWR镜像仓库中需要显示的镜像名称和镜像版本。
      
      
      示例：
      **docker tag cloudeye-exporter:2.0.16 swr.cn-east-3.myhuaweicloud.com/container/cloudeye-exporter:2.0.16**
      
   
   5. 上传镜像至镜像仓库。 **docker push** **\[镜像仓库地址\]/\[组织名称\]/\[镜像名称2:版本名称2\]**
      示例：
      **docker push swr.cn-east-3.myhuaweicloud.com/container/cloudeye-exporter:2.0.16**
      终端显示如下信息，表明上传镜像成功。
      ```
      ... 
      030***: Pushed 
      2.0.16: digest: sha256:eb7e3bbd*** size: **
      ```
      返回容器镜像服务控制台，在"我的镜像"页面，执行刷新操作后可查看到对应的镜像信息。
      
   
   
   
   
 
#### 部署exporter
Prometheus可以动态监测，一般来说给资源打上Prometheus对应的annotations，Prometheus会自动采集监控信息（默认为"/metrics"路径）。本文使用[cloudeye-exporter](https://github.com/huaweicloud/cloudeye-exporter)作为示例。
Prometheus中常用的annotations如下：
- prometheus.io/scrape：true表示该资源会作为监控目标。
- prometheus.io/path：采集的url，默认为/metrics。
- prometheus.io/port：采集endpoint的端口号。
- prometheus.io/scheme：默认为http，如果为了安全设置了https，此处需要改为https。
 
1. 使用kubectl连接集群。
2. 创建Secret，cloudeye-exporter将使用该Secret进行认证。 
   1. 创建clouds.yml文件，文件内容如下：
      ```
      global:
            prefix: "huaweicloud"
            scrape_batch_size: 300
          auth:
            auth_url: "https://iam.cn-east-3.myhuaweicloud.com/v3"
            project_name: "cn-east-3"
            access_key: "********"
            secret_key: "***********"
            region: "cn-east-3"
      ```
      其中：
      - auth_url：IAM终端节点，可通过[地区和终端节点](https://developer.huaweicloud.com/endpoint?IAM)获取。
      
      - project_name：项目名称。您可在"我的凭证"页面，前往"项目列表"区域查看项目名称和项目ID。
      
      - access_key和secret_key：可通过[访问密钥](https://support.huaweicloud.com/usermanual-ca/ca_01_0003.html)获取。
      
      - region：区域名称，需要与project_name中的项目对应。
       
   
   2. 获取上述文件的base64加密内容字符串。
      ```
      cat clouds.yml | base64 -w0 ;echo
      ```
      回显如下：
      ```
      ICAga*****
      ```
      
   
   3. 创建clouds-secret.yaml文件，其内容如下：
      ```
      apiVersion: v1
      kind: Secret
      data: 
        clouds.yml: ICAga*****  #替换为base64加密字符串
      metadata:
        annotations:
          description: ''
        name: 'clouds.yml'
        namespace: default  #密钥所在的命名空间，需和deployment命名空间保持一致
        labels: {}
      type: Opaque
      ```
      
   
   4. 创建密钥。
      ```
      kubectl apply -f clouds-secret.yaml
      ```
      
   
   
   
   
3. 创建cloudeye-exporter-deployment.yaml文件，内容如下： 
   ```
   kind: Deployment
   apiVersion: apps/v1
   metadata:
     name: cloudeye-exporter
     namespace: default
   spec:
     replicas: 1
     selector:
       matchLabels:
         app: cloudeye-exporter
         version: v1
     template:
       metadata:
         labels:
           app: cloudeye-exporter
           version: v1
       spec:
         volumes:
           - name: vol-166055064743016314
             secret:
               secretName: clouds.yml
               defaultMode: 420
         containers:
           - name: container-1
             image: swr.cn-east-3.myhuaweicloud.com/container/cloudeye-exporter:2.0.16  # 上文构建的exporter镜像地址
             command:
               - ./cloudeye-exporter  # 构建cloudeye-exporter镜像时的启动命令
               - '-config=/tmp/clouds.yml'
             resources: {}
             volumeMounts:
               - name: vol-166055064743016314
                 readOnly: true
                 mountPath: /tmp
             terminationMessagePath: /dev/termination-log
             terminationMessagePolicy: File
             imagePullPolicy: IfNotPresent
         restartPolicy: Always
         terminationGracePeriodSeconds: 30
         dnsPolicy: ClusterFirst
         securityContext: {}
         imagePullSecrets:
           - name: default-secret
         schedulerName: default-scheduler
     strategy:
       type: RollingUpdate
       rollingUpdate:
         maxUnavailable: 25%
         maxSurge: 25%
     revisionHistoryLimit: 10
     progressDeadlineSeconds: 600
   ```
   创建上述工作负载。
   ```
   kubectl apply -f cloudeye-exporter-deployment.yaml
   ```
   
   
4. 创建cloudeye-exporter-service.yaml文件。 
   ```
   apiVersion: v1
   kind: Service
   metadata:
     name: cloudeye-exporter
     namespace: default
     labels:
       app: cloudeye-exporter
       version: v1
     annotations:
       prometheus.io/port: '8087'      #采集endpoint的端口号
       prometheus.io/scrape: 'true'    #设置为true表示该资源会作为监控目标
       prometheus.io/path: "/metrics"  #采集的url，默认为/metrics
       prometheus.io/scheme: "http"    #默认为http，如果为了安全设置了https，此处需要改为https
   spec:
     ports:
       - name: cce-service-0
         protocol: TCP
         port: 8087
         targetPort: 8087
     selector:
       app: cloudeye-exporter
       version: v1
     type: ClusterIP
   ```
   创建上述Service。
   ```
   kubectl apply -f cloudeye-exporter-service.yaml
   ```
   
   
#### 对接Prometheus
Prometheus收集到监控数据后，需要将Prometheus的数据转换成Kubernetes metric api提供给HPA controller使用，这样HPA controller就能根据监控数据进行弹性伸缩。
本示例中需要监控工作负载相关联的ELB指标，因此目标工作负载需要使用负载均衡类型的Service或Ingress。
1. 查看需要监控的工作负载访问方式，获取ELB监听器ID。
   
   1. 在CCE集群控制台中，选择左侧"服务发现"，在"服务"或"路由"页签下查看负载均衡类型的Service或Ingress，单击对应的负载均衡器名称跳转至ELB页面。 ![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000002254691066.png "点击放大")
      
      
   
   2. 在"监听器"页面，查看工作负载所对应的监听器，并复制该监听器ID。 ![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000002254584754.png "点击放大")
      
      
   
   
   
   
2. 使用kubectl连接集群，添加Prometheus的配置。本示例中的采集配置为ELB指标，更多高级用法详情请参见[Configuration](https://prometheus.io/docs/prometheus/latest/configuration/configuration/#scrape_config)。
   
   1. 新建prometheus-additional.yaml文件，添加以下内容并保存：
      ```
      - job_name: elb_metric
        params:
          services: ['SYS.ELB']
        kubernetes_sd_configs:
          - role: endpoints
        relabel_configs:
          - action: keep
            regex: '8087'
            source_labels:
              - __meta_kubernetes_service_annotation_prometheus_io_port
          - action: replace
            regex: ([^:]+)(?::\d+)?;(\d+)
            replacement: $1:$2
            source_labels:
              - __address__
              - __meta_kubernetes_service_annotation_prometheus_io_port
            target_label: __address__
          - action: labelmap
            regex: __meta_kubernetes_service_label_(.+)
          - action: replace
            source_labels:
              - __meta_kubernetes_namespace
            target_label: kubernetes_namespace
          - action: replace
            source_labels:
              - __meta_kubernetes_service_name
            target_label: kubernetes_service
      ```
      
   
   2. 使用上述配置文件创建一个名为additional-scrape-configs的Secret。
      ```
      kubectl create secret generic additional-scrape-configs --from-file prometheus-additional.yaml -n monitoring --dry-run=client -o yaml | kubectl apply -f -
      ```
      
   
   3. 编辑persistent-user-config配置项，开启AdditionalScrapeConfigs能力。
      ```
      kubectl edit configmap persistent-user-config -n monitoring
      ```
      在其中operatorConfigOverride字段下新增一行*--common.prom.default-additional-scrape-configs-key=prometheus-additional.yaml*，开启AdditionalScrapeConfigs能力，示例如下：
      ```
      ...
      data:
        lightweight-user-config.yaml: |
          customSettings:
            additionalScrapeConfigs: []
            agentExtraArgs: []
            metricsDeprecated:
              globalDeprecateMetrics: []
            nodeExporterConfigOverride: []
            operatorConfigOverride: 
            - --common.prom.default-additional-scrape-configs-key=prometheus-additional.yaml
      ...
      ```
      
   
   4. 您可以前往Prometheus查看自定义采集指标是否采集成功。
   
   
   
   
3. 修改名为user-adapter-config的配置项。 
   ```
   kubectl edit configmap user-adapter-config -nmonitoring
   ```
   在rules字段下添加以下内容并保存，其中lbaas_listener_id参数需要替换[1]中获取的监听器ID。
   ```
   apiVersion: v1
   data:
     config.yaml: |-
       rules:
       - metricsQuery: sum(<<.Series>>{<<.LabelMatchers>>,lbaas_listener_id="*****"}) by (<<.GroupBy>>)
         resources:
           overrides:
             kubernetes_namespace:
               resource: namespace
             kubernetes_service:
               resource: service
         name:
           matches: huaweicloud_sys_elb_(.*)
           as: "elb01_${1}"
         seriesQuery: '{lbaas_listener_id="*****"}'
       ...
   ```
   
   
4. 重新部署monitoring命名空间下的custom-metrics-apiserver工作负载。 
   ![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000002289210633.png "点击放大")
   
   
   
 
#### 创建HPA弹性伸缩规则
exporter上报到Prometheus的数据，经过Prometheus adapter监控数据转换成Kubernetes metric api后，就可以创建HPA规则实现弹性伸缩。
1. 查询集群中的自定义指标名称，本文以m7_in_Bps（网络流入速率）为例，其他ELB采集的指标请参见[ELB监听器指标]。
   
   ```
   kubectl get --raw="/apis/custom.metrics.k8s.io/v1beta1" |grep m7_in_Bps
   ```
   回显如下：
   ![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000002255230902.png "点击放大")
   
   
2. 创建HPA规则示例如下，使用ELB的入流量来作为扩容的标准，当m7_in_Bps（网络流入速率）的值超过1k时，会触发名为nginx的Deployment弹性伸缩。 
   ```
   apiVersion: autoscaling/v2
   kind: HorizontalPodAutoscaler
   metadata:
     name: nginx
     namespace: default
   spec:
     scaleTargetRef:
       apiVersion: apps/v1
       kind: Deployment
       name: nginx
     minReplicas: 1
     maxReplicas: 10
     metrics:
       - type: Object
         object:
           metric:
             name: elb01_m7_in_Bps  #上一步中查询到的自定义监控指标名称
           describedObject:
             apiVersion: v1
             kind: Service
             name: cloudeye-exporter
           target:
             type: Value
             value: 1000
   ```
   图2已创建的HPA策略   
   ![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000002255334920.png "点击放大")
   
   
   
3. 创建完后，可以对负载进行压测（也就是通过ELB访问Pod），然后HPA controller会根据设置的值计算是否需要扩容。 
   单击HPA策略操作栏中的"事件"，可从K8s事件中查询扩缩容记录。
   图3扩缩容事件   
   ![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000002255335896.png "点击放大")
   
   
   
 
 #### ELB监听器指标
通过本文方法可采集的ELB监听器指标如下：
表1ELB监听器指标 
| 指标                | 指标名称      | 单位     | 说明                                                        |
|:---|:---|:---|:---|
| m1_cps            | 并发连接数     | 个      | 统计负载均衡器当前处理的并发连接数量。                                       |
| m1e_server_rps    | 后端服务器重置数量 | 个/秒    | 该指标用于统计后端服务器发送至客户端的重置（RST）数据包的计数。这些重置由后端服务器生成，然后由负载均衡器转发。 |
| m1f_lvs_rps       | 负载均衡器重置数量 | 个/秒    | 该指标用于统计负载均衡器生成的重置（RST）数据包的计数。                             |
| m21_client_rps    | 客户端重置数量   | 个/秒    | 该指标用于统计客户端发送至后端服务器的重置（RST）数据包的计数。这些重置由客户端生成，然后由负载均衡器转发。   |
| m22_in_bandwidth  | 入网带宽      | bit/s  | 该指标用于统计负载均衡器当前入网带宽。                                       |
| m23_out_bandwidth | 出网带宽      | bit/s  | 该指标用于统计负载均衡器当前出网带宽。                                       |
| m2_act_conn       | 活跃连接数     | 个      | 该指标用于统计当前处理的活跃连接数量。                                       |
| m3_inact_conn     | 非活跃连接数    | 个      | 该指标用于统计当前处理的非活跃连接数量。                                      |
| m4_ncps           | 新建连接数     | 个      | 该指标用于统计当前处理的新建连接数量。                                       |
| m5_in_pps         | 流入数据包数    | 个      | 该指标用于统计流入负载均衡器的数据包。                                       |
| m6_out_pps        | 流出数据包数    | 个      | 该指标用于统计流出负载均衡器的数据包。                                       |
| m7_in_Bps         | 网络流入速率    | byte/s | 该指标用于统计每秒流入负载均衡器的网络流量。                                    |
| m8_out_Bps        | 网络流出速率    | byte/s | 该指标用于统计每秒流出负载均衡器的网络流量。                                    |
   
 #### 附录：自行开发一个exporter
Prometheus通过周期性的调用exporter的"/metrics"接口获取指标信息，应用只需要通过"/metrics"上报监控数据即可。Prometheus提供了各种语言的客户端，在应用中集成Prometheus客户端可以方便地实现"/metrics"接口，客户端具体请参见[Prometheus CLIENT LIBRARIES](https://prometheus.io/docs/instrumenting/clientlibs/)，开发Exporter具体方法请参见[WRITING EXPORTERS](https://prometheus.io/docs/instrumenting/writing_exporters/)。
监控数据需要Prometheus的格式提供，每条数据提供ELB ID、监听器ID、Service所在的命名空间、Service名称以及Service的uid作为标签，如下所示。
![](https://support.huaweicloud.com/bestpractice-cce/zh-cn_image_0000001381152106.png "点击放大")
获取上述数据的方法如下所示。
1. 查询当前所有Service。 
   Service的返回信息中annotations字段可以查出Service关联的ELB。
   - kubernetes.io/elb.id
   
   - kubernetes.io/elb.class
   
   
   
   
2. 根据上一步查询到ELB实例ID，使用[查询监听器](https://support.huaweicloud.com/api-elb/elb_zq_jt_0002.html)接口查询监听器ID。
3. 获取ELB监控数据。 
   ELB的调用CES[批量查询监控数据](https://support.huaweicloud.com/api-ces/ces_03_0034.html)接口，查询ELB的监控数据，ELB详细的监控数据指标请参见[ELB监控指标说明](https://support.huaweicloud.com/usermanual-elb/elb_ug_jk_0001.html)。例如以下几种参数
   - m1_cps：并发连接数
   
   - m5_in_pps：流入数据包数
   
   - m6_out_pps：流出数据包数
   
   - m7_in_Bps：网络流入速率
   
   - m8_out_Bps：网络流出速率
   
   
   
   
4. 按Prometheus的格式汇聚数据，并通过"/metrics"接口开放出去。 
   Prometheus客户端可以方便地实现"/metrics"接口，具体请参见[Prometheus CLIENT LIBRARIES](https://prometheus.io/docs/instrumenting/clientlibs/)，开发Exporter具体方法请参见[WRITING EXPORTERS](https://prometheus.io/docs/instrumenting/writing_exporters/)。
   
   
 
