乐趣区

关于腾讯云:K8s-Scheduler-在调度-pod-过程中遗漏部分节点的问题排查

问题景象

在 TKE 管制台上新建版本为 v1.18.4(具体版本号 < v1.18.4-tke.5)的独立集群,其中,集群的节点信息如下:

有 3 个 master node 和 1 个 worker node,并且 worker 和 master 在不同的可用区。

node 角色 label 信息
ss-stg-ma-01 master label[failure-domain.beta.kubernetes.io/region=sh,failure-domain.beta.kubernetes.io/zone=200002]
ss-stg-ma-02 master label[failure-domain.beta.kubernetes.io/region=sh,failure-domain.beta.kubernetes.io/zone=200002]
ss-stg-ma-03 master label[failure-domain.beta.kubernetes.io/region=sh,failure-domain.beta.kubernetes.io/zone=200002]
ss-stg-test-01 worker label[failure-domain.beta.kubernetes.io/region=sh,failure-domain.beta.kubernetes.io/zone=200004]

待集群创立好之后,再创立出一个 daemonset 对象,会呈现 daemonset 的某个 pod 始终卡住 pending 状态的景象。
景象如下:

$ kubectl  get  pod  -o  wide
NAME        READY STATUS  RESTARTS AGE NODE 
debug-4m8lc 1/1   Running 1        89m  ss-stg-ma-01
debug-dn47c 0/1   Pending 0        89m  <none>
debug-lkmfs 1/1   Running 1        89m   ss-stg-ma-02
debug-qwdbc 1/1   Running 1        89m  ss-stg-test-01

(补充:TKE 以后反对的最新版本号为 v1.18.4-tke.8,新建集群默认应用最新版本

问题论断

k8s 的调度器在调度某个 pod 时,会从调度器的外部 cache 中同步一份快照(snapshot),其中保留了 pod 能够调度的 node 信息。
下面问题(daemonset 的某个 pod 实例卡在 pending 状态)的起因就是同步的过程产生了局部 node 信息失落,导致了 daemonset 的局部 pod 实例无奈调度到指定的节点上,卡在了 pending 状态。

接下来是具体的排查过程。

日志排查

截图中呈现的节点信息(来自客户线上集群):
k8s master 节点:ss-stg-ma-01、ss-stg-ma-02、ss-stg-ma-03
k8s worker 节点:ss-stg-test-01

1、获取调度器的日志
这里首先是通过动静调大调度器的日志级别,比方,间接调大到 V(10),尝试获取一些相干日志。
当日志级别调大之后,有抓取到一些要害信息,信息如下:

解释一下,当调度某个 pod 时,有可能会进入到调度器的抢占 preempt 环节,而下面的日志就是出自于抢占环节。
集群中有 4 个节点(3 个 master node 和 1 个 worker node),然而日志中只显示了 3 个节点,短少了一个 master 节点。
所以,这里临时狐疑下是调度器外部缓存 cache 中少了node info

2、获取调度器外部 cache 信息
k8s v1.18 曾经反对打印调度器外部的缓存 cache 信息。打印进去的调度器外部缓存 cache 信息如下:

能够看出,调度器的外部缓存 cache 中的 node info 是残缺的(3 个 master node 和 1 个 worker node)。
通过剖析日志,能够失去一个初步论断:调度器外部缓存 cache 中的 node info 是残缺的,然而当调度 pod 时,缓存 cache 中又会短少局部 node 信息。

问题根因

在进一步剖析之前,咱们先一起再相熟下调度器调度 pod 的流程(局部展现)和 nodeTree 数据结构。

pod 调度流程(局部展现)

联合上图,一次 pod 的调度过程就是 一次 Scheduler Cycle。在这个Cycle 开始时,第一步就是 update snapshot。snapshot 咱们能够了解为 cycle 内的 cache,其中保留了 pod 调度时所需的node info,而update snapshot,就是一次 nodeTree(调度器外部 cache 中保留的 node 信息)到snapshot 的同步过程。
而同步过程次要是通过 nodeTree.next() 函数来实现,函数逻辑如下:

// next returns the name of the next node. NodeTree iterates over zones and in each zone iterates
// over nodes in a round robin fashion.
func (nt *nodeTree) next() string {if len(nt.zones) == 0 {return ""}
    numExhaustedZones := 0
    for {if nt.zoneIndex >= len(nt.zones) {nt.zoneIndex = 0}
        zone := nt.zones[nt.zoneIndex]
        nt.zoneIndex++
        // We do not check the exhausted zones before calling next() on the zone. This ensures
        // that if more nodes are added to a zone after it is exhausted, we iterate over the new nodes.
        nodeName, exhausted := nt.tree[zone].next()
        if exhausted {
            numExhaustedZones++
            if numExhaustedZones >= len(nt.zones) { // all zones are exhausted. we should reset.
                nt.resetExhausted()}
        } else {return nodeName}
    }
}

再联合下面排查过程得出的论断,咱们能够再进一步放大问题范畴:nodeTree(调度器外部 cache)到的同步过程失落了某个节点信息。

\### nodeTree 数据结构
(不便了解,本文应用了链表来展现)

在 nodeTree 数据结构中,有两个游标 zoneIndex 和 lastIndex(zone 级别),用来管制 nodeTree(调度器外部 cache)到 snapshot.nodeInfoList 的同步过程。并且,重要的一点是:上次同步后的游标值会被记录下来,用于下次同步过程的初始值。

\### 重现问题,定位根因

创立 k8s 集群时,会先退出 master node,而后再退出 worker node(意思是 worker node 工夫上会晚于 master node 退出集群的工夫)。

第一轮同步:3 台 master node 创立好,而后产生 pod 调度(比方,cni 插件,以 daemonset 的形式部署在集群中),会触发一次 nodeTree(调度器外部 cache)到的同步。同步之后,nodeTree 的两个游标就变成了如下后果:

nodeTree.zoneIndex = 1,
nodeTree.nodeArray[sh:200002].lastIndex = 3,

第二轮同步:当 worker node 退出集群中后,而后新建一个 daemonset,就会触发第二轮的同步(nodeTree(调度器外部 cache)到的同步)。同步过程如下:

1、zoneIndex=1, nodeArray[sh:200004].lastIndex=0, we get ss-stg-test-01.

2、zoneIndex=2 >= len(zones); zoneIndex=0, nodeArray[sh:200002].lastIndex=3, return.

3、zoneIndex=1, nodeArray[sh:200004].lastIndex=1, return.

4、zoneIndex=0, nodeArray[sh:200002].lastIndex=0, we get ss-stg-ma-01.

5、zoneIndex=1, nodeArray[sh:200004].lastIndex=0, we get ss-stg-test-01.

6、zoneIndex=2 >= len(zones); zoneIndex=0, nodeArray[sh:200002].lastIndex=1, we get ss-stg-ma-02.

同步实现之后,调度器的 snapshot.nodeInfoList 失去如下的后果:

[
    ss-stg-test-01,
    ss-stg-ma-01,
    ss-stg-test-01,
    ss-stg-ma-02,
]

ss-stg-ma-03 去哪了?在第二轮同步的过程中丢了。

解决方案

问题根因 的剖析中,能够看出,导致问题产生的起因,在于 nodeTree 数据结构中的游标 zoneIndex 和 lastIndex(zone 级别)值被保留了,所以,解决的计划就是在每次同步 SYNC 时,强制重置游标(归 0)。
相干 issue:https://github.com/kubernetes…
相干 pr(k8s v1.18):https://github.com/kubernetes…
TKE 修复版本:v1.18.4-tke.5

【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!

退出移动版