HorizontalPodAutoscaler 自定义指标
解读
在国内一线/二线互联网公司的 PHP 岗位面试里,云原生与弹性伸缩早已不是“加分项”,而是“必答题”。面试官抛出“HPA 自定义指标”这一 Kubernetes 进阶话题,并不是想听你背官方文档,而是考察三层能力:
- 你是否真的把 PHP 业务跑在 K8s 上,而不是“本地宝塔”;
- 你是否理解“业务指标”比“CPU 指标”更能反映 PHP-FPM 的瓶颈;
- 你是否能把 PHP 代码、Prometheus 埋点、K8s 自定义指标链路串成闭环。
因此,回答必须围绕“PHP 场景 → 指标设计 → 指标暴露 → HPA 配置”展开,让面试官听到“我们线上 Laravel 队列积压超过 2000 就自动扩容 Pod”这种真实案例。
知识点
- HPA 工作原理:kube-controller-manager 每 15s 读取 metrics.k8s.io、custom.metrics.k8s.io、external.metrics.k8s.io 三类 API,计算当前副本数 = ceil(当前指标值 / 目标值 × 当前副本数)。
- 自定义指标链路:PHP 业务埋点 → Prometheus Exporter(PHP 端) → ServiceMonitor(Prometheus-Operator) → Prometheus Adapter(custom-metrics-apiserver) → HPA 引用。
- PHP 侧埋点三种姿势:
a) 进程内:使用 promphp/prometheus_client_php,在 Laravel/Symfony 中间件或队列 Job 中计数;
b) 进程外:php-fpm-exporter 采集 pool 活跃连接、慢请求;
c) 日志侧:mtail 解析 access.log 计算 5xx 率、P99。 - Adapter 配置:seriesQuery 匹配指标名,resources/overrides 把标签映射到 Pod,metricsQuery 写 PromQL 返回“每个 Pod 的平均值”。
- HPA 版本差异:autoscaling/v2 才支持 multiple metrics 与 behavior 扩缩容速率,国内集群 ≥1.23 基本可用。
- PHP 典型指标:
- 业务维度:待支付订单数、Laravel 队列长度、Swoole 连接数;
- 运行维度:fpm_active_processes、opcache_hit_ratio、gc_mem_caches() 内存碎片;
- 弹性维度:接口 QPS / 副本数(单 Pod 承载 QPS)。
- 坑点:
- PHP 短生命周期导致 Pull 模式可能漏采,需用 pushgateway 或 Exporter 常驻;
- Adapter 返回的指标必须带 Pod 标签,否则 HPA 无法关联;
- 国内云厂商托管 K8s(ACK、TKE、CCE)默认关闭 custom.metrics,需要手动安装 Adapter 并开白名单;
- 缩容阈值建议设置为扩容阈值的 70%,防止抖动。
答案
线上 Laravel 商城大促期间,我们用“待支付订单数”做弹性指标,具体落地步骤如下:
- 代码埋点:在下单成功 Observer 里用 promphp 客户端递增 shop_order_pending_total 指标,并带 namespace、app 标签。
- 暴露指标:在 Laravel 自定义 Artisan 命令启动 ReactPHP HTTP 服务监听 9502/metrics,镜像里做成边车容器,与 php-fpm 容器共享网络。
- 采集与存储:Prometheus-Operator 的 ServiceMonitor 选中带 app=shop 标签的 Pod,抓取间隔 10s。
- 转换 API:Prometheus Adapter 配置
seriesQuery: 'shop_order_pending_total{namespace="shop",app="shop"}'
resources: {template: "<<.Resource>>"}
metricsQuery: 'avg(shop_order_pending_total{<<.LabelMatchers>>}) by (<<.GroupBy>>)'
生成 custom.metrics.k8s.io/shop_order_pending_total。 - 编写 HPA:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: shop-order-hpa
spec:
scaleTargetRef: {apiVersion: apps/v1, kind: Deployment, name: shop}
minReplicas: 3
maxReplicas: 50
metrics:- type: Pods
pods:
metric: {name: shop_order_pending_total}
target: {type: AverageValue, averageValue: "30"} # 每个 Pod 平均 30 单
behavior:
scaleUp: {stabilizationWindowSeconds: 30, policies: [{type: Percent, value: 100, periodSeconds: 15}]}
scaleDown: {stabilizationWindowSeconds: 300, policies: [{type: Pods, value: 1, periodSeconds: 60}]}
- type: Pods
- 灰度验证:通过压测工具把订单数打到 1200,观察到副本数在 45s 内从 3 扩到 40;压测停止后 5min 缓慢缩到 3,单 Pod CPU 峰值 65%,无 502。
- 兜底策略:同时保留 CPU 指标作为二级指标,防止业务指标失效时无法扩容。
拓展思考
- 多语言混合集群:如果 PHP 业务与 Go 微服务共用 HPA,如何统一指标命名规范与标签体系?建议在公司内部推广“指标即契约”规范,使用统一的 team、app、version 标签,避免 Adapter 重写规则爆炸。
- 事件驱动极致弹性:PHP 的 CLI 模式结合 KEDA,可以直接用队列长度作为“外部指标”,实现缩容到 0,节省夜间低峰成本;注意 PHP 冷启动镜像过大时,扩容延迟可能 >30s,需用预热池或精简镜像。
- 成本治理:国内云厂商按秒计费,扩缩容过于频繁会导致 Pod 生命周期小于 1min,产生大量镜像拉取费用;可通过 behavior 的 stabilizationWindowSeconds 与 CronHPA 组合,在每天 0 点强制缩容到最小副本。
- 安全合规:金融场景下 Prometheus 指标不能带用户手机号等敏感信息,需在 PHP 埋点时做脱敏哈希;同时 Adapter 开启 RBAC,只允许 kube-system 下的 hpa-controller 读取自定义指标,防止数据泄露。