【kubernetes】k8s中的选主机制

这篇具有很好参考价值的文章主要介绍了【kubernetes】k8s中的选主机制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

leader-election选主机制

1 为什么需要leader-election?

在集群中存在某种业务场景,一批相同功能的进程同时运行,但是同一时刻,只能有一个工作,只有当正在工作的进程异常时,才会由另一个进程进行接管。这种业务逻辑通常用于实现一主多从。

如果有人认为,传统应用需要部署多个通常是为了容灾,而在k8s上运行的Pod受控制器管理,如果Pod异常或者Pod所在宿主机宕机,Pod是可以漂移到其他节点的,所以,不需要部署多个Pod,只需要部署一个Pod就行。k8s上的Pod确实可以漂移,但是,如果宿主机宕机,k8s认为Pod异常,并在其他节点重建Pod是有周期的,不能在查询不到Pod状态时立刻就将Pod驱逐掉,也许节点只是临时不可用呢?例如,负载很高,因此,判断宿主机宕机需要有个时间短。

k8s节点故障时,工作负载的调度周期

因此,在k8s中运行一主多从是为了能够实现主的快速切换。

2 kubernetes中的leader-election

k8s中也有这种业务场景,在多master场景下,只能有一个master上的进程工作,例如,scheduler和controller-manager。以scheduler来说,它的工作是给Pod分配合适的宿主机,如果有多个scheduler同时运行,就会出现竞争,因此,如果允许这种场景存在的话,就又需要实现一种调度逻辑:某个Pod由哪个scheduler进行调度,这相当于又要实现一层调度。但是,实际上调度工作是相对比较简单的,不需要多个scheduler进行负载,只需要一个scheduler进行调度就行。因此,k8s提供了leader-election的能力。

leader-election的具体工作方式是:各候选者将自身的信息写入某一个资源,如果写成功,某个后选择就称为了主,其他就是备,同时,在之后主会定期更新资源的时间,如果超过一段时间未更新时间,其他候选者发现资源的最后更新时间超过一定值,就会认为主挂掉,然后会向资源写入自身信息,从而成为新的主。

基于该原理,有一个现成的镜像可以使用:instana/leader-elector。

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: leader
  name: leader
spec:
  replicas: 3
  selector:
    matchLabels:
      app: leader
  template:
    metadata:
      labels:
        app: leader
    spec:
      containers:
      - image: instana/leader-elector:0.5.13
        name: leader
        command: ["/app/server","--id=$(POD_NAME)","--election=leader-election","--http=0.0.0.0:4040"]
        env:
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name

上面的yaml有两个需要注意的地方:

  • /app/server是二进制程序,id参数是候选者的唯一标识,election是资源名称,http是应用监听的IP和端口号
  • 将pod的名称作为id参数,也就是候选者的唯一标识

创建deploy后,会启动三个Pod,通过kubectl logs可以看到只有一个Pod成为主,也就是向资源名称为leader-election的Endpoint写入了自身的Pod名称。然后通过代理(kubectl proxy)访问:http://localhost:8001/api/v1/namespaces/default/pods/:4040/proxy/,就会看到主的Pod名称。

知道了leader-election的大概原理,也知道了上面的镜像可以直接实现主的选举,那么如何使用呢?

2.1 Sidecar

直接将上面的leader-elector镜像作为Sidecar,将Pod名称作为候选者的唯一标识,然后将Pod名称也注入到环境变量,在业务进程起来后,定时调用http://localhost:4040就可以获取主,如果发现主的名称与自身的Pod的名称一致,就执行业务逻辑,否则一直等待。

2.2 SDK

使用Sidecar的好处是比较方便,开发成本低,不便的地方就是,适用场景有限,只能写入Endpoint资源。因此,在某些场景下,可以使用SDK,直接基于leader-election库开发。

k8s-leader-election

创建一个Lease类型的锁(当然,也可以是其他类型,但是lease更加轻量),创建资源时需要指定资源的命名空间、名称、标识(这一批Pod都会该命名空间的资源写入自身的唯一标识)。然后调用leaderelection库中的RunOrDie()函数,此时会指定:文章来源地址https://www.toymoban.com/news/detail-725511.html

  • Lock:资源锁,将前面创建的Lease类型锁填入
  • ReleaseOnCancel:
  • LeaseDuration:租约时间
  • RenewDeadline:leader刷新超时
  • RetryPeriod:刷新租约的时间间隔
  • Callbacks:指定成为leader时要执行的业务逻辑(OnStartedLeading),从leader变成非leader时要执行的逻辑(OnStoppedLeading),leader变更时要执行的逻辑(OnNewLeader)。

3 具体实现机制

// leaderelection/leaderelection.go
func (le *LeaderElector) Run(ctx context.Context) {
	defer runtime.HandleCrash()
	defer func() {
		le.config.Callbacks.OnStoppedLeading()
	}()

	// 申请资源锁,有三种情形:
	// 1 出错,则返回false,Run()直接退出
	// 2 获取到锁了,则返回true,执行回调函数
	// 3 未获取到锁,acquire()函数不会返回
	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 := false
	desc := le.config.Lock.Describe()
	klog.Infof("attempting to acquire leader lease %v...", desc)

	// 每隔RetryPeriod去申请资源锁,或者更新
	wait.JitterUntil(func() {
		succeeded = le.tryAcquireOrRenew(ctx)
		le.maybeReportTransition()
		if !succeeded {
			// 没有获取到锁,下一次再尝试
			klog.V(4).Infof("failed to acquire lease %v", desc)
			return
		}

		// 成功获取到锁,则退出
		le.config.Lock.RecordEvent("became leader")
		le.metrics.leaderOn(le.config.Name)
		klog.Infof("successfully acquired lease %v", desc)
		cancel()
	}, le.config.RetryPeriod, JitterFactor, true, ctx.Done())
	return succeeded
}

func (le *LeaderElector) renew(ctx context.Context) {
	ctx, cancel := context.WithCancel(ctx)
	defer cancel()

	// 每隔RetryPeriod尝试更新租约
	wait.Until(func() {
		timeoutCtx, timeoutCancel := context.WithTimeout(ctx, le.config.RenewDeadline)
		defer timeoutCancel()
		err := wait.PollImmediateUntil(le.config.RetryPeriod, func() (bool, error) {
			return le.tryAcquireOrRenew(timeoutCtx), nil
		}, timeoutCtx.Done())

		le.maybeReportTransition()
		desc := le.config.Lock.Describe()
		if err == nil {
			klog.V(5).Infof("successfully renewed lease %v", desc)
			return
		}
		le.config.Lock.RecordEvent("stopped leading")
		le.metrics.leaderOff(le.config.Name)
		klog.Infof("failed to renew lease %v: %v", desc, err)
		cancel()
	}, le.config.RetryPeriod, ctx.Done())

	// if we hold the lease, give it up
	if le.config.ReleaseOnCancel {
		le.release()
	}
}

// 尝试获取或者更新资源锁
func (le *LeaderElector) tryAcquireOrRenew(ctx context.Context) bool {
	now := metav1.Now()
	leaderElectionRecord := rl.LeaderElectionRecord{
		HolderIdentity:       le.config.Lock.Identity(),
		LeaseDurationSeconds: int(le.config.LeaseDuration / time.Second),
		RenewTime:            now,
		AcquireTime:          now,
	}

	// 1 获取资源锁记录
	oldLeaderElectionRecord, oldLeaderElectionRawRecord, err := le.config.Lock.Get(ctx)
	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(ctx, leaderElectionRecord); err != nil {
			klog.Errorf("error initially creating leader election record: %v", err)
			return false
		}

		le.setObservedRecord(&leaderElectionRecord)

		return true
	}

	// 2 将资源锁记录与缓存的上一次的值进行对比
	// 如果当前不是leader,并且资源锁没有过期,则退出
	if !bytes.Equal(le.observedRawRecord, oldLeaderElectionRawRecord) {
		le.setObservedRecord(oldLeaderElectionRecord)

		le.observedRawRecord = oldLeaderElectionRawRecord
	}
	if len(oldLeaderElectionRecord.HolderIdentity) > 0 &&
		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() {
		// 当前是leader,锁资源未过期,将之前的资源锁的数据填充到新的资源锁中(申请锁时间,切换次数)
		leaderElectionRecord.AcquireTime = oldLeaderElectionRecord.AcquireTime
		leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions
	} else {
		// 当前不是leader
		leaderElectionRecord.LeaderTransitions = oldLeaderElectionRecord.LeaderTransitions + 1
	}

	// 更新资源锁
	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
}

到了这里,关于【kubernetes】k8s中的选主机制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【K8S】docker和K8S(kubernetes)理解?docker是什么?K8S架构、Master节点 Node节点 K8S架构图

    一、docker的问世         在LXC(Linux container)Linux容器虚拟技术出现之前,业界网红是虚拟机。虚拟机技术的代表,是VMWare和OpenStack。 虚拟机是什么?         虚拟机其实就是 在你的操作系统里面,装一个软件,然后通过这个软件,再模拟一台甚至多台“子电脑”出来。

    2024年03月26日
    浏览(71)
  • Docker、Kubernetes(K8s)和KVM辨析

    Docker、Kubernetes(K8s)和KVM都是虚拟化技术,但它们各自的应用场景和功能有所不同。 Docker是一种轻量级的虚拟化技术,它允许开发者将应用程序及其依赖项打包到一个可移植的容器中,然后在任何运行Docker的服务器上部署。与传统的虚拟机相比,Docker容器提供了一种轻量级

    2024年02月21日
    浏览(65)
  • 容器技术,1. Docker,2. Kubernetes(K8s):

    目录 容器技术 1. Docker: 2. Kubernetes(K8s): Docker和Kubernetes 容器的主要应用场景有哪些? 有效的将单个操作系统的资源划分到孤立的组中,以便更好的在孤立的组之间平衡有冲突的资源使用需求,这种技术就是容器技术。 容器技术指通过在物理主机操作系统上创建一个一个

    2024年02月11日
    浏览(71)
  • 基于Docker的K8s(Kubernetes)集群部署

    开始搭建k8s集群 三台服务器修改主机名称 关闭对话窗口,重新连接 三台主机名称呢就修改成功了。 接下来修改每台节点的 hosts 文件 所有节点关闭 setLinux 查看是否关闭成功 为每个节点添加 k8s 数据源 如果安装docker数据源找不到yum-config 所有节点安装kubelet kubelet安装中… k

    2024年02月08日
    浏览(100)
  • 四、Kubernetes(k8s) 工作中的常用命令

    顾名思义, Namespace 是命名空间的意思,在 Kubernetes 中,“命名空间(Namespace)” 提供一种机制,将同一集群中的资源划分为相互隔离的组。 同一命名空间内的资源名称要唯一,但跨命名空间时没有这个要求。 命名空间作用域仅针对带有命名空间的对象,例如 Deployment、Se

    2024年02月08日
    浏览(68)
  • Kind | Kubernetes in Docker 把k8s装进docker!

    有点像杰克船长的黑珍珠 目录 零、说明 一、安装 安装 Docker 安装 kubectl 安装 kind 二、创建/切换/删除集群 创建 切换 删除 将镜像加载到 kind 群集中 官网:kind Kind: Kubernetes in Docker 的简称。kind 是一个使用 Docker 容器“节点”运行本地 Kubernetes 集群的工具。kind 主要设计用于

    2024年02月16日
    浏览(36)
  • 【一起来学kubernetes】7、k8s中的ingress详解

    Ingress 是Kubernetes集群中的一种资源类型,用于实现用域名的方式访问Kubernetes内部应用。它为Kubernetes集群中的服务提供了入口,可以提供负载均衡、SSL终止和基于名称的虚拟主机。在生产环境中常用的Ingress有 Treafik 、 Nginx 、 HAProxy 、 Istio 等。基本概念是在Kubernetes v 1.1版中添

    2024年02月05日
    浏览(46)
  • 【k8s】kubernets和docker之间版本的对应关系

    如果查看1.18版本k8s对应的docker支持的最新版本 https://github.com/kubernetes/kubernetes/blob/release-1.18/build/dependencies.yaml 查看最新版本k8s对应的docker支持的最新版本 https://github.com/kubernetes/kubernetes/blob/master/build/dependencies.yaml

    2024年02月11日
    浏览(47)
  • k8s中的服务发现机制是如何实现的

    Kubernetes的服务发现机制是Kubernetes集群中一个非常核心的功能,它允许集群内的Pod、Service以及其他网络实体相互发现和通信。这种机制对于构建微服务架构的应用程序尤为重要,因为它可以消除硬编码的网络地址和端口号,提供动态的、可扩展的服务访问方式。 在Kubernetes中

    2024年03月28日
    浏览(47)
  • Kubernetes(K8s 1.28.x)部署---创建方式Docker(超详细)

    目录 一、基础环境配置(所有主机均要配置) 1、配置IP地址和主机名、hosts解析 2、关闭防火墙、禁用SELinux 3、安装常用软件 4、配置时间同步 5、禁用Swap分区 6、修改linux的内核参数 7、配置ipvs功能 二、容器环境操作 1、定制软件源 2、安装最新版docker 3、配置docker加速器 4、

    2024年02月09日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包