k8s源码大学从新学v1.27-leader-election

如果你是一个想了解领导者选举以及在 Kubernetes 中如何工作的人,那么我希望你觉得这篇文章有用。在这篇文章中,我们将讨论高可用性系统中领导者选举背后的想法,并探索 kubernetes/client-go 库,以便在 Kubernetes 控制器的上下文中理解它。

介绍

近年来,随着对可靠系统和基础设施的需求增加,"高可用性"一词越来越受欢迎。在分布式系统中,高可用性通常涉及最大化正常运行时间和使系统具有容错能力。高可用性中常见的做法是使用冗余来最大程度地减少单点故障。准备系统和服务以实现冗余可能就像在负载平衡器后面部署更多副本一样简单。尽管这样的配置可以适用于许多应用程序,但某些用例需要跨副本进行仔细协调才能使系统正常工作

一个很好的例子是当 Kubernetes 控制器部署为多个实例时。为了防止任何意外行为,领导者选举过程必须确保在副本中选出一个领导者,并且是唯一主动协调集群的领导者。其他实例应保持非活动状态,但已准备好在领导者实例发生故障时接管

Kubernetes 中的领导者选举

Kubernetes 中的领导者选举过程很简单。它从创建锁定对象开始,其中领导者定期更新当前时间戳,作为通知其他副本有关其领导的一种方式。这个锁对象可以是 Lease 、 ConfigMap 或 Endpoint ,也持有当前领导者的身份。如果领导者未能在给定的时间间隔内更新时间戳,则假定它已崩溃,这是非活动副本通过使用其身份更新锁来竞相获取领导权时。成功获得锁的 Pod 将成为新的领导者

在我们处理任何代码之前,让我们先看看这个过程的实际效果!

第一步是建立一个本地的 Kubernetes 集群。我将使用 KinD ,但请随意选择您选择的本地 k8s 发行版。

sql 复制代码
$ kind create cluster 
Creating cluster "kind" ... 
 ✓ Ensuring node image (kindest/node:v1.21.1) 🖼 
 ✓ Preparing nodes 📦 
 ✓ Writing configuration 📜 
 ✓ Starting control-plane 🕹️ 
 ✓ Installing CNI 🔌 
 ✓ Installing StorageClass 💾 
Set kubectl context to "kind-kind" 
You can now use your cluster with: 
kubectl cluster-info --context kind-kind 
Not sure what to do next? 😅  Check out https://kind.sigs.k8s.io/docs/user/quick-start/ 

我们将使用的示例应用程序可以在这里找到。它使用 kubernetes/client-go 来执行领导者选举。让我们在集群上安装应用程序

bash 复制代码
# Setup required permissions for creating/getting Lease objects 
$ kubectl apply -f rbac.yaml 
serviceaccount/leaderelection-sa created 
role.rbac.authorization.k8s.io/leaderelection-role created 
rolebinding.rbac.authorization.k8s.io/leaderelection-rolebinding created 
# Create deployment 
$ kubectl apply -f deploy.yaml 
deployment.apps/leaderelection created 

这应该创建一个包含 3 个 Pod(副本)的部署。如果等待几秒钟,应会看到它们处于 Running 状态。

sql 复制代码
❯ kubectl get pods 
NAME                              READY   STATUS    RESTARTS   AGE 
leaderelection-6d5b456c9d-cfd2l   1/1     Running   0          19s 
leaderelection-6d5b456c9d-n2kx2   1/1     Running   0          19s 
leaderelection-6d5b456c9d-ph8nj   1/1     Running   0          19s 

运行 Pod 后,让我们尝试查看它们在领导者选举过程中创建的 Lease lock 对象。

vbnet 复制代码
$ kubectl describe lease my-lease 
Name:         my-lease 
Namespace:    default 
Labels:       <none> 
Annotations:  <none> 
API Version:  coordination.k8s.io/v1 
Kind:         Lease 
Metadata: 
... 
Spec: 
  Acquire Time:            2021-10-23T06:51:50.605570Z 
  Holder Identity:         leaderelection-56457b6c5c-fn725 
  Lease Duration Seconds:  15 
  Lease Transitions:       0 
  Renew Time:              2021-10-23T06:52:45.309478Z 
   

据此,我们目前的领导者 pod 是 leaderelection-56457bc5c-fn725 。让我们通过查看我们的 pod 日志来验证这一点。

go 复制代码
# leader pod 
$ kubectl logs leaderelection-56457b6c5c-fn725 
I1023 06:51:50.605439       1 leaderelection.go:248] attempting to acquire leader lease default/my-lease... 
I1023 06:51:50.630111       1 leaderelection.go:258] successfully acquired lease default/my-lease 
I1023 06:51:50.630141       1 main.go:57] still the leader! 
I1023 06:51:50.630245       1 main.go:36] doing stuff... 
# inactive pods 
$ kubectl logs leaderelection-56457b6c5c-n857k 
I1023 06:51:55.400797       1 leaderelection.go:248] attempting to acquire leader lease default/my-lease... 
I1023 06:51:55.412780       1 main.go:60] new leader is %sleaderelection-56457b6c5c-fn725 
# inactive pod 
$ kubectl logs leaderelection-56457b6c5c-s48kx 
I1023 06:51:52.905451       1 leaderelection.go:248] attempting to acquire leader lease default/my-lease... 
I1023 06:51:52.915618       1 main.go:60] new leader is %sleaderelection-56457b6c5c-fn725 

尝试删除领导者 Pod 以模拟崩溃,并检查 Lease 对象是否选出了新的领导者;)

代码分析

这里的基本思想是使用分布式锁定机制来决定哪个进程成为领导者。获取锁的过程可以执行所需的任务。 main 函数是我们应用程序的入口。在这里,我们创建一个对锁定对象的引用,并启动一个领导者选举循环。

go 复制代码
func main() { 
 var ( 
  leaseLockName      string 
  leaseLockNamespace string 
  podName            = os.Getenv("POD_NAME") 
 ) 
 flag.StringVar(&leaseLockName, "lease-name", "", "Name of lease lock") 
 flag.StringVar(&leaseLockNamespace, "lease-namespace", "default", "Name of lease lock namespace") 
 flag.Parse() 
 
 if leaseLockName == "" { 
  klog.Fatal("missing lease-name flag") 
 } 
 if leaseLockNamespace == "" { 
  klog.Fatal("missing lease-namespace flag") 
 } 
 
 config, err := rest.InClusterConfig() 
 client = clientset.NewForConfigOrDie(config) 
 
 if err != nil { 
  klog.Fatalf("failed to get kubeconfig") 
 } 
 
 ctx, cancel := context.WithCancel(context.Background()) 
 defer cancel() 
 
 lock := getNewLock(leaseLockName, podName, leaseLockNamespace) 
 runLeaderElection(lock, ctx, podName) 
} 

我们首先解析 lease-name 和 lease-namespace 标志,以获取副本必须使用的锁定对象的名称和命名空间。 POD_NAME 环境变量的值(填充在 deploy.yaml 清单中)将用于标识 Lease 对象中的领导者。最后,我们使用这些参数创建一个锁定对象来启动领导者选举过程

runLeaderElection 函数是我们通过调用 RunOrDie 来启动领导者选举循环的地方。我们给它传递一个 LeaderElectionConfig :

php 复制代码
func runLeaderElection(lock *resourcelock.LeaseLock, ctx context.Context, id string) { 
 leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{ 
  Lock:            lock, 
  ReleaseOnCancel: true, 
  LeaseDuration:   15 * time.Second, 
  RenewDeadline:   10 * time.Second, 
  RetryPeriod:     2 * time.Second, 
  Callbacks: leaderelection.LeaderCallbacks{ 
   OnStartedLeading: func(c context.Context) { 
    doStuff() 
   }, 
   OnStoppedLeading: func() { 
    klog.Info("no longer the leader, staying inactive.") 
   }, 
   OnNewLeader: func(current_id string) { 
    if current_id == id { 
     klog.Info("still the leader!") 
     return 
    } 
    klog.Info("new leader is %s", current_id) 
   }, 
  }, 
 }) 
} 

现在,让我们看一下 RunOrDie 在 client-go 中的实现。

scss 复制代码
// RunOrDie starts a client with the provided config or panics if the config 
// fails to validate. RunOrDie blocks until leader election loop is 
// stopped by ctx or it has stopped holding the leader lease 
func RunOrDie(ctx context.Context, lec LeaderElectionConfig) { 
 le, err := NewLeaderElector(lec) 
 if err != nil { 
  panic(err) 
 } 
 if lec.WatchDog != nil { 
  lec.WatchDog.SetLeaderElection(le) 
 } 
 le.Run(ctx) 
} 

它使用我们传递给它的 LeaderElectorConfig 创建一个 *LeaderElector ,并在其上调用 Run 方法:

scss 复制代码
// Run starts the leader election loop. Run will not return 
// before leader election loop is stopped by ctx or it has 
// stopped holding the leader lease 
func (le *LeaderElector) Run(ctx context.Context) { 
 defer runtime.HandleCrash() 
 defer func() { 
  le.config.Callbacks.OnStoppedLeading() 
 }() 
 
 if !le.acquire(ctx) { 
  return // ctx signalled done 
 } 
 ctx, cancel := context.WithCancel(ctx) 
 defer cancel() 
 go le.config.Callbacks.OnStartedLeading(ctx) 
 le.renew(ctx) 
} 

此方法负责运行领导者选举循环。它首先尝试获取锁(使用 le.acquire )。成功后,它会运行我们之前配置的 OnStartedLeading 回调,并定期续订租约。如果无法获取锁,它只需运行 OnStoppedLeading 回调并返回

acquire 和 renew 方法实现中最重要的部分是对 tryAcquireOrRenew 的调用,它包含锁定机制的核心逻辑。

乐观锁定(并发控制)

领导者选举过程利用 Kubernetes 操作的原子性质来确保没有两个副本可以同时获取 Lease (否则可能会导致竞争条件和其他意外行为!每当 Lease 更新(更新或获取)时,其上的 resourceVersion 字段也会由 Kubernetes 更新。当另一个进程尝试同时更新 Lease 时,Kubernetes 会检查更新对象的 resourceVersion 字段是否与当前对象匹配 --- 如果没有,更新将失败,从而防止并发问题!

总结

在这篇文章中,我们介绍了领导者选举的概念,以及为什么它对于分布式系统的高可用性至关重要。我们看了一下如何使用 Lease 锁在 Kubernetes 中实现这一点,并尝试使用 kubernetes/client-go 库自行实现它。此外,我们还试图了解 Kubernetes 如何使用原子操作和乐观锁定方法来防止由并发引起的问题。

相关推荐
AillemaC4 分钟前
三分钟看懂回调函数
后端
yeyong6 分钟前
越学越糟心,今天遇到又一种新的服务控制方式 snap,用它来跑snmpd
后端
喷火龙8号8 分钟前
深入理解MSC架构:现代前后端分离项目的最佳实践
后端·架构
Java技术小馆36 分钟前
GitDiagram如何让你的GitHub项目可视化
java·后端·面试
星星电灯猴1 小时前
iOS 性能调试全流程:从 Demo 到产品化的小团队实战经验
后端
程序无bug1 小时前
手写Spring框架
java·后端
JohnYan1 小时前
模板+数据的文档生成技术方案设计和实现
javascript·后端·架构
全干engineer1 小时前
Spring Boot 实现主表+明细表 Excel 导出(EasyPOI 实战)
java·spring boot·后端·excel·easypoi·excel导出
Da_秀1 小时前
软件工程中耦合度
开发语言·后端·架构·软件工程
蓝易云2 小时前
Qt框架中connect()方法的ConnectionType参数使用说明 点击改变文章字体大小
linux·前端·后端