在 Kubernetes 中使用 HPA + Custom / External Metrics(完整中文版)
- 概览 (Overview)
- 核心概念 (Concepts)
- 理解 HPA (Understanding HPA)
- 环境搭建 (Environment Setup)
- 使用 Resource Metrics 的 HPA(基线验证)
- 使用 Custom Metrics 的 HPA
- 使用 External Metrics 的 HPA
- 总结 (Summary)
概览 (Overview)
本篇带你在本地搭建一个最小但真实可解释的链路:应用 / 外部信号 → Prometheus 收集与查询 → Prometheus Adapter 适配 → HPA Controller 计算副本数 → Deployment 扩缩。
目标:读完后你能自信回答:“一个 HTTP endpoint 暴露出的 counter 为什么最后会让集群新增 Pod?”
你将完成:
- 理解 time‑series(时间序列)数据模型,弄清楚 metric name + labels 如何唯一标识一条 series(后面 PromQL 才不显得像魔法)。
- 观察 Prometheus 如何存储和查询你的指标。
- 暴露一个自定义 Counter,并让 HPA 基于它扩缩。
- 模拟一个 External Metric(队列长度)并基于它扩缩。
- 直接访问 HPA 实际调用的 Kubernetes Metrics APIs,消除“黑盒”感。
请先下载示例代码:https://github.com/DanielYWoo/hpa-demo 本文AI翻译, 如果有纰漏请查看英文原文
核心概念 (Concepts)
监控体系失败、SLO 无法落地、扩缩策略失真,往往源于对指标模型理解肤浅。本节是后面所有操作的前提。
TSDB Data Model(通用时序数据库模型)
为什么从这里开始?因为 PromQL、Adapter 规则、HPA 计算都默认你已理解:
“metric name + tag(label) 组合 = 一条随时间变化的数值流 (series)”。
基础示例 (来自 InfluxDB 文档)
Series Measurement Tag set Field key
series 1 census location = 1, scientist = langstroth butterflies
series 2 census location = 2, scientist = langstroth butterflies
series 3 census location = 1, scientist = perpetua butterflies
series 4 census location = 2, scientist = perpetua butterflies
series 5 census location = 1, scientist = langstroth honeybees
series 6 census location = 2, scientist = langstroth honeybees
series 7 census location = 1, scientist = perpetua honeybees
series 8 census location = 2, scientist = perpetua honeybees
- measurement:被测量对象(类似“逻辑表名”)。
- tag set:一组 tag 键值对,指定一个维度切片。
- field key:该 measurement 下存的具体数值字段名(很多场景只用一个 field)。
- series:measurement + field key + tag set 随时间形成的纵向数值序列。
- point:某一时间点该 tag set 下所有 field 的集合。
类比(仅帮助理解,不要过拟合):
- measurement ≈ 表
- tag set ≈ where 条件
- series ≈ 表中同一条件下某“列”的时间演进
- point ≈ 某时刻“行”快照
示例(固定 tag set):
Given Tag Set = "location = 1, scientist = langstroth"
Timestamp butterflies honeybees
t1 12 23
t2 14 21
t3 17 45
一个 series(butterflies):
Timestamp value
t1 12
t2 14
t3 17
某个 point(时间 t1):
butterflies honeybees
12 23
心智快速索引:
- 想“某个值如何随时间变化” → series
- 想“这个瞬间一组相关值” → point
- 想“按主机 / 地域 / 用户切分” → tag set
Prometheus 概念 (Prometheus Concepts)
Prometheus = pull 模式采集 + 内嵌 TSDB + PromQL。
最大雷区:label 组合过多 = High Cardinality → 内存 / CPU 暴炸。
与通用 TSDB 的对应
- measurement 在 Prometheus 中称 metric(并区分 4 种类型)。
- tag = label(换个名字而已)。
- metric name + labels = 唯一 series。
- label 决定过滤与聚合维度;新增 label value 会产生新 series。
- 典型表示:
http_requests_total{method="POST",url="/api/tracks"}
Metric Types(四种)
只记一个优先级口诀:能用 Counter 就别用 Histogram;能用 Gauge 就别上 Summary。
- Counter:单调递增(重启归零),后续用
rate()
得“速率” - Gauge:当前值,可上可下
- Histogram:分桶统计 +
_count
+_sum
- Summary:客户端计算分位;非聚合场景再用
Gauge
代表一个瞬时值:并发数、队列长度、缓存大小。可以升降。
Counter
单调递增;不要自己拼 “xxx_per_second”,直接暴露 Counter,然后 rate(counter[5m])
→ QPS/TPS。
Histogram
一组桶(_bucket{le=".."}
)+ _sum
+ _count
。
近似 P95 等:histogram_quantile(0.95, sum(rate(<metric>_bucket[5m])) by (le))
均值:rate(x_sum[5m]) / rate(x_count[5m])
示意(累计计数随时间累加):
^
100ms | 98 +97 +91
80ms | 66 +71 +68
60ms | 54 +55 +59
40ms | 34 +35 +38
20ms | 23 +21 +22
------------------------------>
t1 t2 t3
Summary
内置 quantile 但:
- 不能跨实例聚合(没法 sum 后再算)
- 量化点必须预定义
PromQL 基本套路
本质模式:选择 → 变形(rate/irate/increase/deriv) → 按标签聚合(sum / avg / max / topk) → 组合运算。
学习阶梯:
- 选择:
http_server_requests_seconds_count
- 过滤:
{method="GET"}
- 速率:
rate(...[5m])
- 聚合:
sum by (status) (...)
- 组合:错误率 =
sum(rate(error_total[5m])) / sum(rate(request_total[5m]))
文档:https://prometheus.io/docs/prometheus/latest/querying/basics/
理解 HPA (Understanding HPA)
如果 HPA 让你觉得“有点神秘”,下面这张链路图和文字会把它拆平:它只是周期性(默认 ~15s)拉几个 API,比例换算一个数字,然后写回 scale
副资源,并受稳定窗口与策略约束。
+------------------+ +----------------------+ +---------------------------+
| cAdvisor | | App Pods | | External Data Source |
| CPU & memory | | Micrometer endpoint | | e.g., Kafka Admin API |
+---------+--------+ +----------+-----------+ +-------------+-------------+
v v v
+-------------------+ +------------------------------------------------------+
| metrics-server | | Prometheus Server |
| short retention | | internal TSDB |
+---------+---------+ +------------------------+-----------------------------+
| | PromQL queries
| v
| +---------------------------------+
| | Prometheus Adapter |
| | expose data via K8S APIs |
| +---+------------------------+----+
metrics.k8s.io custom.metrics.k8s.io external.metrics.k8s.io
| | |
+---------------------------+------------------------+
| (API aggregation layer)
+-------------------------v---------------------+
| kube-apiserver |
| - metrics.k8s.io (direct) |
| - custom.metrics.k8s.io (adapter) |
| - external.metrics.k8s.io (adapter) |
+-------------------------+---------------------+
| watch / list / GET metric values
v
+--------------------------------------+
| HPA Controller |
| (periodic reconcile loop) |
| - Reads HPA specs |
| - Fetches metrics (resource/ |
| custom/external) |
| - Computes desired replicas |
| - Applies scaling policies |
+--------------------+-----------------+
|
scales (Deployment/StatefulSet)
关键拆解:
- Resource Metrics (CPU/Memory):kubelet → metrics-server → metrics.k8s.io(不经 Prometheus)
- Custom / External:Exporter → Prometheus → Adapter(PromQL 模板 + 资源关联)→ 聚合 API
- Adapter 工作:正则匹配命名 → PromQL 查询 → 输出成 Kubernetes MetricValueList
- HPA Controller:对比“当前 vs 目标”→ 比例缩放 → 受 stabilization / scaling policies 限制
- External Metric:不绑定 Pod;可以用 label selector 选择某一条队列、Topic、账户等
核心公式(Value/AverageValue 类):
desiredReplicas = ceil( currentReplicas * currentMetricValue / targetMetricValue )
CPU 利用率(%场景):
desiredReplicas = ceil( sum(currentUsage) / ( targetUtilization% * podRequest ) )
直觉:当前值是目标 4 倍 → 期望副本 ≈ 4 倍;当前值 0.5 倍 → 倾向缩容(受稳定窗口影响)。
环境搭建 (Environment Setup)
组件职责:
- ingress-nginx:Host 方式访问
- metrics-server:Resource Metrics 来源
- Prometheus:统一时间序列存储 + 查询引擎
- Prometheus Adapter:桥接 PromQL 与 Kubernetes Metrics API
- sample app + fake exporter:可控信号注入
Kubernetes & Ingress
编辑 /etc/hosts
:
127.0.0.1 prometheus.local
127.0.0.1 hpa-demo.local
127.0.0.1 monitor.local
工具:
brew install jq
brew install hey
安装 ingress-nginx:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/provider/cloud/deploy.yaml
kubectl patch svc ingress-nginx-controller -n ingress-nginx -p '{"spec":{"type":"LoadBalancer"}}'
安装 metrics-server:
kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
kubectl patch deployment metrics-server -n kube-system --type='json' -p='[
{"op":"add","path":"/spec/template/spec/containers/0/args/-","value":"--kubelet-insecure-tls"}
]'
构建镜像
./gradlew clean build
docker build -t daniel/hpa-demo .
使用 Resource Metrics 的 HPA(基线验证)
kubectl apply -f deploy_resource_metrics.yaml
kubectl get hpa
curl -sv http://hpa-demo.local/test_increase_counter
施压:
curl -sv "http://hpa-demo.local/fakeCPULoad?minutes=10"
kubectl get hpa -w
观察:CPU 利用率超过目标 → 副本数上升 → Deployment 创建新 Pod。
使用 Custom Metrics 的 HPA
我们希望使用“更贴近业务”的信号(如请求吞吐)。链路:
- 应用暴露
/actuator/prometheus
- Prometheus 抓取
- Adapter 规则匹配 + 转换(可能
rate()
) - HPA 配置引用该 metric
常见坑:
- 忘了加 scrape 注解
- Counter 未转
rate()
直接用 → 无限增大 - label 里包含 userId 等高基数
- 规则 regex 不匹配
_total
安装 Prometheus
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
kubectl create namespace prometheus
helm install prometheus prometheus-community/prometheus \
--version 27.39.0 \
-n prometheus \
--set prometheus-node-exporter.enabled=false
kubectl apply -f - <<EOF
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: prometheus
namespace: prometheus
spec:
ingressClassName: nginx
rules:
- host: prometheus.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: prometheus-server
port:
number: 80
EOF
列出所有 metric 名:
curl -s http://prometheus.local/api/v1/label/__name__/values | jq
采集自定义指标
Service / Pod 注解:
prometheus.io/scrape: "true"
prometheus.io/port: "8080"
prometheus.io/path: "/actuator/prometheus"
应用:
kubectl apply -f deploy_custom_metrics_collect.yaml
测试:
curl -s 'http://prometheus.local/api/v1/query?query=my_test_counter_total' | jq
curl -s http://hpa-demo.local/test_increase_counter
sleep 20
curl -s 'http://prometheus.local/api/v1/query?query=my_test_counter_total' | jq
安装 Prometheus Adapter
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 || true
helm install prometheus-adapter prometheus-community/prometheus-adapter \
-n prometheus \
--set-string prometheus.url=http://prometheus-server \
--set-string prometheus.port=80
触发请求后:
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1 | jq '.resources[] | select(.name=="pods/my_test_counter")'
kubectl get pod -l app=hpa-demo
kubectl get --raw /apis/custom.metrics.k8s.io/v1beta1/namespaces/default/pods/<pod>/my_test_counter | jq
默认规则片段(概念说明):
seriesQuery: '{namespace!="",__name__!~"^container_.*"}'
name:
matches: ^(.*)_total$
as: my_test_counter_rate
metricsQuery: sum(rate(<<.Series>>{<<.LabelMatchers>>}[5m])) by (<<.GroupBy>>)
说明:
matches
捕获_total
结尾 Counter- 通过
rate()
转成 5m 平滑速率 - 输出名用
as
重命名 - Go template 占位
<<.Series>>
/<<.LabelMatchers>>
如果值显示 12m
→ 表示 0.012(milli)
基于 Custom Metric 的 HPA
目标:每 Pod 维持 1 TPS。
kubectl apply -f deploy_custom_metrics_collect_hpa.yaml
hey -c 1 -n 10000 -q 5 http://hpa-demo.local/test_increase_counter
kubectl get hpa -w
注意:Custom Metrics 只能用 averageValue
(绝对值),没有 AverageUtilization
概念。
使用 External Metrics 的 HPA
External Metric 不与 Pod 绑定,典型如:Kafka backlog、队列长度、第三方 SaaS 指标。我们用一个模拟服务暴露:
kafka_queue_length{queue_name="hpa-demo"} 10
kafka_queue_length{queue_name="foo-svc"} 1
kafka_queue_length{queue_name="bar-svc"} 2
部署模拟外部源
kubectl apply -f deploy_external_metrics_collect.yaml
curl -s http://monitor.local/metrics
Prometheus 抓取外部指标
添加 scrape job:
- job_name: fake-kafka-monitor
static_configs:
- targets: ['fake-kafka-monitor-service.default.svc.cluster.local:8080']
metrics_path: /metrics
更新:
helm upgrade prometheus prometheus-community/prometheus -n prometheus -f values_scrape_external_metrics.yaml
kubectl exec -ti $(kubectl get pod -n prometheus -l app=prometheus,component=server -o jsonpath='{.items[0].metadata.name}') -n prometheus -- \
grep -n 'fake-kafka-monitor' /etc/config/prometheus.yml
查询 kafka_queue_length
,确认存在。
Adapter 暴露 External Metric
External Metric 不关联资源,规则中 resources
留空。
helm upgrade --install prometheus-adapter prometheus-community/prometheus-adapter -n prometheus -f values_adapter_external_metrics.yaml
kubectl get --raw /apis/external.metrics.k8s.io/v1beta1/namespaces/default/kafka_queue_length | jq
基于 External Metric 的 HPA
HPA 目标:kafka_queue_length{queue_name="hpa-demo"} = 2
,当前为 10 → 扩容直到 maxReplicas。
kubectl apply -f deploy_external_metrics_hpa.yaml
kubectl get hpa -w
降低队列:
kubectl patch configmap fake-kafka-config --type merge -p '{"data":{"queue_length":"1"}}'
curl -s http://monitor.local/metrics
待传播(Exporter → Prometheus scrape → Adapter → HPA),缩容受 stabilization window(默认下调延迟约 5 分钟)影响。
查看:
kubectl get hpa hpa-demo -o yaml
总结 (Summary)
链路拆分为三个层次:
- 采集层:Exporter / scrape / 指标命名
- 适配层:PromQL 模板化、聚合、命名重写、暴露成 K8S Metric API
- 策略层:HPA 比例计算 + 稳定策略 + 上下限
这种解耦让你可以独立演进:新增指标无需改 HPA 控制器;调整扩缩策略无需改应用;优化 PromQL 聚合无需改代码。
如果你现在能向同事解释:“一个 Counter 递增如何最终触发 Deployment scale out”,本教程目的已达成。祝实验顺利。