在 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ā)生的名稱空間 LeaderElectionNamespacestring //該屬性將決定持有l(wèi)eader鎖資源的名稱 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){ //這里填寫你的代碼, //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就是類似于其他資源類型的屬性,包含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 客戶端,結(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) }
到這里大致上了解了資源鎖究竟是什么了,其他種類的資源鎖也是相同的實(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)選舉的客戶端,之后會(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)建的選舉客戶端)。
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是建立的選舉客戶端,
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)的選舉邏輯就是在初始化客戶端時(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ù),類似于延遲隊(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è)線程/進(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 在租約滿時(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 //初始化客戶端的部分 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)行,如果需要類似二進(jìn)制部署的程序,可以選擇 endpoint 類型的資源鎖。
生成鏡像
這里已經(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)的賬戶是沒(méi)有權(quán)限的,默認(rèn)服務(wù)帳戶無(wú)權(quán)訪問(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 的名稱),實(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瀏覽量
10000 -
API接口
+關(guān)注
關(guān)注
1文章
84瀏覽量
10525 -
kubernetes
+關(guān)注
關(guān)注
0文章
227瀏覽量
8757
原文標(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)先 登錄
相關(guān)推薦
Kubernetes的Device Plugin設(shè)計(jì)解讀
Kubernetes之路 2 - 利用LXCFS提升容器資源可見(jiàn)性
Kubernetes Ingress 高可靠部署最佳實(shí)踐
再次升級(jí)!阿里云Kubernetes日志解決方案
在Kubernetes上運(yùn)行Kubernetes
![<b class='flag-5'>在</b><b class='flag-5'>Kubernetes</b>上運(yùn)行<b class='flag-5'>Kubernetes</b>](https://file.elecfans.com/web2/M00/49/EE/pYYBAGKhvHiAO8-iAAAmwMj0d8g546.png)
Kubernetes API詳解
![<b class='flag-5'>Kubernetes</b> API詳解](https://file.elecfans.com/web2/M00/49/FF/pYYBAGKhvIWAYswrAAATssJjcYE841.png)
一種更安全的分布式一致性算法選舉機(jī)制
![一種更安全的分布式一致性算法<b class='flag-5'>選舉</b>機(jī)制](https://file.elecfans.com/web1/M00/E9/A8/pIYBAGBtGa-AbuFTAAHDZIbO6jo899.png)
快速了解kubernetes
Kubernetes中如何實(shí)現(xiàn)灰度發(fā)布
Kubernetes中的網(wǎng)絡(luò)模型
帶你快速了解 kubernetes
基于Kubernetes實(shí)現(xiàn)CI/CD配置的流程
探討Kubernetes中的網(wǎng)絡(luò)模型(各種網(wǎng)絡(luò)模型分析)
![探討<b class='flag-5'>Kubernetes</b><b class='flag-5'>中</b>的網(wǎng)絡(luò)模型(各種網(wǎng)絡(luò)模型分析)](https://file1.elecfans.com/web2/M00/95/29/wKgZomTm4JeAW7_IAAAn4ui8l64483.png)
評(píng)論