Kubernetes选举机制剖析

本文介绍了Kubernetes中Master组件的选举机制,重点阐述了如何利用Etcd的分布式锁确保数据一致性,包括选举实现原理、不同资源锁类型以及Leader选举和续约过程的详细步骤。
摘要由CSDN通过智能技术生成

选举的目的

Kubernetes作为分布式集群,本身运行了许多组件,尤其是Master层面的组件,每个都肩负着重要的功能,其中某个组件的故障,必然会对整个集群造成致命的影响,为了保障高可用,每个组件都需要多节点部署;但多节点会衍生出数据一致性的问题,所以同一时刻,多节点的组件必须有且只能有一个提供正常服务,这就是Kubernetes选举的目的(选出leader节点并保证同一时间只有一个leader)

选举的实现

Kubernetes选举机制采用分布式锁的方式,实现分布式锁的方式需要依赖存储服务,如Redis、zookeeper、Etcd等,由于Kubernetes依赖于etcd,所以Kubernetes的选举实现方式是通过Etcd实现分布式锁,具体原理如下:

        1、分布式锁依赖于Etcd上的一个key,key的操作都是原子操作,将key作为分布式锁,它有两种状态——存在和不存在。
        2、key(分布式锁)不存在时:多节点中的一个节点成功创建该key(获得锁)并写入自身节点的信息,获得锁的节点被称为领导者节点,领导者节点会定时更新(续约)该key的信息。
        3、key(分布式锁)存在时:其他节点处于阻塞状态并定时获取锁,这些节点被称为候选节点。候选节点定时获取锁的过程如下:定时获取key的数据,验证数据中领导者租约是否到期,如果未到期则不能抢占它,如果已到期则更新key并写入自身节点的信息,更新成功则成为领导者节点。

分布式锁的种类

基于Etcd的key而创建的分布式锁也称为资源锁,Kubernetes的资源锁主要有三种:        

  • EndpointsResourceLock :依赖于Endpoints资源,默认资源锁为该类型
  • ConfigMapsResourceLock :依赖于Configmaps资源
  • LeasesResourceLock :依赖于Leases资源
可通过--leader-elect-resource-lock参数指定使用哪种资源锁,如不指定则EndpointsResourceLock为默认资源锁。

Leader选举过程

le.acquire函数尝试从Etcd中获取资源锁,领导者节点获取到资源锁后会执行kube-scheduler的主要逻辑(即 le.renew函数定时(默认值为2秒)对资源锁续约。候选节点获取不资源锁,它不会退出并定时(默认值为2秒)尝试获取资源锁,直到成功为止。代码示例如下:
func (le *LeaderElector) Run(ctx context.Context) {

    defer func(){

        runtime.HandleCrash()

        le.config.Callbacks.OnStoppedLeading()

    }()

    if !le.acquire(ctx) {

        return
    }
    ...
    go le.config.Callbacks.OnStartedLeading(ctx)

    le.renew(ctx)
}

资源锁获取过程

func (le *LeaderElector) acquire(ctx context.Context) bool {
    ...
    wait.JitterUntil(func() {

        succeeded = le.tryAcquireOrRenew()

        le.maybeReportTransition()

        if !succeeded {

            return
        }
        ...
        cancel()

    }, le.config.RetryPeriod, JitterFactor, true, ctx.Done())

    return succeeded
}

获取资源锁的过程通过wait.JitterUntil定时器定时执行,它接收一个func匿名函数和一个stopCh Chan,内部会定时调用匿名函数,只有当stopCh关闭时,该定时器才会停止并退出。

执行le.tryAcquireOrRenew函数来获取资源锁。如果其获取资源锁失败,会通过return等待下一次定时获取资源锁。如果其获取资源锁成功,则说明当前节点可以成为领导者节点,退出acquire函数并返回true。le.tryAcquireOrRenew代码示例如下。
        

        首先,通过le.config.Lock.Get函数获取资源锁,当资源锁不存在时,当前节点创建该key(获取锁)并写入自身节点的信息,创建成功则当前节点成为领导者节点并返回true。

oldLeaderElectionRecord, err := le.config.Lock.Get()

if err !=nil {

    if !errors.IsNotFound(err) {

    return false
    }

    if err = le.config.Lock.Create(leaderElectionRecord); err != nil {

        return false
    }

    le.observedRecord = leaderElectionRecord

    le.observedTime = le.clock.Now()

    return true
}

        当资源锁存在时,更新本地缓存的租约信息

if !reflect.DeepEqual(le.observedRecord, *oIdLeaderElectionRecord) {

    le.observedRecord = *oldLeaderElectionRecord

    le.observedTime = le.clock.Now()
}

        候选节点会验证领导者节点的租约是否到期,如果尚未到期,暂时还不能抢占并返回false

if len(oldLeaderElectionRecord.HolderIdentity) > 0 &&

    le.observedTime.Add (le.config.LeaseDuration).After(now.Time) &&

    !le.IsLeader() {
    ...

    return false
}

         如果是领导者节点,那么AcquireTime(资源锁获得时间)和LeaderTransitions (领导者进行切换的次数)字段保持不变。如果是候选节点,则说明领导者节点的租约到期,给LeaderTransitions字段加1并抢占资源锁

    if le.IsLeader() {

        leaderElectionRecord.AcquireTime = 
oldLeaderElectionRecord.AcquireTime                                     
        leaderElectionRecord. LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions
    } else {
        LeaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1
    }

        通过le.config.Lock.Update函数尝试去更新租约记录,若更新成功,函数返回true

if err = le.config.Lock.Update (leaderElectionRecord); err != nil {

    klog.Errorf("Failed to update lock: %v", err)

    return false
}
...
return true

        在领导者节点获取资源锁以后,会定时(默认值为2秒)循环更新租约信息,以保持长久的领导者身份。若因网络超时而导致租约信息更新失败,则说明被候选节点抢占了领导者身份,当前节点会退出进程。代码示例如下:

func (le *LeaderElector) renew(ctx context.Context) {
    ...
    wait.Until(func()) {
        ...

    err := wait.PollImediate0ntil(le.config.RetryPeriod, func()

(bool,error) {

        done := make(chan bool,1)W

        go func() {

            defer close(done)

            done <- le. tryAcquireOrRenew()

        } ()
        
        ...

    }, timeoutCtx.Done())
    ...

    if err == nil {

        klog.V(5).Infof("successfully renewed lease tv", desc)

        return
    }
    ...
    cancel()

}, le.config.RetryPeriod, ctx.Done())

if le.config.Release0nCancel {

    le.release()
}

        领导者节点续约的过程通过wait.PollImmediateUntil定时器定时执行,它接收一个func匿名函数(条件函数)和一个stopCh,内部会定时调用条件函数,当条件函数返回true或stopCh关闭时,该定时器才会停止并退出。执行le.tryAcquireOrRenew函数来实现领导者节点的续约,其原
理与资源锁获取过程相同。le.tryAcquireOrRenew函数返回true说明续 约 成 功 , 并 进 入 下 一 个 定 时 续 约 ; 返 回 false 则 退 出 并 执 行le.release函数且释放资源锁。

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值