当前位置: 首页 > news >正文

为什么 CPU/内存指标不足以支撑真实业务伸缩

去年双十一大促期间,我们某核心服务因 CPU 利用率一直徘徊在 30%~40%,HPA 始终没有触发扩容。然而实际 QPS 已经飙到正常值的 5 倍,响应延迟从 50ms 飙升到 2s,最终导致大量超时和 502。事后复盘发现:仅靠 CPU/内存指标做弹性伸缩,在 IO 密集型、异步处理或长连接业务中几乎形同虚设

这篇文章会手把手带你配置基于业务 QPS 的自定义指标 HPA,结合 Prometheus Adapter 暴露指标,并对伸缩行为策略做压测调优,最后总结生产环境中常见的坑和应对方案。


为什么 CPU/内存指标不足以支撑真实业务伸缩

原理说明

K8s 原生的HorizontalPodAutoscaler默认支持 Pod 的cpumemory利用率。但这两类指标反映的是系统资源占用,而非业务负载。许多场景下:

  • 异步处理服务:大量请求入队列后立即返回 200,CPU 空闲,但队列深度陡增。
  • I/O 密集型服务:网络、磁盘 IO 成为瓶颈,CPU 利用率低但吞吐已到极限。
  • 长连接 WebSocket:连接数高但 CPU 消耗小,按照 CPU 永远不扩容。
  • Java 应用 GC 频繁:偶尔 CPU 飙升但瞬时过去后复位,HPA 来不及响应。

结论:生产环境必须用业务指标(QPS、请求延迟、队列深度等)定义弹性伸缩策略。


Prometheus Adapter 暴露自定义指标的完整配置

前置条件

  • 已部署 Prometheus Operator 或独立 Prometheus。
  • 业务服务已采集 QPS 指标(例如http_requests_total,通过rate()计算 QPS)。
  • 需要安装prometheus-adapter(也可用kube-metrics-adapter,但社区推荐前者)。

配置步骤

1. 确认服务指标采集

假设你的业务 Pod 已通过prometheus.io/scrape: "true"暴露指标,Prometheus 中可查到:

rate(http_requests_total{namespace="production", job="my-service"}[1m])

2. 安装 prometheus-adapter(Helm)

helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm upgrade --install prometheus-adapter prometheus-community/prometheus-adapter \ --namespace monitoring \ -f adapter-values.yaml

3. 编写核心配置文件adapter-values.yaml

# adapter-values.yaml prometheus: url: http://prometheus.monitoring.svc:9090 # 指向自己的 Prometheus port: 9090 rules: default: false # 不加载默认的 CPU/Mem 指标(可选) custom: - seriesQuery: 'http_requests_total{namespace!="",job!=""}' resources: overrides: namespace: { resource: "namespace" } pod: { resource: "pod" } # 定义如何将 Prometheus 查询映射为 K8s 自定义指标 name: as: "qps_per_pod" metricsQuery: | sum(rate(http_requests_total{<<.LabelMatchers>>}[1m])) by (<<.GroupBy>>)

关键注释:
-seriesQuery:用于发现指标,adapter 会据此自动探测可用的标签。
-resources.overrides:将 Prometheus 的namespacepod标签映射为 K8s 资源对象,HPA 才能正确匹配 Pod。
-name.as:暴露的自定义指标名称,HPA 中引用pods/qps_per_pod
-metricsQuery:最终发往 Prometheus 的查询模板。<<.LabelMatchers>>会被替换为namespace=xxx,pod=xxx<<.GroupBy>>自动分组。

4. 验证指标暴露

# 查看所有自定义指标 kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | jq . # 查看特定 Pod 的 QPS kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/production/pods/*/qps_per_pod | jq .

正确输出应返回类似:

{ "kind": "MetricValueList", "items": [ { "metricName": "qps_per_pod", "value": "12345m", // 12345m = 12.345 qps "describedObject": {"kind": "Pod", "name": "my-service-6f8b7c9d-abc12"} } ] }

踩坑:如果返回"status": "Failure",检查 Prometheus 地址是否可通、seriesQuery能否匹配到指标。


HPA v2 行为策略:scaleUp、scaleDown 与稳定窗口调优

完整 HPA 配置

apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: my-service-hpa namespace: production spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: my-service minReplicas: 3 maxReplicas: 30 metrics: - type: Pods pods: metric: name: qps_per_pod target: type: AverageValue averageValue: 5000m # 目标每 Pod 5 QPS behavior: scaleUp: stabilizationWindowSeconds: 30 # 稳定窗口 30 秒 policies: - type: Pods value: 4 # 每次最多增加 4 个 Pod periodSeconds: 60 # 每 60 秒最多执行一次策略 selectPolicy: Max scaleDown: stabilizationWindowSeconds: 300 # 缩容稳定窗口 5 分钟防止抖动 policies: - type: Pods value: 2 periodSeconds: 120 selectPolicy: Max

策略参数说明与调优建议

参数作用推荐初始值备注
stabilizationWindowSeconds(扩容)在窗口期内取指标最大值,防止毛刺触发扩容30~60s如果业务流量波动剧烈,可设 10s;窗口越小对突发越敏感
stabilizationWindowSeconds(缩容)窗口期内取最小值,防止短暂低谷就缩容300~600s缩容一定要保守,缩容后 Pod 冷启动需要时间,避免频繁震荡
policies.value(扩容)一次扩容最多加 Pod 数量2~5配合periodSeconds控制扩容速率,防止瞬间启动大量 Pod 把集群打满
policies.value(缩容)一次缩容最多减 Pod 数量1~3避免一次性杀死过多 Pod,导致后续请求积压
selectPolicyMax:取所有策略中最大的变化量;Min:取最小扩容用Max,缩容用MaxMin一般用Max保留决策权

调优技巧:将稳定窗口视为低通滤波器——窗口越长,对瞬时抖动过滤越强,但响应越慢。对于突发流量敏感的在线服务(如 API 网关),建议缩短扩容窗口、缩窄稳定窗口,同时用policies限制单次扩容上限,避免 Pod 冷启动抢占集群资源。


压测验证:QPS、延迟、Pod 数量与冷启动时间的关系

测试环境

  • 服务:基于 Spring Boot 的 RESTful API,平均处理时间 20ms。
  • 指标:Prometheus Adapter 暴露qps_per_pod
  • 压测工具:wrk,持续 5 分钟,初始 QPS 1000,每 30 秒递增 500。
  • 冷启动时间:JVM 预热约 12s,加上 K8s Pod Ready 约 20s。

测试步骤与结果

  1. 未配置 HPA,固定 3 Pod:QPS 到 3000 时延迟从 50ms 飙到 800ms,开始超时。
  2. 配置默认 HPA(CPU 80%):CPU 仅有 30%,未触发扩容,结果同上。
  3. 配置自定义 QPS HPA(目标 5 QPS/Pod):QPS 到 1200 时触发扩容,每 60 秒加 4 Pod。压测结束前 Pod 数达到 12,延迟稳定在 80ms 左右。

关键数据记录

压测时间点当前 QPS期望 Pod 数(理想)实际 Pod 数平均延迟
0s10003.3 → 4345ms
60s15005 → 63 → 7(首次扩容)52ms
120s20006.7 → 77 → 1168ms
180s25008.3 → 911 → 1275ms
240s300010 → 101282ms(稳定)

可以看到:由于扩容滞后和冷启动时间,实际 Pod 数始终落后于理想值,但在 3 个周期后趋于收敛。

重要结论
-扩容永远滞后于流量突增。因此业务系统必须设计合理的限流熔断,防止在 Pod 启动完成前过载。
- 冷启动时间越长,需要的稳定窗口越大。例如 Python 应用启动只需 3s,窗口可降到 10s;而 Java 应用建议窗口不低于 30s。


生产踩坑:指标延迟、抖动、突刺流量和回滚策略

坑1:Prometheus 指标延迟导致扩缩容偏差

现象:Prometheus 的rate()计算依赖时间窗口(如 1m),而 HPA 每 15 秒拉取一次指标。当流量突降时,rate()窗口内仍包含旧数据,导致 QPS 指标缓慢下降,HPA 延迟缩容。

解决
- 减小rate()的窗口到 30s,代价是短期抖动更大。
- 组合使用avg_over_time平滑,或者直接使用 Prometheus 的last_over_time(瞬时值)。
- 在metricsQuery中使用rate(...[30s])代替[1m]

坑2:Pod 状态不健康导致指标异常

某次我们发现 QPS 指标突然为 0,导致 HPA 把 Pod 缩到 minReplicas,但实际服务正常运行。原因:某个 Pod 被调度到异常节点,Prometheus 抓取失败,sum(rate(...)) by (pod)返回 0。

解决
- 在metricsQuery中加入or on(pod) absent(...)填充默认值,或者使用max避免被 0 拖低。
- 更稳健的做法:暴露存活 Pod 的指标总和,而非直接用rate() by(pod)取均值。例如暴露namespace+service级别的总 QPS,然后让 HPA 用AverageValue除以 Pod 数。但这种方法需要额外计算。

坑3:突刺流量导致 Pod 总数暴涨

双十一流量瞬时 10 倍,HPA 配置了scaleUp.stabilizationWindowSeconds=10,结果 30s 内扩容了 20 个 Pod,集群资源不足,部分 Pod 启动失败。

解决
-硬性限制maxReplicas设合理上限(根据集群资源 + 业务容灾冗余估算)。
-两阶段扩容:使用policies限制单次扩容数量,例如 60s 内最多加 5 个 Pod。
-水平 + 垂直组合:突发时先对现有 Pod 增加资源请求(VPA),再缓慢扩容。

坑4:回滚策略

生产环境 HPA 配置错误可能导致服务雪崩。必须准备回滚方案:

  • 配置版本化:所有 HPA、Prometheus Adapter 配置都存 Git,并关联 CI/CD 回滚。
  • 手动回滚脚本:一键恢复旧的 HPA 配置,并临时关闭 HPA(spec.paused: true)。
  • 监控告警:设置 Pod 变化速率告警,当 5 分钟内 Pod 数变化超过 50% 时触发通知,人工介入。

总结

  • 放弃纯 CPU/Memory 的迷信,业务 QPS、延迟分位数、队列深度才是实际负载的准确反映。
  • Prometheus Adapter 配置核心在于seriesQuery+metricsQuery模板的正确性,务必用kubectl get --raw验证。
  • HPA v2 的behavior字段是调优主战场:扩容窗口要短(捕获突发)、缩容窗口要长(防抖动)、单次扩容数量要限制(防雪崩)。
  • 压测是唯一检验手段,务必记录 Pod 启动时间 vs 指标收敛时间,据此调整窗口参数。
  • 生产环境必须考虑指标延迟、异常 Pod、突刺流量,并且准备一键回滚能力。

最后一句真心话:弹性伸缩不是万能的,它只能缓解“可预期的繁忙”,无法应对“完全的失控”。业务本身必须有限流、降级、熔断的最后一环。

http://www.jsqmd.com/news/1093472/

相关文章:

  • 软硬一体销售会话分析软硬件一体方案选型与落地参考
  • 长春新房除甲醛避坑!普尔净教你分清通风和专业治理的差距
  • PG 日报|PGConf.EU 2026 开启预约
  • GPT 付款失败怎么办?国内信用卡无法绑定时有哪些替代方案
  • MITK在windows平台的构建
  • SystemVerilog包(package)的三大引用方式与实战场景解析
  • 如何将 HTML 转换为可编辑的 Word 文档(无需安装软件)
  • 从零搭建最简pytest+Playwright UI自动化测试框架
  • Python自动化工具实战:从零构建B站抢票脚本的完整指南
  • 【课程设计/毕业设计】基于 SpringBoot 的餐厅前台点餐后台管理系统 轻量化餐饮订单服务管理系统设计与实现【附源码、数据库、万字文档】
  • 未来真正赚钱的AI项目,往往都长得不像“AI项目”
  • 如何从Redmi恢复已删除的文件:4种简单方法
  • vitest + vue3 踩坑记录
  • Java计算机毕设之基于 SpringBoot 的毕业课题进程督导管理平台(完整前后端代码+说明文档+LW,调试定制等)
  • vide coding软件开发流程
  • wireshark学习小结
  • 一人创业时,内容、开发、客户跟进分别适合用哪些AI工具辅助开篇:一人创业为什么最容易卡在任务切换和推进节奏上
  • 6个真实用户反馈 森优时铁锌维 白发转黑发 改善周期测评
  • 2026 私域全面严打,无层级矩阵拼团为什么能安稳做
  • LEADTOOLS 医疗套件开发人员工具包
  • 2026 APP竞品分析怎么做?一套完整流程分享
  • 高速ADC外围电路设计精要:增益、时钟与接口配置实战指南
  • 二层三层交换机选型
  • 如何从三星帐户恢复联系人?分步指南
  • 2026 年命理排盘工具隐私与数据管理榜:玄易为何更适合长期执业
  • ESXi 直通与共享模式
  • 嵌入式低功耗子系统(LFSS)实战:RTC、看门狗与安全监控设计
  • 我做了一个 macOS 菜单栏日历应用:白纸日历
  • Java毕设选题推荐:基于 SpringBoot 的毕设任务分配与进度督查系统 高校师生毕设文档审核与进程管理系统【附源码、mysql、文档、调试+代码讲解+全bao等】
  • 告别ROI计算滞后!实测AI Agent实现预算实时动态转移,重塑企业利润链