前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >client-go实战之十二:选主(leader-election)

client-go实战之十二:选主(leader-election)

作者头像
程序员欣宸
发布2023-08-14 09:44:03
7540
发布2023-08-14 09:44:03
举报
文章被收录于专栏:实战docker实战docker

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码):https://github.com/zq2599/blog_demos

本篇概览

  • 本文是《client-go实战》系列的第十二篇,又有一个精彩的知识点在本章呈现:选主(leader-election)
  • 在解释什么是选主之前,咱们先来看一个场景(有真实适用场景的技术,学起来才有动力),如下图所示(稍后有详细说明)

上图所描述的业务场景是个普通的controller应用:

  1. 右侧是人工操作,通过kubectl命令修改了service资源
  2. 左侧的业务应用订阅了service的变化,在收到service变更的事件后,对pod进行写操作(例如将收到事件的时间写入pod的label)
  3. 以上的业务应用就是个很普通的controller,很简单,运行起来也没啥问题,但是,如果这个业务应用有多个实例呢?

多实例的问题

  • 所谓多个实例,就是同样的业务应用我们运行了多个进程(例如三个),为什么多个进程?同一个应用运行多个进程不是很正常么?横向扩容不就是多进程嘛
  • 多个进程运行的时候,如果service发生变化,那么每个进程都会去修改pod的label,这不是我们想要的(只要修改一次就行了)
  • 所以,如何解决这个问题呢?三个进程都是同一套代码,都会订阅service的变化,但是最终只修改一次pod
  • 经验丰富的您应该会想到分布式锁,三个进程去抢分布式锁,抢到的负责更新,没错,这是一个正确的解法
  • 但是,分布式锁需要引入相关组件吧,redis的setnx,或者mysql的乐观锁,这样就需要维护新的组件了
  • 其实这在kubernetes是个很典型的问题,毕竟pod多实例在kubernetes是常态了,所以当然也有官方的解法,页就是本文的主题:选主(leader-election)

选主(leader-election)

  • 说到这里您应该能理解选主的含义了:多个进程竞争某个key的leader,咱们可以把特定的代码放在竞争成功后再执行,由于同一时刻只有一个进程可以竞争成功,这就相当于在不引入额外组件的情况下,只用client-go就实现了分布式锁
  • 由于选主只是个特定的小知识点,本篇就没什么多余的理论要研究了,接下来直接开始实战,编码实现一个功能来说明选主的用法
  • 实战的业务需求如下
  • 开发一个应用,该应用同时运行多个进程
  • 当kubernetes的指定namespace下的service发生变化时,在pod的label中记录这个service的变化时间
  • 每次serivce变化,pod的label只能修改一次(尽管此时有多个进程)
  • 让我们少些套路,多一点真诚,不说废话,直接开始动手实战吧

源码下载

名称

链接

备注

项目主页

该项目在GitHub上的主页

git仓库地址(https)

该项目源码的仓库地址,https协议

git仓库地址(ssh)

git@github.com:zq2599/blog_demos.git

该项目源码的仓库地址,ssh协议

  • 这个git项目中有多个文件夹,本篇的源码在leader-tutorials文件夹下,如下图黄框所示:

提前了解选主的代码

  • 接下来会开发一个完整的controller应用,以此来说明选主功能
  • 如果您觉得完整应用的代码太多,懒得看,只想了解选主部分,那就在此提前将整个工程中选主相关的代码贴出来
  • 核心代码如下所示,先创建锁对象,就像分布式锁一样,总要有个key,然后执行leaderelection.RunOrDie方法参与选主,一旦有了结果,OnNewLeader方法会被回调,这时候通过自身id和leader的id比较就知道是不是自己了,另外,当OnStartedLeading被执行的时候,就意味着当前进程就是leader,并且可以立即开始执行只有leader才能做的事情了

实战:部署service和deployment

  • 先执行以下命令创建namespace
  • 再执行以下命令即可完成资源的创建
  • 来查看一下资源情况,如下图,service和pod都创建好了,准备工作完成,可以开始编码了

编码:准备工程

  • 执行命令名为go mod init leader-tutorials,新建module
  • 确保您的goproxy是正常的
  • 执行命令go get k8s.io/client-go@v0.22.8,下载client-go的指定版本
  • 现在工程已经准备好了,接着就是具体的编码

编码:梳理

  • 咱们按照开发顺序开始写代码,如果您看过欣宸的《client-go实战》系列,此刻对使用client-go开发简易版controller应该很熟悉了,这里再简单提一下开发的流程
  • 将controller完整的写出来,功能是监听service,一旦有变化就更新pod的label
  • 在主控逻辑中,根据选主结果决定是否启动步骤1中的controller
  • 下面开始写代码

编码:controller

  • 新建controller.go文件
  • 在controller.go中增加常量和数据结构的定义
  • 然后是controller的套路代码,主要是从队列中不断获取数据并处理的逻辑
  • 从上述代码可见,监听的资源发生变化时,调用的是updatePodsLabel方法,此方法的作用就是查找该namespace下的所有pod,依次用patch的方式更新pod的label
  • 到这里,controller的代码已经写得七七八八了,还剩创建controller对象以及运行informer的代码,这里将它们集中封装在一个方法中,一旦这个方法被调用,就意味着controller会被创建,然后监听service变化再更新pod的label的逻辑就会被执行

编码:主控程序(选主逻辑也在里面)

  • 本文是讲选主(leader-election)的,前面做了这么多铺垫,主角该上场了,新建main.go文件
  • 定义常量,以及全局变量
  • 先把套路的代码写了,就是client-go初始化的那部分,以及main方法,里面是整个程序的启动和业务调用流程,可见选主有关的代码都放在名为startLeaderElection的方法中
  • 最后是选主的代码,如下所示,先创建锁对象,就像分布式锁一样,总要有个key,然后执行leaderelection.RunOrDie方法参与选主,一旦有了结果,OnNewLeader方法会被回调,这时候通过自身id和leader的id比较就知道是不是自己了,另外,当OnStartedLeading被执行的时候,就意味着当前进程就是leader,并且可以立即开始执行只有leader才能做的事情了
  • 上述代码中,请注意LeaderElectionConfig对象的几个重要字段,例如LeaseDuration、RenewDeadline、RetryPeriod这些,是和选主时候的续租、超时、重试相关,需要按照您的实际网络情况进行调整
  • 现在代码写完了,可以开始验证了

验证

  • 这里捋一下验证的步骤
  • 构建项目,生产二进制文件
  • 执行此二进制文件,启动三个进程
  • 观察日志,应该有一个进程选举成功,另外两个只会在日志输出选主结果
  • 修改service资源,再去观察日志,发现leader进程会输出日志,再检查pod的label,发现已经修改
  • 用ctrl+C命令将leader进程退出,可见另外两个进程会有一个成为新的leader
  • 再次修改service资源,新的leader会负责更新pod的label
  • 接下来开始操作
  • 执行命令go build,对当前工程进行编译构建,得到二进制文件leader-tutorials
  • 打开三个终端窗口,输入同样的命令./leader-tutorials,选主成功的进程日志如下,之前操作过的残留,所以没有一开始就选主成功,而是等了几秒后才成为leader,一旦成为leader,全量同步service会触发一次pod的更新操作
  • 再去看另外两个进程的日志,可见已经识别到leader的身份,于是就没有执行controller的逻辑
  • 现在去修改service,用命令kubectl edit service nginx-service -n client-go-tutorials编辑,我这里是给service增加了一个label,如下图所示
  • 此刻,leader进程会监听到service变化,下图黄色箭头以下的内容就是处理pod的日志
  • 去看另外两个进程的日志,不会有任何变化,因为controller都没有
  • 执行以下命令查看pod的修改情况(注意pod的名字要从您自己的环境复制)
  • 可以看到pod的label有变化,如下图黄色箭头所示,这和上面的leader日志的时间是一致的
  • 目前leader进程工作正常,再来试试leader进程退出后的情况,用ctrl+C终止leader进程
  • 再去看另外两个进程的日志,发现其中一个成功成为新的leader
  • 验证完成,都符合预期
  • 至此,client-go的选主功能实战就完成了,如果您在寻找kubernetes原生的分布式锁方案,希望本篇能给您一些参考

你不孤单,欣宸原创一路相伴

  1. Java系列
  2. Spring系列
  3. Docker系列
  4. kubernetes系列
  5. 数据库+中间件系列
  6. DevOps系列
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-08-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 欢迎访问我的GitHub
  • 本篇概览
  • 多实例的问题
  • 选主(leader-election)
  • 源码下载
  • 提前了解选主的代码
  • 实战:部署service和deployment
  • 编码:准备工程
  • 编码:梳理
  • 编码:controller
  • 编码:主控程序(选主逻辑也在里面)
  • 验证
  • 你不孤单,欣宸原创一路相伴
相关产品与服务
消息队列 CMQ
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档