乐趣区

关于kubernetes:k8soperator-系列-基于-k8s-实现-Leader-选举

Leader election 实现形式:

  • 投票和播送:Raft、Paxos 算法。
  • 分布式锁:强制锁、倡议锁。

本文的侧重点是基于 k8s 实现 leader 选举在 operator 中的利用,所次要介绍基于分布式锁实现 Leader 选举。

分布式锁的实现依赖分布式一致性数据中心,k8s 的数据一致性依赖于底层的 etcd,etcd 应用的是 Raft 算法,Raft 的 Leader 选举就是应用投票和播送实现,但 Raft 比较复杂,不是本文的重点,有趣味能够自行查阅材料。

强制锁 和 倡议锁 的术语借鉴于 Linux 文件锁,k8s 上这两种实现与其十分类似。

强制锁

源码:https://github.com/operator-f…

实现原理:

  • Lock:利用 k8s 资源的唯一性,胜利创立资源既抢到锁。例如,创立一个 configMap。
  • UnLock:利用 k8s 的 GC,将 configMap 的 ownerReference 设为以后 pod,pod 销毁则 configMap 被 GC 删除,锁开释。

长处:

  • 实现简略。
  • 不会呈现多 Leader。

毛病:

  • ownerReference 不可跨 namespace。
  • 依赖 k8s GC,如果 GC 故障或提早,则会导致服务一段时间不可用。
  • pod 重启(pod 没有被删除,只是容器重启)也不会开释锁。
  • 抢占式,会导致惊群效应。
  • 没有锁超时机制。

应用

err := leader.Become(context.TODO(), "demo-operator-lock")
if err != nil {log.Error(err, "")
  os.Exit(1)
}

新版 operator-sdk(Ver >= v1.0.0)中已弃用基于 GC 的 LeaderElection。

倡议锁

源码:https://github.com/kubernetes…

实现原理:

  • configMap / Endpoint / Lease 作为 Lock 资源,用于记录 Leader 信息。
  • Lock:

    • Lock 资源不存在时创立成功者成为 Leader。
    • Lock 资源记录的 Leader(HolderIdentity)为空 或者 Lease(LeaseDurationSeconds)过期,更新成功者成为新 Leader。
  • UnLock:调用 release 办法设置 HolderIdentity = ""; LeaseDurationSeconds = 1

长处:

  • 租约机制。
  • 牢靠,被 k8s 本身的外围组件 controller-manager 和 scheduler 应用。
  • 可容忍肯定水平的工夫偏斜。

毛病:

  • 失去 Leader 权之后,容器重启。
  • Lock 资源只创立,无人回收。

实现细节

容忍工夫偏斜

包头的正文中,作者说明选举机制能够容忍肯定水平的工夫偏斜(工夫不统一),但不能容忍工夫速率偏斜(工夫快慢不统一):

// A client only acts on timestamps captured locally to infer the state of the
// leader election. The client does not consider timestamps in the leader
// election record to be accurate because these timestamps may not have been
// produced by a local clock. The implemention does not depend on their
// accuracy and only uses their change to indicate that another client has
// renewed the leader lease. Thus the implementation is tolerant to arbitrary
// clock skew, but is not tolerant to arbitrary clock skew rate.

tryAcquireOrRenew 办法中判断租约过期:

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

应用的是本地工夫,Lease 中的 RenewTimeAcquireTime 仅作为工夫记录,比拟的是 observedTimenow.Time

etcd 乐观锁

场景:A release 之后,B 和 C 同时抢 Leader,同时尝试将本人更新为 Leader。etcd 的乐观锁机制会保障后达到的更新会失败,因为 resourceVersion 曾经扭转,须要从新 Get 再 Update。

非凡场景

场景:Leader A crashed 了,然而 Lease 还未过期。必须等 Lease 过期后 B 能力成为 Leader。

场景:Leader A release 途中,Lease 已过期。B 更新 Lock 成为 Leader,A 和 B 在一小段时间内同时为 Leader。A release 更新会报错,但仍能失常退出。

// release attempts to release the leader lease if we have acquired it.
func (le *LeaderElector) release() bool {if !le.IsLeader() {return true}
    now := metav1.Now()
    leaderElectionRecord := rl.LeaderElectionRecord{
        LeaderTransitions:    le.observedRecord.LeaderTransitions,
        LeaseDurationSeconds: 1,
        RenewTime:            now,
        AcquireTime:          now,
    }
    if err := le.config.Lock.Update(context.TODO(), leaderElectionRecord); err != nil {klog.Errorf("Failed to release lock: %v", err)
        return false
    }
    le.observedRecord = leaderElectionRecord
    le.observedTime = le.clock.Now()
    return true
}

场景:Follower B 所在节点产生网络故障,Get Lock 资源失败,内存中 observed 数据老旧。复原后,与本地 observedRawRecord 比照发现不统一,以远端数据为准,同时更新 observedTime,B 不会误判租约已过期。

if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {
  le.observedRecord = *oldLeaderElectionRecord
  le.observedRawRecord = oldLeaderElectionRawRecord
  le.observedTime = le.clock.Now()}

能够看出 client-go 的 LeaderElection 逻辑是十分强壮的。

Lease Resource

LeaderElection 依赖的 client 是直连 apiserver 的,configMap / endpoint 等资源被大量组件 watch,倡议应用 Leader 选举专用的 Lease 资源,可缩小对 apiserver 的压力。

Migrate all uses of leader-election to use Lease API · Issue #80289 · kubernetes/kubernetes (github.com)

// we use the Lease lock type since edits to Leases are less common
// and fewer objects in the cluster watch "all Leases".
lock := &resourcelock.LeaseLock{
  LeaseMeta: metav1.ObjectMeta{
    Name:      leaseLockName,
    Namespace: leaseLockNamespace,
  },
  Client: client.CoordinationV1(),
  LockConfig: resourcelock.ResourceLockConfig{Identity: id,},
}

应用

client-go

参考:https://github.com/kubernetes…

operator-sdk

operator-sdk 中应用时 controller-runtime 做了封装,传入 LeaderElection 和 LeaderElectionID 即可开启领导选举。

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
  Scheme:                 scheme,
  MetricsBindAddress:     metricsAddr,
  Port:                   9443,
  CertDir:                certDir,
  HealthProbeBindAddress: probeAddr,
  LeaderElection:         enableLeaderElection,
  LeaderElectionID:       "a73bd0c8.gogo.io",
})

controllerManager 中分为 leaderElectionRunnablesnonLeaderElectionRunnables。reconciler 属于 leaderElection,只有 Leader 会执行。webhook 属于 nonLeaderElection,即不须要 LeaderElection,不必放心多正本状况下 webhook 单点问题。

    // leaderElectionRunnables is the set of Controllers that the controllerManager injects deps into and Starts.
    // These Runnables are managed by lead election.
    leaderElectionRunnables []Runnable
    // nonLeaderElectionRunnables is the set of webhook servers that the controllerManager injects deps into and Starts.
    // These Runnables will not be blocked by lead election.
    nonLeaderElectionRunnables []Runnable

Reference

https://kayn.wang/leader/

https://zdyxry.github.io/2019…

退出移动版