P1-多实例leader选举.md
前言
Kubernetes多master场景下,核心组件都是以一主多从的模式来运行的,在前面scheduler部分的文章中,并没有分析其主从选举及工作的流程,那么在本篇中,以controller为例,单独作一篇分析组件之间主从工作模式。
入口
如scheduler一样,controller的cmd启动也是借助的cobra,对cobra不了解可以回到前面的文章中查看,这里不再赘述,直接顺着入口找到启动函数:
总入口
==> cmd/kube-controller-manager/controller-manager.go:38
command := app.NewControllerManagerCommand()
默认初始化的controller配置:
==> cmd/kube-controller-manager/app/controllermanager.go:103
c, err := s.Config(KnownControllers(), ControllersDisabledByDefault.List())
==> cmd/kube-controller-manager/app/controllermanager.go:319
ret := sets.StringKeySet(NewControllerInitializers(IncludeCloudLoops))
==> cmd/kube-controller-manager/app/controllermanager.go:343
1 | func NewControllerInitializers(loopMode ControllerLoopMode) map[string]InitFunc { |
这里是所有在初始化配置阶段,启动的各个资源的controller,这里会在后面的章节中展开讲述其中的两三个。
回到run函数继续往下:
==> cmd/kube-controller-manager/app/controllermanager.go:109
Run(c.Complete(), wait.NeverStop)
==> cmd/kube-controller-manager/app/controllermanager.go:153
func Run(c *config.CompletedConfig, stopCh <-chan struct{}) error {}
入口函数就在这里,代码块中已分段注释:
1 | func Run(c *config.CompletedConfig, stopCh <-chan struct{}) error { |
从这里可以看到,选举成为主领导节点后,才会进入工作流程,先跳过具体的工作流程,来看看leaderelection的选举过程
选举
选举入口
==> cmd/kube-controller-manager/app/controllermanager.go:252
leaderelection.RunOrDie(context.TODO(), leaderelection.LeaderElectionConfig{}
1 | func RunOrDie(ctx context.Context, lec LeaderElectionConfig) { |
==> vendor/k8s.io/client-go/tools/leaderelection/leaderelection.go:196
le.Run(ctx)
1 | // Run starts the leader election loop |
这个函数里包含多个defer和return,这里额外备注一下defer和return的执行先后顺序:
1 | 1.多个defer是以栈结构保存的,后入先出,下文的defer先执行 |
这个函数这里,大概可以看出选举执行的逻辑:
1.选举成功者,开始执行run()函数,即controller的工作函数。同时提供leader状态健康检查的api
2.选举失败者,会结束选举程序。但watchDog会持续运行,监测leader的健康状态
3.选举成功者,在之后会持续刷新自己的leader状态信息
竞选函数:
vendor/k8s.io/client-go/tools/leaderelection/leaderelection.go:212
1 | // acquire loops calling tryAcquireOrRenew and returns true immediately when tryAcquireOrRenew succeeds. |
定时执行函数
来看下定时循环函数JitterUntil的代码:
vendor/k8s.io/apimachinery/pkg/util/wait/wait.go:130
1 | func JitterUntil(f func(), period time.Duration, jitterFactor float64, sliding bool, stopCh <-chan struct{}) { |
k8s定时任务用的是非常原生的time.timer()来实现的,t.C本质上还是一个channel struct {},消费方运用select来触发到达指定计时间隔后,消费消息,进入下一次循环。
这里关于select结合channel的用法说明进行以下备注:
1 | 在select中,代码逻辑执行步骤如下: |
关于go time.Timer,这里有一篇文章讲得很好:
https://tonybai.com/2016/12/21/how-to-use-timer-reset-in-golang-correctly/
申请/刷新leader函数
vendor/k8s.io/client-go/tools/leaderelection/leaderelection.go:293
1 | // tryAcquireOrRenew tries to acquire a leader lease if it is not already acquired, |
这一段代码中有多个leader记录信息相关的变量,很容易混淆,为了便于理解这里抽出来说明下:
1 | LeaderElector # 参选者,每一个controller进程都会参与leader选举 |
先来看第1步中是怎么获取当前leader记录的:
vendor/k8s.io/client-go/tools/leaderelection/resourcelock/leaselock.go:39
1 | // Get returns the election record from a Lease spec |
取得lease对象的方法在这里:
vendor/k8s.io/client-go/kubernetes/typed/coordination/v1/lease.go:66
func (c *leases) Get(name string, options metav1.GetOptions) (result *v1.Lease, err error) {}
转换并返回的LeaderElectionRecord结构体是这样的:
1 | LeaderElectionRecord{ |
对返回的LeaderElectionRecord进行对比,如果是自身,则续约,如果不是自身,则看leader是否过期,对leader lock信息相应处理。
刷新选举状态函数
vendor/k8s.io/client-go/tools/leaderelection/leaderelection.go:234
1 | func (le *LeaderElector) renew(ctx context.Context) { |
tryAcquireOrRenew()和循环间隔执行函数同上面所讲基本一致,这里就不再说明了。
总结
组件选举大致可以概括为以下流程:
初始时,各实例均为LeaderElector,最先开始选举的,成为leader,成为工作实例。同时它会维护一份信息(leader lock)供各个LeaderElector探测,包括状态信息、健康监控接口等。
其余LeaderElector,进入热备状态,监控leader的运行状态,异常时会再次参与选举
leader在运行中会间隔持续刷新自身的leader状态。
不止于controller,其余的几个组件,主从之间的工作关系也应当是如此。
感谢阅读,欢迎指正