HorizontalPodAutoscaler 自定义指标

解读

在国内一线/二线互联网公司的 PHP 岗位面试里,云原生与弹性伸缩早已不是“加分项”,而是“必答题”。面试官抛出“HPA 自定义指标”这一 Kubernetes 进阶话题,并不是想听你背官方文档,而是考察三层能力:

  1. 你是否真的把 PHP 业务跑在 K8s 上,而不是“本地宝塔”;
  2. 你是否理解“业务指标”比“CPU 指标”更能反映 PHP-FPM 的瓶颈;
  3. 你是否能把 PHP 代码、Prometheus 埋点、K8s 自定义指标链路串成闭环。
    因此,回答必须围绕“PHP 场景 → 指标设计 → 指标暴露 → HPA 配置”展开,让面试官听到“我们线上 Laravel 队列积压超过 2000 就自动扩容 Pod”这种真实案例。

知识点

  1. HPA 工作原理:kube-controller-manager 每 15s 读取 metrics.k8s.io、custom.metrics.k8s.io、external.metrics.k8s.io 三类 API,计算当前副本数 = ceil(当前指标值 / 目标值 × 当前副本数)。
  2. 自定义指标链路:PHP 业务埋点 → Prometheus Exporter(PHP 端) → ServiceMonitor(Prometheus-Operator) → Prometheus Adapter(custom-metrics-apiserver) → HPA 引用。
  3. PHP 侧埋点三种姿势:
    a) 进程内:使用 promphp/prometheus_client_php,在 Laravel/Symfony 中间件或队列 Job 中计数;
    b) 进程外:php-fpm-exporter 采集 pool 活跃连接、慢请求;
    c) 日志侧:mtail 解析 access.log 计算 5xx 率、P99。
  4. Adapter 配置:seriesQuery 匹配指标名,resources/overrides 把标签映射到 Pod,metricsQuery 写 PromQL 返回“每个 Pod 的平均值”。
  5. HPA 版本差异:autoscaling/v2 才支持 multiple metrics 与 behavior 扩缩容速率,国内集群 ≥1.23 基本可用。
  6. PHP 典型指标:
    • 业务维度:待支付订单数、Laravel 队列长度、Swoole 连接数;
    • 运行维度:fpm_active_processes、opcache_hit_ratio、gc_mem_caches() 内存碎片;
    • 弹性维度:接口 QPS / 副本数(单 Pod 承载 QPS)。
  7. 坑点:
    • PHP 短生命周期导致 Pull 模式可能漏采,需用 pushgateway 或 Exporter 常驻;
    • Adapter 返回的指标必须带 Pod 标签,否则 HPA 无法关联;
    • 国内云厂商托管 K8s(ACK、TKE、CCE)默认关闭 custom.metrics,需要手动安装 Adapter 并开白名单;
    • 缩容阈值建议设置为扩容阈值的 70%,防止抖动。

答案

线上 Laravel 商城大促期间,我们用“待支付订单数”做弹性指标,具体落地步骤如下:

  1. 代码埋点:在下单成功 Observer 里用 promphp 客户端递增 shop_order_pending_total 指标,并带 namespace、app 标签。
  2. 暴露指标:在 Laravel 自定义 Artisan 命令启动 ReactPHP HTTP 服务监听 9502/metrics,镜像里做成边车容器,与 php-fpm 容器共享网络。
  3. 采集与存储:Prometheus-Operator 的 ServiceMonitor 选中带 app=shop 标签的 Pod,抓取间隔 10s。
  4. 转换 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。
  5. 编写 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}]}
  6. 灰度验证:通过压测工具把订单数打到 1200,观察到副本数在 45s 内从 3 扩到 40;压测停止后 5min 缓慢缩到 3,单 Pod CPU 峰值 65%,无 502。
  7. 兜底策略:同时保留 CPU 指标作为二级指标,防止业务指标失效时无法扩容。

拓展思考

  1. 多语言混合集群:如果 PHP 业务与 Go 微服务共用 HPA,如何统一指标命名规范与标签体系?建议在公司内部推广“指标即契约”规范,使用统一的 team、app、version 标签,避免 Adapter 重写规则爆炸。
  2. 事件驱动极致弹性:PHP 的 CLI 模式结合 KEDA,可以直接用队列长度作为“外部指标”,实现缩容到 0,节省夜间低峰成本;注意 PHP 冷启动镜像过大时,扩容延迟可能 >30s,需用预热池或精简镜像。
  3. 成本治理:国内云厂商按秒计费,扩缩容过于频繁会导致 Pod 生命周期小于 1min,产生大量镜像拉取费用;可通过 behavior 的 stabilizationWindowSeconds 与 CronHPA 组合,在每天 0 点强制缩容到最小副本。
  4. 安全合规:金融场景下 Prometheus 指标不能带用户手机号等敏感信息,需在 PHP 埋点时做脱敏哈希;同时 Adapter 开启 RBAC,只允许 kube-system 下的 hpa-controller 读取自定义指标,防止数据泄露。