背景信息:
某用户反馈,头一天晚上21:05左右,某几个deployment的pod被重建了一遍,客户的pod有特殊限制,基本可以保证1个节点上就只有这一个pod独占,客户怀疑后端异常导致,这种问题可以先去看下元集群的信息,看看各种controller 的pod 在异常时间段有没有崩溃,重启,重建等信息,这个集群没有这个问题
排查思路&日志分析:
1,deployment下的所有pod都重建了,当时优先怀疑deployment的变更,但是根据rs的版本号对比,确认前后pod的rs版本号未变
2,有什么job cronjob删除pod,deployment controller拉起新的pod,根据日志排除掉
3,之前遇到过HPA拉pod,然后缩容时删除旧pod只保留新pod,根据hpa日志排除
HPA 相关日志:实际没有扩容的动作,日志解读就是预计需要39个副本,根据cpu的得分来看,但是最后决定不扩容
content:I110713:05:31.5818611horizontal.go:1156]Successfullyupdatedstatusfordeploy-d****ercontent:I110713:05:46.6098491horizontal.go:661]proposing39desiredreplicas(basedoncpuresourceutilization(percentageofrequest)from2022-11-0713:05:20+0000UTC)forDeployment/default/depl****rcontent:I110713:05:46.6098711horizontal.go:700]decidednottoscaleDeployment/default/ddepl****rto39(lastscaletimewas2022-11-0711:16:27+0000UTC)
4,以被删除的pod depl****rr-7b656b54b7-stwj8 为例(老pod 被抢占删除),看时间线
这个是scheduler的日志的抢占日志,注意毫秒数,这个pod被抢占了content:I110713:05:47.7342711capacity_scheduling.go:662]Poddefault/depl****rr-7b656b54b7-stwj8isapotentialpreemptionvictimonnodeeu-central-1.10.200.104.186.这个是apiserver的日志content:I110721:05:47.8198771httplog.go:108]"HTTP"verb="DELETE"URI="/api/v1/namespaces/default/pods/depl****r-7b656b54b7-stwj8"latency="7.986746ms"userAgent="kube-scheduler/v1.20.4 (linux/amd64) kubernetes/96c418b/scheduler"srcIP="7.8.121.36:45840"resp=200contentType="application/vnd.kubernetes.protobuf"requestID="49832f6f-aa13-412a-92b4-a5e7e62aa976"这个是审计日志的删除记录,注意毫秒数stageTimestamp:2022-11-07T13:05:47.819744Zuser:{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]}userAgent:kube-scheduler/v1.20.4(linux/amd64)kubernetes/96c418b/schedulerverb:delete
我们用日志串联看出来的都是抢占先发生,删除后发生的
5,通过节点绑定这个特性反查当时抢占该pod的新pod名称depl****rr-7b656b54b7-zjmn8 为检索关键字,捋时间线
scheduler日志:content:I110713:05:45.0684921eventhandlers.go:172]addeventforunscheduledpoddefault/depl****r-7b656b54b7-zjmn8apiserver日志:content:I110721:06:03.7567671httplog.go:108]"HTTP"verb="PATCH"URI="/api/v1/namespaces/default/pods/depl****rr-7b656b54b7-zjmn8/status"latency="5.035966ms"userAgent="kube-scheduler/v1.20.4 (linux/amd64) kubernetes/96c418b/scheduler"srcIP="7.8.121.36:45840"resp=200contentType="application/vnd.kubernetes.protobuf"requestID="a0698fb2-d843-4158-b7ff-dbcc46e5f961"KCM日志:content:I110713:05:45.0683931graph_builder.go:632]GraphBuilderprocessobject:v1/Pod, namespacedefault, namedepl****r-7b656b54b7-zjmn8, uid86773988-20ec-4b44-a62e-97c3cd3db054, eventtypeadd, virtual=false审计日志:stageTimestamp:2022-11-07T13:06:03.756637Zuser:{"username":"kubernetes-admin","groups":["system:masters","system:authenticated"]}userAgent:kube-scheduler/v1.20.4(linux/amd64)kubernetes/96c418b/schedulerverb:patch
看到了新pod的不可调度事件,但是并不能说明该pod抢占了老pod
6,继续寻找pod被抢占的原因,这个时候要考虑抢占的上下文日志,sls有个上下文浏览可以看下,这里涉及一个知识点,调度器在尝试驱逐的时候是串行执行的,所以在驱逐pod A的时候,发起驱逐的pod就是这条日志超上翻的第一条Attempting to schedule pod的pod,并且在条日志后面也有postfiler的结果,返回值是0 [] nil表明抢占是成功的,2则是失败
logtail的pod尝试调度
content:I110713:05:20.7024661eventhandlers.go:172]addeventforunscheduledpodkube-system/logtail-ds-xhzgmcontent:I110713:05:47.7287971scheduling_queue.go:812]Abouttotryandschedulepodkube-system/logtail-ds-xhzgmcontent:I110713:05:47.7288091scheduler.go:460]Attemptingtoschedulepod:kube-system/logtail-ds-xhzgm
这个节点发生了潜在的抢占,scheduler日志要看上下文,所以这个时间点只有一个pod在等待调度,且发送抢占的只有1个节点,因此判定这个logtail的pod要调度的就是186这个机器
content:I110713:05:47.7341061capacity_scheduling.go:433]1potentialnodesforpreemption, first1are:[eu-central-1.10.*.*.86]
老pod被抢占的记录日志
content:I110713:05:47.7342711capacity_scheduling.go:662]Poddefault/depl****r-7b656b54b7-stwj8isapotentialpreemptionvictimonnodeeu-central-1.10.*.*.86.
然后logtail的日志记录到了抢占成功
content:I110713:05:47.8207621scheduler.go:484]StatusafterrunningPostFilterpluginsforpodkube-system/logtail-ds-xhzgm:&{0[]}
这里的0是成功 2是失败如
抢占成功后实际也没调度成功,因为内存不够
content:I110713:05:47.8214001factory.go:322]"Unable to schedule pod; no fit; waiting"pod="kube-system/logtail-ds-xhzgm"err="0/2273 nodes are available: 1 Insufficient memory, 2272 node(s) didn't match Pod's node affinity."
(老pod还没杀掉,新pod又被deployment controller尝试拉起,客户将内存资源占的太满了)
最终logtail的pod调度失败了,被删除的pod也被controller重新拉起了一个新的pod起来填补
一句话小结:
不要将节点的内存超卖太多或者占到几乎全部,大部分的DS类型的pod 优先级比较高,超卖率较高时容易发生抢占,设置合理的QOS类型,重要业务可以使用limit == request的QOS类型来保障不被抢占
Scheduler的扩展知识:
Scheduler的简介:
kube-scheduler是Kubernetes中的关键模块,扮演管家的角色遵从一套机制——为Pod提供调度服务,例如基于资源的公平调度、调度Pod到指定节点、或者通信频繁的Pod调度到同一节点等。容器调度本身是一件比较复杂的事,因为要确保以下几个目标:
- 公平性:在调度Pod时需要公平的进行决策,每个节点都有被分配资源的机会,调度器需要对不同节点的使用作出平衡决策。
- 资源高效利用:最大化群集所有资源的利用率,使有限的CPU、内存等资源服务尽可能更多的Pod。
- 效率问题:能快速的完成对大批量Pod的调度工作,在集群规模扩增的情况下,依然保证调度过程的性能。
- 灵活性:在实际运作中,用户往往希望Pod的调度策略是可控的,从而处理大量复杂的实际问题。因此平台要允许多个调度器并行工作,同时支持自定义调度器。
为达到上述目标,kube-scheduler通过结合Node资源、负载情况、数据位置等各种因素进行调度判断,确保在满足场景需求的同时将Pod分配到最优节点。显然,kube-scheduler影响着Kubernetes集群的可用性与性能,Pod数量越多集群的调度能力越重要,尤其达到了数千级节点数时,优秀的调度能力将显著提升容器平台性能。
Scheduler controller的组成:
下图是kube-scheduler的主要组件:
1. Policy
Scheduler 的调度策略启动配置目前支持三种方式,配置文件 / 命令行参数 / ConfigMap。调度策略可以配置指定调度主流程中要用哪些过滤器 (Predicates)、打分器 (Priorities) 、外部扩展的调度器 (Extenders),以及最新支持的 SchedulerFramwork 的自定义扩展点 (Plugins)。
2. Informer
Scheduler 在启动的时候通过 K8s 的 informer 机制以 List+Watch 从 kube-apiserver 获取调度需要的数据例如:Pods、Nodes、Persistant Volume(PV), Persistant Volume Claim(PVC) 等等,并将这些数据做一定的预处理作为调度器的的 Cache。
3.调度流水线
通过 Informer 将需要调度的 Pod 插入 Queue 中,Pipeline 会循环从 Queue Pop 等待调度的 Pod 放入 Pipeline 执行。
调度流水线 (Schedule Pipeline) 主要有三个阶段:Scheduler Thread,Wait Thread,Bind Thread。
Scheduler Thread 阶段: 从如上的架构图可以看到 Schduler Thread 会经历 Pre Filter -> Filter -> Post Filter-> Score -> Reserve,可以简单理解为 Filter -> Score -> Reserve。
Filter 阶段用于选择符合 Pod Spec 描述的 Nodes;Score 阶段用于从 Filter 过后的 Nodes 进行打分和排序;Reserve 阶段将 Pod 跟排序后的最优 Node 的 NodeCache 中,表示这个 Pod 已经分配到这个 Node 上, 让下一个等待调度的 Pod 对这个 Node 进行 Filter 和 Score 的时候能看到刚才分配的 Pod。
Wait Thread 阶段:这个阶段可以用来等待 Pod 关联的资源的 Ready 等待,例如等待 PVC 的 PV 创建成功,或者 Gang 调度中等待关联的 Pod 调度成功等等;
Bind Thread 阶段:用于将 Pod 和 Node 的关联持久化 Kube APIServer。
整个调度流水线只有在 Scheduler Thread 阶段是串行的一个 Pod 一个 Pod 的进行调度,在 Wait 和 Bind 阶段 Pod 都是异步并行执行。
调度框架的扩展点:
下图显示了一个 Pod 的调度上下文以及调度框架公开的扩展点。 在此图片中,“过滤器”等同于“断言”,“评分”相当于“优先级函数”。
一个插件可以在多个扩展点处注册,以执行更复杂或有状态的任务。
队列排序
这些插件用于对调度队列中的 Pod 进行排序。 队列排序插件本质上提供less(Pod1, Pod2)函数。 一次只能启动一个队列插件。
PreFilter
这些插件用于预处理 Pod 的相关信息,或者检查集群或 Pod 必须满足的某些条件。 如果 PreFilter 插件返回错误,则调度周期将终止。
Filter
这些插件用于过滤出不能运行该 Pod 的节点。对于每个节点, 调度器将按照其配置顺序调用这些过滤插件。如果任何过滤插件将节点标记为不可行, 则不会为该节点调用剩下的过滤插件。节点可以被同时进行评估。
PostFilter
这些插件在 Filter 阶段后调用,但仅在该 Pod 没有可行的节点时调用。 插件按其配置的顺序调用。如果任何 PostFilter 插件标记节点为“Schedulable”, 则其余的插件不会调用。典型的 PostFilter 实现是抢占,试图通过抢占其他 Pod 的资源使该 Pod 可以调度。
PreScore
这些插件用于执行 “前置评分(pre-scoring)” 工作,即生成一个可共享状态供 Score 插件使用。 如果 PreScore 插件返回错误,则调度周期将终止。
Score
这些插件用于对通过过滤阶段的节点进行排序。调度器将为每个节点调用每个评分插件。 将有一个定义明确的整数范围,代表最小和最大分数。 在标准化评分阶段之后,调度器将根据配置的插件权重 合并所有插件的节点分数
NormalizeScore
这些插件用于在调度器计算 Node 排名之前修改分数。 在此扩展点注册的插件被调用时会使用同一插件的 Score 结果。 每个插件在每个调度周期调用一次。
Reserve
Reserve 是一个信息性的扩展点。 管理运行时状态的插件(也成为“有状态插件”)应该使用此扩展点,以便 调度器在节点给指定 Pod 预留了资源时能够通知该插件。 这是在调度器真正将 Pod 绑定到节点之前发生的,并且它存在是为了防止 在调度器等待绑定成功时发生竞争情况。
这个是调度周期的最后一步。 一旦 Pod 处于保留状态,它将在绑定周期结束时触发Unreserve插件 (失败时)或PostBind插件(成功时)。
Permit
Permit插件在每个 Pod 调度周期的最后调用,用于防止或延迟 Pod 的绑定。 一个允许插件可以做以下三件事之一:
- 批准
一旦所有 Permit 插件批准 Pod 后,该 Pod 将被发送以进行绑定。 - 拒绝
如果任何 Permit 插件拒绝 Pod,则该 Pod 将被返回到调度队列。 这将触发Unreserve插件。 - 等待(带有超时)
如果一个 Permit 插件返回 “等待” 结果,则 Pod 将保持在一个内部的 “等待中” 的 Pod 列表,同时该 Pod 的绑定周期启动时即直接阻塞直到得到批准。如果超时发生,等待变成拒绝,并且 Pod 将返回调度队列,从而触发Unreserve插件。
说明:尽管任何插件可以访问 “等待中” 状态的 Pod 列表并批准它们 (查看FrameworkHandle)。 我们期望只有允许插件可以批准处于 “等待中” 状态的预留 Pod 的绑定。 一旦 Pod 被批准了,它将发送到PreBind阶段。
PreBind
这些插件用于执行 Pod 绑定前所需的所有工作。 例如,一个 PreBind 插件可能需要制备网络卷并且在允许 Pod 运行在该节点之前 将其挂载到目标节点上。
如果任何 PreBind 插件返回错误,则 Pod 将被拒绝并且 退回到调度队列中。
Bind
Bind 插件用于将 Pod 绑定到节点上。直到所有的 PreBind 插件都完成,Bind 插件才会被调用。 各 Bind 插件按照配置顺序被调用。Bind 插件可以选择是否处理指定的 Pod。 如果某 Bind 插件选择处理某 Pod,剩余的 Bind 插件将被跳过。
PostBind
这是个信息性的扩展点。 PostBind 插件在 Pod 成功绑定后被调用。这是绑定周期的结尾,可用于清理相关的资源。
Unreserve
这是个信息性的扩展点。 如果 Pod 被保留,然后在后面的阶段中被拒绝,则 Unreserve 插件将被通知。 Unreserve 插件应该清楚保留 Pod 的相关状态。
Kube-scheduler的整体逻辑大图:
参考文献:
https://kubernetes.io/zh-cn/docs/concepts/scheduling-eviction/kube-scheduler/