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/kubernetes/issues/97120
相关pr(k8s v1.18): https://github.com/kubernetes/kubernetes/pull/93387
TKE修复版本:v1.18.4-tke.5
K8s Scheduler 在调度 pod 过程中遗漏部分节点的问题排查的更多相关文章
- ambari过程中要求各个节点时间同步
设置时间同步 控制节点机器 cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime #设置时区为北京时间,这里为上海,因为centos里面只有上海... ...
- Kubernetes集群搭建过程中遇到的问题
1. 创建Nginx Pod过程中报如下错误: #kubectlcreate -f nginx-pod.yaml Error from server: error when creating &quo ...
- 国内不fq安装K8S四: 安装过程中遇到的问题和解决方法
目录 4 安装过程中遇到的问题和解决方法 4.1 常见问题 4.2 常用的操作命令 4.3 比较好的博客 国内不fq安装K8S一: 安装docker 国内不fq安装K8S二: 安装kubernet 国 ...
- 十五,K8S集群调度原理及调度策略
目录 k8s调度器Scheduler Scheduler工作原理 请求及Scheduler调度步骤: k8s的调用工作方式 常用预选策略 常用优先函数 节点亲和性调度 节点硬亲和性 节点软亲和性 Po ...
- k8s集群调度方案
Scheduler是k8s集群的调度器,主要的任务是把定义好的pod分配到集群节点上 有以下特征: 1 公平 保证每一个节点都能被合理分配资源或者能被分配资源 2 资源高效利用 集群所有资 ...
- k8s运维之pod排错
k8s运维之pod排错 K8S是一个开源的,用于管理云平台中多个主机上的容器化应用,Kubernetes的目标是让部署容器化变得简单并且高效 K8S的核心优势: 1,基于yaml文件实现容器的自动创建 ...
- k8s核心资源之Pod概念&入门使用讲解(三)
目录 1. k8s核心资源之Pod 1.1 什么是Pod? 1.2 Pod如何管理多个容器? 1.3 Pod网络 1.4 Pod存储 1.5 Pod工作方式 1.5.1 自主式Pod 1.5.2 控制 ...
- Cocos2d-x 3.x 学习笔记(三):Scheduler Timer 调度与定时
1. 概述 Cocos2d-x 的 Scheduler 离不开 Timer.Timer 类是定时器,用来规定一个回调函数应该在何时被触发.Timer 封装了已运行时间.重复次数.已执行次数.延迟秒数 ...
- k8s之深入解剖Pod(三)
目录: Pod的调度 Pod的扩容和缩容 Pod的滚动升级 一.Pod的调度 Pod只是容器的载体,通常需要通过RC.Deployment.DaemonSet.Job等对象来完成Pod的调度和自动控制 ...
随机推荐
- Codeforces Round #546 C. Nastya Is Transposing Matrices
题面: 传送门 题目描述: 给出两个n x m的矩阵A,B.矩阵A可以把正方子矩阵进行"转置操作",问:可不可以对矩阵A进行多次这样的操作,使矩阵A变为矩阵B? 题目分析: 这 ...
- Python之基础算法介绍
一.算法介绍 1. 算法是什么 算法是指解题方案的准确而完整的描述,是一系列解决问题的清晰指令,算法代表着用系统的方法描述解决问题的策略机制.也就是说,能够对一定规范的输入,在有限时间内获得所要求的输 ...
- 阿里二面,面试官居然把 TCP 三次握手问的这么细致
TCP 的三次握手和四次挥手,可以说是老生常谈的经典问题了,通常也作为各大公司常见的面试考题,具有一定的水平区分度.看似是简单的面试问题,如果你的回答不符合面试官期待的水准,有可能就直接凉凉了. 本文 ...
- ubuntu系统编译安装OpenCV 4.4
内容转载自我的博客 目录 前言 1. 下载源码 2. 安装各种依赖 3. 开始编译安装 4. 配置C++开发环境 5. 程序执行时加载动态库*.so 6. 测试cpp文件 7. 配置python3的o ...
- 获取本机外网ip
获取内网ip ifconfig eth0 | grep 'inet'| grep -v '127.0.0.1' | cut -d: -f2 | awk '{ print $2}' 获取公网ip ifc ...
- [Fundamental of Power Electronics]-PART II-8. 变换器传递函数-8.4 变换器传递函数的图形化构建
8.4 变换器传递函数的图形化构建 第7章推导出的buck变换器小信号等效电路模型在图8.55中再次给出.让我们用上一节的图解方法来构造该变换器的传递函数和端阻抗. Fig. 8.55 Small-s ...
- MindSpore函数拟合
技术背景 在前面一篇博客中我们介绍过基于docker的mindspore编程环境配置,这里我们基于这个环境,使用mindspore来拟合一个线性的函数,演示一下mindspore的基本用法. 环境准备 ...
- 如何优雅地学习计算机2<-->Helloworld
0.导入 在进行粗略的学习计算机底层知识和变量后,我们来开始编写年轻人的第一个程序--Helloworld. 我们需要用到的工具有:1.Dev-C++(也可以使用其他软件)2.脑子(最重要) ...
- Java(81-93)【数组】
1.省略格式 静态初始化的时候格式还可以省略一下 int[ ] arrayA={10,20,30}; 静态和动态都可以拆 int[] arrayB; arrayB=new int[ ]{11,21,3 ...
- get_started_3dsctf_2016-Pwn
get_started_3dsctf_2016-Pwn 这个题确实有点坑,在本地能打,在远程就不能打了,于是我就换了另一种方法来做. 确这个题是没有动态链接库,且PIE是关的,所以程序的大部分地址已经 ...