client-go之tools/leaderelection包源码分析
tools/leaderelection包源码分析
tools/leaderelection包
用于需要进行leader控制的controller等.
healthzadaptor.go
- 函数
// 创建一个基本的 healthz 适配器来监控领导者选举。 timeout 确定超过租约到期允许超时的时间。租约到期后的超时时间内的检查仍将正常返回。 func NewLeaderHealthzAdaptor(timeout time.Duration) *HealthzAdaptor { result := &HealthzAdaptor{ timeout: timeout, } return result }
- 结构体
// 将 healthz 端点与 LeaderElection 对象相关联。它有助于处理在 LeaderElection 之前设置的 healthz 端点。 type HealthzAdaptor struct { pointerLock sync.Mutex le *LeaderElector timeout time.Duration } // 返回健康检查对象的名称 func (l *HealthzAdaptor) Name() string { return "leaderElection" } // 检查由 healthz 端点处理程序调用。如果我们拥有租约但在le.config.LeaseDuration+maxTolerableExpiredLease内无法续订,它会失败(返回错误)。 func (l *HealthzAdaptor) Check(req *http.Request) error { l.pointerLock.Lock() defer l.pointerLock.Unlock() if l.le == nil { return nil } return l.le.Check(l.timeout) } // 将领导选举对象绑定到 HealthzAdaptor func (l *HealthzAdaptor) SetLeaderElection(le *LeaderElector) { l.pointerLock.Lock() defer l.pointerLock.Unlock() l.le = le }
metrics.go
- 接口
// 该文件提供了用于设置指标提供者(例如 prometheus)的抽象。 type leaderMetricsAdapter interface { leaderOn(name string) leaderOff(name string) } // GaugeMetric 表示可以任意上下浮动的单个数值。 type SwitchMetric interface { On(name string) Off(name string) } // 使用领导人选举生成各种指标 type MetricsProvider interface { NewLeaderMetric() SwitchMetric }
- 结构体
// 调用者在设置任何指标之前,应该使用lock锁定。 type defaultLeaderMetrics struct { // leader 的值表示当前进程是否是名称租约的所有者 leader SwitchMetric } func (m *defaultLeaderMetrics) leaderOn(name string) { if m == nil { return } m.leader.On(name) } func (m *defaultLeaderMetrics) leaderOff(name string) { if m == nil { return } m.leader.Off(name) } // leaderMetrics的工厂模式 type leaderMetricsFactory struct { metricsProvider MetricsProvider onlyOnce sync.Once } // 注意这里只能设置一次 func (f *leaderMetricsFactory) setProvider(mp MetricsProvider) { f.onlyOnce.Do(func() { f.metricsProvider = mp }) } // 获取leaderMetrics func (f *leaderMetricsFactory) newLeaderMetrics() leaderMetricsAdapter { mp := f.metricsProvider if mp == (noopMetricsProvider{}) { return noMetrics{} } return &defaultLeaderMetrics{ leader: mp.NewLeaderMetric(), } }
leaderelection.go
- 结构体
// LeaderElector使用的各种配置 type LeaderElectionConfig struct { // 客户端 Lock rl.Interface // 获取lease的周期 LeaseDuration time.Duration // 续租周期 RenewDeadline time.Duration // 重试周期 RetryPeriod time.Duration // 需要用户配置的回调函数 Callbacks LeaderCallbacks // 看门狗 用来健康检查 WatchDog *HealthzAdaptor // 判断在cancel的时候如果当前是leader是否需要释放 ReleaseOnCancel bool Name string } // 配置leaderElector的回调函数 type LeaderCallbacks struct { // 成功选举调用 OnStartedLeading func(context.Context) // 停止调用 OnStoppedLeading func() // 产生新的leader调用 OnNewLeader func(identity string) } type LeaderElector struct { // 用于保存当前应用的一些配置 包括该应用的id等等 config LeaderElectionConfig // 远程获取的资源 (不一定自己是leader) 所有想竞争此资源的应用获取的是同一份 observedRecord rl.LeaderElectionRecord // observedRecord 的原始二进制数据 observedRawRecord []byte // 获取的时间 observedTime time.Time reportedLeader string clock clock.Clock metrics leaderMetricsAdapter name string } // 在mgr开启时调用 func (le *LeaderElector) Run(ctx context.Context) { defer func() { runtime.HandleCrash() le.config.Callbacks.OnStoppedLeading() }() // 如果获取失败 那就是ctx signalled done // 不然即使失败, 该client也会一直去尝试获得leader位置 if !le.acquire(ctx) { return // ctx signalled done } // 如果获得leadership 以goroutine和回调的形式启动用户自己的逻辑方法OnStartedLeading ctx, cancel := context.WithCancel(ctx) defer cancel() go le.config.Callbacks.OnStartedLeading(ctx) // 一直去续约 这里也是一个循环操作 // 如果失去了leadership 该方法才会返回 // 该方法返回 整个Run方法就返回了 le.renew(ctx) } // 新产生leader后的传播,可以在OnNewLeader自定义 func (le *LeaderElector) maybeReportTransition() { // 如果没有变化 则不需要更新 if le.observedRecord.HolderIdentity == le.reportedLeader { return } // 更新reportedLeader为最新的leader的id le.reportedLeader = le.observedRecord.HolderIdentity if le.config.Callbacks.OnNewLeader != nil { // 调用当前应用的回调函数OnNewLeader报告新的leader产生 go le.config.Callbacks.OnNewLeader(le.reportedLeader) } } // 一旦获得leadership 立马返回true // 返回false的唯一情况是ctx signals done func (le *LeaderElector) acquire(ctx context.Context) bool { ctx, cancel := context.WithCancel(ctx) defer cancel() succeeded := false desc := le.config.Lock.Describe() klog.Infof("attempting to acquire leader lease %v...", desc) wait.JitterUntil(func() { // 尝试获得或者更新资源 succeeded = le.tryAcquireOrRenew() // 有可能会产生新的leader // 所以调用maybeReportTransition检查是否需要广播新产生的leader le.maybeReportTransition() if !succeeded { // 如果获得leadership失败 则返回后继续竞争 klog.V(4).Infof("failed to acquire lease %v", desc) return } // 自己成为leader // 可以调用cancel方法退出JitterUntil进而从acquire中返回 le.config.Lock.RecordEvent("became leader") le.metrics.leaderOn(le.config.Name) klog.Infof("successfully acquired lease %v", desc) cancel() }, le.config.RetryPeriod, JitterFactor, true, ctx.Done()) return succeeded } // RenewDeadline=10s RetryPeriod=2s // 续租 func (le *LeaderElector) renew(ctx context.Context) { ctx, cancel := context.WithCancel(ctx) defer cancel() // 每隔RetryPeriod会调用 除非cancel()方法被调用才会退出 wait.Until(func() { timeoutCtx, timeoutCancel := context.WithTimeout(ctx, le.config.RenewDeadline) defer timeoutCancel() // 每隔2ms调用该方法直到该方法返回true为止 // 如果超时了也会退出该方法 并且err中有错误信息 err := wait.PollImmediateUntil(le.config.RetryPeriod, func() (bool, error) { done := make(chan bool, 1) go func() { defer close(done) done <- le.tryAcquireOrRenew() }() select { case <-timeoutCtx.Done(): return false, fmt.Errorf("failed to tryAcquireOrRenew %s", timeoutCtx.Err()) case result := <-done: return result, nil } }, timeoutCtx.Done()) // 有可能会产生新的leader 如果有会广播新产生的leader le.maybeReportTransition() desc := le.config.Lock.Describe() if err == nil { // 如果err == nil, 表明上面PollImmediateUntil中返回true了 续约成功 依然处于leader位置 // 返回后 继续运行wait.Until的逻辑 klog.V(5).Infof("successfully renewed lease %v", desc) return } // err != nil 表明超时了 试的总时间超过了RenewDeadline 失去了leader位置 续约失败 // 调用cancel方法退出wait.Until le.config.Lock.RecordEvent("stopped leading") le.metrics.leaderOff(le.config.Name) klog.Infof("failed to renew lease %v: %v", desc, err) cancel() }, le.config.RetryPeriod, ctx.Done()) // if we hold the lease, give it up if le.config.ReleaseOnCancel { le.release() } } // 竞争或者更新leadership // 成功返回true 失败返回false func (le *LeaderElector) tryAcquireOrRenew() bool { now := metav1.Now() leaderElectionRecord := rl.LeaderElectionRecord{ HolderIdentity: le.config.Lock.Identity(), LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second), RenewTime: now, AcquireTime: now, } // 1. obtain or create the ElectionRecord // 从client端中获得ElectionRecord oldLeaderElectionRecord, err := le.config.Lock.Get() if err != nil { if !errors.IsNotFound(err) { // 失败直接退出 klog.Errorf("error retrieving resource lock %v: %v", le.config.Lock.Describe(), err) return false } // 因为没有获取到, 因此创建一个新的进去 if err = le.config.Lock.Create(leaderElectionRecord); err != nil { klog.Errorf("error initially creating leader election record: %v", err) return false } // 然后设置observedRecord为刚刚加入进去的leaderElectionRecord le.observedRecord = leaderElectionRecord le.observedTime = le.clock.Now() return true } // 2. Record obtained, check the Identity & Time // 从远端获取到record(资源)成功存到oldLeaderElectionRecord // 如果oldLeaderElectionRecord与observedRecord不相同 更新observedRecord // 因为observedRecord代表是从远端存在Record // 需要注意的是每个client都在竞争leadership, 而leader一直在续约, leader会更新它的RenewTime字段 // 所以一旦leader续约成功 每个non-leader候选者都需要更新其observedTime和observedRecord if !reflect.DeepEqual(le.observedRecord, *oldLeaderElectionRecord) { le.observedRecord = *oldLeaderElectionRecord le.observedTime = le.clock.Now() } // 如果leader已经被占有并且不是当前自己这个应用, 而且时间还没有到期 // 那就直接返回false, 因为已经无法抢占 时间没有过期 if len(oldLeaderElectionRecord.HolderIdentity) > 0 && le.observedTime.Add(le.config.LeaseDuration).After(now.Time) && !le.IsLeader() { klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity) return false } // 3. We're going to try to update. The leaderElectionRecord is set to it's default // here. Let's correct it before updating. if le.IsLeader() { // 如果当前服务就是以前的占有者 leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions } else { // 如果当前服务不是以前的占有者 LeaderTransitions加1 leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1 } // update the lock itself // 当前client占有该资源 成为leader if err = le.config.Lock.Update(leaderElectionRecord); err != nil { klog.Errorf("Failed to update lock: %v", err) return false } le.observedRecord = leaderElectionRecord le.observedTime = le.clock.Now() return true } func (le *LeaderElector) GetLeader() string { return le.observedRecord.HolderIdentity } // 很明显判断当前进程是不是leader只需要判断config中的id和observedRecord中的id是不是一致即可. func (le *LeaderElector) IsLeader() bool { return le.observedRecord.HolderIdentity == le.config.Lock.Identity() }
- 函数
// RunOrDie 使用提供的配置启动客户端LeaderElector,如果配置验证失败,则会出现异常。 RunOrDie 阻塞,直到 ctx 停止领导者选举循环或它已停止持有领导者租约 func RunOrDie(ctx context.Context, lec LeaderElectionConfig) { le, err := NewLeaderElector(lec) if err != nil { panic(err) } if lec.WatchDog != nil { lec.WatchDog.SetLeaderElection(le) } le.Run(ctx) }
resourcelock包
-
interface.go 定义了各种interface和New函数
// 提供了生成各个子类的方法 func New(lockType string, ns string, name string, coreClient corev1.CoreV1Interface, coordinationClient coordinationv1.CoordinationV1Interface, rlc ResourceLockConfig) (Interface, error) { switch lockType { case EndpointsResourceLock: return &EndpointsLock{ EndpointsMeta: metav1.ObjectMeta{ Namespace: ns, Name: name, }, Client: coreClient, LockConfig: rlc, }, nil ... default: return nil, fmt.Errorf("Invalid lock-type %s", lockType) }
-
EndpointLock.go 它有三个实现类, 分别为EndpointLock, ConfigMapLock和LeaseLock分别可以操作k8s中的endpoint, configmap和lease. 也就是提供了这三种资源类型.
// 这里只分析endpointslock.go,其他两个类似
type EndpointsLock struct {
// 必须包括namespace和name
EndpointsMeta metav1.ObjectMeta
// 访问api-server的客户端
Client corev1client.EndpointsGetter
// 该EndpointsLock的分布式唯一身份id
LockConfig ResourceLockConfig
// 当前操作的endpoint
e *v1.Endpoints
}
type ResourceLockConfig struct {
// 分布式唯一id
Identity string
EventRecorder EventRecorder
}
// Create, Update, Get方法都是利用client去访问k8s的api-server. 通过这里可以看得更明白, 就是操作EndpointsLock.e.Annotations中的
// control-plane.alpha.kubernetes.io/leader.
// 例如 GET
func (el *EndpointsLock) Get() (*LeaderElectionRecord, error) {
var record LeaderElectionRecord
var err error
// client从etcd中获取resource
el.e, err = el.Client.Endpoints(el.EndpointsMeta.Namespace).Get(el.EndpointsMeta.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
if el.e.Annotations == nil {
el.e.Annotations = make(map[string]string)
}
// 从annotations中获取对应key的value
if recordBytes, found := el.e.Annotations[LeaderElectionRecordAnnotationKey]; found {
if err := json.Unmarshal([]byte(recordBytes), &record); err != nil {
return nil, err
}
}
return &record, nil
}