0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

leader选举在kubernetes controller中是如何实现的

马哥Linux运维 来源:Cylon 作者:Cylon 2022-07-21 10:03 次阅读

在 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-scheduler3d4h

这里以 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

    浏览量

    9874
  • API接口
    +关注

    关注

    1

    文章

    79

    浏览量

    10315
  • kubernetes
    +关注

    关注

    0

    文章

    219

    浏览量

    8568

原文标题:巧用 Kubernetes 中的 Leader 选举机制来实现自己的 HA 应用

文章出处:【微信号:magedu-Linux,微信公众号:马哥Linux运维】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Job: SOC实现技术专家(Leader)1名 + IC Layout leader 1名

    的Application Process的SOC开发。附件请查看6个职位:2. SOC实现技术专家(Leader)1名6. IC Layout 技术专家(Leader)1名注:1.1
    发表于 04-16 09:44

    Kubernetes的Device Plugin设计解读

    ,无需修改Kubelet主干代码,就可以实现支持GPU、FPGA、高性能 NIC、InfiniBand 等各种设备的扩展。该能力Kubernetes 1.8和1.9版本处于Alpha版本,
    发表于 03-12 16:23

    Kubernetes之路 2 - 利用LXCFS提升容器资源可见性

    lxcfs-proc-meminfo (rw)/proc/stat from lxcfs-proc-stat (rw)...Kubernetes,还可以通过 Preset 实现
    发表于 04-17 14:05

    Kubernetes Ingress 高可靠部署最佳实践

    摘要: Kubernetes集群,Ingress作为集群流量接入层,Ingress的高可靠性显得尤为重要,今天我们主要探讨如何部署一套高性能高可靠的Ingress接入层。简介
    发表于 04-17 14:35

    再次升级!阿里云Kubernetes日志解决方案

    /删除/修改AliyunLogConfig资源时,alibaba-log-controller会监听到资源变化,并对应的日志服务上创建/删除/修改相应的采集配置。以此实现K8S内部
    发表于 05-28 19:08

    Kubernetes 从懵圈到熟练:集群服务的三个要点和一种实现

    通信员。熟悉服务网格的同学肯定对这个很熟悉了。但是可能比较少人注意到,其实 Kubernetes 集群原始服务的实现,也是基于 Sidecar 模式的。 Kubernetes 集群
    发表于 09-24 15:35

    浅析Kubernetes

    【k8s】Kubernetes基础概念
    发表于 09-27 09:11

    不吹不黑,今天我们来聊一聊 Kubernetes 落地的三种方式

    Kubernetes 作为自己的基础设施重心,"一万个人眼中就有一万个哈姆雷特",虽说 Kubernetes 是容器管理领域的事实标准,但实际上不同背景的企业
    发表于 10-12 16:07

    AspNetCore2.1Controller介绍

    AspNetCore 项目中,我们首先使用的类就是ControllerController表示MVC的C,用于协调M(Model)和V(View)。我们
    发表于 11-04 07:13

    !来货 LEADER 952 LEADER952 欧阳婧1

    !来货 LEADER 952  LEADER952 欧阳婧13415387172中山市华仪通电子仪器有限公司 联系人:欧阳婧 手机: 13415387172
    发表于 09-03 09:30 606次阅读

    一种更安全的分布式一致性算法选举机制

    目前应用于分布式系统中的基于选举的分布式一致性算法(类 Paxos算法),都是采用得到50%以上选票者当选 Leader的方式进行选举。此种选举机制类似现实生活中的
    发表于 04-07 10:29 9次下载
    一种更安全的分布式一致性算法<b class='flag-5'>选举</b>机制

    快速了解kubernetes

    Master 即主节点,负责控制整个 kubernetes 集群。它包括 Api Server、Scheduler、Controller 等组成部分。它们都需要和 Etcd 进行交互以存储数据。
    发表于 08-03 10:38 254次阅读

    带你快速了解 kubernetes

    即主节点,负责控制整个 kubernetes 集群。它包括 Api Server、Scheduler、Controller 等组成部分。它们都需要和 Etcd 进行交互以存储数据。 Api Server:
    的头像 发表于 01-17 10:00 468次阅读

    基于Kubernetes实现CI/CD配置的流程

    基于 Kubernetes 实现 CI/CD 配置,其实和往常那些 CI/CD 配置并没有太大区别。
    的头像 发表于 02-08 16:51 917次阅读

    zookeeper的选举机制

    ZooKeeper是一个分布式协调服务,主要用于管理分布式系统中的配置信息、命名服务、分布式锁和分布式队列等。在ZooKeeper集群中,为了保证高可用性,需要选举出一个主节点(Leader),其他
    的头像 发表于 12-04 10:39 431次阅读