Overview
在 Kubernetes的 kube-controller-manager
, kube-scheduler
, 以及使用 Operator
的底层实现 controller-rumtime
都支持高可用系统中的leader选举,本文将以理解 controller-rumtime
(底层的实现是 client-go
) 中的leader选举以在kubernetes controller中是如何实现的。
Background
在运行 kube-controller-manager
时,是有一些参数提供给cm进行leader选举使用的,可以参考官方文档提供的 参数 来了解相关参数。
|
|
本身以为这些组件的选举动作时通过etcd进行的,但是后面对 controller-runtime
学习时,发现并没有配置其相关的etcd相关参数,这就引起了对选举机制的好奇。怀着这种好奇心搜索了下有关于 kubernetes的选举,发现官网是这么介绍的,下面是对官方的说明进行一个通俗总结。simple leader election with kubernetes
通过阅读文章得知,kubernetes API 提供了一中选举机制,只要运行在集群内的容器,都是可以实现选举功能的。
Kubernetes API通过提供了两个属性来完成选举动作的
- ResourceVersions:每个API对象唯一一个ResourceVersion
- Annotations:每个API对象都可以对这些key进行注释
注:这种选举会增加APIServer的压力。也就对etcd会产生影响
那么有了这些信息之后,我们来看一下,在Kubernetes集群中,谁是cm的leader(我们提供的集群只有一个节点,所以本节点就是leader)
在Kubernetes中所有启用了leader选举的服务都会生成一个 EndPoint
,在这个 EndPoint
中会有上面提到的label(Annotations)来标识谁是leader。
|
|
这里以 kube-controller-manager
为例,来看下这个 EndPoint
有什么信息
|
|
可以看出 Annotations: control-plane.alpha.kubernetes.io/leader:
标出了哪个node是leader。
election in controller-runtime
controller-runtime
有关leader选举的部分在 pkg/leaderelection 下面,总共100行代码,我们来看下做了些什么?
可以看到,这里只提供了创建资源锁的一些选项
|
|
通过 NewResourceLock
可以看到,这里是走的 client-go/tools/leaderelection下面,而这个leaderelection也有一个 example 来学习如何使用它。
通过 example 可以看到,进入选举的入口是一个 RunOrDie() 的函数
|
|
到这里,我们了解了锁的概念和如何启动一个锁,下面看下,client-go都提供了那些锁。
在代码 tools/leaderelection/resourcelock/interface.go 定义了一个锁抽象,interface提供了一个通用接口,用于锁定leader选举中使用的资源。
|
|
那么实现这个抽象接口的就是,实现的资源锁,我们可以看到,client-go提供了四种资源锁
- leaselock
- configmaplock
- multilock
- endpointlock
leaselock
Lease是kubernetes控制平面中的通过ETCD来实现的一个Leases的资源,主要为了提供分布式租约的一种控制机制。相关对这个API的描述可以参考于:Lease 。
在Kubernetes集群中,我们可以使用如下命令来查看对应的lease
|
|
下面来看下leaselock的实现,leaselock会实现了作为资源锁的抽象
|
|
下面来看下leaselock实现了那些方法?
Get
Get 是从spec中返回选举的记录
|
|
Create
Create 是在kubernetes集群中尝试去创建一个租约,可以看到,Client就是API提供的对应资源的REST客户端,结果会在Kubernetes集群中创建这个Lease
|
|
Update
Update 是更新Lease的spec
|
|
RecordEvent
RecordEvent 是记录选举时出现的事件,这时候我们回到上部分 在kubernetes集群中查看 ep 的信息时可以看到的event中存在 became leader
的事件,这里就是将产生的这个event添加到 meta-data
中。
|
|
到这里大致上了解了资源锁究竟是什么了,其他种类的资源锁也是相同的实现的方式,这里就不过多阐述了;下面的我们来看看选举的过程。
election workflow
选举的代码入口是在 leaderelection.go ,这里会继续上面的 example 向下分析整个选举的过程。
前面我们看到了进入选举的入口是一个 RunOrDie() 的函数,那么就继续从这里开始来了解。进入 RunOrDie,看到其实只有几行而已,大致上了解到了RunOrDie会使用提供的配置来启动选举的客户端,之后会阻塞,直到 ctx 退出,或停止持有leader的租约。
|
|
下面看下 NewLeaderElector 做了些什么?可以看到,LeaderElector是一个结构体,这里只是创建他,这个结构体提供了我们选举中所需要的一切(LeaderElector就是RunOrDie创建的选举客户端)。
|
|
LeaderElector 是建立的选举客户端,
|
|
可以看到 Run 实现的选举逻辑就是在初始化客户端时传入的 三个 callback
|
|
在 Run 中调用了 acquire,这个是 通过一个loop去调用 tryAcquireOrRenew,直到ctx传递过来结束信号
|
|
这里实际上选举动作在 tryAcquireOrRenew 中,下面来看下tryAcquireOrRenew;tryAcquireOrRenew 是尝试获得一个leader租约,如果已经获得到了,则更新租约;否则可以得到租约则为true,反之false
|
|
summary
到这里,已经完整知道利用kubernetes进行选举的流程都是什么了;下面简单回顾下,上述leader选举所有的步骤:
- 首选创建的服务就是该服务的leader,锁可以为
lease
,endpoint
等资源进行上锁 - 已经是leader的实例会不断续租,租约的默认值是15秒 (
leaseDuration
);leader在租约满时更新租约时间(renewTime
)。 - 其他的follower,会不断检查对应资源锁的存在,如果已经有leader,那么则检查
renewTime
,如果超过了租用时间(),则表明leader存在问题需要重新启动选举,直到有follower提升为leader。 - 而为了避免资源被抢占,Kubernetes API使用了
ResourceVersion
来避免被重复修改(如果版本号与请求版本号不一致,则表示已经被修改了,那么APIServer将返回错误)