在 K8s 中利用 HPA 根据 RPS 自动扩容 Locust slave,如何防止脑裂与重复统计
解读
- 场景本质:Locust 采用 master-slave 架构,master 负责分发任务、聚合指标;slave 只执行请求。HPA 以 RPS 为指标横向扩缩 slave Pod,若扩缩过程缺乏“成员一致性”保障,就会出现:
- 脑裂:同一时刻出现两个 master 或 slave 被错误识别为 master,导致指标聚合通道分叉;
- 重复统计:一个请求被多个 slave 重复上报,或 slave 在滚动重启瞬间被 master 重复计数,RPS/RT 失真。
- 国内面试常追问:HPA 只是“起 Pod”,业务层如何“安全下线”?Locust 自带心跳超时 3s,默认 graceful stop 为立即 SIGTERM,若直接缩容,极易丢数据或重复。
- 考点定位:既考 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 再退出。
答案
-
唯一 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 脑裂。 -
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 选中。 -
优雅缩容防止重复统计
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 发现旧版本数据直接丢弃,解决因网络闪断重连带来的重复上报。 -
数据去重兜底
master 聚合层采用 “request_id + slave_id + timestamp 秒级窗口” 作为唯一键,内存 LRU 缓存 10s 窗口内已处理键,重复键直接丢弃,确保 RPS、RT 指标精确。 -
灰度验证
在测试 Namespace 使用 Chaos Mesh 模拟 slave 网络分区或突然宕机,观察:- master 是否仍能正常聚合;
- HPA 缩容曲线与 RPS 曲线是否匹配;
- Grafana 看板无异常毛刺。通过验证后再上生产。
拓展思考
- 如果压测目标本身有缓存层,RPS 上涨不一定代表负载上涨,HPA 是否应改用 “CPU 利用率+响应时间 P99” 多维度指标?如何设计权重融合公式?
- 当 Locust 需要多地域分布式压测时,跨可用区延迟造成 heartbeat 超时,K8s 联邦(KubeFed)或 KEDA 的 TriggerAuthentication 如何分别管理不同地域的 HPA 阈值?
- 在 Serverless 场景(如阿里云 ECI 或华为 CCI)中,slave 冷启动 3~5s,HPA 扩容滞后,如何结合 Knative 的 KPA 预测算法做“提前扩容”,保证压测波峰不丢 RPS?
- 若压测协议改为 gRPC 双向流,Locust 的 master-slave 通信模型需要改造成基于消息队列(Pulsar 分区顺序写)才能横向扩展,如何重新设计“只一次”语义并兼容现有 HPA 机制?