首页 文章 精选 留言 我的

精选列表

搜索[安全机制],共10000篇文章
优秀的个人博客,低调大师

醒一醒,讲到 ZooKeeper 的选举机制了

本文作者:HelloGitHub-老荀 Hi,这里是 HelloGitHub 推出的 HelloZooKeeper 系列,免费开源、有趣、入门级的 ZooKeeper 教程,面向有编程基础的新手。 项目地址:https://github.com/HelloGitHub-Team/HelloZooKeeper 今天开始我们将继续深入 ZK 选举相关的知识 一、选举的基本规则 ZKr~这次我决定一反常态,先不讲故事了~先得聊聊在 ZK 选举中非常重要的一些东西。 1.1 zxid zxid 就是我们之前提到的事务编号,是一个 8 字节的整型数字,但是 ZK 设计的时候把这一个数字拆成了两部分使用,一鱼两吃! 8 个字节的整数一共有 64 位长度,前 32 位用来记录 epoch,后 32 位就是用来计数。你可能要问了?epoch?是啥? zxid 初始化是 0,也就是这样 0000000000000000000000000000000000000000000000000000000000000000 每一次写请求都会增加后 32 位,假设现在进行了 10 次写请求(无论该请求有没有真的修改到数据),zxid 就会变成这样 0000000000000000000000000000000000000000000000000000000000001010 当进行一次选举的时候,前 32 位就会增加 1,并且清零后 32 位 0000000000000000000000000000000100000000000000000000000000000000 除了选举以外,当后 32 位彻底用完(变成全 1,也就是 ZK 正常执行了 2^32 - 1 次写请求都没进行过一次选举,牛逼!)也会让前 32 位增加 1,相当于进位 #进位前0000000000000000000000000000000011111111111111111111111111111111#进位后0000000000000000000000000000000100000000000000000000000000000000 到这里我就可以回答大家前面的问题了,epoch 就是 zxid 前 32 位的这个数字,epoch 本身的翻译是“纪元,时代”的意思,意味着更新换代,而 zxid 的后 32 位数字仅仅是写请求的计数罢了 1.2 myid 在之前的小故事里,我给 ZK 的集群中的各个节点都起了一个好记的名字(神特么好记!)。但是 ZK 官方自己是如何给每一个集群中的节点起名字的呢?用的就是 myid! ZK 的启动配置 zoo.cfg 中有一项 dataDir 指定了数据存放的路径(默认是 /tmp/zookeeper),在此路径下新建一个文本文件,命名为 myid, 文本内容就是一个数字,这个数字就是当前节点的 myid /tmp└──zookeeper├──myid└──... 然后在 zoo.cfg 是这样配置集群信息 server.1=zoo1:2888:3888server.2=zoo2:2888:3888server.3=zoo3:2888:3888 这个 server. 之后的数字就是 myid,这个 myid 在整个集群中,各个节点之间是不能重复的。我忘记之前在哪儿看到的了,说是 myid 只能是 1 到 255 的数字,我一直信以为真,直到这次,我本着严谨的态度去做了实践,一切以事实为主,并且我的实验覆盖了 3.4、3.5、3.6 三大版本(都是三台机器的简单集群),结论是:myid 只要是不等于 -1 就行(-1 是一个固定的值会导致当前节点启动报错),不能大于 Long.MAX_VALUE 或者小于 Long.MIN_VALUE,但是如果在当前的节点中配置了 zookeeper.extendedTypesEnabled=true 那当前节点的最大 myid 是 254(负数不影响,我也不知道这个 254 的用意,但是代码中的确有判断) 是不是奇怪的知识又增加了呢~ 关于配置更多的信息,之后单独再整理,今天就点到为止 1.3 选举规则 知道了上面这些有什么用呢?非常重要!因为选举 Leader 完全看的就是这几个值 epoch 写请求次数 myid 优先级从上到下逐级比较,谁大谁就更有资格成为 Leader,当前级一样就比较下一级,直到分出胜负为止!因为 myid 是不能重复的,所以最终是一定能分出胜负的! 好了,现在大家知道了最基本的选举规则了~让我们进入下一节吧 二、三马之争 马果果一定想不到,这辈子自己可以和两位鼎鼎大名的明星企业家相提并论,让我们一起去看看发生了什么吧~ 2.1 准备开工 之前马果果规定了三个办事处在对外开张前必须选出一个 Leader,在正式开始选之前,每一个办事处也有一些准备工作需要做: 每一个办事处必须得知道一共有多少个办事处 额外聘请一些专门负责和其他办事处沟通的话务员 准备好一个票箱用来对投票统计和归票 为每一个办事处设置一个固定的 myid 所以现在办公室的布置变成了这样(我省略了之前章节的其他要素): 有了这些准备工作以后所有办事处都可以进入选举的阶段了,并且村委会规定了几种状态用于表示当前办事处正处在的阶段: LOOKING,正在寻找 Leader,处于此阶段的办事处不能对外提供服务 LEADING,当前办事处就是 Leader,可以对外提供服务 FOLLOWING,当前办事处正在跟随 Leader,可以对外提供服务 很明显刚刚准备好的各个办事处现在都处于 LOOKING 状态,下面让我们正式进入选举流程吧 2.2 开始选举 由于各个办事处刚准备好,所以彼此之间还没有通过信,又加上大家都是姓马的,心里面都是想当老大的,所以每一个办事都会率先拟一张写着自己的选票发给其他办事处。主要有这些信息: sid:我是谁 leader:我选谁 state:我当前的状态 epoch:我当前的 epoch zxid:我选择的 leader 的最大的事务编号 以马果果举例: 马小云和马小腾也一样,一开始都选了自己做 Leader 候选人,并且都把自己认为的候选人(当前场景下就是自己)的票分别发送给了其他两位(以及自己) 2.2.1 马果果视角 每个办事处各自也会收到来自其他办事处的选票(也有可能是自己的),每拿到一张选票,都需要和当前自己认为的 Leader 候选人做比较,理论上自己投给自己的选票会先一步达到自己的票箱,因为不需要经过通讯减少了传输的路径,自己的选票和自己的候选人是一致的所以不需要比较,只需要在票箱中记上一笔,我们还是以马果果举例: =》的左边是办事处的名字,右边是该办事处选的 Leader。当前投票统计是指,当前节点所选的 Leader 获得的选票统计。 假设他再收到了马小云的选票: 马果果首先看到的是 马小云也处在 LOOKING 状态 接着就会比较自己候选人和 马小云的选票(左边代表当前办事处的候选人,右边代表收到的选票信息,下同) e:0==e:0z:0==z:0l:马果果(69)>l:马小云(56) 最终因为马果果的 myid 69 要比马小云的 myid 56 要大,所以马果果最终胜出!虽然马小云胜出了,但是当前投票统计是不能修改的,因为马小云这一轮的选票就是选的马小云,需要等待他重新改票后再投才能修改投票统计。 之后会往投票箱记录: 紧接着是马小腾的投票: e:0==e:0z:0==z:0l:马果果(69)>l:马小腾(49) 马果果还是胜出! 记录投票箱: 每次收到投票的时候,马果果都会依据当前的投票统计进行归票,但是很遗憾选举仍然无法结束,因为结束的规则必须有某一个办事处获得半数以上的选票,现在只有一个马果果自己的选票,不满足半数以上,所以马果果只能再等等了。 而在马果果这边忙的热火朝天的同时,马小云和马小腾也在进行着同样的动作。 2.2.2 马小云视角 我们这省略描述马小云记录自己选票的过程,假设他这边是先收到马果果的选票,是怎么处理的呢? e:0==e:0z:0==z:0l:马小云(56)<l:马果果(69) 马小云看到自己认为的 Leader 候选人被马果果的选票击败了,所以将自己的候选人改为马果果,并将新的选票重新广播出去 然后在自己的投票箱中记录: 为了叙述的完整性,我们还是把马小腾的票也看完 e:0==e:0z:0==z:0l:马果果(69)>l:马小腾(49) 马果果还是胜出了,所以马小云的投票箱最终变成这样: 讲道理接下来应该以马小腾为主视角,再讲一遍刚才的过程,但是可以认为几乎和马小云是一样的,为了故事的顺畅,我们需要回到马果果的视角,因为马小云输给马果果之后改票了,又发了一轮选票 2.2.3 马果果视角(再) 马果果又再一次收到了马小云的选票(改票后),投票箱就会改成这样: 收到这个投票后,当前投票统计就会增加马小云的记录,然后马果果进行归票就发现了这次自己的选票超过半数了,然后会进行二次确认,会等待一会看看还能不能收到更新的选票,这里假设没有收到更新的投票,就会进行判断,当前过半数的候选人是不是自己?如果是的话,那自己就是 Leader,不是的话,自己就是 Follower。 很明显,马果果就是 Leader,然后会把自己的状态修改为 LEADING。 与此同时,马小云、马小腾也进行归票,归票结果自己为 Follower,把自己状态修改为 FOLLOWING,然后各自都会和 Leader 进行数据的同步,同步完成之后整个办事处就都可以对外提供服务了。 2.3 马小腾停电啦 选举本身涉及到集群间的通信、节点自身的状态管理和状态变更,本身就是一个比较复杂的过程,刚才只是举例了一个最简单的启动选举流程,下面会举更多的例子帮助大家能理解整个选举的逻辑。 现在假设办事处安然无恙得对外提供了一段时间服务后,马小腾的办事处突然停电了,就不能和另外两马进行通讯了,而另外两马在一段时间内都没有收到过马小腾的信息的时候就知道,出事了!但是各自盘点了下目前仍然还有两个办事处可以对外提供服务,是达到整个集群总数的半数以上的,是可以继续让村民们来办理业务的,所以现在整个集群变成了这样: 没过一会,因为电力公司的积极抢修,马小腾的办事处恢复供电了,重新开张了,但是每一个办事处在开张前都是处在 LOOKING 状态的,还是会优先投票给自己,并会通过复盘本地的存档来得到自己办事处最新的数据,假设马小腾停电前是这样: e:0z:21l:马小腾(49)LOOKING 他和之前一样会给另外两个办事处发自己的选票 但和之前的情况不同,无论是马果果还是马小云他们现在都处在工作的状态,收到了马小腾的选票后就会把当前的 Leader 也就是马果果的选票信息以及自己当前的状态发送给他。 马果果发送的选票信息: e:0z:30l:马果果(69)LEADING 马小云发送的选票信息: e:0z:30l:马果果(69)FOLLOWING 马小腾收到两位的选票信息后,知道了当前的 Leader 是马果果,并且马果果本人也确认了是 LEADING 状态,就马上把自己的状态修改为了 FOLLOWING 状态,并且会和之前一样与 Leader 进行数据的同步,关于具体怎么同步的,我打算留到之后再进行讲解~ 同步之后,马小腾的状态变成了和马小云一样的了。 我再假设这里有一个平行世界,回到马小腾刚恢复完供电准备开张上线的时候,此时的马小腾的状态假设是这样的: e:1z:7l:马小腾(49)LOOKING 哪怕 epoch 比目前的 Leader 还要大,其实照道理是更有资格当 Leader,但是由于当前集群中的其他办事处已经有了一个明确的 Leader,马小腾也只能忍辱负重(谁让你停电了呢)还是以 Follower 的身份加入到集群中来,并且仍然以当前 Leader 的信息来同步,你也可以理解为降级(把自己的 epoch 降级回 0 ) 职场就是这么残忍,你稍微请个长假再回来可能已经是物是人非了~ 2.4 马果果又病啦 马果果毕竟年事已高,又又又生病了,办事处只能含泪关门,但是和上一次马小腾停电不同,这次是作为 Leader 的马果果停止服务了,因为之前定下的规定,整个办事处集群必须得有一个 Leader。现在马小云和马小腾发现 Leader 联系不上了,说明 Leader 无法服务了,他们就知道必须选出一个新的 Leader。于是纷纷将自己的状态都修改为 LOOKING 状态,并且再次把候选人选为自己,重新向其他仍然可以提供服务的办事处广播自己的选票(当前这个场景就是互相发选票了)。 无论谁收到选票后经过比较后都会知道是马小腾胜出 e:1==e:1z:77<z:80l:马小云(56)l:马小腾(49) 马小云会把自己的候选人修改为马小腾之后重新再把自己的选票发出去,现在马小腾就获得了 2 票通过,同时也满足大于整个办事处集群半数以上,所以马小腾和马小云各自修改状态为 LEADING 和 FOLLOWING 后,并且会和之前说的一样,把 epoch 加 1 同时清空计数部分,最后重新恢复对村民提供服务。 而马果果这边病好以后,会重新开张和之前的例子一样也是先从 LOOKING 状态开始,最后会从其他两马那里得知目前的 Leader 是马小腾之后,就会主动和马小腾同步数据并以 Follower 的身份加入到办事处集群中对外提供服务。 2.5 招商引资 办事处的热火朝天被村委会看在了眼里,心想只有三个办事处就能达到这样的效果,如果有更多的办事处呢?于是和三马商量了下,决定对外招商引入社会资本,让他们自己按照现有模式建立新的办事处,这样村委会不用出一分钱,村民还能获得实在的好处,秒啊! 此举一度引来社会资本的大量关注,但是商量过后,三马又觉得如果过多的引入外部力量势必会削弱自己手中的权力,所以又出了一个规定,三马自封为 Participant 只有他们三个才有资格进行 Leader 的竞选,而引入的社会资本所创建的办事处只能作为 Observer 加入办事处的集群中对外提供只读服务,没有资格竞争 Leader,这样就可以在不增加选举复杂程度的同时,提升整个办事处集群对读请求的吞吐量。 要声明当前节点是 Observer,需要在 zoo.cfg 中先配置 peerType=observer 同时声明的集群信息最后要多加一个 :observer 用来标识,这样其他节点也会知道当前 myid 为 1 和 2 都是 Observer server.69=maguoguo:2888:3888server.56=maxiaoyun:2888:3888server.49=maxiaoteng:2888:3888server.1=dongdong:2888:3888:observerserver.2=jitaimei:2888:3888:observer 而在 LOOKING 状态的 Observer 一开始的 Leader 候选人也会选自己,但是选票信息被设置成了这样,以东东举例: e:Long.MIN_VALUEz:Long.MIN_VALUEl:东东(1)LOOKING 因为 epoch 被设置成了最小值所以这个选票等同于形同虚设,可以被直接忽略,并且在三马那里会维护一个 Participant 的列表,如果他们收到了来自 Participant 以外的办事处的选票会直接选择忽略,所以可以说 Observer 的选票对选举结果是完全没有影响的。最终是等待 Participant 之间的选举结果通知,Observer 自身修改状态为 OBSERVING,开始和 Leader 进行同步数据,这点和 Follower 没区别,之后 Observer 和 Follower 会统称为 Learner 2.6 小结 竞选 Leader 看的是 epoch、写请求操作数、myid 三个字段,依次比较谁大谁就更有资格成为 Leader 获选超过半数以上的办事处正式成为 Leader,修改自己状态为 LEADING 其他 Participant 修改为 FOLLOWING,Observer 则修改为 OBSERVING 如果集群中已经存在一个 Leader,其他办事处如果中途加入的话,直接跟随该 Leader 即可 还得提一句,如果当前可提供服务的节点已经不足半数以上了,那么这个选举就永远无法选出结果,每个节点都会一直处在 LOOKING 状态,整个办事处集群也就无法对外提供服务了 三、猿话一下 扯蛋扯完了,现在用咱的行话对有一些概念再深入一下。 首先我必须要说的是,故事里的三马,为了一定的节目效果,我描述成了三个角色,但是实际中 ZK 服务端是不会做这样的区分的,都是相同的代码,根据不同的配置启动,才有了运行时期 Leader、Follower、Observer 的角色之分,所以更贴近于实际的应该类似于火影里的影分身或者龙珠里的残像拳之类的(好像混入了什么奇怪的东西)。 我画了下选举的简单流程图: 其他地方我基本上都讲过了,这里再讲下红色部分,因为可能一些网络因素,发出去的选票对方却没收到,这个发起重新广播投票就是为了能让对方再重新发一次刚刚的选票。 同监听客户端 2181 端口不同的是,服务端集群之间相互通信,直接使用的是原生的 Socket 并没有使用 NIO 或者是 Netty,因为服务端节点一共就这么几个而且针对每一个其他节点都会启动一个线程去监听,所以直接采用了这种比较原始的并且是阻塞的方式通信,更简单直接,而且假设对方服务不可用了的话, Socket 会直接报错退出。 收发选票也是采用了 ZK 中非常常见的生产者-消费者模式,分别维护了两个阻塞队列,一个对应发送出去的选票,一个对应收到的选票,各自使用一个子线程去轮询该阻塞队列。 之前的 ZK 是拥有 3 种选举策略的,虽然另外两种之前都是被废弃的状态,不建议使用,但是通过配置文件还是可以强行使用的。不过在最新的 3.6.2 中另两种策略直接从源码中删除了,现在只有一种选举的策略,源码中对应 FastLeaderElection,另外两个我也没研究过,就不展开了。 关于服务端之间的心跳检测: 服务端之间的心跳检测(PING)是由 Leader 发起的,发向所有集群中的其他节点 Follower 收到 PING 后会回一个PING 给 Leader 并带上自己这边的客户端会话数据 而 Leader 收到 Follower 的 PING 后,就会对这些客户端进行会话连接 关于会话相关的知识点留到之后再说~ 四、总结 今天我们介绍了选举的规则,以及举例了一些选举的场景并加以说明。为了介绍 Follower 或者 Observer 是如何在选举完成之后和 Leader 同步数据的,下一篇我们会先介绍 ZK 是如何进行持久化的,期待一下吧,ZKr~ 老规矩,如果你有任何对文章中的疑问也可以是建议或者是对 ZK 原理部分的疑问,欢迎来仓库中提问,或者阅读原文来语雀话题讨论。 地址:https://github.com/HelloGitHub-Team/HelloZooKeeper 👆「点击关注」更多惊喜等待你! 本文分享自微信公众号 - HelloGitHub(GitHub520)。如有侵权,请联系 support@oschina.cn 删除。本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

优秀的个人博客,低调大师

图解kubernetes容器状态同步机制核心实现

在K8s中将Pod调度到某一台Node节点之后,后续的状态维护信息则是由对应机器上的kubelet进行维护,如何实时反馈本地运行状态,并通知apiserver则是设计的难点, 本节主要是通过感知Pod状态变化和探测状态改变两个流程来实际分析其核心数据结构,来了解内部设计 1. 状态管理 1.1 静态Pod 静态Pod主要是指的那些不是通过感知apiserver创建的pod, 因为apiserver上并不包含,但是同时也需要维护和获取这类Pod的状态, k8s中就设计了一个镜像Pod的概念,其实就是为静态Pod镜像出来一个Pod该Pod的主要信息与静态Pod一致,并且在apiserver中进行创建,通过apiserver可以感知的这个镜像Pod来反映真实的静态Pod的状态, 1.2 状态数据源 statusManager是进行状态同步的关键组件其需要综合当前Pod运行中的数据和apiserver存储的数据,从而决定最终的状态转换, 这里先关注图上画出来的,更多的状态等后续会一一介绍 2. 版本一致性 type versionedPodStatus struct { status v1.PodStatus // 单调递增的版本号(每个pod) version uint64 // Pod name &amp; namespace, for sending updates to API server. podName string podNamespace string } 在Kubelet中为保证与apiserver端信息的同步,在本地保存了一个Pod状态版本信息,其里面除了保存当前Pod的状态数据还有一个版本版本号,通过单调递增的版本号的对比来确定是否进行状态的同步 3. 核心源码实现 statusManager的流程其实还是蛮复杂的,今天我们就只讲一个场景,即kubelet通过apiserver感知到一个Pod更新,然后顺着该功能的数据流来进行梳理statusMangaer里面的数据流转 3.1 核心数据结构 manager中的核心状态相关的数据结构可以主要分为两大类:映射数据维护(podManager、podStatuses、apiStatusVersions)数据通信管道(podStatusChannel), 剩余的则是对与apiserver通信的kublet和进行pod删除检查的podDeletionSafety type manager struct { kubeClient clientset.Interface // 管理缓存Pod,包含镜像pod和静态pod的映射 podManager kubepod.Manager // 从pod UID映射到相应pod的版本状态信息 。 podStatuses map[types.UID]versionedPodStatus podStatusesLock sync.RWMutex podStatusChannel chan podStatusSyncRequest // 存储镜像pod的版本 apiStatusVersions map[kubetypes.MirrorPodUID]uint64 podDeletionSafety PodDeletionSafetyProvider } 3.2 设置Pod状态 设置Pod状态主要是位于kubelet中的syncPod中,在接收到pod事件变更之后,会与apiserver进行 Pod最新数据的同步从而获取当前pod在apiserver端的最新状态 func (m *manager) SetPodStatus(pod *v1.Pod, status v1.PodStatus) { m.podStatusesLock.Lock() defer m.podStatusesLock.Unlock() for _, c := range pod.Status.Conditions { if !kubetypes.PodConditionByKubelet(c.Type) { klog.Errorf("Kubelet is trying to update pod condition %q for pod %q. "+ "But it is not owned by kubelet.", string(c.Type), format.Pod(pod)) } } // Make sure we're caching a deep copy. status = *status.DeepCopy() // 如果Pod被删除了则需要强制与apiserver进行信息的同步 m.updateStatusInternal(pod, status, pod.DeletionTimestamp != nil) } 3.3 更新内部缓存状态产生同步事件 3.3.1 获取缓存状态 var oldStatus v1.PodStatus // 检测之前的本地缓存数据 cachedStatus, isCached := m.podStatuses[pod.UID] if isCached { oldStatus = cachedStatus.status } else if mirrorPod, ok := m.podManager.GetMirrorPodByPod(pod); ok { oldStatus = mirrorPod.Status } else { oldStatus = pod.Status } 3.3.2 检测容器状态 检测容器状态主要是针对容器终止状态转发的合法性进行检测,其实就是根据设定的Pod的RestartPolicy来检测针对一个终止的容器是否可以进行重启 if err := checkContainerStateTransition(oldStatus.ContainerStatuses, status.ContainerStatuses, pod.Spec.RestartPolicy); err != nil { klog.Errorf("Status update on pod %v/%v aborted: %v", pod.Namespace, pod.Name, err) return false } if err := checkContainerStateTransition(oldStatus.InitContainerStatuses, status.InitContainerStatuses, pod.Spec.RestartPolicy); err != nil { klog.Errorf("Status update on pod %v/%v aborted: %v", pod.Namespace, pod.Name, err) return false } 3.3.3 更新PodCondition最后转换时间 通过最新的status里面的condition设定对应PodCondition的LastTransitionTime更新时间未当前时间 // Set ContainersReadyCondition.LastTransitionTime. updateLastTransitionTime(&amp;status, &amp;oldStatus, v1.ContainersReady) // Set ReadyCondition.LastTransitionTime. updateLastTransitionTime(&amp;status, &amp;oldStatus, v1.PodReady) // Set InitializedCondition.LastTransitionTime. updateLastTransitionTime(&amp;status, &amp;oldStatus, v1.PodInitialized) // Set PodScheduledCondition.LastTransitionTime. updateLastTransitionTime(&amp;status, &amp;oldStatus, v1.PodScheduled) 3.3.4 校对时间截断过长信息 首先会根据当前容器的个数,从而决定每个容器最大的字节数大小,然后对容器里面的终止状态里面的Message信息,进行截断,同时进行时间的校对 normalizeStatus(pod, &amp;status) 3.3.5 状态更新条件检测 如果之前已经缓存了对应的数据,并且缓存的数据与当前的状态未发生改变,也不需要强制更新,就直接返回 if isCached &amp;&amp; isPodStatusByKubeletEqual(&amp;cachedStatus.status, &amp;status) &amp;&amp; !forceUpdate { // 如果不强制更新 ,默认是true此处不会成立 klog.V(3).Infof("Ignoring same status for pod %q, status: %+v", format.Pod(pod), status) return false // No new status. } 3.3.6 生成同步事件更新缓存 生成最新的状态缓存数据,并且递增本地的版本信息 // 构建新的状态 newStatus := versionedPodStatus{ status: status, version: cachedStatus.version + 1, // 更新器缓存 podName: pod.Name, podNamespace: pod.Namespace, } // 更新新的缓存状态 m.podStatuses[pod.UID] = newStatus select { case m.podStatusChannel &lt;- podStatusSyncRequest{pod.UID, newStatus}: // 构建一个新的同步请求 klog.V(5).Infof("Status Manager: adding pod: %q, with status: (%d, %v) to podStatusChannel", pod.UID, newStatus.version, newStatus.status) return true default: // Let the periodic syncBatch handle the update if the channel is full. // We can't block, since we hold the mutex lock. klog.V(4).Infof("Skipping the status update for pod %q for now because the channel is full; status: %+v", format.Pod(pod), status) return false } 3.4 探测状态更新 探测状态其实就是Pod内容器的运行状态,比如如果设置了Readiness探测,当某个容器探测失败的时候,就会通知对应的service从后端的enpoint中移除该Pod, 让我们一起看看Kubelet是如何将运行状态通知到apiserver端的 3.4.1 获取当前状态 func (m *manager) SetContainerReadiness(podUID types.UID, containerID kubecontainer.ContainerID, ready bool) { m.podStatusesLock.Lock() defer m.podStatusesLock.Unlock() // 获取本地的容器 pod, ok := m.podManager.GetPodByUID(podUID) if !ok { klog.V(4).Infof("Pod %q has been deleted, no need to update readiness", string(podUID)) return } // 获取当前的状态 oldStatus, found := m.podStatuses[pod.UID] if !found { klog.Warningf("Container readiness changed before pod has synced: %q - %q", format.Pod(pod), containerID.String()) return } // 获取当前的容器状态 containerStatus, _, ok := findContainerStatus(&amp;oldStatus.status, containerID.String()) if !ok { klog.Warningf("Container readiness changed for unknown container: %q - %q", format.Pod(pod), containerID.String()) return } 3.4.2 检测状态是否发生改变 // 检测前后的就绪状态是否发生改变 if containerStatus.Ready == ready { klog.V(4).Infof("Container readiness unchanged (%v): %q - %q", ready, format.Pod(pod), containerID.String()) return } 3.4.3 修改容器的就绪状态 获取容器的状态,修改就绪为当前的状态 status := *oldStatus.status.DeepCopy() containerStatus, _, _ = findContainerStatus(&amp;status, containerID.String()) containerStatus.Ready = ready 3.4.4 根据最新的容器状态修改 会根据当前运行时的容器探测的状态,来修改对应PodCondition里面的状态,最后调用内部的更新逻辑 updateConditionFunc := func(conditionType v1.PodConditionType, condition v1.PodCondition) { conditionIndex := -1 // 获取Pod对应的PodCondition状态 for i, condition := range status.Conditions { if condition.Type == conditionType { conditionIndex = i break } } // 修改或追加Pod对应的PodCondition状态 if conditionIndex != -1 { status.Conditions[conditionIndex] = condition } else { klog.Warningf("PodStatus missing %s type condition: %+v", conditionType, status) status.Conditions = append(status.Conditions, condition) } } // 计算Ready状态 updateConditionFunc(v1.PodReady, GeneratePodReadyCondition(&amp;pod.Spec, status.Conditions, status.ContainerStatuses, status.Phase)) // 计算容器Ready状态 updateConditionFunc(v1.ContainersReady, GenerateContainersReadyCondition(&amp;pod.Spec, status.ContainerStatuses, status.Phase)) m.updateStatusInternal(pod, status, false) 3.5 启动后台同步任务 statusManager会启动一个后台的线程来进行更新管道里面同步请求的消费 func (m *manager) Start() { // 省略非核心代码 go wait.Forever(func() { select { case syncRequest := &lt;-m.podStatusChannel: // 获取最新的状态信息,更新apiserver klog.V(5).Infof("Status Manager: syncing pod: %q, with status: (%d, %v) from podStatusChannel", syncRequest.podUID, syncRequest.status.version, syncRequest.status.status) m.syncPod(syncRequest.podUID, syncRequest.status) case &lt;-syncTicker: m.syncBatch() } }, 0) } 3.6 同步Pod状态 3.6.1 同步条件检测 同步条件检测主要是检测镜像Pod的版本是否发送变化、Pod当前是否被删除,如果pod没有被删除则返回false,即对一个没有删除的Pod我们还是需要继续更新其状态的 if !m.needsUpdate(uid, status) { klog.V(1).Infof("Status for pod %q is up-to-date; skipping", uid) return } 3.6.2 通过apiserver获取最新Pod数据 如果没有获取到Pod信息,则直接进行退出即可 pod, err := m.kubeClient.CoreV1().Pods(status.podNamespace).Get(status.podName, metav1.GetOptions{}) if errors.IsNotFound(err) { klog.V(3).Infof("Pod %q does not exist on the server", format.PodDesc(status.podName, status.podNamespace, uid)) // 如果Pod已经被删除了,就直接退出就行 return } if err != nil { klog.Warningf("Failed to get status for pod %q: %v", format.PodDesc(status.podName, status.podNamespace, uid), err) return } 3.6.3 调用Patch接口进行更新 这里面会通过将最小的状态与之前的状态来进行merge合并,然后调用kubeClient进行apiserver端状态的修改 oldStatus := pod.Status.DeepCopy() // 更新服务端的状态 newPod, patchBytes, err := statusutil.PatchPodStatus(m.kubeClient, pod.Namespace, pod.Name, pod.UID, *oldStatus, mergePodStatus(*oldStatus, status.status)) klog.V(3).Infof("Patch status for pod %q with %q", format.Pod(pod), patchBytes) if err != nil { klog.Warningf("Failed to update status for pod %q: %v", format.Pod(pod), err) return } 3.6.4 更新本地的Apiserver端的版本信息 // 当前是最新的状态 pod = newPod klog.V(3).Infof("Status for pod %q updated successfully: (%d, %+v)", format.Pod(pod), status.version, status.status) m.apiStatusVersions[kubetypes.MirrorPodUID(pod.UID)] = status.version 3.6.5 检测删除Pod 这里主要是最后阶段,即Pod对应的资源都已经释放了,则才最终删除apiserver端的Pod // 如果pod的DeletionTimestamp被设置,则对应的Pod需要被删除 if m.canBeDeleted(pod, status.status) { deleteOptions := metav1.NewDeleteOptions(0) deleteOptions.Preconditions = metav1.NewUIDPreconditions(string(pod.UID)) // 调用apiserver对Pod进行删除 err = m.kubeClient.CoreV1().Pods(pod.Namespace).Delete(pod.Name, deleteOptions) if err != nil { klog.Warningf("Failed to delete status for pod %q: %v", format.Pod(pod), err) return } klog.V(3).Infof("Pod %q fully terminated and removed from etcd", format.Pod(pod)) m.deletePodStatus(uid) } 探活整体的设计大概就是这样,希望大佬们多多关注,一起交流。 k8s源码阅读电子书地址: https://www.yuque.com/baxiaoshi/tyado3 > 微信号:baxiaoshi2020 > 关注公告号阅读更多源码分析文章 > 更多文章关注 www.sreguide.com > 本文由博客一文多发平台 OpenWrite 发布

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

用户登录
用户注册