关于K8S库中高可用的锁机制详解
简介
对于无状态的组件来说,天然具备高可用特性,无非就是多开几个副本而已;
而对于有状态组件来说,实现高可用则要麻烦很多,一般来说通过选主来达到同一时刻只能有一个组件在处理业务逻辑。
在Kubernetes中,为了实现组件高可用,同一个组件需要部署多个副本,例如多个apiserver、scheduler、controller-manager等,其中apiserver是无状态的,每个组件都可以工作,而scheduler与controller-manager是有状态的,同一时刻只能存在一个活跃的,需要进行选主。
Kubernetes中是通过leaderelection来实现组件的高可用的。在Kubernetes本身的组件中,kube-scheduler和kube-manager-controller两个组件是有leader选举的,这个选举机制是Kubernetes对于这两个组件的高可用保障。即正常情况下kube-scheduler或kube-manager-controller组件的多个副本只有一个是处于业务逻辑运行状态,其它副本则不断的尝试去获取锁,去竞争leader,直到自己成为leader。如果正在运行的leader因某种原因导致当前进程退出,或者锁丢失,则由其它副本去竞争新的leader,获取leader继而执行业务逻辑。
在Kubernetes client-go包中就提供了接口供用户使用。代码路径在client-go/tools/leaderelection下。
如何使用
因为client-go帮我们封装了大部分逻辑,使用起来非常简单
rl, err := resourcelock.New(resourcelock.EndpointsResourceLock,"namespace",lockName,ctx.KubeClient.CoreV1(),resourcelock.ResourceLockConfig{Identity: id,EventRecorder: ctx.Recorder("namespace"),})
if err != nil {log.Fatalf("error creating lock: %v", err)panic(err)
}// Try and become the leader and start cloud controller manager loops
leaderelection.RunOrDie(context.Background(), leaderelection.LeaderElectionConfig{Lock: rl,LeaseDuration: ctx.LeaseDuration,RenewDeadline: ctx.RenewDeadline,RetryPeriod: ctx.RetryPeriod,Callbacks: leaderelection.LeaderCallbacks{OnStartedLeading: func(_ context.Context) {log.Infof("cmdb running in leader elect")run(ctx)},OnStoppedLeading: func() {log.Fatalf("leaderelection lost")},},
})
首先是新建给资源锁对象
开始选举,同时业务会提供回调方法给leaderelection:针对不同的选举结果,leaderelection会回调业务相应的方法,有三种回调方法
-
成为leader时的回调
失去leader时的回调
leader变更时的回调
下面就从源码来分析下它是怎么实现的
源码解析
leaderelection基本原理其实就是利用通过Kubernetes中configmap 、endpoints资源实现一个分布式锁,获取到锁的进程成为leader,并且定期更新租约(renew)。其他进程也在不断的尝试进行抢占,抢占不到则继续等待下次循环。当leader节点挂掉之后,租约到期,其他节点就成为新的leader。
为了针对不同资源的锁机制,leaderelection
定义了一个接口协议:
type Interface interface {// Get returns the LeaderElectionRecordGet() (*LeaderElectionRecord, error)// Create attempts to create a LeaderElectionRecordCreate(ler LeaderElectionRecord) error// Update will update and existing LeaderElectionRecordUpdate(ler LeaderElectionRecord) error// RecordEvent is used to record eventsRecordEvent(string)// Identity will return the locks IdentityIdentity() string// Describe is used to convert details on current resource lock// into a stringDescribe() string
}
目前有configmap和endpoints两种资源的实现。
从上面的使用来说:
第一步,我们首先会新建一个资源锁,对应源码如下:
func New(lockType string, ns string, name string, client corev1.CoreV1Interface, rlc ResourceLockConfig) (Interface, error) {switch lockType {case EndpointsResourceLock:return &EndpointsLock{EndpointsMeta: metav1.ObjectMeta{Namespace: ns,Name: name,},Client: client,LockConfig: rlc,}, nilcase ConfigMapsResourceLock:return &ConfigMapLock{ConfigMapMeta: metav1.ObjectMeta{Namespace: ns,Name: name,},Client: client,LockConfig: rlc,}, nildefault:return nil, fmt.Errorf("Invalid lock-type %s", lockType)}
}
可以看到返回的是接口类型,对应到configmap和endpoints两种锁的实现。
第二步,开始选举,对应源码如下:
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)
}
func (le *LeaderElector) Run(ctx context.Context) {defer func() {runtime.HandleCrash()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)
}
上面的代码就是尝试去获取锁,然后根据处理结果,回调业务相应的方法,下面来看下具体的选举逻辑:
func (le *LeaderElector) acquire(ctx context.Context) bool {ctx, cancel := context.WithCancel(ctx)defer cancel()succeeded := falsedesc := le.config.Lock.Describe()klog.Infof("attempting to acquire leader lease %v...", desc)wait.JitterUntil(func() {succeeded = le.tryAcquireOrRenew()le.maybeReportTransition()if !succeeded {klog.V(4).Infof("failed to acquire lease %v", desc)return}le.config.Lock.RecordEvent("became leader")klog.Infof("successfully acquired lease %v", desc)cancel()}, le.config.RetryPeriod, JitterFactor, true, ctx.Done())return succeeded
}
wait是一个周期性任务,该任务主要就是如下逻辑:
尝试去获取锁
如果没有获取到,则直接返回,等待下一次周期的到来再次尝试获取
如果获取到,表示自己已经是leader,可以处理业务逻辑了,此时就通过取消context来退出wait周期性任务
下面来看下具体的获取锁代码:
func (le *LeaderElector) tryAcquireOrRenew() bool {now := metav1.Now()leaderElectionRecord := rl.LeaderElectionRecord{HolderIdentity: le.config.Lock.Identity(),LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second),RenewTime: now,AcquireTime: now,}// 1. obtain or create the ElectionRecordoldLeaderElectionRecord, err := le.config.Lock.Get()if err != nil {if !errors.IsNotFound(err) {klog.Errorf("error retrieving resource lock %v: %v", le.config.Lock.Describe(), err)return false}if err = le.config.Lock.Create(leaderElectionRecord); err != nil {klog.Errorf("error initially creating leader election record: %v", err)return false}le.observedRecord = leaderElectionRecordle.observedTime = le.clock.Now()return true}// 2. Record obtained, check the Identity & Timeif !reflect.DeepEqual(le.observedRecord, *oldLeaderElectionRecord) {le.observedRecord = *oldLeaderElectionRecordle.observedTime = le.clock.Now()}if le.observedTime.Add(le.config.LeaseDuration).After(now.Time) &&!le.IsLeader() {klog.V(4).Infof("lock is held by %v and has not yet expired", oldLeaderElectionRecord.HolderIdentity)return false}// 3. We're going to try to update. The leaderElectionRecord is set to it's default// here. Let's correct it before updating.if le.IsLeader() {leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTimeleaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions} else {leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1}// update the lock itselfif err = le.config.Lock.Update(leaderElectionRecord); err != nil {klog.Errorf("Failed to update lock: %v", err)return false}le.observedRecord = leaderElectionRecordle.observedTime = le.clock.Now()return true
}
上面的逻辑如下:
获取锁对象信息LeaderElectionRecord
如果没有获取到,说明此时还没有leader,则去创建一个
如果获取到了,则说明系统中存在leader了,根据id标识检查自己是否是leader;如果自己不是leader,检查锁是否过期,如果没有过期,则退出后面的逻辑,等待wait的下一次调用;如果过期了,则更新锁对象,自己成为leader(续约成功或者新成为)
上面对对象锁信息的处理调用了一系列的接口,根据前面说的,有两种实现configmap和endpoints,下面以endpoints为例看看接口都是怎么处理对象锁的:
func (el *EndpointsLock) Get() (*LeaderElectionRecord, error) {var record LeaderElectionRecordvar err errorel.e, err = el.Client.Endpoints(el.EndpointsMeta.Namespace).Get(el.EndpointsMeta.Name, metav1.GetOptions{})if err != nil {return nil, err}if el.e.Annotations == nil {el.e.Annotations = make(map[string]string)}if recordBytes, found := el.e.Annotations[LeaderElectionRecordAnnotationKey]; found {if err := json.Unmarshal([]byte(recordBytes), &record); err != nil {return nil, err}}return &record, nil
}// Create attempts to create a LeaderElectionRecord annotation
func (el *EndpointsLock) Create(ler LeaderElectionRecord) error {recordBytes, err := json.Marshal(ler)if err != nil {return err}el.e, err = el.Client.Endpoints(el.EndpointsMeta.Namespace).Create(&v1.Endpoints{ObjectMeta: metav1.ObjectMeta{Name: el.EndpointsMeta.Name,Namespace: el.EndpointsMeta.Namespace,Annotations: map[string]string{LeaderElectionRecordAnnotationKey: string(recordBytes),},},})return err
}
从上面可以看出,其实所谓的提供接口,无非就是提供一个锁对象信息的存储位置,因为k8s中的资源都是给业务使用的,k8s不想专门提供一种资源来代表组件锁,而是将锁信息存储在了接口实现资源的annotations标签中。
相关文章:
关于K8S库中高可用的锁机制详解
简介 对于无状态的组件来说,天然具备高可用特性,无非就是多开几个副本而已;而对于有状态组件来说,实现高可用则要麻烦很多,一般来说通过选主来达到同一时刻只能有一个组件在处理业务逻辑。 在Kubernetes中,…...

常用中外文献检索网站大盘点
一、常用中文文献检索权威网站: 1、知网:是全球最大的中文数据库。提供中国学术文献、外文文献、学位论文、报纸、会议、年鉴、工具书等各类资源,并提供在线阅读和下载服务。涵盖领域包括:基础科学、文史哲、工程科技、社会科学、…...

公司招了一个00后,以为是个小年轻,没想到人家是个卷王...
公司前段缺人,也面了不少测试,结果竟然没有一个合适的。一开始瞄准的就是中级的水准,也没指望来大牛,提供的薪资也不低,面试的人很多,但平均水平很让人失望。 令我印象最深的是一个00后测试员,…...
数字化转型难?怎么转?听听厂商、CIO、CEO怎么说
数字化转型已经成为当今商业领域中的热门话题。对于许多企业来说,数字化转型是一项重要而且必不可少的战略,以适应快速变化的市场环境并保持竞争力。然而,数字化转型并不是一项容易的任务,它涉及到许多方面,需要综合考虑技术、组织和文化等因素。那么,让我们来听听一些厂…...
C++面试题汇总
C面试题汇总 1. new/delete和malloc/free:2. delete和delete[]:3. 常引用:4. overload、override、overwrite的介绍5. C是不是类型安全的?6. main 函数执行以前,还会执行什么代码?7. 数组与指针的区别&…...
OpenAi编写基于Python+OpenCV的人脸识别实现带墨镜效果
要基于Python和OpenCV实现带墨镜效果的人脸识别,你可以按照以下步骤进行操作: 安装所需的库:确保你已经安装了Python和OpenCV库。你可以使用pip命令来安装OpenCV库:pip install opencv-python。 导入必要的库:在Pytho…...
安卓闲谈吹水
一、熟练掌握 Java 语言,面向对象分析设计能力,反射原理,自定义注解及泛型,多次采用设计模式重构项目 首先我们先了解什么是对象。 1.对象是由我们自己定义的类来创建出来的。 2.对象实际上就是类的具体实现。 (对象是类的一个实…...
测试类的使用
1.在pom文件中添加依赖 <dependencies> <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>compile</scope> </dependency> </dependencies>2.在s…...
【物联网技术对生活的影响与展望】
随着科技日新月异的发展,物联网(IoT)技术正在快速地影响着我们的生活。它是将各种设备和物品连接在一起,通过互联网使它们可以相互交流和传递数据的技术。它的应用范围广泛,可以涵盖从智能家居到工业网络的各个领域。 …...
MySQL数据库函数详解及示例
以下是一份按照常见MySQL数据库函数,并且包含函数示例: 字符串函数 字符串函数用于处理和操作文本数据。 CONCAT:将多个字符串连接为一个字符串。SUBSTRING:提取字符串的一部分。LENGTH:返回字符串的长度。REPLACE&…...

ES6对象新增了哪些扩展?
一、属性的简写 ES6中,当对象键名与对应值名相等的时候,可以进行简写 const baz {foo:foo}// 等同于 const baz {foo} 方法也能够进行简写 const o {method() {return "Hello!";} };// 等同于const o {method: function() {return &qu…...

深入理解 Linux 内核
Linux 内核系列文章 Linux 内核设计与实现 深入理解 Linux 内核 深入理解 Linux 内核(二) Linux 设备驱动程序 Linux设备驱动开发详解 文章目录 Linux 内核系列文章前言一、绪论二、内存寻址1、内存地址2、硬件中的分段(1)段选择符…...
机器人科普--evoBOT
机器人科普--evoBOT 1 介绍2 视频3 Modular prototype4 Mechanical construction5 Hardware architecture and technology6 Autonomy und navigation7 Operation of the robot8 Technical data参考 1 介绍 The evolution of autonomous mobile robotic systems 2 视频 evoBOT…...

数据结构课程设计——运动会分数统计
运动会分数统计 数据结构课程设计任务书 学生姓名:xxx 专业班级:软件工程 指导教师: 工作单位: 题 目: 运动会分数统计 基础要求: 要求具有C语言的理论基础…...

C语言递归算法实现经典例题
一.递归 1.什么是递归 递归是一种编程技术,它通过在函数内部反复调用自身来解决问题。当一个程序调用自己时,这就称为递归调用。递归可以有助于简化某些算法的实现和理解。在递归过程中,每个调用都会将一些数据保存在栈上,直到递…...

ST典型碳化硅MOSFET驱动应用方案
ST典型碳化硅MOSFET驱动应用方案 1.栅极驱动器规格和功能实现 参考资料:ST官网应用手册《AN4671》 作者:Xiou 1.栅极驱动器规格和功能实现 以下是对栅极驱动要求的简短列表: dv / dt 的瞬变抗扰度:在整个温度范围内 50 V/ns。 …...
对比AMD和英特尔显卡的区别
✨求关注~ 😀博客:www.protaos.com AMD和英特尔都是著名的半导体公司,它们都生产处理器和显卡。在显卡领域,AMD生产Radeon系列显卡,而英特尔则生产Intel HD Graphics和Intel Iris Graphics系列显卡。 使用群体对比&…...
Linux系统c语言socket实现UDP通信
UDP全称 User Datagram Protocol,即:用户数据报协议。是面向无连接的协议。通常,UDP 通信还会被冠以不可靠的头衔。这里的不可靠指的是:无法可靠地得知对方是否收到数据。 UDP有如下特征: 无连接:通信双方不需要事先连接无确认:收到数据不给对方发回执确认不保证有序、丢…...

常用五大类RFID系统,实践领域广泛,加强现代化管理
随着信息技术的不断进步,RFID技术已逐渐成为企业管理及社会服务领域中不可或缺的一种重要技术手段。根据其不同的应用场景,RFID技术广泛应用于药品监管、固定资产管理、仓储管理、智慧工厂和消费服务等领域。本文将从五个方面介绍常用的RFID系统。 一、…...

卡方检验.医学统计实例详解
卡方检验是一种常用的假设检验方法,通常用于分析两个或多个分类变量之间的关系。在医学研究中,卡方检验被广泛应用于分析两种或多种治疗方法的疗效,或者分析某种疾病的发病率与某些危险因素之间的关系。下面我们来看一个卡方检验在医学实例中…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

NLP学习路线图(二十三):长短期记忆网络(LSTM)
在自然语言处理(NLP)领域,我们时刻面临着处理序列数据的核心挑战。无论是理解句子的结构、分析文本的情感,还是实现语言的翻译,都需要模型能够捕捉词语之间依时序产生的复杂依赖关系。传统的神经网络结构在处理这种序列依赖时显得力不从心,而循环神经网络(RNN) 曾被视为…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
Spring AI与Spring Modulith核心技术解析
Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

python执行测试用例,allure报乱码且未成功生成报告
allure执行测试用例时显示乱码:‘allure’ �����ڲ����ⲿ���Ҳ���ǿ�&am…...

OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...