最近在查看一个kubernetes集群中node not ready的奇怪现象,顺便阅读了一下kubernetes kube-controller-manager中管理node健康状态的组件node lifecycle controller。我们知道kubernetes是典型的master-slave架构,master node负责整个集群元数据的管理,然后将具体的启动执行pod的任务分发给各个salve node执行,各个salve node会定期与master通过心跳信息来告知自己的存活状态。其中slave node上负责心跳的是kubelet程序, 他会定期更新apiserver中node lease或者node status数据,然后kube-controller-manager会监听这些信息变化,如果一个node很长时间都没有进行状态更新,那么我们就可以认为该node发生了异常,需要进行一些容错处理,将该node上面的pod进行安全的驱逐,使这些pod到其他node上面进行重建。这部分工作是由node lifecycel controller模块负责。

在目前的版本(v1.16)中,默认开启了TaintBasedEvictions, TaintNodesByCondition这两个feature gate,则所有node生命周期管理都是通过condition + taint的方式进行管理。其主要逻辑由三部分组成:

  1. 不断地检查所有node状态,设置对应的condition
  2. 不断地根据node condition 设置对应的taint
  3. 不断地根据taint驱逐node上面的pod

一. 检查node状态

检查node状态其实就是循环调用monitorNodeHealth函数,该函数首先调用tryUpdateNodeHealth检查每个node是否还有心跳,然后判断如果没有心跳则设置对应的condtion。

node lifecycle controller内部会维护一个nodeHealthMap 数据结构来保存所有node的心跳信息,每次心跳之后都会更新这个结构体,其中最重要的信息就是每个node上次心跳时间probeTimestamp, 如果该timestamp很长时间都没有更新(超过--node-monitor-grace-period参数指定的值),则认为该node可能已经挂了,设置node的所有condition为unknown状态。

    gracePeriod, observedReadyCondition, currentReadyCondition, err = nc.tryUpdateNodeHealth(node)

tryUpdateNodeHealth传入的参数为每个要检查的node, 返回值中observedReadyCondition为当前从apiserver中获取到的数据,也就是kubelet上报上来的最新的node信息, currentReadyCondition为修正过的数据。举个例子,如果node很长时间没有心跳的话,observedReadyCondition中nodeReadyCondion为true, 但是currentReadyCondion中所有的conditon已经被修正的实际状态unknown了。

如果observedReadyCondition 状态为true, 而currentReadyCondition状态不为true, 则说明node状态状态发生变化,由ready变为not-ready。此时不光会更新node condition,还会将该node上所有的pod状态设置为not ready,这样的话,如果有对应的service资源选中该pod, 流量就可以从service上摘除了,但是此时并不会直接删除pod。

node lifecycle controller会根据currentReadyCondition的状态将该node加入到zoneNoExecuteTainter的队列中,等待后面设置taint。如果此时已经有了taint的话则会直接更新。zoneNoExecuteTainter队列的出队速度是根据node所处zone状态决定的,主要是为了防止出现集群级别的故障时,node lifecycle controller进行误判,例如交换机,loadbalancer等故障时,防止node lifecycle controller错误地认为所有node都不健康而大规模的设置taint进而导致错误地驱逐很多pod,造成更大的故障。

设置出队速率由handleDisruption函数中来处理,首先会选择出来各个zone中不健康的node, 并确定当前zone所处的状态。分为以下几种情况:

  • Initial: zone刚加入到集群中,初始化完成。
  • Normal: zone处于正常状态
  • FullDisruption: 该zone中所有的node都notReady了
  • PartialDisruption: 该zone中部分node notReady,此时已经超过了unhealthyZoneThreshold设置的阈值

对于上述不同状态所设置不同的rate limiter, 从而决定出队速度。该速率由函数setLimiterInZone决定具体数值, 具体规则是:

  1. 当所有zone都处于FullDisruption时,此时limiter为0
  2. 当只有部分zone处于FullDisruption时,此时limiter为正常速率: --node-eviction-rate
  3. 如果某个zone处于PartialDisruption时,则此时limiter为二级速率:--secondary-node-eviction-rate

二. 设置node taint

根据node condition设置taint主要由两个循环来负责, 这两个循环在程序启动后会不断执行:

  1. doNodeProcessingPassWorker中主要的逻辑就是: doNoScheduleTaintingPass, 该函数会根据node当前的condition设置unschedulable的taint,便于调度器根据该值进行调度决策,不再调度新pod至该node。
  2. doNoExecuteTaintingPass 会不断地从上面提到的zoneNoExecuteTainter队列中获取元素进行处理,根据node condition设置对应的NotReadyUnreachable的taint, 如果NodeReadycondition为false则taint为NotReady, 如果为unknown,则taint为Unreachable, 这两种状态只能同时存在一种!

上面提到从zoneNoExecuteTainter队列中出队时是有一定的速率限制,防止大规模快速驱逐pod。该元素是由RateLimitedTimedQueue数据结构来实现:

// RateLimitedTimedQueue is a unique item priority queue ordered by
// the expected next time of execution. It is also rate limited.
type RateLimitedTimedQueue struct {
queue UniqueQueue
limiterLock sync.Mutex
limiter flowcontrol.RateLimiter
}

从其定义就可以说明了这是一个 去重的优先级队列, 对于每个加入到其中的node根据执行时间(此处即为加入时间)进行排序,优先级队列肯定是通过heap数据结构来实现,而去重则通过set数据结构来实现。在每次doNoExecuteTaintingPass执行的时候,首先尽力从TokenBucketRateLimiter中获取token,然后从队头获取元素进行处理,这样就能控制速度地依次处理最先加入的node了。

三. 驱逐pod

在node lifecycle controller启动的时候,会启动一个NoExecuteTaintManager。 该模块负责不断获取node taint信息,然后删除其上的pod。

首先会利用informer会监听pod和node的各种事件,每个变化都会出发对应的update事件。分为两类: 1.优先处理nodeUpdate事件; 2.然后是podUpdate事件

  • 对于nodeUpdate事件,会首先获取该node的taint,然后获取该node上面所有的pod,依次对每个pod调用processPodOnNode: 判断是否有对应的toleration,如果没有则将其加入到对应的taintEvictionQueue中,该queue是个定时器队列,对于队列中的每个元素会有一个定时器来来执行,该定时器执行时间由toleration中的tolerationSecond进行设置。对于一些在退出时需要进行清理的程序,toleration必不可少,可以保证给容器退出时留下足够的时间进行清理或者恢复。 出队时调用的是回调函数deletePodHandler来删除pod。
  • 对于podUpdate事件则相对简单,首先获取所在的node,然后从taintNode map中获取该node的taint, 最后调用processPodOnNode,后面的处理逻辑就同nodeUpdate事件一样了。

为了加快处理速度,提高性能,上述处理会根据nodename hash之后交给多个worker进行处理。

上述就是controller-manager中心跳处理逻辑,三个模块层层递进,依次处理,最后将一个异常node上的pod安全地迁移。

kubernetes中node心跳处理逻辑分析的更多相关文章

  1. 【转】干货,Kubernetes中的Source Ip机制。

    准备工作 你必须拥有一个正常工作的 Kubernetes 1.5 集群,用来运行本文中的示例.该示例使用一个简单的 nginx webserver 回送它接收到的请求的 HTTP 头中的源 IP 地址 ...

  2. kubernetes中的Pause容器如何理解?

    前几篇文章都是讲的Kubernetes集群和相关组件的部署,但是部署只是入门的第一步,得理解其中的一些知识才行.今天给大家分享下Kubernets的pause容器的作用. Pause容器 全称infr ...

  3. Kubernetes中的RBAC

    Kubernetes中,授权有ABAC(基于属性的访问控制).RBAC(基于角色的访问控制).Webhook.Node.AlwaysDeny(一直拒绝)和AlwaysAllow(一直允许)这6种模式. ...

  4. 关于 Kubernetes 中的 Volume 与 GlusterFS 分布式存储

    容器中持久化的文件生命周期是短暂的,如果容器中程序崩溃宕机,kubelet 就会重新启动,容器中的文件将会丢失,所以对于有状态的应用容器中持久化存储是至关重要的一个环节:另外很多时候一个 Pod 中可 ...

  5. Kubernetes 中的核心组件与基本对象概述

    Kubernetes 是 Google 基于 Borg 开源的容器编排调度,用于管理容器集群自动化部署.扩容以及运维的开源平台.作为云原生计算基金会 CNCF(Cloud Native Computi ...

  6. 在Kubernetes中部署GlusterFS+Heketi

    目录 简介 Gluster-Kubernetes 部署 环境准备 下载相关文件 部署glusterfs 部署heketi server端 配置heketi client 简介 在上一篇<独立部署 ...

  7. Kubernetes中的亲和性与反亲和性

    通常情况下,Pod分配到哪些Node是不需要管理员操心的,这个过程会由scheduler自动实现.但有时,我们需要指定一些调度的限制,例如某些应用应该跑在具有SSD存储的节点上,有些应用应该跑在同一个 ...

  8. Kubernetes中的nodePort,targetPort,port的区别和意义(转)

    原文https://blog.csdn.net/u013760355/article/details/70162242 https://blog.csdn.net/xinghun_4/article/ ...

  9. Kubernetes中pod创建流程

    转自:https://blog.csdn.net/yan234280533/article/details/72567261 Pod是Kubernetes中最基本的部署调度单元,可以包含contain ...

随机推荐

  1. 小小知识点(十五)——origin pro 2018 安装和消除demo字样

    安装 1.安装过成中选择语言为中文或者英文,安装完成后可在注册表中切换语言. 2.安装过程中使用序列号 中文版:DF2W8-9089-7991320英文版:GF3S4-9089-7991320 3.安 ...

  2. Sorted 内置函数的排序使用

    Sorted 内置函数的排序使用 Sorted 排序列表 1.倒序对列表进行排序 # 对列表进行降序序排序 list = [1,3,4,23,6,7] list = sorted(list,rever ...

  3. 简单快速破解IDEA

    ====================================2019.09.16更新==================================== 可以直接去掉第三步,直接在激活 ...

  4. swiper如何禁止左右箭头切换

    swiper做项目时,需求 带着左右两边的箭头, 场景1:swiper自动切换,此时左右箭头点击时不能切换   场景2:swiper手动切换,左右箭头可以实现切换,通过翻阅api 终于找到 <s ...

  5. Python中函数参数 *args 和 **kwargs

    普通参数,即在调用函数时必须按照准确的顺序来进行参数传递. 默认参数,即参数含有默认值,在调用函数时可以进行参数传递,若没有进行参数传递则使用默认值,要注意,默认参数必须在普通参数的右侧(否则解释器无 ...

  6. 枚举 + exgcd

    题意:已知xi=(a*xi-1+b) mod 10001,且告诉你x1,x3.........x2*t-1,让你求出其偶数列 思路分析 : 题目所要求的的是对 10001 取余,由模运算的性质可知,a ...

  7. 《C# 爬虫 破境之道》:第一境 爬虫原理 — 第六节:第一境尾声

    在第一境中,我们主要了解了爬虫的一些基本原理,说原理也行,说基础知识也罢,结果就是已经知道一个小爬虫是如何诞生的了~那么现在,请默默回想一下,在第一境中,您都掌握了哪些内容?哪些还比较模糊?如果还有什 ...

  8. 【转】oracle条件子句执行顺序

    Oracle WHERE条件执行顺序:ORACLE采用自下而上的顺序解析WHERE子句 1.据此那些可以过滤掉最大数量记录的条件必须写在WHERE子句的末尾例如:SELECT … FROM EMP E ...

  9. Vue中的nextTick()浅析

    引言 在开发过程中,我们经常遇到这样的问题:我明明已经更新了数据,为什么当我获取某个节点的数据时,却还是更新前的数据? 一,浅析 为什么会这样呢?带着这个疑问先往下看. 先看一个小的例子: <d ...

  10. 如何通过Java8的方式去统计程序执行时间?

    代码如下所示 import java.time.Duration; import java.time.Instant; import java.util.concurrent.TimeUnit; pu ...