一、整体架构
kube-controller-manager 的 Leader Election 代码横跨三层:
| 层次 | 路径 | 职责 |
|---|---|---|
| 编排层 | cmd/kube-controller-manager/app/controllermanager.go | 决定是否启用 LE、编排多锁竞速、Controller 分配 |
| 选举引擎 | staging/src/k8s.io/client-go/tools/leaderelection/leaderelection.go | acquire → renew → release 核心循环 |
| 锁实现 | staging/src/k8s.io/client-go/tools/leaderelection/resourcelock/ | 基于 Lease API 对象的分布式锁 |
二、三个核心时间参数
Leader Election 的行为由三个时间参数控制(默认值):
ini
LeaseDuration = 15s ← 租约有效期。非 Leader 等待这么久未观察到续约后,才能尝试抢占
RenewDeadline = 10s ← 续约截止时间。Leader 必须在此时间内成功续约,否则放弃
RetryPeriod = 2s ← 重试间隔。每次尝试 Acquire/Renew 的间隔
约束条件 (在 NewLeaderElector 中强制校验):
scss
LeaseDuration > RenewDeadline > RetryPeriod × 1.2 (JitterFactor)
时序关系示意:
ini
时间轴: 0s 2s 4s 6s 8s 10s 12s 15s
|---------|---------|---------|---------|---------|---------|---------|
Leader: [Renew] [Renew] [Renew] [Renew] [Renew] ↑
RenewDeadline=10s
(必须在此前续约成功)
↑
LeaseDuration=15s
(超时后其他候选者可抢占)
三、Leader Election 主流程
核心入口在 leaderelection.go 的 Run() 方法(L211):
scss
┌──────────────────────────────────────────────────────────┐
│ LeaderElector.Run(ctx) │
├──────────────────────────────────────────────────────────┤
│ 1. acquire(ctx) ← 阻塞直到获取锁或 ctx 取消 │
│ ├─ 循环: tryAcquireOrRenew(ctx) 每 RetryPeriod 一次 │
│ └─ 成功 → 退出循环 │
│ │
│ 2. go OnStartedLeading(ctx) ← 异步启动业务逻辑 │
│ │
│ 3. renew(ctx) ← 阻塞直到续约失败或 ctx 取消 │
│ ├─ 循环: PollUntilContextTimeout( │
│ │ RetryPeriod, RenewDeadline, │
│ │ tryAcquireOrRenew) │
│ ├─ 在 RenewDeadline 内续约成功 → 继续循环 │
│ └─ 续约失败 → 退出,放弃 Leader 身份 │
│ │
│ 4. OnStoppedLeading() ← 总是调用(无论是否当过 Leader) │
│ └─ 如果 ReleaseOnCancel → tryRelease() 主动释放锁 │
└──────────────────────────────────────────────────────────┘
四、核心方法:tryAcquireOrRenew 的细节
这是同时用于 获取锁 和 续约锁 的核心方法(L444)。
Fast Path(快速路径)------ Leader 乐观续约:
kotlin
if 我是 Leader && 租约未过期:
直接 Update Lease 对象(使用上次观察到的 ResourceVersion)
├─ 成功 → 更新本地 observedRecord,return true
└─ 失败(冲突)→ 进入 Slow Path
大多数情况下 Leader 的续约走 Fast Path,只需一次 API 调用。
Slow Path(慢速路径)------ 读-改-写:
sql
1. Get Lease 对象
├─ NotFound → Create Lease(我是第一个候选者,直接成为 Leader)
└─ 获取到记录 → 进入判断
2. 判断是否可抢占
if HolderIdentity 非空 && 租约未过期 && 我不是 Holder:
return false ← 别人持有有效租约,不能抢占
3. Update Lease 对象
├─ 我是 Leader → 续约(LeaderTransitions 不变)
└─ 我不是 Leader → 抢占(LeaderTransitions + 1)
租约过期判断 (isLeaseValid):
Go
arduino
// 上次观察到续约的时间 + LeaseDuration > 当前时间 → 租约有效
observedTime + LeaseDuration > now
五、ResourceLock:基于 Lease API 的锁实现
锁的具体实现是 LeaseLock(leaselock.go),操作的是 coordination.k8s.io/v1 的 Lease 资源。
历史演进:Endpoints → ConfigMaps → ConfigMapsLeases/EndpointsLeases → Leases。现在只支持 leases 类型,旧类型已移除。
Lease 对象的 .spec 包含:
| 字段 | 含义 |
|---|---|
HolderIdentity |
当前持有者的唯一标识(hostname_UUID) |
LeaseDurationSeconds |
租约时长(秒) |
AcquireTime |
获取锁的时间 |
RenewTime |
最近一次续约时间 |
LeaseTransitions |
Leader 切换次数 |
PreferredHolder |
优先持有者(用于协调选举) |
Strategy |
协调选举策略(OldestEmulationVersion) |
Identity 生成 (controllermanager.go L307-L313):
Go
ini
id = hostname + "_" + uuid.NewUUID()
这样即使同一台机器上运行两个进程,也不会冲突。
六、kube-controller-manager 层的编排逻辑
Run() 方法(L199)中的编排分为三种模式:
模式 1:无 Leader Election(--leader-elect=false)
scss
直接启动所有 Controller → run(ctx, allDescriptors)
单实例部署或调试时使用。
模式 2:标准 Leader Election(默认)
ini
┌─ 获锁成功 ─→ run(ctx, allControllers)
leaderElectAndRun ──┤
(main lock) └─ 获锁失败 ─→ 阻塞等待,持续竞争
┌─ 获锁成功 ─→ OnStartedLeading → run()
LeaderElector.Run ──┤
└─ 续约失败 ─→ OnStoppedLeading
├─ ReleaseOnCancel=true → 主动释放锁,优雅退出
└─ ReleaseOnCancel=false → os.Exit(1) 硬退出
关键:ReleaseOnCancel
当 ControllerManagerReleaseLeaderElectionLockOnExit Feature Gate 启用时,续约失败后不会立即 os.Exit(1),而是主动将 Lease 的 LeaseDurationSeconds 设为 1 秒(L344),让其他候选者更快接管,减少 Leader 切换的停机时间。
模式 3:Leader Migration(Leader 迁移)
当配置了 LeaderMigration 时(L319-L454),存在 两个独立的锁:
scss
┌─────────────────────────────────────┐
│ Main Lock (主锁) │
│ kube-controller-manager │
│ ┌─────────────────────────────┐ │
│ │ Non-Migrated Controllers │ │
│ │ (Deployment, ReplicaSet...) │ │
│ │ SA Token Controller │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│ Migration Lock (迁移锁) │
│ kube-controller-manager │
│ ┌─────────────────────────────┐ │
│ │ Migrated Controllers │ │
│ │ (如 ServiceAccount, HPA...) │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘
流程:
- 先竞争 Main Lock,启动 SA Token Controller
- SA Token Controller 启动后 → 关闭
MigrationReadychannel - 收到信号后 → 竞争 Migration Lock
- 两个锁可以分别被 不同的 KCM 实例 持有
- 每个 Controller 通过
FilterFunc决定归属哪个锁
这样可以将部分 Controller 从主 KCM 实例迁移到另一个实例运行,实现 Controller 的平滑迁移。
七、Coordinated Leader Election(协调选举,Alpha)
当启用 CoordinatedLeaderElection Feature Gate 时(L345-L371):
- 额外创建一个
LeaseCandidate对象(类型为coordination.k8s.io/v1beta1) LeaseCandidate定期RenewTime续约(每 5 分钟)- API Server 侧根据所有
LeaseCandidate的状态,协调决定谁是 Leader tryCoordinatedRenew替代tryAcquireOrRenew,使用协调策略
与传统模式的核心区别:
| 特性 | 传统模式 | Coordinated 模式 |
|---|---|---|
| 选举决策 | 客户端竞争,先到先得 | 服务端根据策略协调 |
| LeaseCandidate | 无 | 有(声明候选资格) |
| PreferredHolder | 不支持 | 支持(优雅交接) |
| 策略 | 无 | OldestEmulationVersion(优先选择最低模拟版本的实例) |
当 PreferredHolder 被设置时,当前 Leader 会主动放弃续约(L408),实现无中断的 Leader 交接。
八、Health Check & Watchdog
Leader Election 的状态通过 Healthz 暴露(L228-L231):
Go
ini
electionChecker = leaderelection.NewLeaderHealthzAdaptor(time.Second * 20)
- 当 Leader 续约成功时 → healthz 返回 OK
- 当续约失败超过 20 秒 → healthz 返回失败,触发就绪探测失败
- 配合
Check()方法检测死锁场景(如续约 goroutine 卡死但进程未退出)
九、完整时序图
scss
实例 A (候选者) API Server 实例 B (候选者)
│ │ │
│ acquire(): tryAcquireOrRenew │ │
│──Get Lease────────────────────────────→│ │
│←─NotFound──────────────────────────────│ │
│──Create Lease (HolderIdentity=A)──────→│ │
│←─OK (A 成为 Leader)────────────────────│ │
│ │ │
│ OnStartedLeading → run(controllers) │ │
│ │ │
│ renew(): tryAcquireOrRenew (FastPath) │ acquire(): tryAcquireOrRenew
│──Update Lease─────────────────────────→│←─Get Lease─────────────────│
│←─OK────────────────────────────────────│──Lease{A, 未过期}──────────→│
│ │ return false (等待...) │
│ ... 每 2s 续约一次 ... │ ... 每 2s 检查一次 ... │
│ │ │
│ ❌ 续约失败 (网络超时) │ │
│ renew() 在 RenewDeadline=10s 内重试 │ │
│ 全部失败 │ │
│ │ │
│ OnStoppedLeading │ LeaseDuration=15s 到期 │
│ ReleaseOnCancel → tryRelease() │ │
│ os.Exit(1) │ Get Lease → 租约过期! │
│ │ Update Lease (Holder=B) │
│ │←─OK─────────────────────────│
│ │ B 成为新 Leader │
│ │ OnStartedLeading → run() │
十、总结
kube-controller-manager 的 Leader Election 核心策略:
- 基于 Lease API 的乐观锁:利用 Kubernetes 的 ResourceVersion 实现 CAS 语义
- Fast Path + Slow Path:Leader 续约走乐观更新,减少 API 调用;冲突时回退到读-改-写
- Jitter 防惊群:RetryPeriod 使用 1.2 倍随机抖动,避免所有候选者同时竞争
- 主动释放 :
ReleaseOnCancel将 Lease 时长缩短为 1 秒,加速 Leader 切换 - Leader Migration:通过双锁机制实现 Controller 在不同实例间的平滑迁移
- Coordinated Election(Alpha):服务端策略协调替代客户端盲目竞争