深入解析kubernetes中的选举机制

打印 上一主题 下一主题

主题 745|帖子 745|积分 2235

Overview

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

在运行 kube-controller-manager 时,是有一些参数提供给cm进行leader选举使用的,可以参考官方文档提供的 参数 来了解相关参数。
  1. --leader-elect                               Default: true
  2. --leader-elect-renew-deadline duration       Default: 10s
  3. --leader-elect-resource-lock string          Default: "leases"
  4. --leader-elect-resource-name string              Default: "kube-controller-manager"
  5. --leader-elect-resource-namespace string     Default: "kube-system"
  6. --leader-elect-retry-period duration         Default: 2s
  7. ...
复制代码
本身以为这些组件的选举动作时通过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。
  1. $ kubectl get ep -n kube-system
  2. NAME                      ENDPOINTS   AGE
  3. kube-controller-manager   <none>      3d4h
  4. kube-dns                              3d4h
  5. kube-scheduler            <none>      3d4h
复制代码
这里以 kube-controller-manager 为例,来看下这个 EndPoint 有什么信息
  1. [root@master-machine ~]# kubectl describe ep kube-controller-manager -n kube-system
  2. Name:         kube-controller-manager
  3. Namespace:    kube-system
  4. Labels:       <none>
  5. Annotations:  control-plane.alpha.kubernetes.io/leader:
  6.                 {"holderIdentity":"master-machine_06730140-a503-487d-850b-1fe1619f1fe1","leaseDurationSeconds":15,"acquireTime":"2022-06-27T15:30:46Z","re...
  7. Subsets:
  8. Events:
  9.   Type    Reason          Age    From                     Message
  10.   ----    ------          ----   ----                     -------
  11.   Normal  LeaderElection  2d22h  kube-controller-manager  master-machine_76aabcb5-49ff-45ff-bd18-4afa61fbc5af became leader
  12.   Normal  LeaderElection  9m     kube-controller-manager  master-machine_06730140-a503-487d-850b-1fe1619f1fe1 became leader
复制代码
可以看出 Annotations:  control-plane.alpha.kubernetes.io/leader: 标出了哪个node是leader。
election in controller-runtime

controller-runtime 有关leader选举的部分在 pkg/leaderelection 下面,总共100行代码,我们来看下做了些什么?
可以看到,这里只提供了创建资源锁的一些选项
  1. type Options struct {
  2.         // 在manager启动时,决定是否进行选举
  3.         LeaderElection bool
  4.         // 使用那种资源锁 默认为租用 lease
  5.         LeaderElectionResourceLock string
  6.         // 选举发生的名称空间
  7.         LeaderElectionNamespace string
  8.         // 该属性将决定持有leader锁资源的名称
  9.         LeaderElectionID string
  10. }
复制代码
通过 NewResourceLock 可以看到,这里是走的 client-go/tools/leaderelection下面,而这个leaderelection也有一个 example 来学习如何使用它。
通过 example 可以看到,进入选举的入口是一个 RunOrDie() 的函数
  1. // 这里使用了一个lease锁,注释中说愿意为集群中存在lease的监听较少
  2. lock := &resourcelock.LeaseLock{
  3.     LeaseMeta: metav1.ObjectMeta{
  4.         Name:      leaseLockName,
  5.         Namespace: leaseLockNamespace,
  6.     },
  7.     Client: client.CoordinationV1(),
  8.     LockConfig: resourcelock.ResourceLockConfig{
  9.         Identity: id,
  10.     },
  11. }
  12. // 开启选举循环
  13. leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{
  14.     Lock: lock,
  15.     // 这里必须保证拥有的租约在调用cancel()前终止,否则会仍有一个loop在运行
  16.     ReleaseOnCancel: true,
  17.     LeaseDuration:   60 * time.Second,
  18.     RenewDeadline:   15 * time.Second,
  19.     RetryPeriod:     5 * time.Second,
  20.     Callbacks: leaderelection.LeaderCallbacks{
  21.         OnStartedLeading: func(ctx context.Context) {
  22.             // 这里填写你的代码,
  23.             // usually put your code
  24.             run(ctx)
  25.         },
  26.         OnStoppedLeading: func() {
  27.             // 这里清理你的lease
  28.             klog.Infof("leader lost: %s", id)
  29.             os.Exit(0)
  30.         },
  31.         OnNewLeader: func(identity string) {
  32.             // we're notified when new leader elected
  33.             if identity == id {
  34.                 // I just got the lock
  35.                 return
  36.             }
  37.             klog.Infof("new leader elected: %s", identity)
  38.         },
  39.     },
  40. })
复制代码
到这里,我们了解了锁的概念和如何启动一个锁,下面看下,client-go都提供了那些锁。
在代码 tools/leaderelection/resourcelock/interface.go 定义了一个锁抽象,interface提供了一个通用接口,用于锁定leader选举中使用的资源。
  1. type Interface interface {
  2.         // Get 返回选举记录
  3.         Get(ctx context.Context) (*LeaderElectionRecord, []byte, error)
  4.         // Create 创建一个LeaderElectionRecord
  5.         Create(ctx context.Context, ler LeaderElectionRecord) error
  6.         // Update will update and existing LeaderElectionRecord
  7.         Update(ctx context.Context, ler LeaderElectionRecord) error
  8.         // RecordEvent is used to record events
  9.         RecordEvent(string)
  10.         // Identity 返回锁的标识
  11.         Identity() string
  12.         // Describe is used to convert details on current resource lock into a string
  13.         Describe() string
  14. }
复制代码
那么实现这个抽象接口的就是,实现的资源锁,我们可以看到,client-go提供了四种资源锁

  • leaselock
  • configmaplock
  • multilock
  • endpointlock
leaselock

Lease是kubernetes控制平面中的通过ETCD来实现的一个Leases的资源,主要为了提供分布式租约的一种控制机制。相关对这个API的描述可以参考于:Lease
在Kubernetes集群中,我们可以使用如下命令来查看对应的lease
  1. $ kubectl get leases -A
  2. NAMESPACE         NAME                      HOLDER                                                AGE
  3. kube-node-lease   master-machine            master-machine                                        3d19h
  4. kube-system       kube-controller-manager   master-machine_06730140-a503-487d-850b-1fe1619f1fe1   3d19h
  5. kube-system       kube-scheduler            master-machine_1724e2d9-c19c-48d7-ae47-ee4217b27073   3d19h
  6. $ kubectl describe leases kube-controller-manager -n kube-system
  7. Name:         kube-controller-manager
  8. Namespace:    kube-system
  9. Labels:       <none>
  10. Annotations:  <none>
  11. API Version:  coordination.k8s.io/v1
  12. Kind:         Lease
  13. Metadata:
  14.   Creation Timestamp:  2022-06-24T11:01:51Z
  15.   Managed Fields:
  16.     API Version:  coordination.k8s.io/v1
  17.     Fields Type:  FieldsV1
  18.     fieldsV1:
  19.       f:spec:
  20.         f:acquireTime:
  21.         f:holderIdentity:
  22.         f:leaseDurationSeconds:
  23.         f:leaseTransitions:
  24.         f:renewTime:
  25.     Manager:         kube-controller-manager
  26.     Operation:       Update
  27.     Time:            2022-06-24T11:01:51Z
  28.   Resource Version:  56012
  29.   Self Link:         /apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-controller-manager
  30.   UID:               851a32d2-25dc-49b6-a3f7-7a76f152f071
  31. Spec:
  32.   Acquire Time:            2022-06-27T15:30:46.000000Z
  33.   Holder Identity:         master-machine_06730140-a503-487d-850b-1fe1619f1fe1
  34.   Lease Duration Seconds:  15
  35.   Lease Transitions:       2
  36.   Renew Time:              2022-06-28T06:09:26.837773Z
  37. Events:                    <none>
复制代码
下面来看下leaselock的实现,leaselock会实现了作为资源锁的抽象
  1. type LeaseLock struct {
  2.         // LeaseMeta 就是类似于其他资源类型的属性,包含name ns 以及其他关于lease的属性
  3.         LeaseMeta  metav1.ObjectMeta
  4.         Client     coordinationv1client.LeasesGetter // Client 就是提供了informer中的功能
  5.         // lockconfig包含上面通过 describe 看到的 Identity与recoder用于记录资源锁的更改
  6.     LockConfig ResourceLockConfig
  7.     // lease 就是 API中的Lease资源,可以参考下上面给出的这个API的使用
  8.         lease      *coordinationv1.Lease
  9. }
复制代码
下面来看下leaselock实现了那些方法?
Get

Get 是从spec中返回选举的记录
  1. func (ll *LeaseLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {
  2.         var err error
  3.         ll.lease, err = ll.Client.Leases(ll.LeaseMeta.Namespace).Get(ctx, ll.LeaseMeta.Name, metav1.GetOptions{})
  4.         if err != nil {
  5.                 return nil, nil, err
  6.         }
  7.         record := LeaseSpecToLeaderElectionRecord(&ll.lease.Spec)
  8.         recordByte, err := json.Marshal(*record)
  9.         if err != nil {
  10.                 return nil, nil, err
  11.         }
  12.         return record, recordByte, nil
  13. }
  14. // 可以看出是返回这个资源spec里面填充的值
  15. func LeaseSpecToLeaderElectionRecord(spec *coordinationv1.LeaseSpec) *LeaderElectionRecord {
  16.         var r LeaderElectionRecord
  17.         if spec.HolderIdentity != nil {
  18.                 r.HolderIdentity = *spec.HolderIdentity
  19.         }
  20.         if spec.LeaseDurationSeconds != nil {
  21.                 r.LeaseDurationSeconds = int(*spec.LeaseDurationSeconds)
  22.         }
  23.         if spec.LeaseTransitions != nil {
  24.                 r.LeaderTransitions = int(*spec.LeaseTransitions)
  25.         }
  26.         if spec.AcquireTime != nil {
  27.                 r.AcquireTime = metav1.Time{spec.AcquireTime.Time}
  28.         }
  29.         if spec.RenewTime != nil {
  30.                 r.RenewTime = metav1.Time{spec.RenewTime.Time}
  31.         }
  32.         return &r
  33. }
复制代码
Create

Create 是在kubernetes集群中尝试去创建一个租约,可以看到,Client就是API提供的对应资源的REST客户端,结果会在Kubernetes集群中创建这个Lease
  1. func (ll *LeaseLock) Create(ctx context.Context, ler LeaderElectionRecord) error {
  2.         var err error
  3.         ll.lease, err = ll.Client.Leases(ll.LeaseMeta.Namespace).Create(ctx, &coordinationv1.Lease{
  4.                 ObjectMeta: metav1.ObjectMeta{
  5.                         Name:      ll.LeaseMeta.Name,
  6.                         Namespace: ll.LeaseMeta.Namespace,
  7.                 },
  8.                 Spec: LeaderElectionRecordToLeaseSpec(&ler),
  9.         }, metav1.CreateOptions{})
  10.         return err
  11. }
复制代码
Update

Update 是更新Lease的spec
  1. func (ll *LeaseLock) Update(ctx context.Context, ler LeaderElectionRecord) error {
  2.         if ll.lease == nil {
  3.                 return errors.New("lease not initialized, call get or create first")
  4.         }
  5.         ll.lease.Spec = LeaderElectionRecordToLeaseSpec(&ler)
  6.         lease, err := ll.Client.Leases(ll.LeaseMeta.Namespace).Update(ctx, ll.lease, metav1.UpdateOptions{})
  7.         if err != nil {
  8.                 return err
  9.         }
  10.         ll.lease = lease
  11.         return nil
  12. }
复制代码
RecordEvent

RecordEvent 是记录选举时出现的事件,这时候我们回到上部分 在kubernetes集群中查看 ep 的信息时可以看到的event中存在 became leader 的事件,这里就是将产生的这个event添加到 meta-data 中。
  1. func (ll *LeaseLock) RecordEvent(s string) {
  2.    if ll.LockConfig.EventRecorder == nil {
  3.       return
  4.    }
  5.    events := fmt.Sprintf("%v %v", ll.LockConfig.Identity, s)
  6.    subject := &coordinationv1.Lease{ObjectMeta: ll.lease.ObjectMeta}
  7.    // Populate the type meta, so we don't have to get it from the schema
  8.    subject.Kind = "Lease"
  9.    subject.APIVersion = coordinationv1.SchemeGroupVersion.String()
  10.    ll.LockConfig.EventRecorder.Eventf(subject, corev1.EventTypeNormal, "LeaderElection", events)
  11. }
复制代码
到这里大致上了解了资源锁究竟是什么了,其他种类的资源锁也是相同的实现的方式,这里就不过多阐述了;下面的我们来看看选举的过程。
election workflow

选举的代码入口是在 leaderelection.go ,这里会继续上面的 example 向下分析整个选举的过程。
前面我们看到了进入选举的入口是一个 RunOrDie() 的函数,那么就继续从这里开始来了解。进入 RunOrDie,看到其实只有几行而已,大致上了解到了RunOrDie会使用提供的配置来启动选举的客户端,之后会阻塞,直到 ctx 退出,或停止持有leader的租约。
  1. func RunOrDie(ctx context.Context, lec LeaderElectionConfig) {
  2.         le, err := NewLeaderElector(lec)
  3.         if err != nil {
  4.                 panic(err)
  5.         }
  6.         if lec.WatchDog != nil {
  7.                 lec.WatchDog.SetLeaderElection(le)
  8.         }
  9.         le.Run(ctx)
  10. }
复制代码
下面看下 NewLeaderElector 做了些什么?可以看到,LeaderElector是一个结构体,这里只是创建他,这个结构体提供了我们选举中所需要的一切(LeaderElector就是RunOrDie创建的选举客户端)。
[code]func NewLeaderElector(lec LeaderElectionConfig) (*LeaderElector, error) {        if lec.LeaseDuration
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

兜兜零元

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表