k3s hpa 全指南

2022-04-01
6分钟阅读时长
k3s

HPA 概念介绍

HPA 是英文 Horizo​​ntal Pod Autoscaler 的缩写,就是我们说的「水平POD自动扩缩容」,也就是横向扩缩容。它可以根据监控指标,自动扩缩 ReplicationController、Deployment、ReplicaSet 和 StatefulSet 中的 Pod 数量。HPA 可以很好的帮助服务来应对突发的流量,和普通服务中的针对vm的扩缩容有异曲同工之处。

当然除了 HPA,还有集群、纵向等类型的自动扩缩容,本文不做讨论,有兴趣的可参考官方仓库和文档

K3S 和 K8S 的 HPA 概念和使用基本一致,以下统称容器平台为 K8S。

HPA 工作机制

K8S 中 HPA的工作机制大致如下:

HPA控制器会周期性的查询HPA定义的监控指标,当达到触发条件时,触发扩缩容动作。这个周期时间默认是 15s,可通过Controller的参数 --horizontal-pod-autoscaler-sync-period 来自定义。

HPA 指标类型

HPA控制器查询监控指标是通过 APIServer 查询的,这些监控指标是通过 APIServer 的 聚合 (Aggregator )插件机制集成到 APIServer的,结构大致如下:

目前HPA支持的聚合API主要有以下三种:

  • metrics.k8s.io 资源类API,由K8S自带的 Metric server提供,其中主要为 nodes和pods的相关监控数据;
  • custom.metrics.k8s.io 自定义类API,由第三方监控方案提供的适配器(Adapter)提供,目前常用为 Prometheus-Adapter
  • external.metrics.k8s.io 外部的API,和k8s集群没有关系的外部指标,由第三方监控方案适配器提供;

更多聚合API信息可参考官方文档这里

指标类型可分为如下几类:

  • POD 资源使用率:POD级别的性能指标,通常为一个比率值。
  • POD 自定义指标:POD级别的性能指标,通常为一个数值。
  • 容器 资源使用率:容器级别的性能指标,K8S 1.20 引入,主要针对一个POD中多个容器的情况,可指定核心业务容器作为资源扩缩阈值。
  • Object 自定义和外部指标:通常为一个数值,需要容器以某种方式提供,或外部服务提供的指标采集URL。

HPA ApiVersion

K8S 中各种资源在定义时,使用 apiVsersion 来区分不同版本。HPA 目前主要有以下几个常用版本:

  • autoscaling/v1 只支持针对CPU指标的扩缩容;
  • autoscaling/V2beta1 除了cpu,新引入内存利用率指标来扩缩容;
  • autoscaling/V2beta2 除了cpu和内存利用率,新引入了自定义和外部指标来扩缩容,支持同时指定多个指标;

v2 版本相较 v1 做了不少扩展,除了增加自义定和外部指标外,还支持多指标,当定义多个指标时,HPA 会根据每个指标进行计算,其中缩放幅度最大的指标会被采纳。

在定义 HPA 时,需要合理选择版本,以免造成不必要的错误。更多APIVersion信息,可参考官方文档这里

HPA 实战

接下来我们配置测试下上边说的几种类型的指标 HPA,自定义指标和外部指标需要第三方的适配器(Adapter)来提供,针对 Rancher 生态可使用其集成的 Monitoring (100.1.0+up19.0.3) charts 来解决,其中组件 Prometheus Adapter 便是适配器,提供了自定义和外部类型的API。详细安装方法可参考我之前的文章k3s 中 helm 自定义安装 traefik v2,Monitoring 安装方法一样。

安装好适配器之后,我们准备一个nginx服务用来进行自动扩缩容使用,我们使用 cnych/nginx-vts:v1.0 镜像,它集成了 nginx vts 模块,可以查看nginx的各种请求数据。创建 deploymentsservice 如下:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-hpa-test
  namespace: hd-ops
spec:
  selector:
    matchLabels:
      app: nginx-server
  template:
    metadata:
      labels:
        app: nginx-server
    spec:
      containers:
      - name: nginx-demo
        image: cnych/nginx-vts:v1.0
        resources:
          limits:
            cpu: 50m
          requests:
            cpu: 50m
        ports:
        - containerPort: 80
          name: http
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-hpa-test
  namespace: hd-ops
  labels:
    app: nginx-svc
spec:
  ports:
  - port: 80
    targetPort: 80
    name: http
  selector:
    app: nginx-server
#  type: ClusterIP
  type: NodePort

v1 版本 Pod 资源类

首先,我们来测试 v1 版本的HPA,仅支持 POD 资源 CPU 的指标判断。我们可直接使用 kubectl 命令行来创建,命令如下:

# 创建
$ kubectl autoscale deployment nginx-hpa-test --cpu-percent=20 --min=1 --max=10
# 查看
$ kubectl get hpa -A 
NAMESPACE   NAME               REFERENCE                   TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
hd-ops      nginx-hpa-test     Deployment/nginx-hpa-test   2%/20%    1         10        1          1m

其中关注下 TARGETS 列,前边为 CPU 当前使用,后边为触发阈值。我们给 nginx 服务增加压力,观察 hpa 情况,可使用如下命令:

# 加压 替换 node-ip 为 pod 部署的具体 node ip,node-port 为随机映射暴露的节点端口
for i in {0..30000};do echo "start-------->:${i}";curl http://<node-ip>:<node-port>/status/format/prometheus;echo "=====================================:${i}";sleep 0.01;done

# 查看hpa情况
$ kubectl get hpa
NAME             REFERENCE                   TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
nginx-hpa-test   Deployment/nginx-hpa-test   26%/20%   1         10        2          3h48m

我们可以看到pod的副本数由1个变为了2个,成功触发hpa,扩容1个pod。

当我们停止压力之后再观察HPA,并没有马上缩容,这是因为k8s的冷却延迟机制。可通过控制器参数--horizontal-pod-autoscaler-downscale-stabilization来自定义,默认为 5m。

# 五分钟后查看,pod 成功缩容。
~ ⌚ 19:16:08
$ kubectl get hpa -n hd-ops -o wide 
NAME             REFERENCE                   TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
nginx-hpa-test   Deployment/nginx-hpa-test   0%/20%    1         10        2          3h54m

~ ⌚ 19:16:11
$ kubectl get hpa -n hd-ops -o wide 
NAME             REFERENCE                   TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
nginx-hpa-test   Deployment/nginx-hpa-test   0%/20%    1         10        1          4h1m

至此,v1 版本的 HPA 行为符合预期,当CPU 的使用率超过阈值后,会扩容目标 pod,当使用率低于阈值后,会在冷却期结束后缩容。

除了使用 kubectl 命令行来定义 hpa,也可以通过 yaml 来定义,如上 hpa,与下边的 yaml 文件等效。

apiVersion: autoscaling/v1
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-hpa-demo
spec:
  scaleTargetRef:  # 目标作用对象
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-hpa-test
  minReplicas: 1  # 最小副本数
  maxReplicas: 10  # 最大副本数
  metrics:  # 目标指标值
  - type: Resource  # 资源类型指标
    resource:
      name: cpu
      targetAverageUtilization: 20

v2版本自定义指标

接下来,我们看下 v2 版本的自定义指标,需要你 K8S 在 1.6 以上。我们可按如下步骤来配置:

1/ 确保自定义指标数据已推送到 Prometheus

理论上来说,只要在 Prometheus 中的数据,我们都可以作为触发指标来配置 HPA。Rancher平台中,使用 Prometheus Operator 的 CRD 来安装管理的监控组件,我们可通过自定义 ServiceMonitor 来给 Prometheus 添加 target 采集对象。针对上边我们Nginx 的数据,我们可以编写如下的 ServiceMonitor:

apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: nginx-hpa-svc-monitor
  namespace: cattle-monitoring-system
  labels:
    app: nginx-hpa-svc-monitor
spec:
  jobLabel: app
  selector:
    matchLabels:
      app: nginx-svc
  namespaceSelector:
    matchNames:
    - hd-ops
  endpoints:
  - port: http
    interval: 30s
    path: /status/format/prometheus

应用之后,我们可以在 prometheus 的 target 页面看到如下的 target 列表项:

serviceMonitor/cattle-monitoring-system/nginx-hpa-svc-monitor/0 (1/1 up)

2/ 选择指标,注册到自定义指标API

Nginx vts 接口 /status/format/prometheus中,提供了很多 nginx 的请求数据指标,我们拿 nginx_vts_server_requests_total 指标来计算一个实时的请求数作为 HPA 的触发指标。我们需要将该指标注册到 HPA 请求的 custom.metrics.k8s.io 中。Rancher 平台中,我们可以覆盖 Monitoring (100.1.0+up19.0.3) charts 的 values 来注册指标 ,配置如下:

prometheus-adapter:
  enabled: true
  prometheus:
    # Change this if you change the namespaceOverride or nameOverride of prometheus-operator
    url: http://rancher-monitoring-prometheus.cattle-monitoring-system.svc
    port: 9090
  # hpa metrics  
  rules:
    default: false  # 需要设置为 false,否则与下边 custom 的指标冲突,会造成整个 custom 指标为空,不知道是不是bug;
    custom:
    # 具体详细的参数配置可参考官方文档:https://docs.rancher.cn/docs/rancher2/cluster-admin/tools/cluster-monitoring/cluster-metrics/custom-metrics/_index
    - seriesQuery: "nginx_vts_server_requests_total"  # '{__name__=~"^nginx_vts_.*_total",namespace!="",pod!=""}'
      seriesFilters: []
      resources:
        overrides:
          namespace: # 这里的namespace和pod是prometheus里面指标的标签,会作为分组
            resource: namespace
          service:
            resource: services
          pod:
            resource: pod
      name:
        matches: ^(.*)_total
        as: "${1}_per_second"
      metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)

运行如下命令,更新组件。

helm upgrade  --install rancher-monitoring ./ -f k3s-values.yaml -n cattle-monitoring-system

可我们通过 API 查看是否注册成功:

# 查看 api 列表
$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/" |python3 -m json.tool
{
    "kind": "APIResourceList",
    "apiVersion": "v1",
    "groupVersion": "custom.metrics.k8s.io/v1beta1",
    "resources": [
        {
            "name": "namespaces/nginx_vts_server_requests_per_second",
            "singularName": "",
            "namespaced": false,
            "kind": "MetricValueList",
            "verbs": [
                "get"
            ]
        },
        {
            "name": "services/nginx_vts_server_requests_per_second",
            "singularName": "",
            "namespaced": true,
            "kind": "MetricValueList",
            "verbs": [
                "get"
            ]
        },
        {
            "name": "pods/nginx_vts_server_requests_per_second",
            "singularName": "",
            "namespaced": true,
            "kind": "MetricValueList",
            "verbs": [
                "get"
            ]
        }
    ]
}
# 查看具体某个指标的数值 
$ kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/hd-ops/pods/*/nginx_vts_server_requests_per_second" |python3 -m json.tool
{
    "kind": "MetricValueList",
    "apiVersion": "custom.metrics.k8s.io/v1beta1",
    "metadata": {
        "selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/hd-ops/pods/%2A/nginx_vts_server_requests_per_second"
    },
    "items": [
        {
            "describedObject": {
                "kind": "Pod",
                "namespace": "hd-ops",
                "name": "nginx-hpa-test-7695f97455-mmpxz",
                "apiVersion": "/v1"
            },
            "metricName": "nginx_vts_server_requests_per_second",
            "timestamp": "2022-04-01T07:21:28Z",
            "value": "133m",  # 此处单位m 为千分之的意思
            "selector": null
        }
    ]
}

看到如上返回,说明注册成功了,之后我们就可以使用其中的指标名称来配置 HPA 了。

3/ 配置HPA

编写 HPA yaml 如下:

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-hpa-custom
  namespace: hd-ops
spec:
  scaleTargetRef:  # 应用目标服务 Deployment 
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-hpa-test
  minReplicas: 1  # 保留最小副本数
  maxReplicas: 10  # 扩容最大副本数
  metrics:
    - type: Pods  # 指标类型 
      pods:
        metric:
          name: nginx_vts_server_requests_per_second  # 指标名称
        target:
          type: AverageValue
          averageValue: 5

应用之后,我们可以看到数据已经采集到:

$ kubectl get hpa -A 
NAMESPACE   NAME               REFERENCE                   TARGETS   MINPODS   MAXPODS   REPLICAS   AGE
hd-ops      nginx-hpa-custom   Deployment/nginx-hpa-test   133m/5    1         10        1          1m

4/ 测试 HPA

运行如下命令加压测试:

# 加压
for i in {0..30000};do echo "start-------->:${i}";curl http://<node-ip>:<node-port>/status/format/prometheus;echo "=====================================:${i}";sleep 0.01;done

# 查看hpa情况
$ kubectl get hpa -A 
NAMESPACE   NAME               REFERENCE                   TARGETS    MINPODS   MAXPODS   REPLICAS   AGE
hd-ops      nginx-hpa-custom   Deployment/nginx-hpa-test   11333m/5   1         10        3          72m

请求数超过了阈值 5,可以看到 REPLICAS 副本数变成了3。停止压测后,5分钟后查看 hpa,又缩容回去,符合预期。

v2 版本的 HPA 还可以使用其他类型,且支持多个指标,如下示例:

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-hpa-custom
  namespace: hd-ops
spec:
  scaleTargetRef:  # 应用目标服务 Deployment 
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-hpa-test
  minReplicas: 1  # 保留最小副本数
  maxReplicas: 10  # 扩容最大副本数
  metrics:
    - type: Pods  # 指标类型 
      pods:
        metric:
          name: nginx_vts_server_requests_per_second  # 指标名称
        target:
          type: AverageValue
          averageValue: 5
    - type: Resource  # 资源类型和v1版本写法略有区别,需要注意
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 50
    - type: Object  # 除pod外的其他资源对象类型
      object:
        metric:
          name: requests-per-second
        describedObject:
          apiVersion: networking.k8s.io/v1beta1
          kind: Ingress
          name: main-route
        target:
          kind: Value
          value: 10k  # 数值,直接与获取到的指标值比较

指标类型 type 有下边4种取值:

  • Resource: 指当前伸缩对象pod的 cpu 和 内存 支持 utiliztion 和 averagevalue 类型的目标值。一般情况下,计算CPU使用率使用averageUtilization定义平均使用率。计算内存资源,使用 AverageValue 定义平均使用值。
  • Pods: 指的是伸缩对象Pod的指标,数据有第三方的 Adapter 提供,只支持 AverageValue 类型的目标值。
  • Object: K8S 内部除pod之外的其他对象的指标,数据有第三方的 Adapter 提供,支持 Value 和 AverageValue 类型的目标值。
  • External: K8S 外部的指标,数据有第三方 Adapter 提供,支持 Value 和 AverageValue 类型的目标值。

更多 type 类型信息,可参考官方文档这里

v2版本外部指标

最后我们来测试下外部指标,要求K8S版本在 1.10 以上,配置步骤和自定义表一致,在注册指标到 /apis/external.metrics.k8s.io/v1beta1/ 时配置略有区别,

我们这里随便拿一个与K8S无关的外部主机的 cpu 使用率做下测试,注册配置如下:

prometheus-adapter:
  enabled: true
  prometheus:
    # Change this if you change the namespaceOverride or nameOverride of prometheus-operator
    #url: http://rancher-monitoring-prometheus.cattle-monitoring-system:9090/
    url: http://rancher-monitoring-prometheus.cattle-monitoring-system.svc
    port: 9090
  # hpa metrics
  rules:
    default: false
    # 如下为外部配置
    external:
    - seriesQuery: 'cpu_usage_idle{cpu="cpu-total"}'  # '{__name__=~"^nginx_vts_.*_total",namespace!="",pod!=""}'
      seriesFilters: []
      resources:
        template: <<.Resource>>
      name:
        matches: "^(.*)"
        as: "cpu_usage"
      metricsQuery: (100-cpu_usage_idle{cpu="cpu-total",ip="192.168.10.10"})/100 #<<.Series>>{<<.LabelMatchers>>}

这里 resources 我们直接使用 template 来模糊定义让系统自动处理,因为外部指标和 K8S API 资源并不一定有关系,我们 metricsQery 这块也使用明确写出的方式来处理。

应用之后,我们同样可以使用 API 来查看:

# 查看指标列表
$ kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/"|python3 -m json.tool
{
    "kind": "APIResourceList",
    "apiVersion": "v1",
    "groupVersion": "external.metrics.k8s.io/v1beta1",
    "resources": [
        {
            "name": "cpu_usage",
            "singularName": "",
            "namespaced": true,
            "kind": "ExternalMetricValueList",
            "verbs": [
                "get"
            ]
        }
    ]
}
# 查看具体的指标数据
$ kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/hd-ops/cpu_usage"|python3 -m json.tool
{
    "kind": "ExternalMetricValueList",
    "apiVersion": "external.metrics.k8s.io/v1beta1",
    "metadata": {},
    "items": [
        {
            "metricName": "cpu_usage",
            "metricLabels": {
                "__name__": "cpu_usage",
                "bg": "ops",
                "cpu": "cpu-total",
                "idc": "ali",
                "ident": "xxxx-host",
                "ip": "192.168.10.10",
                "region": "BJ",
                "svc": "demo-test"
            },
            "timestamp": "2022-04-02T03:28:50Z",
            "value": "11m"
        },
        ......
  ]
}

有时候 API 会返回为空,大概率是我们注册的API rule 配置有问题,可修改配置明确查询 PromQL 测试。

定义 HPA 和 custom 类型也一样,如下:

apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
  name: nginx-hpa-custom
  namespace: hd-ops
spec:
  scaleTargetRef:  # 应用目标服务 Deployment
    apiVersion: apps/v1
    kind: Deployment
    name: nginx-hpa-test
  minReplicas: 1  # 保留最小副本数
  maxReplicas: 10  # 扩容最大副本数
  metrics:
    - type: External
      external:
        metric:
          name: cpu_usage
          selector:
            matchLabels:
              ip: "192.168.10.10"
        target:
          type: AverageValue
          averageValue: 10m  # 该数值为使用率 1% ,测试无实际意义

应用后查看 hpa 情况:

$ kubectl get hpa -A 
NAMESPACE   NAME                        REFERENCE                   TARGETS          MINPODS   MAXPODS   REPLICAS   AGE
hd-ops      nginx-hpa-custom-external   Deployment/nginx-hpa-test   18m/10m (avg)    1         10        2          11m

可以看到目标值 18m 超过了 阈值 10m,目标服务副本有1个变为2个,符合预期。

总结及问题回顾

至此,HPA 的三种大类型我们都测试完成。Rancher K3S 生态中,总体上来说和 K8S 并没有什么不同。在 Rancher 平台中有下边几个问题需要需要注意下:

  • 1、Rancher Charts 商店中 Monitor 集成的 Prometheus Adapter 中,当定义 custom 和 external rule规则时,需要关闭 default 默认的规则,否则会认证失败,造成整个API请求为空。错误信息如下:
client.go:233: [debug] error updating the resource "rancher-monitoring-alertmanager.rules":
	 cannot patch "rancher-monitoring-alertmanager.rules" with kind PrometheusRule: Internal error occurred: failed calling webhook "prometheusrulemutate.monitoring.coreos.com": Post "https://rancher-monitoring-operator.cattle-monitoring-system.svc:443/admission-prometheusrules/validate?timeout=10s": context deadline exceeded
  • 2、Rule 配置问题,注意版本和配置的关系,有些配置格式不同版本不一致。

参考

Avatar
DeanWu 大家好,我是 DeanWu,一个努力成为「真正」 SRE 的人。