在 K8s 中利用 HPA 根据 RPS 自动扩容 Locust slave,如何防止脑裂与重复统计

解读

  1. 场景本质:Locust 采用 master-slave 架构,master 负责分发任务、聚合指标;slave 只执行请求。HPA 以 RPS 为指标横向扩缩 slave Pod,若扩缩过程缺乏“成员一致性”保障,就会出现:
    • 脑裂:同一时刻出现两个 master 或 slave 被错误识别为 master,导致指标聚合通道分叉;
    • 重复统计:一个请求被多个 slave 重复上报,或 slave 在滚动重启瞬间被 master 重复计数,RPS/RT 失真。
  2. 国内面试常追问:HPA 只是“起 Pod”,业务层如何“安全下线”?Locust 自带心跳超时 3s,默认 graceful stop 为立即 SIGTERM,若直接缩容,极易丢数据或重复。
  3. 考点定位:既考 K8s 滚动+生命周期钩子,又考 Locust 分布式协议与数据一致性,属于“云原生性能测试”高阶题。

知识点

  • HPA 自定义指标 pipeline:Prometheus Adapter 或 KEDA,把 Locust master 暴露的 locust_requests_per_second 注册为 metrics.k8s.io 外部指标;
  • Locust 分布式通信:master 在 5557/5558 端口与 slave 建 TCP 长连接,slave 定时发 heartbeat,master 按 client_id 去重;
  • Pod 优雅终止:terminationGracePeriodSeconds、preStop Hook、SIGTERM 与 SIGKILL 时间窗;
  • K8s 有状态身份:StatefulSet + stable network ID,或 Deployment + 分布式锁(etcd/redlock)保证“master 唯一”;
  • 去重策略:slave 上报时带 UUID 请求标识,master 用滑动窗口 Set 去重;或改走 Kafka 单分区顺序写,再消费聚合;
  • 滚动升级顺序:Readiness 探针返回 true 且 Locust 状态为 “ready” 后才接入流量;旧副本 preStop 先调用 /stop 接口,等待 master 返回 200 再退出。

答案

  1. 唯一 master 方案
    a) 用 StatefulSet 部署 master,固定 0 号实例,serviceName=locust-master-headless,slave 通过 DNS A 记录 locust-master-0.locust-master-headless.default.svc.cluster.local:5557 连接,保证任何时刻只有一个 master;
    b) 若坚持 Deployment,则在 master 启动前通过 etcd 分布式锁(lease TTL 10s)竞选,成功后再监听 5557,失败则直接退出容器,防止多 master 脑裂。

  2. slave 弹性扩容
    a) 自定义指标:Prometheus 抓取 master 的 locust_requests_per_second,Prometheus Adapter 暴露指标 locust_rps_per_slave;HPA 目标值=单 slave 额定 RPS(如 500),算法:replicas = ceil(total_rps / 500);
    b) 扩容时新 slave Pod readinessProbe 调用 http://localhost:8089/stats 返回 state=ready 且已连上 master 才置为 true,避免未初始化就被 Service 选中。

  3. 优雅缩容防止重复统计
    a) slave Pod 配置 preStop Hook:

    #!/bin/bash
    curl -X POST http://localhost:8089/stop   # 通知 Locust 停止发压
    while [ $(curl -s http://localhost:8089/stats | jq .state) != \"stopped\" ]; do sleep 0.5; done
    

    保证 Locust 先停止发请求,再退出进程;
    b) terminationGracePeriodSeconds=60,给足时间让 master 检测到连接断开并清理该 slave 的累计数据;
    c) master 端维护 “slave 生命周期版本号”,slave 每次重连需递增版本,master 发现旧版本数据直接丢弃,解决因网络闪断重连带来的重复上报。

  4. 数据去重兜底
    master 聚合层采用 “request_id + slave_id + timestamp 秒级窗口” 作为唯一键,内存 LRU 缓存 10s 窗口内已处理键,重复键直接丢弃,确保 RPS、RT 指标精确。

  5. 灰度验证
    在测试 Namespace 使用 Chaos Mesh 模拟 slave 网络分区或突然宕机,观察:

    • master 是否仍能正常聚合;
    • HPA 缩容曲线与 RPS 曲线是否匹配;
    • Grafana 看板无异常毛刺。通过验证后再上生产。

拓展思考

  1. 如果压测目标本身有缓存层,RPS 上涨不一定代表负载上涨,HPA 是否应改用 “CPU 利用率+响应时间 P99” 多维度指标?如何设计权重融合公式?
  2. 当 Locust 需要多地域分布式压测时,跨可用区延迟造成 heartbeat 超时,K8s 联邦(KubeFed)或 KEDA 的 TriggerAuthentication 如何分别管理不同地域的 HPA 阈值?
  3. 在 Serverless 场景(如阿里云 ECI 或华为 CCI)中,slave 冷启动 3~5s,HPA 扩容滞后,如何结合 Knative 的 KPA 预测算法做“提前扩容”,保证压测波峰不丢 RPS?
  4. 若压测协议改为 gRPC 双向流,Locust 的 master-slave 通信模型需要改造成基于消息队列(Pulsar 分区顺序写)才能横向扩展,如何重新设计“只一次”语义并兼容现有 HPA 机制?