快速上手

下面这个代码就是一个选主的大概逻辑

package mainimport ("context""flag""fmt"_ "net/http/pprof""os""path/filepath""time""golang.org/x/exp/rand"v1 "k8s.io/api/core/v1"metav1 "k8s.io/apimachinery/pkg/apis/meta/v1""k8s.io/apimachinery/pkg/util/uuid""k8s.io/client-go/kubernetes""k8s.io/client-go/kubernetes/scheme"corev1 "k8s.io/client-go/kubernetes/typed/core/v1""k8s.io/client-go/tools/clientcmd""k8s.io/client-go/tools/leaderelection""k8s.io/client-go/tools/leaderelection/resourcelock""k8s.io/client-go/tools/record""k8s.io/client-go/util/homedir"
)func main() {ctx := context.Background()var kubeconfig *stringif home := homedir.HomeDir(); home != "" {kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "config"), "")}config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)if err != nil {panic(err)}clientset, err := kubernetes.NewForConfig(config)if err != nil {panic(err)}broadcaster := record.NewBroadcaster()broadcaster.StartRecordingToSink(&corev1.EventSinkImpl{Interface: clientset.CoreV1().Events("default"),})eventRecorder := broadcaster.NewRecorder(scheme.Scheme,v1.EventSource{Component: "hello-word"})createIdentity := func() string {hostname, err := os.Hostname()if err != nil {hostname = fmt.Sprintf("rand%d", rand.Intn(10000))}return fmt.Sprintf("%s_%s", hostname, string(uuid.NewUUID()))}lock := &resourcelock.LeaseLock{LeaseMeta: metav1.ObjectMeta{Namespace: "default",Name:      "hello-world",},Client: clientset.CoordinationV1(),LockConfig: resourcelock.ResourceLockConfig{Identity:      createIdentity(),EventRecorder: eventRecorder,},}leaderelection.RunOrDie(ctx, leaderelection.LeaderElectionConfig{Lock:          lock,LeaseDuration: 5 * time.Second,RenewDeadline: 4 * time.Second,RetryPeriod:   2 * time.Second,Callbacks: leaderelection.LeaderCallbacks{OnStartedLeading: func(ctx context.Context) {fmt.Println("start leading")},OnStoppedLeading: func() {fmt.Println("stop leading")},OnNewLeader: func(identity string) {fmt.Printf("new leader: %s\n", identity)},},Coordinated: false,})
}

我们同时启动多个终端来运行这个程序,并且杀掉主节点来模拟节点挂掉,观察是否会重新进行选举出新的master
在这里插入图片描述

从图中可以看到,第一个程序选为主节点之后,第二三个程序自动成为slave,我们 kill 掉第一个程序之后,第二个程序抢到了锁成为了 master

租约 Lease

k8s 内置很多种资源,其中 lease 也是k8s的一种资源,顾名思义表达的是租户对某种资源的

占有的信息表示

➜  ~ k get lease -A                                                                 
NAMESPACE         NAME                      HOLDER                                                      AGE
default           hello-world               VM-221-245-tencentos_bada2219-3a27-4b19-8b80-23fc05604391   6d7h
kube-node-lease   vm-221-245-tencentos      vm-221-245-tencentos                                        36d
kube-system       kube-controller-manager   VM-221-245-tencentos_8f5a4f85-ca0c-4b5f-ac81-8b0ff5ff2e49   36d
kube-system       kube-scheduler            VM-221-245-tencentos_4fab96c4-156b-4a77-b862-87224be44cb2   36d

比如我们查看一个 k8s 的 node 的 lease

➜  ~ k get lease vm-221-245-tencentos -n kube-node-lease -oyaml                   
apiVersion: coordination.k8s.io/v1
kind: Lease                                    # 资源的种类
metadata:creationTimestamp: "2024-09-27T12:24:07Z"    # 这个资源的创建时间戳name: vm-221-245-tencentos                   # 名称namespace: kube-node-lease                   # 命名空间ownerReferences:- apiVersion: v1kind: Nodename: vm-221-245-tencentos                 # 这个资源的占有名称uid: 5df61ad6-cc1a-4669-8b0e-d48a5b0ffb91resourceVersion: "3811080"uid: 411c6d4e-5afb-4eba-a1a0-8a56d00b75db
spec:holderIdentity: vm-221-245-tencentosleaseDurationSeconds: 40                    # 租约的时常40srenewTime: "2024-11-03T01:35:05.171458Z"    # 租约的更新时间

而实际上 leader 选举中的资源 lock 其实就是一种 lease,表明 master 主节点持有对某个资源
的唯一性

查看 https://github.com/kubernetes/client-go/tree/master/tools/leaderelection 的源文件

可以看到 leaderElection 的目录结构,主要分为 resourcelock 和 leaderelection 的主文件,

文件内容不是很多

➜  leaderelection git:(master) tree                                                                                                  jinchaozhu@VM-221-245-tencentos leaderelection %
.
├── healthzadaptor.go
├── healthzadaptor_test.go
├── leaderelection.go
├── leaderelection_test.go
├── leasecandidate.go
├── leasecandidate_test.go
├── metrics.go
├── OWNERS
└── resourcelock├── interface.go├── leaselock.go└── multilock.go

数据结构

存储 Lease 相关的信息

// LeaderElectionRecord is the record that is stored in the leader election annotation.
// This information should be used for observational purposes only and could be replaced
// with a random string (e.g. UUID) with only slight modification of this code.
// TODO(mikedanese): this should potentially be versioned
type LeaderElectionRecord struct {// leader的标识HolderIdentity       string                      `json:"holderIdentity"`// 选举间隔LeaseDurationSeconds int                         `json:"leaseDurationSeconds"`// 选举成为leader的时间AcquireTime          metav1.Time                 `json:"acquireTime"`// 续任时间RenewTime            metav1.Time                 `json:"renewTime"`// leader位置的转让次数LeaderTransitions    int                         `json:"leaderTransitions"`// 选举策略Strategy             v1.CoordinatedLeaseStrategy `json:"strategy"`PreferredHolder      string                      `json:"preferredHolder"`
}

Elector 相关的配置文件

type LeaderElectionConfig struct {// 锁,用来保证时序竞态Lock rl.Interface// 非leader候选者尝试获取leadership的间隔时间// Core clients default this value to 15 seconds.LeaseDuration time.Duration// leade 放弃leadership角色之前的确认时间// Core clients default this value to 10 seconds.RenewDeadline time.Duration// 候选者应该获取leader角色的重试时间// Core clients default this value to 2 seconds.RetryPeriod time.Duration// 回掉函数// 比如开始leader选举触发什么、成为leader触发什么、放弃leader触发什么Callbacks LeaderCallbacks// WatchDog is the associated health checker// WatchDog may be null if it's not needed/configured.WatchDog *HealthzAdaptor// ReleaseOnCancel should be set true if the lock should be released// when the run context is cancelled. If you set this to true, you must// ensure all code guarded by this lease has successfully completed// prior to cancelling the context, or you may have two processes// simultaneously acting on the critical path.ReleaseOnCancel bool// Name is the name of the resource lock for debuggingName string// Coordinated will use the Coordinated Leader Election feature// WARNING: Coordinated leader election is ALPHA.Coordinated bool
}

主要逻辑

选举的逻辑大概如下:

  1. 刚开始实例启动的时候,各个实例都是一个 LeaderElector 的角色,最先开始选举的就成

为 leader;成为 leader 之后便会维护一个 LeaseLock 供每个 LeaderElector 进行访问查询

  1. 其余的 LeaderElector 进入候选状态,hang 住监控 leader 的状态,必要时异常会再次参与选举
  2. Leader 获取到 Leadership 之后会持续性的刷新自己的 leader 状态
func (le *LeaderElector) Run(ctx context.Context) {defer runtime.HandleCrash()defer le.config.Callbacks.OnStoppedLeading()  // StoppedLeading 函数每个节点都会执行// 未获得leadership的节点这里就会返回// acquire 就是各个节点来争抢 leadershipif !le.acquire(ctx) {return // ctx signalled done}ctx, cancel := context.WithCancel(ctx)defer cancel()// 这里只有获取到leadership的角色的节点才会执行 StartedLeadinggo le.config.Callbacks.OnStartedLeading(ctx)// 获取到 leadership 之后不停的刷新当前的状态信息le.renew(ctx)
}func (le *LeaderElector) acquire(ctx context.Context) bool {...klog.Infof("attempting to acquire leader lease %v...", desc)wait.JitterUntil(func() {if !le.config.Coordinated {succeeded = le.tryAcquireOrRenew(ctx)    // 尝试竞争} else {succeeded = le.tryCoordinatedRenew(ctx)}....klog.Infof("successfully acquired lease %v", desc)cancel().....}, le.config.RetryPeriod, JitterFactor, true, ctx.Done())return succeeded
}

核心逻辑 tryAcquireOrRenew

// tryAcquireOrRenew tries to acquire a leader lease if it is not already acquired,
// else it tries to renew the lease if it has already been acquired. Returns true
// on success else returns false.
func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool {now := metav1.NewTime(le.clock.Now())leaderElectionRecord := rl.LeaderElectionRecord{// 这里 identity 为当前竞选者的标识HolderIdentity:       le.config.Lock.Identity(),    LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second),RenewTime:            now,AcquireTime:          now,}// 1.判断是否是Leader,如果是Leader并且Lease有效,则进行 Lock 的信息更新if le.IsLeader() && le.isLeaseValid(now.Time) {oldObservedRecord := le.getObservedRecord()leaderElectionRecord.AcquireTime = oldObservedRecord.AcquireTimeleaderElectionRecord.LeaderTransitions = oldObservedRecord.LeaderTransitionserr := le.config.Lock.Update(ctx, leaderElectionRecord)........}// 2.不是Leader,则进行锁的获取oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get(ctx)if err != nil {........}// 3.对比检查是否 Elctection 的 Record 信息// 需要更新则刷新本地的缓存信息if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {le.setObservedRecord(oldLeaderElectionRecord)le.observedRawRecord = oldLeaderElectionRawRecord}if len(oldLeaderElectionRecord.HolderIdentity) > 0 && le.isLeaseValid(now.Time) && !le.IsLeader() {klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity)return false}// 4. 按照是否Leader判断是否进行更新 ElectionRecordif le.IsLeader() {leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTimeleaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitionsle.metrics.slowpathExercised(le.config.Name)} else {leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1}// 5.更新锁本身的信息if err = le.config.Lock.Update(ctx, leaderElectionRecord); err != nil {klog.Errorf("Failed to update lock: %v", err)return false}// 更新锁成功则说明当前节点持有锁:抢锁成功/续期成功le.setObservedRecord(&leaderElectionRecord)return true
}

上面的 Lock 其实是自实现的一种 LeaseLock

// Interface offers a common interface for locking on arbitrary
// resources used in leader election.  The Interface is used
// to hide the details on specific implementations in order to allow
// them to change over time.  This interface is strictly for use
// by the leaderelection code.
type Interface interface {// Get returns the LeaderElectionRecordGet(ctx context.Context) (*LeaderElectionRecord, []byte, error)// Create attempts to create a LeaderElectionRecordCreate(ctx context.Context, ler LeaderElectionRecord) error// Update will update and existing LeaderElectionRecordUpdate(ctx context.Context, ler LeaderElectionRecord) error.....
}// Get returns the election record from a Lease spec
func (ll *LeaseLock) Get(ctx context.Context) (*LeaderElectionRecord, []byte, error) {lease, err := ll.Client.Leases(ll.LeaseMeta.Namespace).Get(ctx, ll.LeaseMeta.Name, metav1.GetOptions{})if err != nil {return nil, nil, err}ll.lease = leaserecord := LeaseSpecToLeaderElectionRecord(&ll.lease.Spec)recordByte, err := json.Marshal(*record)if err != nil {return nil, nil, err}return record, recordByte, nil
}// Create attempts to create a Lease
func (ll *LeaseLock) Create(ctx context.Context, ler LeaderElectionRecord) error {var err errorll.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{})return err
}

怎么判断当前leader是持有租约的呢?

func (le *LeaderElector) isLeaseValid(now time.Time) bool {return le.observedTime.Add(time.Second * time.Duration(le.getObservedRecord().LeaseDurationSeconds)).After(now)
}

其实就是判断上次观察到的时间与当前之间的差是否在 LeaseDurationSeconds 的范围内,在
范围内就代表是有效的

那这个选主的主到底是怎么判断的呢?

我们查看下面的判断逻辑

// IsLeader returns true if the last observed leader was this client else returns false.
func (le *LeaderElector) IsLeader() bool {return le.getObservedRecord().HolderIdentity == le.config.Lock.Identity()
}

其实就是拿当前的 ElectionRecord 和每个实例启动时的配置文件里面的 Identity 来进行比较
判断是否一致即可

而这个 ObservedRecord 的信息是从 k8s 里面进行获取的,这就保证了唯一性

Identity 是每个实例启动的唯一标识,这个字段千万不能重复,否则选举一定失败,报错如下

E1103 15:44:21.019391 3024650 leaderelection.go:429] Failed to update lock optimitically: Operation cannot be fulfilled on leases.coordination.k8s.io "hello-world": the object has been modified; please apply your changes to the latest version and try again, falling back to slow path

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/90306.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/90306.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/90306.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

为什么Java的String不可变?

为什么Java的String不可变? 场景: 你在开发多线程用户系统时,发现用户密码作为String传递后,竟被其他线程修改。这种安全隐患源于对String可变性的误解。Java将String设计为不可变类,正是为了解决这类核心问题。 1️⃣…

在Ubuntu上使用QEMU学习RISC-V程序(1)起步第一个程序

文章目录一、 引言二、 环境准备三、编写简单的RISC-V程序四、 编译步骤详解五、使用QEMU运行程序六、程序详解七、退出QEMU八、总结附录:QEMU中通过UTRA显示字符工作原理1、内存映射I/O原理2、add.s程序工作流程3、关键指令解析4、QEMU模拟的UART控制器5、为什么不…

R拟合 | 一个分布能看到三个峰,怎么拟合出这三个正态分布的参数? | 高斯混合模型 与 EM算法

1. 效果已知数据符合上图分布,怎么求下图的三个分布的参数mu, sigma,及每个分布的权重 lambda? 2. 代码: 高斯混合模型(Gaussian Mixture Model,简称GMM) library(mixtools) set.seed(123) # 确保结果可重复…

Excel自动分列开票工具推荐

软件介绍 本文介绍一款基于Excel VBA开发的自动分列开票工具,可高效处理客户对账单并生成符合要求的发票清单。 软件功能概述 该工具能够将客户对账单按照订单号自动拆分为独立文件,并生成可直接导入发票清单系统的标准化格式。 软件特点 这是一款体…

【自用】JavaSE--Stream流

概述获取Stream流集合的stream流集合名.stream( );collection集合List集合与Set集合都属于Collection集合,因此可以直接调用stream方法获取stream流,示例如下结果>map集合map集合存在键值对,因此无法使用该方法直接获取stream流&#xff0…

【Elasticsearch】快照与恢复功能详解

《Elasticsearch 集群》系列,共包含以下文章: 1️⃣ 冷热集群架构2️⃣ 合适的锅炒合适的菜:性能与成本平衡原理公式解析3️⃣ ILM(Index Lifecycle Management)策略详解4️⃣ Elasticsearch 跨机房部署5️⃣ 快照与恢…

技嘉z370主板开启vtx

技嘉z370vtx应该默认就是开启状态,虽然主板开启的vtx但是系统默认设置会导致vtx不能使用 1. 关闭hyper-V,Windows虚拟机监控程序平台,虚拟机平台 控制面板->程序->启用或关闭windows功能 2.以管理员身份运行CMD bcdedit /set hypervisorlaunchtype off 3.…

Springmvc的自动解管理

中央转发器&#xff08;DispatcherServlet&#xff09;控制器视图解析器静态资源访问消息转换器格式化静态资源管理一、中央转发器Xml无需配置<servlet><servlet-name>chapter2</servlet-name><servlet-class>org.springframework.web.servlet.Dispatc…

C#_定时器_解析

问题一:这里加lock是啥意思?它的原理是, 为什么可以锁住? private readonly Timer _timer;/// <summary>/// 构造函数中初始化定时器/// </summary>public FtpTransferService(){// 初始化定时器_timer new Timer(_intervalMinutes * 60 * 1000);_timer.Elapsed…

Trae开发uni-app+Vue3+TS项目飘红踩坑

前情 Trae IDE上线后我是第一时间去使用体验的&#xff0c;但是因为一直排队问题不得转战Cursor&#xff0c;等到Trae出付费模式的时候&#xff0c;我已经办了Cursor的会员&#xff0c;本来是想等会员过期了再转战Trae的&#xff0c;但是最近Cursor开始做妖了 网上有一堆怎么…

低代码中的统计模型是什么?有什么作用?

低代码开发平台中的统计模型是指通过可视化配置、拖拽操作或少量代码即可应用的数据分析工具&#xff0c;旨在帮助技术人员及非技术人员快速实现数据描述、趋势预测和业务决策。其核心价值在于降低数据分析门槛&#xff0c;使业务人员无需深入掌握统计原理或编程技能&#xff0…

Linux 下在线安装启动VNC

描述 Linux中的VNC就类似于Windows中的远程桌面系统 本文只记录在Cent OS 7的系统下在线安装VNC。 安装VNC 1、安装VNC yum install tigervnc-server2、配置VNC的密码 为用户设置 VNC 密码&#xff08;首次运行会提示输入&#xff0c;也可以提前输入&#xff09; vncpasswd密码…

支持OCR和AI解释的Web PDF阅读器:解决大文档阅读难题

支持OCR和AI解释的Web PDF阅读器&#xff1a;解决大文档阅读难题一、背景&#xff1a;为什么需要这个工具&#xff1f;问题场景解决方案二、技术原理&#xff1a;如何实现这些功能&#xff1f;1、核心技术组件2、工作流程3、关键点三、操作指南1、环境准备2、生成Html代码3、We…

研发过程都有哪些

产品规划与定义 (Product Planning & Definition) 在详细的需求调研之前&#xff0c;通常会进行市场分析、竞品分析、确立产品目标和核心价值。这个阶段决定了“我们要做什么”以及“为什么要做”。 系统设计与架构 (System & Architectural Design) 这是开发的“蓝图”…

旧物回收小程序系统开发——开启绿色生活新篇章

在当今社会&#xff0c;环保已经成为全球关注的焦点话题。随着人们生活水平的提高&#xff0c;消费能力不断增强&#xff0c;各类物品的更新换代速度日益加快&#xff0c;大量旧物被随意丢弃&#xff0c;不仅造成了资源的巨大浪费&#xff0c;还对环境产生了严重的污染。在这样…

UE5 UI 水平框

文章目录slot区分尺寸和对齐方式尺寸&#xff1a;自动模式尺寸&#xff1a;填充模式对齐常用设置所有按钮大小一致&#xff0c;不受文本影响靠右排列和unity的HorizontalLayout不太一样slot 以在水平框中放入带文字的按钮为例 UI如下布置 按钮的大小受slot的尺寸、对齐和内部…

【Golang】Go语言变量

Go语言变量 文章目录Go语言变量一、Go语言变量二、变量声明2.1、第一种声明方式2.2、第二种声明方式2.3、第三种声明方式2.4、多变量声明2.5、打印变量占用字节一、Go语言变量 变量来源于数学&#xff0c;是计算机语言中能存储计算结果或能表示值抽象的概念变量可以通过变量名…

Qt WebEngine Widgets的使用

一、Qt WebEngine基本概念Qt WebEngine中主要分为三个模块&#xff1a;Qt WebEngine Widgets模块&#xff0c;主要用于创建基于C Widgets部件的Web程序&#xff1b;Qt WebEngine模块用来创建基于Qt Quick的Web程序&#xff1b;Qt WebEngine Core模块用来与Chromeium交互。网页玄…

【C++】标准模板库(STL)—— 学习算法的利器

【C】标准模板库&#xff08;STL&#xff09;—— 学习算法的利器学习 STL 需要注意的几点及 STL 简介一、什么是 STL&#xff1f;二、学习 STL 前的先修知识三、STL 常见容器特点对比四、学习 STL 的关键注意点五、STL 学习路线建议六、总结七、下一章 vector容器快速上手学习…

YOLO算法演进综述:从YOLOv1到YOLOv13的技术突破与应用实践,一文掌握YOLO家族全部算法!

引言&#xff1a;介绍目标检测技术背景和YOLO算法的演进意义。YOLO算法发展历程&#xff1a;使用阶段划分方式系统梳理各代YOLO的技术演进&#xff0c;包含早期奠基、效率优化、注意力机制和高阶建模四个阶段。YOLOv13的核心技术创新&#xff1a;详细解析HyperACE机制、FullPAD…