阅读 147

Kubernetes控制器Node生命周期管理的实现

Node其实就对应着kubernetes中的工作组件,今天我们来看下kubernetes中针对Node的生命周期的管理包括心跳检测/污点/容忍/中断等机制的实现。



基础概念


1.  心跳机制

图片心跳机制是分布式调度系统中常见的设计, kubernetes中通过Lease这种资源来进行实现心跳信息的传递,其在kubelet上运行nodelease的线程定时向apiserver更新自己的心跳信息, 同时controller控制器中的nodelifecycle会通过informer来监听集群中的lease信息。

2.  node状态

图片Node的状态除了相关资源压力、网络不可达之外,其实只有一个Ready状态, 对应的Status则同其他资源一样三种:ConditionTrue、conditionFalse、conditionUnknown,那什么时候会是一个ready状态呢, 其实在kubernetes中我们有三大列资源: CRI、CNI、CSI,如果任一一个运行时出现错误,则当前节点就不是Ready状态。

3. Taint和Toleration

图片Taint(污点)和Toleration(容忍)是kubernetes中的调度约束的一种实现,其通过一种打标签的方式,来进行调度约束,当一个Node节点被打上了一个Taint则调度器在进行调度的时候,如果发现当前调度的pod没有容忍这种污点,则当前节点就不能调度过去, 其次如果发现对应节点上已经运行的pod不能容忍node新标记的Taint, 则可能会被从当前节点中驱逐。

4. 基于心跳/Taint/Toleration的故障转移

图片kubernetes中针对Node节点down机的故障转移的实现,其实就是记住这几个机制来进行的,在发现无法接收到对应节点的心跳之后,kubernetes就会给对应节点打上一些异常的Taint, 并且根据之前缓存的节点上的Pod来进行检查,如果发现没有对应的Toleration,则就会尝试驱逐这些Pod, 然后由对应的控制器发现Pod的删除, 就开始重新进行补偿, 然后经过调度器选择新的节点运行。

容忍与驱逐设计

前面我们介绍了基础的一些概念, 接下来我们一起去探索其内部的具体设计与实现。

1. Node的Taints由来

图片在node生命周期控制器中,Node为了实现驱逐节点的目标,主要是通过为Node生成对应的taings来进行,那Node中的taints除了运维为指定节点赋予的之外,其余的则是通过Status里面的Conditions来进行计算而来。

    nodeConditionToTaintKeyStatusMap = [v1.NodeConditionType][v1.ConditionStatus]{
        v1.NodeReady: {
            v1.ConditionFalse:   v1.TaintNodeNotReady,
            v1.ConditionUnknown: v1.TaintNodeUnreachable,
        },
        v1.NodeMemoryPressure: {
            v1.ConditionTrue: v1.TaintNodeMemoryPressure,
        },
        v1.NodeDiskPressure: {
            v1.ConditionTrue: v1.TaintNodeDiskPressure,
        },
        v1.NodeNetworkUnavailable: {
            v1.ConditionTrue: v1.TaintNodeNetworkUnavailable,
        },
        v1.NodePIDPressure: {
            v1.ConditionTrue: v1.TaintNodePIDPressure,
        },
    }

这里是一个对应表,当节点的Status里面的状态还有COnditionType的值来决定为其添加哪个对应的Taint。

2. Node Taints增量更新

图片上面通过Condition状态计算出节点需要增加或者删除的Taint之后,其实就会调用对应的apiserver当前node的最新的Taints, 至此node controller完成了第一阶段的工作, 即根据当前的状态来进行更新对应的Taint, 有了这些Taint别的组件就可以进行Pod的驱逐了。

3. Pod状态更新

那如果Pod的状态更新,我也需要关注吗或者说node controller关注了Pod主要会做什么呢?其实这是一个联动操作,如果发现一个pod当前是Ready状态,但是node已经不是Ready状态了,则此时就要立马更新pod的Status,其实就是为了通知对应的endpoints摘除对应的pod因为他所在的node已经出现问题了。

4. Taint管理器

图片前面我们提到k8s中会根据conditionType计算出要为node添加的Taints, 然后就直接更新apiserver了,其实剩下的工作都是交给NoExecuteTaintManager这个组件来完成的,这个组件是干什么的呢,从名字就可以看出NoExecute就跟我们自己给Node添加 Taint一样,立即驱逐不满足的Pod

5. 容忍检测与驱逐

图片当一个Node更新的时候,首先会获取当前Node上面所有Taint的Effect为NoExecute的Taints,然后会去获取当node上面的所有Pod然后逐个检测Pod是否有对应的Tolerations, 然后没有就通过定时器创建一个驱逐的任务延迟执行。如果一个节点可以正常反馈状态,通过如上步骤其实就可以不断的进行对比那些不能容忍节点Taint的Pod从而达到驱逐的目的,但是如果无法反馈信息呢,则就需要靠心跳检测模块来进行。

心跳检测

心跳检测主要是根据当前缓存的node信息来确定在过去一段时间内, node是否正常发送信息, 如果没有发生,则对应的node可能就已经挂了,针对这部分node我们就要进行处理。


1. 自我保护

图片自我保护是分布式系统里面比较常见的一种机制,其核心是为了防止当发生网络分区的时候,因为主节点被隔离而导致正常节点的心跳全部失效,从而触发异常处理流程,自我保护就是如果发生大面积心跳中断的情况下,集群的一种保护机制。

2. zone

图片上面提到的自我保护那么如何判断是否达到自我保护的条件呢, 在k8s中通过zone来将node分区,中断则是判断如果发现当前zone里面一个ready的节点都没有,但是却发现有没有ready节点, 这样就可以知道一个的zone对应的状态,如果遍历当前的所有zone发现全都这样,则就会开启保护关闭中断。

3. 限速队列

图片清除Node上面Pod的这个操作可不算小, 在kubernetes中以一种比较保守的数值在做这件事情, 官方文档上也有说明, 限速主要是为了保证系统的稳定性,因为其实清除任务是可以被取消的,比如说节点升级的时候,可能几十秒就起来了,那其上的pod还可以依旧运行,没有必要全部转移,通过限速队列可以让删除节点的操作慢下来,从而容忍更多的超时时间,注意这里的限速队列是每个zone都会有一个。

4. 中断标签

node.kubernetes.io/exclude-disruption

在k8s中的node可以打上一个特殊的label标签如上,当发现该标签的时候,node controller就不会对对应的node进行检测,这种情况下,除非对应的Pod/Node重新发送事件来驱动整个系统,否则就好比一座孤岛。

5. 中断控制

图片在真正健康检查之前,node controller首先会过滤掉前面提到的中断标签,然后对剩余的node这里我们称为当前的zone的node集合,然后对当前的zone下面的状态进行检测,检测上面提到的zone的自我保护,即是当前的中断状态。

     {
     readyNodes ==  && notReadyNodes > :
        
         notReadyNodes, stateFullDisruption
     notReadyNodes >  && (notReadyNodes)/(notReadyNodes+readyNodes) >= nc.unhealthyZoneThreshold: 
        
         notReadyNodes, statePartialDisruption
    :
         notReadyNodes, stateNormal
    }

其次还会对之前的状态做一个检测(每次计算完都会讲之前的zone的结果进行保存),在这两次检测中会去检测一个关键的状态即stateFullDisruption,即是是否发生全部中断,这里为什么要进行前后状态的检测呢,其实是为了状态的转移,即进入和退出FullDIsruption。

6. 完全中断的幕后任务

图片因为是完全中断所以当前节点的之前所有的驱逐任务都要被取消,其次还要关闭后续的健康检查,怎么关闭呢?答案其实就是用到之前的限速队列,让其值为0,这样就不会继续进行健康检查了。

7. Node健康检查之检查

图片前面主要了解了Node健康检查的整体的设计,那究竟是怎么确定一个Node的状态呢,答案其实就是Lease和probeTimestamp, 在每次获取到一个新的Lease的时候,都会更新probeTimestamp即探测时间为当前时间,检测的时候如果发现probeTimestamp加上延迟时间小于当前时间,则就会为其更新对应的Condition的Type和status字段, 并且会缓存当前的数据, 同时如果发现节点前后的ready状态发生改变还会更新apiserver。

8. 根据观测状态进行状态转移

图片状态转移主要是指的根据观察到的ReadCondition的状态来进行转移,如果发现对应的Ready状态为ConditionFalse和ConditionUnknown则就会将其加入到对应zone的map中的等待后续处理,如果Ready为COnditionTrue则会移除对应的taint从而正常调度pod。

设计总结

图片看完了全部的设计,那如果我们要做一个Node升级的管理器该怎么做呢?我们需要从如下几个点去考虑1.从上面的设计里面我们可以看出如果Node的状态为Ready则表明至少我们的CRI/CNI/CSI组件没有问题,从这个角度至少证明这个Node已经基本正常2.一个Node要被使用则至少需要被apiserver/scheduler/controller所感知,我们可以结合Node的驱逐机制来去通过一个特殊的Taint和一个特殊的Pod来进行调度,这样就可以证明这个Node在集群中是一个可用的状态3.通过上面两种办法我们可以基本证明单台机器基本可用, 则剩下的其实就是怎么加快这个流程,这个其实可以通过设定当前集群的低水位来实现,从而决定当前集群最多可以允许同时灰度多少Node。



文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐