Kubernetes自带的Horizontal Pod Autoscaler(HPA)在默认配置下只能根据CPU和内存使用率来决定Pod的扩缩容。但很多业务的压力并不能体现在这两个指标上:比如一个消息队列消费者服务,CPU占用很低,队列里却积压了几万条消息;又比如一个Web服务在QPS暴涨时CPU还没来得及升高,接口已经开始超时了。这时候就需要自定义指标(Custom Metrics)和外部指标(External Metrics)出场了。本文会从HPA的原理讲起,一步步演示如何搭建一套基于Prometheus的自定义指标扩缩容体系。

一、先弄懂HPA支持的三种指标类型
HPA从v1到v2演进后,autoscaling/v2 API引入了三种指标来源,理解它们的区别是自定义指标的第一步。第一种是Resource类型,也就是CPU和内存,由metrics-server组件直接采集,开箱即用但只有这两个指标。第二种是Pods类型,允许你为每个Pod暴露自定义指标,HPA会对所有副本的指标求平均值,比如每个Pod的每秒请求数。第三种是External类型,指标来源不在Pod本身,而是集群外部的东西,典型例子就是RabbitMQ的队列长度、Redis的某个key的值。
三者的本质区别在于指标的归属对象。Resource和Pods类型都挂在Pod上,删除Pod指标随之消失;而External类型与Pod无关,即使副本数为零,指标依然存在。这一点在缩容到0或者依赖外部资源排队场景中非常关键。另外要注意,Pods类型指标在做除法时的分母是Pod数量,如果你的指标本身就是全局值(比如整个服务的总QPS),直接用Pods类型会导致副本越多每个Pod的值越小,扩缩容逻辑会完全错乱,这种情况下应该用External类型并自己控制计算逻辑。
还需要了解HPA的控制循环逻辑:HPA Controller默认每15秒(可通过--horizontal-pod-autoscaler-sync-period调整)从指标API拉取一次数据,计算期望副本数并与当前值对比。公式大致是期望副本数 = ceil(当前副本数 × 当前指标值 / 目标指标值)。如果指标接口返回错误或者数据缺失,HPA会跳过本轮计算并记录事件,这也是排查扩容不生效时首先要看的点。
二、搭建Prometheus与Prometheus Adapter指标链路
自定义指标的核心组件是Prometheus Adapter,它的作用是充当翻译官:Prometheus负责抓取应用暴露的指标,Adapter把这些时序数据转换成Kubernetes的Custom Metrics API格式,HPA才能查询到。整套链路是:应用暴露metrics端点到Prometheus抓取,Adapter查询Prometheus并对外提供API。
首先确认Prometheus已经部署并能正常抓取到业务指标。以一个暴露HTTP QPS指标的应用为例,通过ServiceMonitor或prometheus.io/scrape注解让Prometheus采集,然后在Prometheus里验证查询语句:
# 验证Prometheus中已有该指标
curl -s http://prometheus:9090/api/v1/query \
--data-urlencode 'query=sum(rate(http_requests_total{app="web"}[2m]))'
接下来部署Prometheus Adapter。最关键的配置是custom-metrics-config.yaml中的规则映射,它决定了Prometheus查询结果如何映射成API指标:
rules:
- seriesQuery: 'http_requests_total{app!=""}'
seriesFilters: []
resources:
overrides:
namespace: {resource: "namespace"}
app: {resource: "service"}
name:
matches: "^(.*)_total$"
as: "http_requests_per_second"
metricsQuery: 'sum(rate(<<.Series>>{<<.LabelMatchers>>}[2m])) by (<<.GroupBy>>)'
这段配置里有几个容易踩坑的地方。resources.overrides把Prometheus的label映射成Kubernetes的资源维度,app label映射为service后,HPA里才能用service/web这样的格式引用指标。metricsQuery中的<<.Series>>、<<.LabelMatchers>>是模板变量,Adapter会自动填充。部署完成后用kubectl get --raw验证:
# 查看Adapter暴露了哪些自定义指标 kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1" | jq . # 查询具体指标值 kubectl get --raw "/apis/custom.metrics.k8s.io/v1beta1/namespaces/default/services/web/http_requests_per_second"
如果返回404,优先检查Adapter的规则配置和Prometheus查询是否能出数据;如果返回的数据是空的,多半是label映射对不上。验证通过后再创建HPA资源:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: web-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: web
minReplicas: 2
maxReplicas: 20
metrics:
- type: Pods
pods:
metric:
name: http_requests_per_second
target:
type: AverageValue
averageValue: "1000"
三、外部指标实战:基于消息队列堆积量扩缩容
消费类服务的最佳扩容信号往往不是CPU,而是消息堆积量。假设Prometheus中已经采集了RabbitMQ的rabbitmq_queue_messages_ready指标,我们可以把它配置成外部指标。Adapter的配置中,External指标不需要映射resource,直接用seriesQuery过滤出目标队列即可:
rules:
- seriesQuery: 'rabbitmq_queue_messages_ready{queue="order-queue"}'
name:
matches: "^rabbitmq_queue_messages_ready$"
as: "order_queue_backlog"
metricsQuery: 'max(rabbitmq_queue_messages_ready{queue="order-queue"})'
对应的HPA配置改为External类型,目标设定为每个副本处理5000条堆积消息。这种模式下有一个实用技巧:设置一个合理的minReplicas保底,同时把目标值设得偏保守一点。因为消息消费速率有上限,堆积量短期波动很正常,如果目标太敏感会导致副本频繁增减,反而增加调度开销。
除了单一指标,autoscaling/v2还支持多指标组合,HPA会取所有指标算出的最大期望副本数。比如同时配置CPU和队列堆积两个指标,CPU指标负责应对计算密集型突发,队列指标负责消费能力不足的场景,两者取最大值,扩容更及时。
四、常见问题与调优建议
第一个高频问题是扩缩容抖动。流量在临界值附近波动时,副本数会来回变化。解决办法是配置HPA的行为参数(behavior字段),比如缩容时设置stabilizationWindowSeconds: 300让系统等待5分钟再缩容,扩容时限制每分钟最多增加一定比例的副本,这些策略能显著减少震荡。
第二个问题是指标延迟。Prometheus抓取间隔加上rate函数的计算窗口,整体可能滞后30秒到1分钟,对于秒级流量突刺,扩容永远慢半拍。实践中的做法是缩短抓取间隔、使用更小的rate窗口,同时把扩容目标值调低留出缓冲,而不是追求指标零延迟。另外建议在Pod里预留好resources.requests,否则扩出来的新Pod会因为资源不足卡在Pending状态,扩容形同虚设。
第三个问题是多指标冲突排查。当HPA不生效时,依次执行kubectl describe hpa查看事件信息、确认Adapter返回值是否为<unknown>、检查Prometheus查询本身是否有数据,基本能定位到问题层。最后提醒一点,自定义指标体系建设完成后,一定要在低峰期做一次压测演练,验证从指标上升到副本扩容再到流量承接的完整链路,避免故障时刻才发现配置有漏洞。
KubernetesHPA自动扩缩容自定义指标修改时间:2026-09-05 12:05:08