必须用client-go/tools/leaderelection包而非etcd concurrency.Election,因后者90%线上故障源于续租失败后未退场:Proclaim()仅获初始key,lease需显式KeepAlive(),过期后key被删但程序仍运行;必须监听Observe()通道接收退位信号,且不可复用同一Election对象。

别自己封装 etcd Election,线上稳定跑的微服务 Leader 选举,必须用 client-go/tools/leaderelection 包,走 Kubernetes API Server 的 Lease 资源。
为什么不能直接用 etcd concurrency.Election
它能快速跑通 demo,但 90% 的线上故障切换失败,都源于 lease 续租失败后程序没退场:
-
Proclaim()成功只代表抢到初始 key,不代表能一直 hold 住;etcdlease 必须显式KeepAlive(),客户端不会自动续 - 网络抖动或 goroutine 阻塞导致续租失败 → lease 过期 → key 被删 → 但你的业务逻辑还在跑,形成“幽灵 leader”
-
Election.IsLeader()返回true可能是缓存旧状态,实际早已被踢出;必须监听Observe()通道才能收到 etcd 主动推送的退位信号 - 多个实例共用同一个
concurrency.Election对象,CAS 冲突率飙升,leader 切换像抽风
client-go leaderelection 启动三要素
核心不是写逻辑,而是配对锁、参数和回调。漏掉任一环节,选举就不可靠:
- 锁类型必须选
LeasesResourceLock(K8s 1.14+ 默认),比ConfigMapsResourceLock更轻量,更新原子性强,TTL 由 server 强制保障 -
identity必须唯一且稳定:推荐用Pod UID(os.Getenv("POD_UID"))或启动时生成的 UUID;用随机字符串会导致重启后旧 lease 残留,新实例抢不到锁 -
LeaseDuration≥ 15s,RenewDeadline设为 10s,RetryPeriod设为 2s —— 这组值经大规模集群验证,平衡了故障发现速度与 GC/网络抖动误判风险
OnStartedLeading 回调里最容易踩的坑
这个回调看似是“当上 leader 就执行业务”,但实际是整个机制最脆弱的一环:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
立即学习“go语言免费学习笔记(深入)”;
- 千万别在回调里同步执行耗时操作(比如初始化 DB 连接池、加载大配置、调用外部 HTTP 接口);否则会卡住 lease 续期,触发主动让位
- 必须起独立 goroutine 执行业务逻辑,且该 goroutine 要能响应 context 取消(
ctx.Done())—— 否则 leader 退位时业务还在跑 - 不要复用同一
LeaderElector实例多次调用Run();每次运行应新建实例,避免内部状态污染
怎么验证当前是不是真正的 leader
别信 IsLeader() 或本地变量,真实状态只在 APIServer 的 Lease 对象里:
- 查资源:
kubectl get lease -n <namespace><lease-name> -o yaml</lease-name></namespace> - 看
holderIdentity字段是否匹配你进程的identity - 看
renewTime是否在持续更新(间隔约RetryPeriod);如果停滞超过LeaseDuration,说明续租已断 - 注意:APIServer 压力会上升,尤其高频率选举场景下,
Lease更新比ConfigMap注解更高效,但也不是零成本
真正难的不是“怎么启动选举”,而是“怎么确保退位时业务干净退出 + 续租不被 goroutine 阻塞 + identity 不因 Pod 重建而冲突”。这些细节不压测、不看日志、不查 Lease 对象,上线后第一波网络抖动就会暴露。

















