leader-election選主機(jī)制
1 為什么需要leader-election?
在集群中存在某種業(yè)務(wù)場(chǎng)景,一批相同功能的進(jìn)程同時(shí)運(yùn)行,但是同一時(shí)刻,只能有一個(gè)工作,只有當(dāng)正在工作的進(jìn)程異常時(shí),才會(huì)由另一個(gè)進(jìn)程進(jìn)行接管。這種業(yè)務(wù)邏輯通常用于實(shí)現(xiàn)一主多從。
如果有人認(rèn)為,傳統(tǒng)應(yīng)用需要部署多個(gè)通常是為了容災(zāi),而在k8s上運(yùn)行的Pod受控制器管理,如果Pod異?;蛘逷od所在宿主機(jī)宕機(jī),Pod是可以漂移到其他節(jié)點(diǎn)的,所以,不需要部署多個(gè)Pod,只需要部署一個(gè)Pod就行。k8s上的Pod確實(shí)可以漂移,但是,如果宿主機(jī)宕機(jī),k8s認(rèn)為Pod異常,并在其他節(jié)點(diǎn)重建Pod是有周期的,不能在查詢不到Pod狀態(tài)時(shí)立刻就將Pod驅(qū)逐掉,也許節(jié)點(diǎn)只是臨時(shí)不可用呢?例如,負(fù)載很高,因此,判斷宿主機(jī)宕機(jī)需要有個(gè)時(shí)間短。
k8s節(jié)點(diǎn)故障時(shí),工作負(fù)載的調(diào)度周期
因此,在k8s中運(yùn)行一主多從是為了能夠?qū)崿F(xiàn)主的快速切換。
2 kubernetes中的leader-election
k8s中也有這種業(yè)務(wù)場(chǎng)景,在多master場(chǎng)景下,只能有一個(gè)master上的進(jìn)程工作,例如,scheduler和controller-manager。以scheduler來(lái)說(shuō),它的工作是給Pod分配合適的宿主機(jī),如果有多個(gè)scheduler同時(shí)運(yùn)行,就會(huì)出現(xiàn)競(jìng)爭(zhēng),因此,如果允許這種場(chǎng)景存在的話,就又需要實(shí)現(xiàn)一種調(diào)度邏輯:某個(gè)Pod由哪個(gè)scheduler進(jìn)行調(diào)度,這相當(dāng)于又要實(shí)現(xiàn)一層調(diào)度。但是,實(shí)際上調(diào)度工作是相對(duì)比較簡(jiǎn)單的,不需要多個(gè)scheduler進(jìn)行負(fù)載,只需要一個(gè)scheduler進(jìn)行調(diào)度就行。因此,k8s提供了leader-election的能力。
leader-election的具體工作方式是:各候選者將自身的信息寫入某一個(gè)資源,如果寫成功,某個(gè)后選擇就稱為了主,其他就是備,同時(shí),在之后主會(huì)定期更新資源的時(shí)間,如果超過(guò)一段時(shí)間未更新時(shí)間,其他候選者發(fā)現(xiàn)資源的最后更新時(shí)間超過(guò)一定值,就會(huì)認(rèn)為主掛掉,然后會(huì)向資源寫入自身信息,從而成為新的主。
基于該原理,有一個(gè)現(xiàn)成的鏡像可以使用:instana/leader-elector。
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: leader
name: leader
spec:
replicas: 3
selector:
matchLabels:
app: leader
template:
metadata:
labels:
app: leader
spec:
containers:
- image: instana/leader-elector:0.5.13
name: leader
command: ["/app/server","--id=$(POD_NAME)","--election=leader-election","--http=0.0.0.0:4040"]
env:
- name: POD_NAME
valueFrom:
fieldRef:
fieldPath: metadata.name
上面的yaml有兩個(gè)需要注意的地方:
- /app/server是二進(jìn)制程序,id參數(shù)是候選者的唯一標(biāo)識(shí),election是資源名稱,http是應(yīng)用監(jiān)聽(tīng)的IP和端口號(hào)
- 將pod的名稱作為id參數(shù),也就是候選者的唯一標(biāo)識(shí)
創(chuàng)建deploy后,會(huì)啟動(dòng)三個(gè)Pod,通過(guò)kubectl logs可以看到只有一個(gè)Pod成為主,也就是向資源名稱為leader-election的Endpoint寫入了自身的Pod名稱。然后通過(guò)代理(kubectl proxy)訪問(wèn):http://localhost:8001/api/v1/namespaces/default/pods/:4040/proxy/,就會(huì)看到主的Pod名稱。
知道了leader-election的大概原理,也知道了上面的鏡像可以直接實(shí)現(xiàn)主的選舉,那么如何使用呢?
2.1 Sidecar
直接將上面的leader-elector鏡像作為Sidecar,將Pod名稱作為候選者的唯一標(biāo)識(shí),然后將Pod名稱也注入到環(huán)境變量,在業(yè)務(wù)進(jìn)程起來(lái)后,定時(shí)調(diào)用http://localhost:4040
就可以獲取主,如果發(fā)現(xiàn)主的名稱與自身的Pod的名稱一致,就執(zhí)行業(yè)務(wù)邏輯,否則一直等待。
2.2 SDK
使用Sidecar的好處是比較方便,開(kāi)發(fā)成本低,不便的地方就是,適用場(chǎng)景有限,只能寫入Endpoint資源。因此,在某些場(chǎng)景下,可以使用SDK,直接基于leader-election庫(kù)開(kāi)發(fā)。
k8s-leader-election文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-725511.html
創(chuàng)建一個(gè)Lease類型的鎖(當(dāng)然,也可以是其他類型,但是lease更加輕量),創(chuàng)建資源時(shí)需要指定資源的命名空間、名稱、標(biāo)識(shí)(這一批Pod都會(huì)該命名空間的資源寫入自身的唯一標(biāo)識(shí))。然后調(diào)用leaderelection庫(kù)中的RunOrDie()函數(shù),此時(shí)會(huì)指定:文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-725511.html
- Lock:資源鎖,將前面創(chuàng)建的Lease類型鎖填入
- ReleaseOnCancel:
- LeaseDuration:租約時(shí)間
- RenewDeadline:leader刷新超時(shí)
- RetryPeriod:刷新租約的時(shí)間間隔
- Callbacks:指定成為leader時(shí)要執(zhí)行的業(yè)務(wù)邏輯(OnStartedLeading),從leader變成非leader時(shí)要執(zhí)行的邏輯(OnStoppedLeading),leader變更時(shí)要執(zhí)行的邏輯(OnNewLeader)。
3 具體實(shí)現(xiàn)機(jī)制
// leaderelection/leaderelection.go
func (le *LeaderElector) Run(ctx context.Context) {
defer runtime.HandleCrash()
defer func() {
le.config.Callbacks.OnStoppedLeading()
}()
// 申請(qǐng)資源鎖,有三種情形:
// 1 出錯(cuò),則返回false,Run()直接退出
// 2 獲取到鎖了,則返回true,執(zhí)行回調(diào)函數(shù)
// 3 未獲取到鎖,acquire()函數(shù)不會(huì)返回
if !le.acquire(ctx) {
return // ctx signalled done
}
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// 申請(qǐng)成功后,執(zhí)行回調(diào)函數(shù)
go le.config.Callbacks.OnStartedLeading(ctx)
// 定時(shí)刷新租約
le.renew(ctx)
}
// 申請(qǐng)資源鎖
func (le *LeaderElector) acquire(ctx context.Context) bool {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
succeeded := false
desc := le.config.Lock.Describe()
klog.Infof("attempting to acquire leader lease %v...", desc)
// 每隔RetryPeriod去申請(qǐng)資源鎖,或者更新
wait.JitterUntil(func() {
succeeded = le.tryAcquireOrRenew(ctx)
le.maybeReportTransition()
if !succeeded {
// 沒(méi)有獲取到鎖,下一次再嘗試
klog.V(4).Infof("failed to acquire lease %v", desc)
return
}
// 成功獲取到鎖,則退出
le.config.Lock.RecordEvent("became leader")
le.metrics.leaderOn(le.config.Name)
klog.Infof("successfully acquired lease %v", desc)
cancel()
}, le.config.RetryPeriod, JitterFactor, true, ctx.Done())
return succeeded
}
func (le *LeaderElector) renew(ctx context.Context) {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// 每隔RetryPeriod嘗試更新租約
wait.Until(func() {
timeoutCtx, timeoutCancel := context.WithTimeout(ctx, le.config.RenewDeadline)
defer timeoutCancel()
err := wait.PollImmediateUntil(le.config.RetryPeriod, func() (bool, error) {
return le.tryAcquireOrRenew(timeoutCtx), nil
}, timeoutCtx.Done())
le.maybeReportTransition()
desc := le.config.Lock.Describe()
if err == nil {
klog.V(5).Infof("successfully renewed lease %v", desc)
return
}
le.config.Lock.RecordEvent("stopped leading")
le.metrics.leaderOff(le.config.Name)
klog.Infof("failed to renew lease %v: %v", desc, err)
cancel()
}, le.config.RetryPeriod, ctx.Done())
// if we hold the lease, give it up
if le.config.ReleaseOnCancel {
le.release()
}
}
// 嘗試獲取或者更新資源鎖
func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool {
now := metav1.Now()
leaderElectionRecord := rl.LeaderElectionRecord{
HolderIdentity: le.config.Lock.Identity(),
LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second),
RenewTime: now,
AcquireTime: now,
}
// 1 獲取資源鎖記錄
oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get(ctx)
if err != nil {
if !errors.IsNotFound(err) {
klog.Errorf("error retrieving resource lock %v: %v", le.config.Lock.Describe(), err)
return false
}
// 創(chuàng)建資源鎖
if err = le.config.Lock.Create(ctx, leaderElectionRecord); err != nil {
klog.Errorf("error initially creating leader election record: %v", err)
return false
}
le.setObservedRecord(&leaderElectionRecord)
return true
}
// 2 將資源鎖記錄與緩存的上一次的值進(jìn)行對(duì)比
// 如果當(dāng)前不是leader,并且資源鎖沒(méi)有過(guò)期,則退出
if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {
le.setObservedRecord(oldLeaderElectionRecord)
le.observedRawRecord = oldLeaderElectionRawRecord
}
if len(oldLeaderElectionRecord.HolderIdentity) > 0 &&
le.observedTime.Add(le.config.LeaseDuration).After(now.Time) &&
!le.IsLeader() {
klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity)
return false
}
// 3. We're going to try to update. The leaderElectionRecord is set to it's default
// here. Let's correct it before updating.
if le.IsLeader() {
// 當(dāng)前是leader,鎖資源未過(guò)期,將之前的資源鎖的數(shù)據(jù)填充到新的資源鎖中(申請(qǐng)鎖時(shí)間,切換次數(shù))
leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime
leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions
} else {
// 當(dāng)前不是leader
leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1
}
// 更新資源鎖
if err = le.config.Lock.Update(ctx, leaderElectionRecord); err != nil {
klog.Errorf("Failed to update lock: %v", err)
return false
}
le.setObservedRecord(&leaderElectionRecord)
return true
}
到了這里,關(guān)于【kubernetes】k8s中的選主機(jī)制的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!