在 Kubernetes 的 kube-controller-manager , kube-scheduler, 以及使用 Operator 的底层实现 controller-rumtime 都支持高可用系统中的 leader 选举,本文将以理解 controller-rumtime (底层的实现是 client-go) 中的 leader 选举以在 kubernetes controller 中是如何实现的。
Background
在运行 kube-controller-manager 时,是有一些参数提供给 cm 进行 leader 选举使用的,可以参考官方文档提供的 参数来了解相关参数。
--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 进行的,但是后面对 controller-runtime 学习时,发现并没有配置其相关的 etcd 相关参数,这就引起了对选举机制的好奇。怀着这种好奇心搜索了下有关于 kubernetes 的选举,发现官网是这么介绍的,下面是对官方的说明进行一个通俗总结。simple leader election with kubernetes
❝
通过阅读文章得知,kubernetes API 提供了一中选举机制,只要运行在集群内的容器,都是可以实现选举功能的。
Kubernetes API 通过提供了两个属性来完成选举动作的
ResourceVersions:每个 API 对象唯一一个 ResourceVersion
Annotations:每个 API 对象都可以对这些 key 进行注释
注:这种选举会增加 APIServer 的压力。也就对 etcd 会产生影响
那么有了这些信息之后,我们来看一下,在 Kubernetes 集群中,谁是 cm 的 leader(我们提供的集群只有一个节点,所以本节点就是 leader)。
在 Kubernetes 中所有启用了 leader 选举的服务都会生成一个 EndPoint ,在这个 EndPoint 中会有上面提到的 label(Annotations)来标识谁是 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: 标出了哪个 node 是 leader。
election in controller-runtime
controller-runtime 有关 leader 选举的部分在 pkg/leaderelection下面,总共 100 行代码,我们来看下做了些什么?
可以看到,这里只提供了创建资源锁的一些选项
typeOptionsstruct{ //在manager启动时,决定是否进行选举 LeaderElectionbool //使用那种资源锁默认为租用lease LeaderElectionResourceLockstring //选举发生的名称空间 LeaderElectionNamespacestring //该属性将决定持有leader锁资源的名称 LeaderElectionIDstring }
通过 NewResourceLock 可以看到,这里是走的 client-go/tools/leaderelection下面,而这个 leaderelection 也有一个 example来学习如何使用它。
通过 example 可以看到,进入选举的入口是一个 RunOrDie() 的函数
//这里使用了一个lease锁,注释中说愿意为集群中存在lease的监听较少 lock:=&resourcelock.LeaseLock{ LeaseMeta:metav1.ObjectMeta{ Name:leaseLockName, Namespace:leaseLockNamespace, }, Client:client.CoordinationV1(), LockConfig:resourcelock.ResourceLockConfig{ Identity:id, }, } //开启选举循环 leaderelection.RunOrDie(ctx,leaderelection.LeaderElectionConfig{ Lock:lock, //这里必须保证拥有的租约在调用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创建一个LeaderElectionRecord Create(ctxcontext.Context,lerLeaderElectionRecord)error //UpdatewillupdateandexistingLeaderElectionRecord Update(ctxcontext.Context,lerLeaderElectionRecord)error //RecordEventisusedtorecordevents RecordEvent(string) //Identity返回锁的标识 Identity()string //Describeisusedtoconvertdetailsoncurrentresourcelockintoastring Describe()string }
那么实现这个抽象接口的就是,实现的资源锁,我们可以看到,client-go 提供了四种资源锁
leaselock
configmaplock
multilock
endpointlock
leaselock
Lease 是 kubernetes 控制平面中的通过 ETCD 来实现的一个 Leases 的资源,主要为了提供分布式租约的一种控制机制。相关对这个 API 的描述可以参考于:Lease 。
在 Kubernetes 集群中,我们可以使用如下命令来查看对应的 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 的实现,leaselock 会实现了作为资源锁的抽象
typeLeaseLockstruct{ //LeaseMeta就是类似于其他资源类型的属性,包含namens以及其他关于lease的属性 LeaseMetametav1.ObjectMeta Clientcoordinationv1client.LeasesGetter//Client就是提供了informer中的功能 //lockconfig包含上面通过describe看到的Identity与recoder用于记录资源锁的更改 LockConfigResourceLockConfig //lease就是API中的Lease资源,可以参考下上面给出的这个API的使用 lease*coordinationv1.Lease }
下面来看下 leaselock 实现了那些方法?
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 集群中尝试去创建一个租约,可以看到,Client 就是 API 提供的对应资源的 REST 客户端,结果会在 Kubernetes 集群中创建这个 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是记录选举时出现的事件,这时候我们回到上部分 在 kubernetes 集群中查看 ep 的信息时可以看到的 event 中存在 became leader 的事件,这里就是将产生的这个 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) }
到这里大致上了解了资源锁究竟是什么了,其他种类的资源锁也是相同的实现的方式,这里就不过多阐述了;下面的我们来看看选举的过程。
election workflow
选举的代码入口是在 leaderelection.go,这里会继续上面的 example 向下分析整个选举的过程。
前面我们看到了进入选举的入口是一个 RunOrDie()的函数,那么就继续从这里开始来了解。进入 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 是一个结构体,这里只是创建他,这个结构体提供了我们选举中所需要的一切(LeaderElector 就是 RunOrDie 创建的选举客户端)。
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//这个的配置,包含一些时间参数,健康检查 //recoder相关属性 observedRecordrl.LeaderElectionRecord observedRawRecord[]byte observedTimetime.Time //usedtoimplementOnNewLeader(),maylagslightlyfromthe //valueobservedRecord.HolderIdentityifthetransitionhas //notyetbeenreported. reportedLeaderstring //clockiswrapperaroundtimetoallowforlessflakytesting clockclock.Clock //锁定observedRecord observedRecordLocksync.Mutex metricsleaderMetricsAdapter }
可以看到 Run 实现的选举逻辑就是在初始化客户端时传入的 三个 callback
func(le*LeaderElector)Run(ctxcontext.Context){ deferruntime.HandleCrash() deferfunc(){//退出时执行callbacke的OnStoppedLeading le.config.Callbacks.OnStoppedLeading() }() if!le.acquire(ctx){ return } ctx,cancel:=context.WithCancel(ctx) defercancel() gole.config.Callbacks.OnStartedLeading(ctx)//选举时,执行OnStartedLeading le.renew(ctx) }
在 Run 中调用了 acquire,这个是 通过一个 loop 去调用 tryAcquireOrRenew,直到 ctx 传递过来结束信号
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是执行定时的函数func()是定时任务的逻辑 //RetryPeriod是周期间隔 //JitterFactor是重试系数,类似于延迟队列中的系数(duration+maxFactor*duration) //sliding逻辑是否计算在时间内 //上下文传递 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 租约,如果已经获得到了,则更新租约;否则可以得到租约则为 true,反之 false
func(le*LeaderElector)tryAcquireOrRenew(ctxcontext.Context)bool{ now:=metav1.Now()//时间 leaderElectionRecord:=rl.LeaderElectionRecord{//构建一个选举record HolderIdentity:le.config.Lock.Identity(),//选举人的身份特征,ep与主机名有关 LeaseDurationSeconds:int(le.config.LeaseDuration/time.Second),//默认15s RenewTime:now,//重新获取时间 AcquireTime:now,//获得时间 } //1.从API获取或创建一个recode,如果可以拿到则已经有租约,反之创建新租约 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 } //创建租约的动作就是新建一个对应的resource,这个lock就是leaderelection提供的四种锁, //看你在runOrDie中初始化传入了什么锁 iferr=le.config.Lock.Create(ctx,leaderElectionRecord);err!=nil{ klog.Errorf("errorinitiallycreatingleaderelectionrecord:%v",err) returnfalse } //到了这里就已经拿到或者创建了租约,然后记录其一些属性,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,进行HolderIdentity比较,再加上时间,这个时候没有到竞选其,跳出 klog.V(4).Infof("lockisheldby%vandhasnotyetexpired",oldLeaderElectionRecord.HolderIdentity) returnfalse } // 3.我们将尝试更新。在这里leaderElectionRecord设置为默认值。让我们在更新之前更正它。 ifle.IsLeader(){//到这就说明是leader,修正他的时间 leaderElectionRecord.AcquireTime=oldLeaderElectionRecord.AcquireTime leaderElectionRecord.LeaderTransitions=oldLeaderElectionRecord.LeaderTransitions }else{//LeaderTransitions就是指leader调整(转变为其他)了几次,如果是, //则为发生转变,保持原有值 //反之,则+1 leaderElectionRecord.LeaderTransitions=oldLeaderElectionRecord.LeaderTransitions+1 } //完事之后更新APIServer中的锁资源,也就是更新对应的资源的属性信息 iferr=le.config.Lock.Update(ctx,leaderElectionRecord);err!=nil{ klog.Errorf("Failedtoupdatelock:%v",err) returnfalse } //setObservedRecord是通过一个新的record来更新这个锁中的record //操作是安全的,会上锁保证临界区仅可以被一个线程/进程操作 le.setObservedRecord(&leaderElectionRecord) returntrue }
到这里,已经完整知道利用 kubernetes 进行选举的流程都是什么了;下面简单回顾下,上述 leader 选举所有的步骤:
首选创建的服务就是该服务的 leader,锁可以为 lease , endpoint 等资源进行上锁
已经是 leader 的实例会不断续租,租约的默认值是 15 秒 (leaseDuration);leader 在租约满时更新租约时间(renewTime)。
其他的 follower,会不断检查对应资源锁的存在,如果已经有 leader,那么则检查 renewTime,如果超过了租用时间(),则表明 leader 存在问题需要重新启动选举,直到有 follower 提升为 leader。
而为了避免资源被抢占,Kubernetes API 使用了 ResourceVersion 来避免被重复修改(如果版本号与请求版本号不一致,则表示已经被修改了,那么 APIServer 将返回错误)
利用 Leader 机制实现 HA 应用
下面就通过一个 example 来实现一个,利用 kubernetes 提供的选举机制完成的高可用应用。
代码实现
如果仅仅是使用 Kubernetes 中的锁,实现的代码也只有几行而已。
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){ //实现的业务逻辑,这里仅仅为实验,就直接打印了 klog.Info("Controllerloop...") for{ fmt.Println("Iamleader,Iwasworking.") time.Sleep(time.Second*5) } } //useaGocontextsowecantelltheleaderelectioncodewhenwe //wanttostepdown ctx,cancel:=context.WithCancel(context.Background()) defercancel() //监听系统中断 ch:=make(chanos.Signal,1) signal.Notify(ch,os.Interrupt,syscall.SIGTERM) gofunc(){ <-ch klog.Info("Received termination, signaling shutdown") cancel() }() // 创建一个资源锁 lock := &resourcelock.LeaseLock{ LeaseMeta: metav1.ObjectMeta{ Name: leaseLockName, Namespace: leaseLockNamespace, }, Client: client.CoordinationV1(), LockConfig: resourcelock.ResourceLockConfig{ Identity: id, }, } // 开启一个选举的循环 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) { // 当选举为leader后所运行的业务逻辑 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 模式下运行,如果需要类似二进制部署的程序,可以选择 endpoint 类型的资源锁。
生成镜像
这里已经制作好了镜像并上传到 dockerhub(cylonchau/leaderelection:v0.0.2)上了,如果只要学习运行原理,则忽略此步骤
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"]
准备资源清单
默认情况下,Kubernetes 运行的 pod 在请求 Kubernetes 集群内资源时,默认的账户是没有权限的,默认服务帐户无权访问协调 API,因此我们需要创建另一个 serviceaccount 并相应地设置 对应的 RBAC 权限绑定;在清单中配置上这个 sa,此时所有的 pod 就会有协调锁的权限了。
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
集群中运行
执行完清单后,当 pod 启动后,可以看到会创建出一个 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:
通过其持有者的信息查看对应 pod(因为程序中对 holder Identity 设置的是 pod 的名称),实际上是工作的 pod。
如上实例所述,这是利用 Kubernetes 集群完成的 leader 选举的方案,虽然这不是最完美解决方案,但这是一种简单的方法,因为可以无需在集群上部署更多东西或者进行大量的代码工作就可以利用 Kubernetes 集群来实现一个高可用的 HA 应用。
审核编辑:刘清
-
LEADER
+关注
关注
0文章
89浏览量
9978 -
API接口
+关注
关注
1文章
84浏览量
10489 -
kubernetes
+关注
关注
0文章
227浏览量
8744
原文标题:巧用 Kubernetes 中的 Leader 选举机制来实现自己的 HA 应用
文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论