一、需求说明
水平弹性伸缩是 k8s 的一个重要的特性,我们在使用 k8s 的过程中,通常会使用 HPA 功能基于一些指标让 k8s 自动调整 pod 数量,在设置了 cpu 和 memory 资源 request 的情况下,在不借助其他三方组件的情况下,HPA 就可以基于其中的 Pods 的资源用量来实现目标资源的扩缩。但是 cpu 和 memory 的用量很多时候时候并不能体现出业务应用的真实负载情况,比如说非资源密集型应用,存在资源使用率不高、但实际上已经达到负载瓶颈的场景;还有多线程并发应用,线程池配置较小也会导致在业务高峰期间线程池耗尽,部分请求进入队列排队等候处理进而造成响应超时等问题,所以需要使用能精确反映真实负载情况的监控指标作为 HPA 的依据,比如 QPS、线程池使用率等。
以下就我们使用阿里云 ACK 服务、自建 Prometheus 收集业务应用上报的 metric 监控数据样本实现基于自定义指标的 HPA(水平伸缩)方案。
二、环境说明
- 运行在 k8s 集群(阿里云 ack 托管版)中的、基于 Spring Cloud 框架的 java 微服务
- 包含核心接口的 java 服务使用 Actuator 组件暴露出 http 请求情况和线程池使用情况,metric 示例:tomcat_threads_busy_threads(tomcat 忙碌线程数)、tomcat_threads_config_max_threads(tomcat 线程池最大线程数);http_server_requests_seconds_count(http 请求计数)等。
- 自建 Prometheus 已经完成上述监控样本的收集,并且在收集时注入用于辨识 namespace 的 label。
三、实现说明
- 通过 adapter 插件将 Prometheus 指标转化为 HPA 可识别的格式,ACK 在自己的应用市场提供了 alibaba-cloud-metrics-adapter 组件,按照说明安装即可;
- 常见的 hpa 指标类型有:资源指标(resource metrics)、自定义度量指标(custom metrics)、与 Kubernetes 对象无关的度量指标(External metrics),以下我们使用 External 指标类型进行配置。
- 在 alibaba-cloud-metrics-adapter 组件中对接自建 Prometheus,并且添加配置从聚合 API(metrics.k8s.io、custom.metrics.k8s.io 或 external.metrics.k8s.io)获取指标。
- 对接自建 Prometheus 只需要在 alibaba-cloud-metrics-adapter 安装中,将 url 配置为自建 Prometheus 的数据访问地址(需保证集群内服务和该地址网络互通)即可,如:
http://172.168.1.1:9090
- 添加新的转换规则,将自定义 metric 转换为 HPA 可识别的格式,如以下配置实现:计算 Prometheus 里的 http_server_requests_seconds_count 指标两分钟内的每秒变化速率(QPS),并将其转化名为 http_index_qps 指标供为 HPA 使用:
- metricsQuery: sum(rate(<<.Series>>{uri="/api/v1/openapi/index",<<.LabelMatchers>>}[2m])) by (env) name: as: ${1}_index_qps matches: (.*)_server_requests_seconds_count resources: namespaced: false overrides: env: resource: namespace seriesQuery: http_server_requests_seconds_count
- 对接自建 Prometheus 只需要在 alibaba-cloud-metrics-adapter 安装中,将 url 配置为自建 Prometheus 的数据访问地址(需保证集群内服务和该地址网络互通)即可,如:
-
配置无误后即可在命令行中使用 kubectl 获取指标详情:
kubectl get --raw "/apis/external.metrics.k8s.io/v1beta1/namespaces/demo-prod/http_index_qps" | jq .
,返回内容示例:{ "kind": "ExternalMetricValueList", "apiVersion": "external.metrics.k8s.io/v1beta1", "metadata": {}, "items": [ { "metricName": "http_index_qps", "metricLabels": { "env": "demo-prod" }, "timestamp": "2022-06-09T06:59:39Z", "value": "715700m" } ] }
- 添加 HPA 规则,实现基于 QPS 的水平伸缩
- QPS 扩缩容阈值为 1200000m,这里的 m 为“豪”的意思,类似于毫秒和秒的关系,100m = 1,这里的 1200000m 意思为 1200 QPS。
- 期望副本数 = ceil[当前副本数 * (当前指标 / 期望指标)],则不同阶段的节点数变化情况为(假如当前 QPS 为 700,副本节点数为 2),ceil 表示向上取整数。
- 设置缩容行为:添加窗口期避免误触、每次缩容 1 个节点或者节点数的 5%(取较少值)。
```yaml
apiVersion: autoscaling/v2beta2
kind: HorizontalPodAutoscaler
metadata:
name: demo-micro-service
namespace: demo-prod
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: demo-micro-service
minReplicas: 2
maxReplicas: 10
behavior:
scaleDown:
stabilizationWindowSeconds: 600
policies:
- type: Pods
value: 1
periodSeconds: 60
- type: Percent
value: 5
periodSeconds: 60
selectPolicy: Min
metrics:
- type: External external: metric: name: http_index_qps selector: matchLabels: env: “demo-prod” target: type: Value value: 1200000m ```
- 实际 pod 数量调整过程
- 当实际 QPS 为 700 时,ceil[2 * (700/1200)] = 1.16,实际调整为:2;
- 当实际 QPS 提升到 1000 时,ceil[2 * (1000/1200)] = 1.67,实际调整为:2;
- 当实际 QPS 提升到 1300 时,ceil[2 * (1300/1200)] = 2.17,实际调整为:3;
- 当实际 QPS 提升到 2000 时,ceil[3 * (2000/1200)] = 5.01,实际调整为:6;
- 当实际 QPS 提升到 2500 时,ceil[6 * (2500/1200)] = 12.49,向上取整为 13,但是受到 maxReplicas 限制,实际调整为:10;
- 当实际 QPS 大于 2500 时,受到 maxReplicas 限制,最高保持 10 个节点;
- 当实际 QPS 降低到 2000 时,ceil[10 * (2000/1200)] = 15,向上取整为 16,但是受到 maxReplicas 限制,实际调整为:10;
- 当实际 QPS 降低到 1300 时,ceil[10 * (1300/1200)] = 10.83,向上取整为 11,但是受到 maxReplicas 限制,实际调整为:10;
- 当实际 QPS 降低到 1000 时,ceil[10 * (1000/1200)] = 8.33,向上取整为 9,实际调整为:9;
- 实际 QPS 在 1000 保持一段时间,下一个检测周期:ceil[9 * (1000/1200)] = 7.4,向上取整为 8,实际调整为:8;
- 实际 QPS 在 1000 保持一段时间,下一个检测周期:ceil[8 * (1000/1200)] = 6.7,向上取整为 7,实际调整为:7;
- 重复上述步骤,最终降低至 2 个节点。
-
基于多个指标来执行扩缩
判断服务的实际负载情况,有时候一个维度可能不够精准,就需要结合多个指标综合判断,如果给一个微服务添加多个 HPA 规则,肯定会导致异常:规则 A 扩容上去的节点可能会被规则 B 缩下去,俩规则反复拉扯;如果使用一个复合指标进行扩缩容的话,因为各个指标的维度、表示的意义都不一样,很难将两个毫不相关的数值整合为一个真实有效的指标数据;如果你的 k8s 版本为 v1.23+,那么 autoscaling/v2 的 API 就支持基于多个指标来执行扩缩,如果 k8s 版本小于 v1.23,那么此功能作为 beta 特性在 autoscaling/v2beta2 API 版本中提供,工作原理为:HorizontalPodAutoscaler 控制器评估每个指标,并根据该指标提出一个新的比例。HorizontalPodAutoscaler 采用为每个指标推荐的最大比例,并将副本节点设置为该大小(前提是这不大于你配置的总体最大值)
四、实际规划方案:
- 可用于 HPA 的指标列表:
- 池化资源使用率,我们已有的池化资源可分类为:tomcat 线程池、Hystrix 线程池、jedis 连接池等;
- 核心接口 QPS;
- 基础资源使用率。
- 分析:
- 池化资源使用率:
池化资源在配置不合理的情况下可能会导致:实际业务压力不高、pod 基础资源(CPU、内存、IO)使用率不高的时候,仍耗尽池内资源造成阻塞,这种情况建议通过监控告警等方式发现问题,一次性彻底优化解决,将单节点池化资源使用率和 QPS 正相关关联起来;
- tomcat 线程池只有一个名为 http-nio-8001 的 pool name,可以较低成本实现基于使用率的伸缩规则;
- jedis 连接池只有一个名为 redisConnectionFactory 的 pool name,可以较低成本实现基于使用率的伸缩规则;
- Hystrix 线程池,这个有点特殊,每个服务有各自不同的 pool name,而且个数从 1 到 10+不等,如果将其全转化成 HPA 规则加到具体微服务上,投入产出比太低。
- 核心 QPS:只要各项池化资源配置合理,它一个基本上就能说明问题了,随着核心接口 QPS 的变化,各副本节点数、各项可用的池化资源也随之变化。
- 基础资源使用率,如:CPU 使用率。
- 建议方案:
- 对需要水平弹性伸缩的服务设置 HPA 规则;
- 对外暴露 API 的核心服务,以 QPS 和 tomcat 线程池使用率、CPU 使用率三个指标作为 HPA 的期望值(三者取较大值),实现效果为:尽量压制每个节点的平均 QPS 小于设置的期望值、tomcat 线程池使用率小于 70%、节点 CPU 使用率小于 80%;扩缩容后节点数最少为 min 的值,最多为 max 的值。
- 有特殊需求的服务,额外单独定制专享的 HPA 规则,不事无巨细将所有自定义线程全当作 HPA 的参考指标。