在 Kubernetes 的 kube-controller-manager , kube-scheduler, 以及使用 Operator 的底層實(shí)現(xiàn) controller-rumtime 都支持高可用系統(tǒng)中的 leader 選舉,本文將以理解 controller-rumtime (底層的實(shí)現(xiàn)是 client-go) 中的 leader 選舉以在 kubernetes controller 中是如何實(shí)現(xiàn)的。
Background
在運(yùn)行 kube-controller-manager 時(shí),是有一些參數(shù)提供給 cm 進(jìn)行 leader 選舉使用的,可以參考官方文檔提供的 參數(shù)來(lái)了解相關(guān)參數(shù)。
--leader-electDefault:true --leader-elect-renew-deadlinedurationDefault:10s --leader-elect-resource-lockstringDefault:"leases" --leader-elect-resource-namestringDefault:"kube-controller-manager" --leader-elect-resource-namespacestringDefault:"kube-system" --leader-elect-retry-perioddurationDefault:2s ...
本身以為這些組件的選舉動(dòng)作時(shí)通過(guò) etcd 進(jìn)行的,但是后面對(duì) controller-runtime 學(xué)習(xí)時(shí),發(fā)現(xiàn)并沒(méi)有配置其相關(guān)的 etcd 相關(guān)參數(shù),這就引起了對(duì)選舉機(jī)制的好奇。懷著這種好奇心搜索了下有關(guān)于 kubernetes 的選舉,發(fā)現(xiàn)官網(wǎng)是這么介紹的,下面是對(duì)官方的說(shuō)明進(jìn)行一個(gè)通俗總結(jié)。simple leader election with kubernetes
?
通過(guò)閱讀文章得知,kubernetes API 提供了一中選舉機(jī)制,只要運(yùn)行在集群內(nèi)的容器,都是可以實(shí)現(xiàn)選舉功能的。
Kubernetes API 通過(guò)提供了兩個(gè)屬性來(lái)完成選舉動(dòng)作的
ResourceVersions:每個(gè) API 對(duì)象唯一一個(gè) ResourceVersion
Annotations:每個(gè) API 對(duì)象都可以對(duì)這些 key 進(jìn)行注釋
注:這種選舉會(huì)增加 APIServer 的壓力。也就對(duì) etcd 會(huì)產(chǎn)生影響
那么有了這些信息之后,我們來(lái)看一下,在 Kubernetes 集群中,誰(shuí)是 cm 的 leader(我們提供的集群只有一個(gè)節(jié)點(diǎn),所以本節(jié)點(diǎn)就是 leader)。
在 Kubernetes 中所有啟用了 leader 選舉的服務(wù)都會(huì)生成一個(gè) EndPoint ,在這個(gè) EndPoint 中會(huì)有上面提到的 label(Annotations)來(lái)標(biāo)識(shí)誰(shuí)是 leader。
$kubectlgetep-nkube-system NAMEENDPOINTSAGE kube-controller-manager3d4h kube-dns3d4h kube-scheduler 3d4h
這里以 kube-controller-manager 為例,來(lái)看下這個(gè) EndPoint 有什么信息
[root@master-machine~]#kubectldescribeepkube-controller-manager-nkube-system Name:kube-controller-manager Namespace:kube-system Labels:Annotations:control-plane.alpha.kubernetes.io/leader: {"holderIdentity":"master-machine_06730140-a503-487d-850b-1fe1619f1fe1","leaseDurationSeconds":15,"acquireTime":"2022-06-27T1546Z","re... Subsets: Events: TypeReasonAgeFromMessage ------------------------- NormalLeaderElection2d22hkube-controller-managermaster-machine_76aabcb5-49ff-45ff-bd18-4afa61fbc5afbecameleader NormalLeaderElection9mkube-controller-managermaster-machine_06730140-a503-487d-850b-1fe1619f1fe1becameleader
可以看出 Annotations: control-plane.alpha.kubernetes.io/leader: 標(biāo)出了哪個(gè) node 是 leader。
election in controller-runtime
controller-runtime 有關(guān) leader 選舉的部分在 pkg/leaderelection下面,總共 100 行代碼,我們來(lái)看下做了些什么?
可以看到,這里只提供了創(chuàng)建資源鎖的一些選項(xiàng)
typeOptionsstruct{
//在manager啟動(dòng)時(shí),決定是否進(jìn)行選舉
LeaderElectionbool
//使用那種資源鎖默認(rèn)為租用lease
LeaderElectionResourceLockstring
//選舉發(fā)生的名稱(chēng)空間
LeaderElectionNamespacestring
//該屬性將決定持有l(wèi)eader鎖資源的名稱(chēng)
LeaderElectionIDstring
}
通過(guò) NewResourceLock 可以看到,這里是走的 client-go/tools/leaderelection下面,而這個(gè) leaderelection 也有一個(gè) example來(lái)學(xué)習(xí)如何使用它。
通過(guò) example 可以看到,進(jìn)入選舉的入口是一個(gè) RunOrDie() 的函數(shù)
//這里使用了一個(gè)lease鎖,注釋中說(shuō)愿意為集群中存在lease的監(jiān)聽(tīng)較少
lock:=&resourcelock.LeaseLock{
LeaseMeta:metav1.ObjectMeta{
Name:leaseLockName,
Namespace:leaseLockNamespace,
},
Client:client.CoordinationV1(),
LockConfig:resourcelock.ResourceLockConfig{
Identity:id,
},
}
//開(kāi)啟選舉循環(huán)
leaderelection.RunOrDie(ctx,leaderelection.LeaderElectionConfig{
Lock:lock,
//這里必須保證擁有的租約在調(diào)用cancel()前終止,否則會(huì)仍有一個(gè)loop在運(yùn)行
ReleaseOnCancel:true,
LeaseDuration:60*time.Second,
RenewDeadline:15*time.Second,
RetryPeriod:5*time.Second,
Callbacks:leaderelection.LeaderCallbacks{
OnStartedLeading:func(ctxcontext.Context){
//這里填寫(xiě)你的代碼,
//usuallyputyourcode
run(ctx)
},
OnStoppedLeading:func(){
//這里清理你的lease
klog.Infof("leaderlost:%s",id)
os.Exit(0)
},
OnNewLeader:func(identitystring){
//we'renotifiedwhennewleaderelected
ifidentity==id{
//Ijustgotthelock
return
}
klog.Infof("newleaderelected:%s",identity)
},
},
})
到這里,我們了解了鎖的概念和如何啟動(dòng)一個(gè)鎖,下面看下,client-go 都提供了那些鎖。
在代碼 tools/leaderelection/resourcelock/interface.go[6] 定義了一個(gè)鎖抽象,interface 提供了一個(gè)通用接口,用于鎖定 leader 選舉中使用的資源。
typeInterfaceinterface{
//Get返回選舉記錄
Get(ctxcontext.Context)(*LeaderElectionRecord,[]byte,error)
//Create創(chuàng)建一個(gè)LeaderElectionRecord
Create(ctxcontext.Context,lerLeaderElectionRecord)error
//UpdatewillupdateandexistingLeaderElectionRecord
Update(ctxcontext.Context,lerLeaderElectionRecord)error
//RecordEventisusedtorecordevents
RecordEvent(string)
//Identity返回鎖的標(biāo)識(shí)
Identity()string
//Describeisusedtoconvertdetailsoncurrentresourcelockintoastring
Describe()string
}
那么實(shí)現(xiàn)這個(gè)抽象接口的就是,實(shí)現(xiàn)的資源鎖,我們可以看到,client-go 提供了四種資源鎖
leaselock
configmaplock
multilock
endpointlock
leaselock
Lease 是 kubernetes 控制平面中的通過(guò) ETCD 來(lái)實(shí)現(xiàn)的一個(gè) Leases 的資源,主要為了提供分布式租約的一種控制機(jī)制。相關(guān)對(duì)這個(gè) API 的描述可以參考于:Lease 。
在 Kubernetes 集群中,我們可以使用如下命令來(lái)查看對(duì)應(yīng)的 lease
$kubectlgetleases-A NAMESPACENAMEHOLDERAGE kube-node-leasemaster-machinemaster-machine3d19h kube-systemkube-controller-managermaster-machine_06730140-a503-487d-850b-1fe1619f1fe13d19h kube-systemkube-schedulermaster-machine_1724e2d9-c19c-48d7-ae47-ee4217b270733d19h $kubectldescribeleaseskube-controller-manager-nkube-system Name:kube-controller-manager Namespace:kube-system Labels:Annotations: APIVersion:coordination.k8s.io/v1 Kind:Lease Metadata: CreationTimestamp:2022-06-24T1151Z ManagedFields: APIVersion:coordination.k8s.io/v1 FieldsType:FieldsV1 fieldsV1: f f f f f f Manager:kube-controller-manager Operation:Update Time:2022-06-24T1151Z ResourceVersion:56012 SelfLink:/apis/coordination.k8s.io/v1/namespaces/kube-system/leases/kube-controller-manager UID:851a32d2-25dc-49b6-a3f7-7a76f152f071 Spec: AcquireTime:2022-06-27T1546.000000Z HolderIdentity:master-machine_06730140-a503-487d-850b-1fe1619f1fe1 LeaseDurationSeconds:15 LeaseTransitions:2 RenewTime:2022-06-28T0626.837773Z Events:
下面來(lái)看下 leaselock 的實(shí)現(xiàn),leaselock 會(huì)實(shí)現(xiàn)了作為資源鎖的抽象
typeLeaseLockstruct{
//LeaseMeta就是類(lèi)似于其他資源類(lèi)型的屬性,包含namens以及其他關(guān)于lease的屬性
LeaseMetametav1.ObjectMeta
Clientcoordinationv1client.LeasesGetter//Client就是提供了informer中的功能
//lockconfig包含上面通過(guò)describe看到的Identity與recoder用于記錄資源鎖的更改
LockConfigResourceLockConfig
//lease就是API中的Lease資源,可以參考下上面給出的這個(gè)API的使用
lease*coordinationv1.Lease
}
下面來(lái)看下 leaselock 實(shí)現(xiàn)了那些方法?
Get
Get是從 spec 中返回選舉的記錄
func(ll*LeaseLock)Get(ctxcontext.Context)(*LeaderElectionRecord,[]byte,error){
varerrerror
ll.lease,err=ll.Client.Leases(ll.LeaseMeta.Namespace).Get(ctx,ll.LeaseMeta.Name,metav1.GetOptions{})
iferr!=nil{
returnnil,nil,err
}
record:=LeaseSpecToLeaderElectionRecord(&ll.lease.Spec)
recordByte,err:=json.Marshal(*record)
iferr!=nil{
returnnil,nil,err
}
returnrecord,recordByte,nil
}
//可以看出是返回這個(gè)資源spec里面填充的值
funcLeaseSpecToLeaderElectionRecord(spec*coordinationv1.LeaseSpec)*LeaderElectionRecord{
varrLeaderElectionRecord
ifspec.HolderIdentity!=nil{
r.HolderIdentity=*spec.HolderIdentity
}
ifspec.LeaseDurationSeconds!=nil{
r.LeaseDurationSeconds=int(*spec.LeaseDurationSeconds)
}
ifspec.LeaseTransitions!=nil{
r.LeaderTransitions=int(*spec.LeaseTransitions)
}
ifspec.AcquireTime!=nil{
r.AcquireTime=metav1.Time{spec.AcquireTime.Time}
}
ifspec.RenewTime!=nil{
r.RenewTime=metav1.Time{spec.RenewTime.Time}
}
return&r
}
Create
Create是在 kubernetes 集群中嘗試去創(chuàng)建一個(gè)租約,可以看到,Client 就是 API 提供的對(duì)應(yīng)資源的 REST 客戶(hù)端,結(jié)果會(huì)在 Kubernetes 集群中創(chuàng)建這個(gè) Lease
func(ll*LeaseLock)Create(ctxcontext.Context,lerLeaderElectionRecord)error{
varerrerror
ll.lease,err=ll.Client.Leases(ll.LeaseMeta.Namespace).Create(ctx,&coordinationv1.Lease{
ObjectMeta:metav1.ObjectMeta{
Name:ll.LeaseMeta.Name,
Namespace:ll.LeaseMeta.Namespace,
},
Spec:LeaderElectionRecordToLeaseSpec(&ler),
},metav1.CreateOptions{})
returnerr
}
Update
Update是更新 Lease 的 spec
func(ll*LeaseLock)Update(ctxcontext.Context,lerLeaderElectionRecord)error{
ifll.lease==nil{
returnerrors.New("leasenotinitialized,callgetorcreatefirst")
}
ll.lease.Spec=LeaderElectionRecordToLeaseSpec(&ler)
lease,err:=ll.Client.Leases(ll.LeaseMeta.Namespace).Update(ctx,ll.lease,metav1.UpdateOptions{})
iferr!=nil{
returnerr
}
ll.lease=lease
returnnil
}
RecordEvent
RecordEvent是記錄選舉時(shí)出現(xiàn)的事件,這時(shí)候我們回到上部分 在 kubernetes 集群中查看 ep 的信息時(shí)可以看到的 event 中存在 became leader 的事件,這里就是將產(chǎn)生的這個(gè) event 添加到 meta-data 中。
func(ll*LeaseLock)RecordEvent(sstring){
ifll.LockConfig.EventRecorder==nil{
return
}
events:=fmt.Sprintf("%v%v",ll.LockConfig.Identity,s)
subject:=&coordinationv1.Lease{ObjectMeta:ll.lease.ObjectMeta}
//Populatethetypemeta,sowedon'thavetogetitfromtheschema
subject.Kind="Lease"
subject.APIVersion=coordinationv1.SchemeGroupVersion.String()
ll.LockConfig.EventRecorder.Eventf(subject,corev1.EventTypeNormal,"LeaderElection",events)
}
到這里大致上了解了資源鎖究竟是什么了,其他種類(lèi)的資源鎖也是相同的實(shí)現(xiàn)的方式,這里就不過(guò)多闡述了;下面的我們來(lái)看看選舉的過(guò)程。
election workflow
選舉的代碼入口是在 leaderelection.go,這里會(huì)繼續(xù)上面的 example 向下分析整個(gè)選舉的過(guò)程。
前面我們看到了進(jìn)入選舉的入口是一個(gè) RunOrDie()的函數(shù),那么就繼續(xù)從這里開(kāi)始來(lái)了解。進(jìn)入 RunOrDie,看到其實(shí)只有幾行而已,大致上了解到了 RunOrDie 會(huì)使用提供的配置來(lái)啟動(dòng)選舉的客戶(hù)端,之后會(huì)阻塞,直到 ctx 退出,或停止持有 leader 的租約。
funcRunOrDie(ctxcontext.Context,lecLeaderElectionConfig){
le,err:=NewLeaderElector(lec)
iferr!=nil{
panic(err)
}
iflec.WatchDog!=nil{
lec.WatchDog.SetLeaderElection(le)
}
le.Run(ctx)
}
下面看下 NewLeaderElector做了些什么?可以看到,LeaderElector 是一個(gè)結(jié)構(gòu)體,這里只是創(chuàng)建他,這個(gè)結(jié)構(gòu)體提供了我們選舉中所需要的一切(LeaderElector 就是 RunOrDie 創(chuàng)建的選舉客戶(hù)端)。
funcNewLeaderElector(lecLeaderElectionConfig)(*LeaderElector,error){
iflec.LeaseDuration<=?lec.RenewDeadline?{
??return?nil,?fmt.Errorf("leaseDuration?must?be?greater?than?renewDeadline")
?}
?if?lec.RenewDeadline?<=?time.Duration(JitterFactor*float64(lec.RetryPeriod))?{
??return?nil,?fmt.Errorf("renewDeadline?must?be?greater?than?retryPeriod*JitterFactor")
?}
?if?lec.LeaseDuration?1?{
??return?nil,?fmt.Errorf("leaseDuration?must?be?greater?than?zero")
?}
?if?lec.RenewDeadline?1?{
??return?nil,?fmt.Errorf("renewDeadline?must?be?greater?than?zero")
?}
?if?lec.RetryPeriod?1?{
??return?nil,?fmt.Errorf("retryPeriod?must?be?greater?than?zero")
?}
?if?lec.Callbacks.OnStartedLeading?==?nil?{
??return?nil,?fmt.Errorf("OnStartedLeading?callback?must?not?be?nil")
?}
?if?lec.Callbacks.OnStoppedLeading?==?nil?{
??return?nil,?fmt.Errorf("OnStoppedLeading?callback?must?not?be?nil")
?}
?if?lec.Lock?==?nil?{
??return?nil,?fmt.Errorf("Lock?must?not?be?nil.")
?}
?le?:=?LeaderElector{
??config:??lec,
??clock:???clock.RealClock{},
??metrics:?globalMetricsFactory.newLeaderMetrics(),
?}
?le.metrics.leaderOff(le.config.Name)
?return?&le,?nil
}
LeaderElector是建立的選舉客戶(hù)端,
typeLeaderElectorstruct{
configLeaderElectionConfig//這個(gè)的配置,包含一些時(shí)間參數(shù),健康檢查
//recoder相關(guān)屬性
observedRecordrl.LeaderElectionRecord
observedRawRecord[]byte
observedTimetime.Time
//usedtoimplementOnNewLeader(),maylagslightlyfromthe
//valueobservedRecord.HolderIdentityifthetransitionhas
//notyetbeenreported.
reportedLeaderstring
//clockiswrapperaroundtimetoallowforlessflakytesting
clockclock.Clock
//鎖定observedRecord
observedRecordLocksync.Mutex
metricsleaderMetricsAdapter
}
可以看到 Run 實(shí)現(xiàn)的選舉邏輯就是在初始化客戶(hù)端時(shí)傳入的 三個(gè) callback
func(le*LeaderElector)Run(ctxcontext.Context){
deferruntime.HandleCrash()
deferfunc(){//退出時(shí)執(zhí)行callbacke的OnStoppedLeading
le.config.Callbacks.OnStoppedLeading()
}()
if!le.acquire(ctx){
return
}
ctx,cancel:=context.WithCancel(ctx)
defercancel()
gole.config.Callbacks.OnStartedLeading(ctx)//選舉時(shí),執(zhí)行OnStartedLeading
le.renew(ctx)
}
在 Run 中調(diào)用了 acquire,這個(gè)是 通過(guò)一個(gè) loop 去調(diào)用 tryAcquireOrRenew,直到 ctx 傳遞過(guò)來(lái)結(jié)束信號(hào)
func(le*LeaderElector)acquire(ctxcontext.Context)bool{
ctx,cancel:=context.WithCancel(ctx)
defercancel()
succeeded:=false
desc:=le.config.Lock.Describe()
klog.Infof("attemptingtoacquireleaderlease%v...",desc)
//jitterUntil是執(zhí)行定時(shí)的函數(shù)func()是定時(shí)任務(wù)的邏輯
//RetryPeriod是周期間隔
//JitterFactor是重試系數(shù),類(lèi)似于延遲隊(duì)列中的系數(shù)(duration+maxFactor*duration)
//sliding邏輯是否計(jì)算在時(shí)間內(nèi)
//上下文傳遞
wait.JitterUntil(func(){
succeeded=le.tryAcquireOrRenew(ctx)
le.maybeReportTransition()
if!succeeded{
klog.V(4).Infof("failedtoacquirelease%v",desc)
return
}
le.config.Lock.RecordEvent("becameleader")
le.metrics.leaderOn(le.config.Name)
klog.Infof("successfullyacquiredlease%v",desc)
cancel()
},le.config.RetryPeriod,JitterFactor,true,ctx.Done())
returnsucceeded
}
這里實(shí)際上選舉動(dòng)作在 tryAcquireOrRenew 中,下面來(lái)看下 tryAcquireOrRenew;tryAcquireOrRenew 是嘗試獲得一個(gè) leader 租約,如果已經(jīng)獲得到了,則更新租約;否則可以得到租約則為 true,反之 false
func(le*LeaderElector)tryAcquireOrRenew(ctxcontext.Context)bool{
now:=metav1.Now()//時(shí)間
leaderElectionRecord:=rl.LeaderElectionRecord{//構(gòu)建一個(gè)選舉record
HolderIdentity:le.config.Lock.Identity(),//選舉人的身份特征,ep與主機(jī)名有關(guān)
LeaseDurationSeconds:int(le.config.LeaseDuration/time.Second),//默認(rèn)15s
RenewTime:now,//重新獲取時(shí)間
AcquireTime:now,//獲得時(shí)間
}
//1.從API獲取或創(chuàng)建一個(gè)recode,如果可以拿到則已經(jīng)有租約,反之創(chuàng)建新租約
oldLeaderElectionRecord,oldLeaderElectionRawRecord,err:=le.config.Lock.Get(ctx)
iferr!=nil{
if!errors.IsNotFound(err){
klog.Errorf("errorretrievingresourcelock%v:%v",le.config.Lock.Describe(),err)
returnfalse
}
//創(chuàng)建租約的動(dòng)作就是新建一個(gè)對(duì)應(yīng)的resource,這個(gè)lock就是leaderelection提供的四種鎖,
//看你在runOrDie中初始化傳入了什么鎖
iferr=le.config.Lock.Create(ctx,leaderElectionRecord);err!=nil{
klog.Errorf("errorinitiallycreatingleaderelectionrecord:%v",err)
returnfalse
}
//到了這里就已經(jīng)拿到或者創(chuàng)建了租約,然后記錄其一些屬性,LeaderElectionRecord
le.setObservedRecord(&leaderElectionRecord)
returntrue
}
//2.獲取記錄檢查身份和時(shí)間
if!bytes.Equal(le.observedRawRecord,oldLeaderElectionRawRecord){
le.setObservedRecord(oldLeaderElectionRecord)
le.observedRawRecord=oldLeaderElectionRawRecord
}
iflen(oldLeaderElectionRecord.HolderIdentity)>0&&
le.observedTime.Add(le.config.LeaseDuration).After(now.Time)&&
!le.IsLeader(){//不是leader,進(jìn)行HolderIdentity比較,再加上時(shí)間,這個(gè)時(shí)候沒(méi)有到競(jìng)選其,跳出
klog.V(4).Infof("lockisheldby%vandhasnotyetexpired",oldLeaderElectionRecord.HolderIdentity)
returnfalse
}
// 3.我們將嘗試更新。在這里leaderElectionRecord設(shè)置為默認(rèn)值。讓我們?cè)诟轮案?ifle.IsLeader(){//到這就說(shuō)明是leader,修正他的時(shí)間
leaderElectionRecord.AcquireTime=oldLeaderElectionRecord.AcquireTime
leaderElectionRecord.LeaderTransitions=oldLeaderElectionRecord.LeaderTransitions
}else{//LeaderTransitions就是指leader調(diào)整(轉(zhuǎn)變?yōu)槠渌┝藥状?,如果是?//則為發(fā)生轉(zhuǎn)變,保持原有值
//反之,則+1
leaderElectionRecord.LeaderTransitions=oldLeaderElectionRecord.LeaderTransitions+1
}
//完事之后更新APIServer中的鎖資源,也就是更新對(duì)應(yīng)的資源的屬性信息
iferr=le.config.Lock.Update(ctx,leaderElectionRecord);err!=nil{
klog.Errorf("Failedtoupdatelock:%v",err)
returnfalse
}
//setObservedRecord是通過(guò)一個(gè)新的record來(lái)更新這個(gè)鎖中的record
//操作是安全的,會(huì)上鎖保證臨界區(qū)僅可以被一個(gè)線(xiàn)程/進(jìn)程操作
le.setObservedRecord(&leaderElectionRecord)
returntrue
}
到這里,已經(jīng)完整知道利用 kubernetes 進(jìn)行選舉的流程都是什么了;下面簡(jiǎn)單回顧下,上述 leader 選舉所有的步驟:
首選創(chuàng)建的服務(wù)就是該服務(wù)的 leader,鎖可以為 lease , endpoint 等資源進(jìn)行上鎖
已經(jīng)是 leader 的實(shí)例會(huì)不斷續(xù)租,租約的默認(rèn)值是 15 秒 (leaseDuration);leader 在租約滿(mǎn)時(shí)更新租約時(shí)間(renewTime)。
其他的 follower,會(huì)不斷檢查對(duì)應(yīng)資源鎖的存在,如果已經(jīng)有 leader,那么則檢查 renewTime,如果超過(guò)了租用時(shí)間(),則表明 leader 存在問(wèn)題需要重新啟動(dòng)選舉,直到有 follower 提升為 leader。
而為了避免資源被搶占,Kubernetes API 使用了 ResourceVersion 來(lái)避免被重復(fù)修改(如果版本號(hào)與請(qǐng)求版本號(hào)不一致,則表示已經(jīng)被修改了,那么 APIServer 將返回錯(cuò)誤)
利用 Leader 機(jī)制實(shí)現(xiàn) HA 應(yīng)用
下面就通過(guò)一個(gè) example 來(lái)實(shí)現(xiàn)一個(gè),利用 kubernetes 提供的選舉機(jī)制完成的高可用應(yīng)用。
代碼實(shí)現(xiàn)
如果僅僅是使用 Kubernetes 中的鎖,實(shí)現(xiàn)的代碼也只有幾行而已。
packagemain
import(
"context"
"flag"
"fmt"
"os"
"os/signal"
"syscall"
"time"
metav1"k8s.io/apimachinery/pkg/apis/meta/v1"
clientset"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/leaderelection"
"k8s.io/client-go/tools/leaderelection/resourcelock"
"k8s.io/klog/v2"
)
funcbuildConfig(kubeconfigstring)(*rest.Config,error){
ifkubeconfig!=""{
cfg,err:=clientcmd.BuildConfigFromFlags("",kubeconfig)
iferr!=nil{
returnnil,err
}
returncfg,nil
}
cfg,err:=rest.InClusterConfig()
iferr!=nil{
returnnil,err
}
returncfg,nil
}
funcmain(){
klog.InitFlags(nil)
varkubeconfigstring
varleaseLockNamestring
varleaseLockNamespacestring
varidstring
//初始化客戶(hù)端的部分
flag.StringVar(&kubeconfig,"kubeconfig","","absolutepathtothekubeconfigfile")
flag.StringVar(&id,"id","","theholderidentityname")
flag.StringVar(&leaseLockName,"lease-lock-name","","theleaselockresourcename")
flag.StringVar(&leaseLockNamespace,"lease-lock-namespace","","theleaselockresourcenamespace")
flag.Parse()
ifleaseLockName==""{
klog.Fatal("unabletogetleaselockresourcename(missinglease-lock-nameflag).")
}
ifleaseLockNamespace==""{
klog.Fatal("unabletogetleaselockresourcenamespace(missinglease-lock-namespaceflag).")
}
config,err:=buildConfig(kubeconfig)
iferr!=nil{
klog.Fatal(err)
}
client:=clientset.NewForConfigOrDie(config)
run:=func(ctxcontext.Context){
//實(shí)現(xiàn)的業(yè)務(wù)邏輯,這里僅僅為實(shí)驗(yàn),就直接打印了
klog.Info("Controllerloop...")
for{
fmt.Println("Iamleader,Iwasworking.")
time.Sleep(time.Second*5)
}
}
//useaGocontextsowecantelltheleaderelectioncodewhenwe
//wanttostepdown
ctx,cancel:=context.WithCancel(context.Background())
defercancel()
//監(jiān)聽(tīng)系統(tǒng)中斷
ch:=make(chanos.Signal,1)
signal.Notify(ch,os.Interrupt,syscall.SIGTERM)
gofunc(){
<-ch
??klog.Info("Received?termination,?signaling?shutdown")
??cancel()
?}()
?//?創(chuàng)建一個(gè)資源鎖
?lock?:=?&resourcelock.LeaseLock{
??LeaseMeta:?metav1.ObjectMeta{
???Name:??????leaseLockName,
???Namespace:?leaseLockNamespace,
??},
??Client:?client.CoordinationV1(),
??LockConfig:?resourcelock.ResourceLockConfig{
???Identity:?id,
??},
?}
?//?開(kāi)啟一個(gè)選舉的循環(huán)
?leaderelection.RunOrDie(ctx,?leaderelection.LeaderElectionConfig{
??Lock:????????????lock,
??ReleaseOnCancel:?true,
??LeaseDuration:???60?*?time.Second,
??RenewDeadline:???15?*?time.Second,
??RetryPeriod:?????5?*?time.Second,
??Callbacks:?leaderelection.LeaderCallbacks{
???OnStartedLeading:?func(ctx?context.Context)?{
????//?當(dāng)選舉為leader后所運(yùn)行的業(yè)務(wù)邏輯
????run(ctx)
???},
???OnStoppedLeading:?func()?{
????//?we?can?do?cleanup?here
????klog.Infof("leader?lost:?%s",?id)
????os.Exit(0)
???},
???OnNewLeader:?func(identity?string)?{?//?申請(qǐng)一個(gè)選舉時(shí)的動(dòng)作
????if?identity?==?id?{
?????return
????}
????klog.Infof("new?leader?elected:?%s",?identity)
???},
??},
?})
}
?
注:這種 lease 鎖只能在 in-cluster 模式下運(yùn)行,如果需要類(lèi)似二進(jìn)制部署的程序,可以選擇 endpoint 類(lèi)型的資源鎖。
生成鏡像
這里已經(jīng)制作好了鏡像并上傳到 dockerhub(cylonchau/leaderelection:v0.0.2)上了,如果只要學(xué)習(xí)運(yùn)行原理,則忽略此步驟
FROMgolang:alpineASbuilder MAINTAINERcylon WORKDIR/election COPY./election ENVGOPROXYhttps://goproxy.cn,direct RUNGOOS=linuxGOARCH=amd64CGO_ENABLED=0gobuild-oelectormain.go FROMalpineASrunner WORKDIR/go/elector COPY--from=builder/election/elector. VOLUME["/election"] ENTRYPOINT["./elector"]
準(zhǔn)備資源清單
默認(rèn)情況下,Kubernetes 運(yùn)行的 pod 在請(qǐng)求 Kubernetes 集群內(nèi)資源時(shí),默認(rèn)的賬戶(hù)是沒(méi)有權(quán)限的,默認(rèn)服務(wù)帳戶(hù)無(wú)權(quán)訪(fǎng)問(wèn)協(xié)調(diào) API,因此我們需要?jiǎng)?chuàng)建另一個(gè) serviceaccount 并相應(yīng)地設(shè)置 對(duì)應(yīng)的 RBAC 權(quán)限綁定;在清單中配置上這個(gè) sa,此時(shí)所有的 pod 就會(huì)有協(xié)調(diào)鎖的權(quán)限了。
apiVersion:v1 kind:ServiceAccount metadata: name:sa-leaderelection --- apiVersion:rbac.authorization.k8s.io/v1 kind:Role metadata: name:leaderelection rules: -apiGroups: -coordination.k8s.io resources: -leases verbs: -'*' --- apiVersion:rbac.authorization.k8s.io/v1 kind:RoleBinding metadata: name:leaderelection roleRef: apiGroup:rbac.authorization.k8s.io kind:Role name:leaderelection subjects: -kind:ServiceAccount name:sa-leaderelection --- apiVersion:apps/v1 kind:Deployment metadata: labels: app:leaderelection name:leaderelection namespace:default spec: replicas:3 selector: matchLabels: app:leaderelection template: metadata: labels: app:leaderelection spec: containers: -image:cylonchau/leaderelection:v0.0.2 imagePullPolicy:IfNotPresent command:["./elector"] args: -"-id=$(POD_NAME)" -"-lease-lock-name=test" -"-lease-lock-namespace=default" env: -name:POD_NAME valueFrom: fieldRef: apiVersion:v1 fieldPath:metadata.name name:elector serviceAccountName:sa-leaderelection
集群中運(yùn)行
執(zhí)行完清單后,當(dāng) pod 啟動(dòng)后,可以看到會(huì)創(chuàng)建出一個(gè) lease。
$kubectlgetlease NAMEHOLDERAGE testleaderelection-5644c5f84f-frs5n1s $kubectldescribelease Name:test Namespace:default Labels:Annotations: APIVersion:coordination.k8s.io/v1 Kind:Lease Metadata: CreationTimestamp:2022-06-28T1645Z ManagedFields: APIVersion:coordination.k8s.io/v1 FieldsType:FieldsV1 fieldsV1: f f f f f f Manager:elector Operation:Update Time:2022-06-28T1645Z ResourceVersion:131693 SelfLink:/apis/coordination.k8s.io/v1/namespaces/default/leases/test UID:bef2b164-a117-44bd-bad3-3e651c94c97b Spec: AcquireTime:2022-06-28T1645.931873Z HolderIdentity:leaderelection-5644c5f84f-frs5n LeaseDurationSeconds:60 LeaseTransitions:0 RenewTime:2022-06-28T1655.963537Z Events:
通過(guò)其持有者的信息查看對(duì)應(yīng) pod(因?yàn)槌绦蛑袑?duì) holder Identity 設(shè)置的是 pod 的名稱(chēng)),實(shí)際上是工作的 pod。
如上實(shí)例所述,這是利用 Kubernetes 集群完成的 leader 選舉的方案,雖然這不是最完美解決方案,但這是一種簡(jiǎn)單的方法,因?yàn)榭梢詿o(wú)需在集群上部署更多東西或者進(jìn)行大量的代碼工作就可以利用 Kubernetes 集群來(lái)實(shí)現(xiàn)一個(gè)高可用的 HA 應(yīng)用。
審核編輯:劉清
-
LEADER
+關(guān)注
關(guān)注
0文章
89瀏覽量
10749 -
API接口
+關(guān)注
關(guān)注
1文章
94瀏覽量
11121 -
kubernetes
+關(guān)注
關(guān)注
0文章
255瀏覽量
9373
原文標(biāo)題:巧用 Kubernetes 中的 Leader 選舉機(jī)制來(lái)實(shí)現(xiàn)自己的 HA 應(yīng)用
文章出處:【微信號(hào):magedu-Linux,微信公眾號(hào):馬哥Linux運(yùn)維】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
Kubernetes Helm入門(mén)指南
Kubernetes的Device Plugin設(shè)計(jì)解讀
Kubernetes之路 2 - 利用LXCFS提升容器資源可見(jiàn)性
Kubernetes Ingress 高可靠部署最佳實(shí)踐
再次升級(jí)!阿里云Kubernetes日志解決方案
在Kubernetes上運(yùn)行Kubernetes
Kubernetes API詳解
一種更安全的分布式一致性算法選舉機(jī)制

leader選舉在kubernetes controller中是如何實(shí)現(xiàn)的
評(píng)論