在 Kubernetes 的 kube-controller-manager , kube-scheduler, 以及使用 Operator 的底層實現(xiàn) controller-rumtime 都支持高可用系統(tǒng)中的 leader 選舉,本文將以理解 controller-rumtime (底層的實現(xiàn)是 client-go) 中的 leader 選舉以在 kubernetes controller 中是如何實現(xiàn)的。
Background
在運行 kube-controller-manager 時,是有一些參數(shù)提供給 cm 進(jìn)行 leader 選舉使用的,可以參考官方文檔提供的 參數(shù)來了解相關(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 ...
本身以為這些組件的選舉動作時通過 etcd 進(jìn)行的,但是后面對 controller-runtime 學(xué)習(xí)時,發(fā)現(xiàn)并沒有配置其相關(guān)的 etcd 相關(guān)參數(shù),這就引起了對選舉機制的好奇。懷著這種好奇心搜索了下有關(guān)于 kubernetes 的選舉,發(fā)現(xiàn)官網(wǎng)是這么介紹的,下面是對官方的說明進(jìn)行一個通俗總結(jié)。simple leader election with kubernetes
?
通過閱讀文章得知,kubernetes API 提供了一中選舉機制,只要運行在集群內(nèi)的容器,都是可以實現(xiàn)選舉功能的。
Kubernetes API 通過提供了兩個屬性來完成選舉動作的
ResourceVersions:每個 API 對象唯一一個 ResourceVersion
Annotations:每個 API 對象都可以對這些 key 進(jìn)行注釋
注:這種選舉會增加 APIServer 的壓力。也就對 etcd 會產(chǎn)生影響
那么有了這些信息之后,我們來看一下,在 Kubernetes 集群中,誰是 cm 的 leader(我們提供的集群只有一個節(jié)點,所以本節(jié)點就是 leader)。
在 Kubernetes 中所有啟用了 leader 選舉的服務(wù)都會生成一個 EndPoint ,在這個 EndPoint 中會有上面提到的 label(Annotations)來標(biāo)識誰是 leader。
$kubectlgetep-nkube-system NAMEENDPOINTSAGE kube-controller-manager3d4h kube-dns3d4h kube-scheduler 3d4h
這里以 kube-controller-manager 為例,來看下這個 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)出了哪個 node 是 leader。
election in controller-runtime
controller-runtime 有關(guān) leader 選舉的部分在 pkg/leaderelection下面,總共 100 行代碼,我們來看下做了些什么?
可以看到,這里只提供了創(chuàng)建資源鎖的一些選項
typeOptionsstruct{ //在manager啟動時,決定是否進(jìn)行選舉 LeaderElectionbool //使用那種資源鎖默認(rèn)為租用lease LeaderElectionResourceLockstring //選舉發(fā)生的名稱空間 LeaderElectionNamespacestring //該屬性將決定持有l(wèi)eader鎖資源的名稱 LeaderElectionIDstring }
通過 NewResourceLock 可以看到,這里是走的 client-go/tools/leaderelection下面,而這個 leaderelection 也有一個 example來學(xué)習(xí)如何使用它。
通過 example 可以看到,進(jìn)入選舉的入口是一個 RunOrDie() 的函數(shù)
//這里使用了一個lease鎖,注釋中說愿意為集群中存在lease的監(jiān)聽較少 lock:=&resourcelock.LeaseLock{ LeaseMeta:metav1.ObjectMeta{ Name:leaseLockName, Namespace:leaseLockNamespace, }, Client:client.CoordinationV1(), LockConfig:resourcelock.ResourceLockConfig{ Identity:id, }, } //開啟選舉循環(huán) leaderelection.RunOrDie(ctx,leaderelection.LeaderElectionConfig{ Lock:lock, //這里必須保證擁有的租約在調(diào)用cancel()前終止,否則會仍有一個loop在運行 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) }, }, })
到這里,我們了解了鎖的概念和如何啟動一個鎖,下面看下,client-go 都提供了那些鎖。
在代碼 tools/leaderelection/resourcelock/interface.go[6] 定義了一個鎖抽象,interface 提供了一個通用接口,用于鎖定 leader 選舉中使用的資源。
typeInterfaceinterface{ //Get返回選舉記錄 Get(ctxcontext.Context)(*LeaderElectionRecord,[]byte,error) //Create創(chuàng)建一個LeaderElectionRecord Create(ctxcontext.Context,lerLeaderElectionRecord)error //UpdatewillupdateandexistingLeaderElectionRecord Update(ctxcontext.Context,lerLeaderElectionRecord)error //RecordEventisusedtorecordevents RecordEvent(string) //Identity返回鎖的標(biāo)識 Identity()string //Describeisusedtoconvertdetailsoncurrentresourcelockintoastring Describe()string }
那么實現(xiàn)這個抽象接口的就是,實現(xiàn)的資源鎖,我們可以看到,client-go 提供了四種資源鎖
leaselock
configmaplock
multilock
endpointlock
leaselock
Lease 是 kubernetes 控制平面中的通過 ETCD 來實現(xiàn)的一個 Leases 的資源,主要為了提供分布式租約的一種控制機制。相關(guān)對這個 API 的描述可以參考于:Lease 。
在 Kubernetes 集群中,我們可以使用如下命令來查看對應(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:
下面來看下 leaselock 的實現(xiàn),leaselock 會實現(xiàn)了作為資源鎖的抽象
typeLeaseLockstruct{ //LeaseMeta就是類似于其他資源類型的屬性,包含namens以及其他關(guān)于lease的屬性 LeaseMetametav1.ObjectMeta Clientcoordinationv1client.LeasesGetter//Client就是提供了informer中的功能 //lockconfig包含上面通過describe看到的Identity與recoder用于記錄資源鎖的更改 LockConfigResourceLockConfig //lease就是API中的Lease資源,可以參考下上面給出的這個API的使用 lease*coordinationv1.Lease }
下面來看下 leaselock 實現(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 } //可以看出是返回這個資源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)建一個租約,可以看到,Client 就是 API 提供的對應(yīng)資源的 REST 客戶端,結(jié)果會在 Kubernetes 集群中創(chuàng)建這個 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是記錄選舉時出現(xiàn)的事件,這時候我們回到上部分 在 kubernetes 集群中查看 ep 的信息時可以看到的 event 中存在 became leader 的事件,這里就是將產(chǎn)生的這個 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) }
到這里大致上了解了資源鎖究竟是什么了,其他種類的資源鎖也是相同的實現(xiàn)的方式,這里就不過多闡述了;下面的我們來看看選舉的過程。
election workflow
選舉的代碼入口是在 leaderelection.go,這里會繼續(xù)上面的 example 向下分析整個選舉的過程。
前面我們看到了進(jìn)入選舉的入口是一個 RunOrDie()的函數(shù),那么就繼續(xù)從這里開始來了解。進(jìn)入 RunOrDie,看到其實只有幾行而已,大致上了解到了 RunOrDie 會使用提供的配置來啟動選舉的客戶端,之后會阻塞,直到 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 是一個結(jié)構(gòu)體,這里只是創(chuàng)建他,這個結(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//這個的配置,包含一些時間參數(shù),健康檢查 //recoder相關(guān)屬性 observedRecordrl.LeaderElectionRecord observedRawRecord[]byte observedTimetime.Time //usedtoimplementOnNewLeader(),maylagslightlyfromthe //valueobservedRecord.HolderIdentityifthetransitionhas //notyetbeenreported. reportedLeaderstring //clockiswrapperaroundtimetoallowforlessflakytesting clockclock.Clock //鎖定observedRecord observedRecordLocksync.Mutex metricsleaderMetricsAdapter }
可以看到 Run 實現(xiàn)的選舉邏輯就是在初始化客戶端時傳入的 三個 callback
func(le*LeaderElector)Run(ctxcontext.Context){ deferruntime.HandleCrash() deferfunc(){//退出時執(zhí)行callbacke的OnStoppedLeading le.config.Callbacks.OnStoppedLeading() }() if!le.acquire(ctx){ return } ctx,cancel:=context.WithCancel(ctx) defercancel() gole.config.Callbacks.OnStartedLeading(ctx)//選舉時,執(zhí)行OnStartedLeading le.renew(ctx) }
在 Run 中調(diào)用了 acquire,這個是 通過一個 loop 去調(diào)用 tryAcquireOrRenew,直到 ctx 傳遞過來結(jié)束信號
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ù)func()是定時任務(wù)的邏輯 //RetryPeriod是周期間隔 //JitterFactor是重試系數(shù),類似于延遲隊列中的系數(shù)(duration+maxFactor*duration) //sliding邏輯是否計算在時間內(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 }
這里實際上選舉動作在 tryAcquireOrRenew 中,下面來看下 tryAcquireOrRenew;tryAcquireOrRenew 是嘗試獲得一個 leader 租約,如果已經(jīng)獲得到了,則更新租約;否則可以得到租約則為 true,反之 false
func(le*LeaderElector)tryAcquireOrRenew(ctxcontext.Context)bool{ now:=metav1.Now()//時間 leaderElectionRecord:=rl.LeaderElectionRecord{//構(gòu)建一個選舉record HolderIdentity:le.config.Lock.Identity(),//選舉人的身份特征,ep與主機名有關(guān) LeaseDurationSeconds:int(le.config.LeaseDuration/time.Second),//默認(rèn)15s RenewTime:now,//重新獲取時間 AcquireTime:now,//獲得時間 } //1.從API獲取或創(chuàng)建一個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)建租約的動作就是新建一個對應(yīng)的resource,這個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.獲取記錄檢查身份和時間 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比較,再加上時間,這個時候沒有到競選其,跳出 klog.V(4).Infof("lockisheldby%vandhasnotyetexpired",oldLeaderElectionRecord.HolderIdentity) returnfalse } // 3.我們將嘗試更新。在這里leaderElectionRecord設(shè)置為默認(rèn)值。讓我們在更新之前更正它。 ifle.IsLeader(){//到這就說明是leader,修正他的時間 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中的鎖資源,也就是更新對應(yīng)的資源的屬性信息 iferr=le.config.Lock.Update(ctx,leaderElectionRecord);err!=nil{ klog.Errorf("Failedtoupdatelock:%v",err) returnfalse } //setObservedRecord是通過一個新的record來更新這個鎖中的record //操作是安全的,會上鎖保證臨界區(qū)僅可以被一個線程/進(jìn)程操作 le.setObservedRecord(&leaderElectionRecord) returntrue }
到這里,已經(jīng)完整知道利用 kubernetes 進(jìn)行選舉的流程都是什么了;下面簡單回顧下,上述 leader 選舉所有的步驟:
首選創(chuàng)建的服務(wù)就是該服務(wù)的 leader,鎖可以為 lease , endpoint 等資源進(jìn)行上鎖
已經(jīng)是 leader 的實例會不斷續(xù)租,租約的默認(rèn)值是 15 秒 (leaseDuration);leader 在租約滿時更新租約時間(renewTime)。
其他的 follower,會不斷檢查對應(yīng)資源鎖的存在,如果已經(jīng)有 leader,那么則檢查 renewTime,如果超過了租用時間(),則表明 leader 存在問題需要重新啟動選舉,直到有 follower 提升為 leader。
而為了避免資源被搶占,Kubernetes API 使用了 ResourceVersion 來避免被重復(fù)修改(如果版本號與請求版本號不一致,則表示已經(jīng)被修改了,那么 APIServer 將返回錯誤)
利用 Leader 機制實現(xiàn) HA 應(yīng)用
下面就通過一個 example 來實現(xiàn)一個,利用 kubernetes 提供的選舉機制完成的高可用應(yīng)用。
代碼實現(xiàn)
如果僅僅是使用 Kubernetes 中的鎖,實現(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){ //實現(xiàn)的業(yè)務(wù)邏輯,這里僅僅為實驗,就直接打印了 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)中斷 ch:=make(chanos.Signal,1) signal.Notify(ch,os.Interrupt,syscall.SIGTERM) gofunc(){ <-ch ??klog.Info("Received?termination,?signaling?shutdown") ??cancel() ?}() ?//?創(chuàng)建一個資源鎖 ?lock?:=?&resourcelock.LeaseLock{ ??LeaseMeta:?metav1.ObjectMeta{ ???Name:??????leaseLockName, ???Namespace:?leaseLockNamespace, ??}, ??Client:?client.CoordinationV1(), ??LockConfig:?resourcelock.ResourceLockConfig{ ???Identity:?id, ??}, ?} ?//?開啟一個選舉的循環(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è)務(wù)邏輯 ????run(ctx) ???}, ???OnStoppedLeading:?func()?{ ????//?we?can?do?cleanup?here ????klog.Infof("leader?lost:?%s",?id) ????os.Exit(0) ???}, ???OnNewLeader:?func(identity?string)?{?//?申請一個選舉時的動作 ????if?identity?==?id?{ ?????return ????} ????klog.Infof("new?leader?elected:?%s",?identity) ???}, ??}, ?}) }
?
注:這種 lease 鎖只能在 in-cluster 模式下運行,如果需要類似二進(jìn)制部署的程序,可以選擇 endpoint 類型的資源鎖。
生成鏡像
這里已經(jīng)制作好了鏡像并上傳到 dockerhub(cylonchau/leaderelection:v0.0.2)上了,如果只要學(xué)習(xí)運行原理,則忽略此步驟
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 運行的 pod 在請求 Kubernetes 集群內(nèi)資源時,默認(rèn)的賬戶是沒有權(quán)限的,默認(rèn)服務(wù)帳戶無權(quán)訪問協(xié)調(diào) API,因此我們需要創(chuàng)建另一個 serviceaccount 并相應(yīng)地設(shè)置 對應(yīng)的 RBAC 權(quán)限綁定;在清單中配置上這個 sa,此時所有的 pod 就會有協(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
集群中運行
執(zhí)行完清單后,當(dāng) pod 啟動后,可以看到會創(chuàng)建出一個 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:
通過其持有者的信息查看對應(yīng) pod(因為程序中對 holder Identity 設(shè)置的是 pod 的名稱),實際上是工作的 pod。
如上實例所述,這是利用 Kubernetes 集群完成的 leader 選舉的方案,雖然這不是最完美解決方案,但這是一種簡單的方法,因為可以無需在集群上部署更多東西或者進(jìn)行大量的代碼工作就可以利用 Kubernetes 集群來實現(xiàn)一個高可用的 HA 應(yīng)用。
審核編輯:劉清
-
LEADER
+關(guān)注
關(guān)注
0文章
89瀏覽量
9953 -
API接口
+關(guān)注
關(guān)注
1文章
84瀏覽量
10437 -
kubernetes
+關(guān)注
關(guān)注
0文章
224瀏覽量
8710
原文標(biāo)題:巧用 Kubernetes 中的 Leader 選舉機制來實現(xiàn)自己的 HA 應(yīng)用
文章出處:【微信號:magedu-Linux,微信公眾號:馬哥Linux運維】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論