# k8s集群应用基于Prometheus实现HPA

> 作者/来源: UCloud 运营管理员
> 发布时间: 2023-01-11T05:19:00.000Z
> 分类: 其他
> 标签: AI, Kubernetes
> 原文链接: http://117.50.162.249:3000/yun/articles/1414

---

# k8s集群应用基于Prometheus实现HPA

> 来源: https://www.ucloud.cn/yun/129487.html
> 作者: IT那活儿
> 发布日期: 发布于2023-01-11 13:19

k8s集群应用基于Prometheus实现HPA

**点击上方“IT那活儿”公众号，关注后了解更多内容，不管IT什么活儿，干就完了！！！**

# 01

在k8s集群中，我们希望当应用负载过大的时候，可以对应用进行自动扩容，提升pod的副本数来应对大量的流量，当负载小的时候可以对应用进行自动缩容，以避免资源浪费，这个时候就**需要给应用做一个HPA**。

但是并非所有系统应用都可以通过多带带依靠CPU/内存使用量度来满足其SLA。有时候，我们希望除了CPU/内存指标外有更多的指标能用于实现HPA。**这样不仅能保证实现HPA指标的多样性，还可以通过多指标对系统应用做HPA，以更好地处理突发事件并确保高可用性。**

# 02

## **HPA原理**

对k8s比较熟悉的人，都知道k8s集群里的应用在调度和扩缩容的时候都有自己的一套算法，自动弹性伸缩的原理是怎样的呢，**下面举一个实际例子进行阐述**。

假设存在一个叫A的Deployment，包含3个Pod，每个副本的Request值是1核，当前3个Pod的CPU利用率分别是60%、70%与80%，此时我们设置HPA阈值为50%，最小副本为3，最大副本为10。

**接下来我们将上述的数据带入公式中：**

- 总的Pod的利用率是60%+70%+80% = 210%；
- 当前的Target是3；
- 算式的结果是70%，大于50%阈值，因此当前的Target 数目过小，需要进行扩容；
- 重新设置 ，此时算式的结果为42%低于50%，判断还需要扩容两个容器；
- 此时HPA设置Replicas为5，进行Pod的水平扩容。

# 03

## **影响HPA的细节**

**经过前面的推演，可以协助我们快速理解HPA最核心的原理**，不过上面的推演结果和实际情况下是有所出入的，如果我们进一步试验的话，会发现 Replicas最终的结果是6而不是5。这是由于 HPA 中一些细节的处理导致的，**主要包含如下三个主要的方面：**

### **3.1 噪声处理**

通过前面的公式可以发现，Target的数目很大程度上会影响最终的结果，而在Kubernetes中，无论是变更或者升级，都更倾向于使用Recreate而不是Restart的方式进行处理。

这就导致了在Deployment的生命周期中，可能会出现某一个时间，Target会由于计算了Starting或者 Stopping的Pod而变得很大。这就会给HPA的计算带来非常大的噪声，在HPA Controller的计算中，如果发现当前的对象存在Starting或者Stopping的Pod会直接跳过当前的计算周期，等待状态都变为Running再进行计算。

### **3.2 冷却周期**

HPA控制器观测资源使用率并作出决策是有周期的，执行是需要时间的，在执行自动伸缩过程中metrics不是静止不变的，可能降低或者升高，如果执行太频繁可能导致资源的使用快速抖动，因此控制器每次决策后的一段时间内不再进行新的决策。

在弹性伸缩中，冷却周期是不能逃避的一个话题，很多时候我们期望快速弹出与快速回收，而另一方面，我们又不希望集群震荡，所以一个弹性伸缩活动冷却周期的具体数值是多少，一直被开发者所挑战。在HPA中，默认的扩容冷却周期是3min，缩容冷却周期是5min。可以通过调整**kube-controller-manager**组件启动参数设置冷却时间：

--horizontal-pod-autoscaler-downscale-delay扩容冷却

--horizontal-pod-autoscaler-upscale-delay缩容冷却

针对不同的k8s版本，可以参考相应k8s版本kube-controller-manager组件关于HPA的启动参数：

![](https://ucloud-blog.cn-bj.ufileos.com/articles/129487/images/129487_000.png)

### **3.3 边界值计算**

我们回到刚才的计算公式，第一次我们算出需要弹出的容器数目是 5，此时扩容后整体的负载是42%，但是我们似乎忽略了一个问题：

**一个全新的 Pod启动会不会自己就占用了部分资源？**

**此外，8%的缓冲区是否就能够缓解整体的负载情况？**

要知道当一次弹性扩容完成后，下一次扩容要最少等待3分钟才可以继续扩容。为了解决这些问题，HPA引入了边界值，目前在计算边界条件时，会自动加入10%的缓冲，这也是为什么在刚才的例子中最终的计算结果为6的原因。

# 04

## **HPA 的API版本**

目前HPA已经支持**三大版本**：autoscaling/v1、autoscaling/v2beta1和autuscaling/v2beta2 三个大版本。

- **API的v1版本**，在当前稳定版本（autoscaling/v1）中只支持基于 CPU 指标的扩缩。
- **API的beta版本**，autoscaling/v2beta1版和autuscaling/v2beta2版引入了基于内存和自定义指标的扩缩，在autoscaling/v2beta1中增加支持custom metrics，在 autoscaling/v2beta2 中增加支持 external metrics。

# 05

## **实现流程图**

![](https://ucloud-blog.cn-bj.ufileos.com/articles/129487/images/129487_001.png)

**关键组件介绍：**

- **Prometheus：**采集Pod的性能指标数据。
- **Custom Metrics server：**从Prometheus中采集性能指标数据。它是资源指标数据的聚合器，实现了自定义指标API（Resource Metres API），通过Kubernetes的Custom Metrics server（Prometheus Adapter)层将自定义指标HPA注册Maste API server中，以/apis/custom.metrics.k8s.io路径提供指标数据。
- **HAI Controler：**为APA控制器，通过自定义指标API从API Server中获取指标数据，以决策扩缩容操作。

## 06 **安装**

组件包括Prometheus，Metrics server，Custom Metrics server（prometheus-adapter），如果集群已经安装了Prometheus，Metrics server，只需安装Custom Metrics server即可。

### **6.1 安装Custom Metrics server**

测试环境当中已经安装了Prometheus，Metrics server，这里只需安装Custom Metrics server即可，下载prometheus-adapter的helm chart，使用helm一键部署。

编辑values.yaml添加平台Prometheus.service地址，使用如下命令一键部署：

```
helm install  prometheus-adapter --namespace monitoring ../prometheus-adapter/ -f values.yaml
```

### **6.2 配置**

我们可以将Prometheus中的任何一个指标都用于HPA，但是前提是我们能通过查询语句将它拿到（包括指标名称和其对应的值）。

Prometheus适配器在configmap里配置了如何从prometheus获取数据，并与k8s的资源做对应，以及如何在api接口中展示的规则。

![](https://ucloud-blog.cn-bj.ufileos.com/articles/129487/images/129487_002.png)

# 07

## **验证**

列出prometheus提供的自定义指标，发现所有Prometheus里能查看的监控指标在这里都被获取到了。

```
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq .
```

获取monitoring命名孔径下所有Pod的FS使用率：

```
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/monitoring/pods/*/fs_usage_bytes" | jq .
```

从自定义metrics API中获取每秒请求总数。

例如Prometheus监控的应用，暴露了名为http\_requests\_total的自定义指标。Prometheus适配器删除\_total后缀，并将度量标记为计数器度量(counter metric)。

```
kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/*/http_requests" | jq .{"kind": "MetricValueList","apiVersion": "custom.metrics.k8s.io/v1beta1","metadata": {"selfLink": "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/%2A/http_requests"},"items": [{"describedObject": {"kind": "Pod","namespace": "default","name": "podinfo-6c994884cf-m6l6m","apiVersion": "/v1"},"metricName": "http_requests","timestamp": "2020-10-09T03:01:01Z","value": "1072m"},{"describedObject": {"kind": "Pod","namespace": "default","name": "podinfo-6c994884cf-pns2n","apiVersion": "/v1"},"metricName": "http_requests","timestamp": "2020-10-09T03:01:01Z","value": "1035m"}]}
```

自定义API SERVER收到请求后会从Prometheus里面查询http\_requests\_total的值，然后把这个值换算成一个以时间为单位的请求率。

m代表milli-units，例如1035m代表1035 milli-requests（就是大约1个请求），用十进制表示为 1.035。可能度量指标API将返回没有后缀的整数，否则返回以千分单位的数量。

这意味着我们可能会看到你的度量指标在1和1035m（也就是在十进制记数法中的 1 和 1.035之间波动）。

### **7.1 创建HPA**

基于pod/svc/CPU/memory创建HPA：

```
apiVersion: autoscaling/v2beta2kind: HorizontalPodAutoscalermetadata:name: podinfo-hpa-fnamespace: defaultspec:scaleTargetRef:apiVersion: apps/v1kind: Deploymentname: podinfominReplicas: 1maxReplicas: 5metrics:- type: Resourceresource:name: cputarget:type: UtilizationaverageUtilization: 80- type: Resourceresource:name: memorytarget:type: AverageValueaverageValue: 200Mi- type: Podspods:metric:name: http_requeststarget:type: AverageValueaverageValue: 50- type: Objectobject:metric:name: http_requestsdescribedObject:apiVersion: v1kind: servicename: podinfotarget:type: Valuevalue: 50
```

一个HPA中可以定义多种指标，如果定义多个指标将针对每种类型指标都计算Pod副本数量，取最大的进行扩缩容。

换句话说，系统会根据CPU和pod的自定义指标计算，在每个调度周期（默认为30s）都会计算出一个缩放的推荐值并记录下来，在每次计算缩放值时都会查看历史的推荐值，从最近的一段历史推荐值中挑选最大的，任何一个达到了都进行扩容。

**上面HPA相关配置如下：**

**1）scaleTargetRef：**自动扩容缩容的对象，可以是Deployment或者ReplicaSet，这里写具体的Deployment的名称。

**2）metrics：**这里是指标的目标值。在type中定义类型；通过target来定义指标的阈值，系统将在指标达到阈值的时候出发扩缩容操作。

**3）****可以指定资源度量指标使用绝对数值或者百分比**，需要将 target 类型 AverageUtilization（百分比）替换成AverageValue（绝对数值），同时将target.averageUtilization替换成target.averageValue并设定相应的值。百分比适用于cpu/内存指标。

**4）metrics中的type有如下类型：**

- **Resource：**基于资源的指标，可以是CPU或者是内存，如果基于这个类型的指标来做只需要部署Metric-server即可，不需要部署自定义APISERVER。
- **Pods：**基于Pod的指标，系统将对Deployment中的全部Pod副本指标进行平均值计算，如果是Pod则该指标必须来源于Pod本身。
- **Object：**基于Ingress或者其他自定义指标，比如ServiceMonitor。它的target类型可以是Value或者AverageValue（根据Pod副本数计算平均值）。

### **7.2 压测服务并验证HPA**

以每秒200个请求的速度为podinfo服务加压，

```
[root@ysgz-33 home]# ./bin/hey -n 10000 –q 2 -c 100 http://10.3.37.189:9898
```

一段时间后发现podinfo服务的pod数量增加了，撤掉加压，过一会儿pod数量发生了缩减。

可以通过kubectl describe hpa 命令查看当前影响HPA的各种状态条件信息。

![](https://ucloud-blog.cn-bj.ufileos.com/articles/129487/images/129487_003.png)

使用 autoscaling/v2beta2 格式的 HorizontalPodAutoscale时，可以看到Kubernetes为 HPA设置的状态条件（Status Conditions）。

这些状态条件可以显示当前HPA是否能够执行扩缩以及是否受到一定的限制。**status.conditions字段展示了这些状态条件：**

- **AbleToScale**表明HPA是否可以获取和更新扩缩信息，以及是否存在阻止扩缩的各种回退条件。
- **ScalingActive**表明HPA是否被启用（即目标的副本数量不为零）以及是否能够完成扩缩计算。当这一状态为False时，通常表明获取度量指标存在问题。
- **ScalingLimitted** 表明所需扩缩的值被HPA所定义的最大或者最小值所限制。（即已经达到最大或者最小扩缩值）

# 08

## **扩展**

本次测试使用的压测工具安装。

hey压测工具安装：

```
yum install golang -yexport GOROOT=/usr/lib/golangexport GOPATH=/home
```

# 生效配置：

```
source /etc/profilecd /homego get -u github.com/rakyll/heygo install github.com/rakyll/hey./bin/hey -n 10000 -q 10 -c 50 http://地址
```

使用域名时，注意添加本地域名解析：

-n 请求次数

-q 请求速度

-c 请求并发数

# 09

## **总结**

**9.1** 在集群中做了这样一个配置后，集群可支持一个HPA中定义多种指标（cpu/内存，访问量及其他Prometheus能获取的指标）实现HPA；

**9.2** 如果定义了多种指标，系统会根据CPU和pod的自定义指标计算，在每个调度周期（默认为30s）都会计算出一个缩放的推荐值并记录下来，在每次计算缩放值时都会查看历史的推荐值，从最近的一段历史推荐值中挑选最大的，任何一个达到了都进行扩容。

![](https://ucloud-blog.cn-bj.ufileos.com/articles/129487/images/129487_004.png)

END

## 本文作者：符 海

## 本文来源：IT那活儿（上海新炬王翦团队）

![](https://ucloud-blog.cn-bj.ufileos.com/articles/129487/images/129487_005.png)