美文网首页kubernetes
【k8s】 kubernetes leaderelection

【k8s】 kubernetes leaderelection

作者: Bogon | 来源:发表于2023-05-20 00:13 被阅读0次

在 Kubernetes的 kube-controller-manager , kube-scheduler, 以及使用 Operator 的底层实现 controller-rumtime 都支持高可用系统中的leader选举,本文将以理解 controller-rumtime (底层的实现是 client-go) 中的leader选举以在kubernetes controller中是如何实现的。

image.png

使用 clinet-go leader election 库进行选举的例子
首先实例化一个 resourcelock.LeaseLock 对象, 需要定义锁的名字, 命名空间及唯一identity. 然后调用 leaderelection 的 RunOrDie 接口进行选举.

重点介绍下 leaderelection.LeaderElectionConfig 字段含义:

  • LeaseDuration: 锁的时长, 通过该值判断是否超时, 超时则认定丢失锁

  • RenewDeadline: 续约时长, 每次 lease ttl 的时长

  • retryPeriod: 周期进行重试抢锁的时长

  • Callbacks:注册的自定义方法
    OnStartedLeading: 作为 leader 启动时进行回调
    OnStoppedLeading: 关闭时进行回调
    OnNewLeader: 转换为 leader 时进行回调

kube-scheduler 和 kube-manager-controller 选举的实现 ?

kube-scheduler 和 kube-manager-controller 为了实现保证高可用性,通常会启动启动个实例副本,但多个实例同时使用 informer 去监听处理必然会出现不一致或者重复执行的问题.

为了规避这个问题 kubernetes 中是通过 leaderelection 来实现选举, 只有一个实例能拿到锁, 拿到锁的实例才可以运行, 其他实例拿不到锁后会不断重试.

需要注意的是当某个 leader 实例由于不可控的原因不再续约或锁丢失时, 会触发 OnStoppedLeading 方法, 该方法会刷新日志后退出进程, 后面其他实例会竞争锁, 拿到锁的实例执行逻辑.

先前的 leader 实例在进程退出后, 会被 docker 或者 systemd 再次拉起, 拉起后再次尝试竞争锁, 拿不到锁就不断重试.

为什么从 leader 转变为 candidate 候选者时, 粗暴退出?

可以发现 kube-scheduler 和 kube-manager-controller 的代码设计, 当 leader 节点出于各种的异常, 导致不是 leader 节点时, 为什么需要进程直接退出, 而没有做到优雅退出?

首先 kube-manager-controller 里面的十几个 controller 控制器只是实现了 Run() 运行, 但没有 Stop 的方法, 其实就算实现了 Stop 退出, 也不好解决优雅退出, 因为在 controller 内部有大量的异步逻辑, 不好收敛.

另外各个 controller 的逻辑很严谨, 判断各种的状态, 在 controoler 控制器重启后也可以准确的同步配置, 最后的状态为一致性.

leaderelection 使用轮询的方式来选主抢锁吗 ?

是的, k8s leader election 抢锁选主的逻辑是周期轮询实现的, 相比社区中标准的分布式锁来说, 不仅增加了由于无效轮询带来的性能开销, 也不能解决公平性, 谁抢到了锁谁就是主 leader.

leader election 虽然有这些缺点, 但由于 k8s 里需要高可用组件就那么几个, 调度器和控制器组件开多个副本加起来也没多少个实例, 开销可以不用担心, 这些组件都是跟无状态的 apiserver 对接的, apiserver 作为无状态的服务可以横向扩展, 后端的 etcd 对付这些默认 2s 的锁请求也什么问题. 另外, 选主的逻辑不在乎公平性, 谁先谁后无所谓. 总结起来这类选举的场景, 轮询没啥问题, 实现起来也简单.

以前在 etcd v2 的时候是通过 put cas 实现的锁, 依赖关键的 prevExist 参数. 但 etcd 在 v3 的时候默认集成了 lock 的功能, 且在各个语言的 sdk 的 concurrent 里实现了 lock 功能.

简单说下 etcd v3 分布式锁的逻辑:

v3 的 lock 则是利用 lease (ttl), Revision (版本), Watch prefix 来实现的.

  1. 往自定义的 etcd 目录写一个 key, 并配置 key 的 lease ttl 超时
  2. 然后获取该目录下的所有 key. 判断当前最小 revision 的key 是否是由自身创建的, 如果是则拿到锁.
  3. 拿不到则监听 watch 比自身 revison 小一点的 key
  4. 当监听的 key 发生事件时, 则再次判断当前 key revison 是否最最小, 重新走第二个步骤.
image.png

并发 leaderelection 操作是否有冲突 ?

不会的 !!!

因为 create 创建操作在 etcd 层面是原子的, 同一时间只能有一个协程可以创建成功, 另一个协程会拿到 key 已存在的错误.

另外, 更新操作则使用 etcd 软事务来实现的, 当 Identity 值一致才更新。

kube-manager-controller leader election 默认参数配置:

leader-elect-lease-duration duration, 默认值: 15s
leader-elect-renew-deadline duration, 默认值: 10s
leader-elect-retry-period duration, 默认值: 2s

源码地址:https://github.com/kubernetes/client-go/blob/master/tools/leaderelection/leaderelection.go

关于 client-go leaderelection 选举的实现原理

Run() 内部调用是 acquire() 不断的尝试拿锁和续约, 当拿到锁或者更新已有锁后, 调用 renew() 方法来周期性的续约。

tryAcquireOrRenew 是 leaderelection 选举的关键代码, 里面不仅实现了拿锁也实现了 renew 续约逻辑:

  1. 首先尝试获取锁对象
  2. 如果没有锁对象, 那么尝试创建锁资源
  3. 如果拿到锁对象, 则判断是否超过 LeaseDuration 时长且当前实例不是 leader, 没有满足则直接退出
  4. 如果锁没有超时, 且锁的holdID 是当前实例, 则更新锁对象, 具体更新 acquireTime, renewTime, leasetDuration, holderID 等字段。
image.png

k8s 为了保证 kube-scheduler 和 kube-manager-controller 的高可用性, 所以各个核心组件可以启动多个实例, 但由于这些组件是通过 informer 来监听并处理事件的, 本身没有互斥特性, 多个相同组件启动监听会出现混乱的问题。

为了保证这些组件的唯一性, k8s 设计了 leader election 选举特性, 只有拿到锁的实例才可以执行任务, 拿不到锁就不断尝试去拿锁. 当实例丢失 leader 后, 直接退出进程, 然后重新启动后再次抢锁。

参考

深入解析kubernetes中的选举机制
https://dev.to/cylon/shen-ru-jie-xi-kuberneteszhong-de-xuan-ju-ji-zhi-5hbe

基于Kubernetes选主及应用
https://qiankunli.github.io/2021/01/13/kubernetes_leader_election.html

源码分析 kubernetes leaderelection 选举的实现原理
https://xiaorui.cc/archives/7331

Kubernetes 核心组件 Leader 选举机制
https://blog.imoe.tech/2023/01/18/principle-of-kubernetes-leaderelection

相关文章

网友评论

    本文标题:【k8s】 kubernetes leaderelection

    本文链接:https://www.haomeiwen.com/subject/ijlnsdtx.html