我是 LEE,老李,一个在 IT 职业摸爬滚打 16 年的技术老兵。
作业背景
随着公司的 k8s 中 Node 节点越来越多,Pod 数量奔着单 cluster 50K 的量级冲了。单 cluster 中这个数量尽管并不是十分惊人,可是许多日常的忽略的小问题,现在逐步都是让人头疼的问题。目前由于 k8s 中存在事务种类多种多样,各种使用的负载压力和资源使用状况十分纷歧致,本来咱们认为这么多 Pod 之间资源消耗能够起到“削峰填谷”的样子,而现实状况是许多 k8s node 节点会时不时 CPU 接近 90,内存容量只剩下 5G 不到。此刻假设还有 pod 调度到这样的 k8s node 上,后面的故事,我想许多小伙伴都能自己脑补出来那种惨状,这个十分费劲的 k8s node 节点总算是由于承受不住这样的压力,已经运转的 pod 就会呈现服务质量下降或者 OOM 后被驱赶。
面临咱们需求一个在 pod 调度的时分不光只看 request values 的能力,还需求将 k8s node 节点 cpu/memory 的负载也考虑进来,让 pod 在一个最合理的环境中运转。最后完成 k8s node 之间 cpu/memory 使用负载趋近于“相同”。
在这样夸姣的希望下,我参与了团队内部的 k8s scheduler 调研、评估和少数测试。 今日我就提一个我觉得全体架构比较喜欢的,也比较合理的 scheduler。
我的想法
调研别人的 scheduler,仍是自研 scheduler 都是我的考虑选项,目前自研 scheduler 门槛被 k8s 小组压到了很低,根本一个会 golang 的小伙伴看几个小时后,就能写一个不错的 scheduler,可是这个并不是我介意的点。我介意的问题反而是怎么让定制 scheduler 有通用性,让这个 scheduler 能够在 k8s 版本之间晋级中,长期使用,最小价值。其次全体危险可控,能有一个“兜底”战略能够保证 scheduler 失效后使用原有的 k8s 的调度战略。
pod 运转起来,完成使用交给,与 pod 运转状况是不是最优是“两件事”,前者才是最重要的,后者是锦上添花。而后者假设做好了,能够很大的程度优化资源装备,进步资源的出产功率,必经 k8s node 节点都是成本。
依据上面的思路,我调研多加云厂商的 scheduler,也研讨了他们的架构。有一家的 scheduler 让我为之眼亮,并不是他们的代码质量多好,而是 如下几点 吸引了我:
- 足够轻。这个十分重要,认为这个没有太多入侵,能够随来随走。好依据自己状况 diy。
- 官方标准。用的 scheduler framework, 这个意味是 k8s 框架原生技术(1.19 进入了 stable),危险小。(时刻久了知道一个有标准的代码框架有多重要)
- 插件化。能够深入研讨 k8s 架构的小伙伴必经不多,关于我来说投入人力去构建一个完成 scheduler 和周边生态是彻底不可控的(违背第 2 点)。功用够“微服务化”,快进快出的开发方式,每个 plugin 的完成自己固定小功用。既保证输出效果,一起能够依据 cluster 实践运转状况,编列自己要的调度功用。
- 易筛选。意味着这个模块要能装得上体系,也要能垂手可得地的卸载掉。由于任何代码都有它的历史使命,一旦存在原因消失,那么它也失去了运转的含义。小伙伴能够想想,自己产线上有多少“祖传”代码下不掉,而大量时刻在翻体系。
说了这么多,那么这个神秘的 scheduler 到时是谁?
好,它是 crane-scheduler,出自腾讯云。
github: github.com/gocrane/cra…
架构解析
唉,不是讲解 scheduler 架构吗?怎样贴上来了一大堆乱七八糟的东西,这些都有联系吗?我想说:“有,都有联系!!”。假设没有这边的体系那么调度含义就不存在了。
世界观
提到这儿,咱们先用置疑情绪一起学习下 crane-scheduler 整个体系的世界观。
Crane-scheduler
人物:【智者】
调度核心,调度功用的完成,直接影响 pod 调度成果。算法很轻量,主要是完成过滤不符合条件的 node(cpu/memory 超过阈值)。随后把计算成果交给 k8s scheduler 内其他模块持续处理。
Node-annotator
人物:【搬砖的】
从 tsdb 中读取的数据,依据设置的公式,每隔一段时刻把这些值写到 Node CRD 中的 metadata/annotations。 它也只干这样一件作业,没有状况。 大白话说:读取 metrics –> 依据公式计算–> 写入 Node(CRD 资源) metadata/annotations。
Prometheus/telegraf
人物:【监工】
监控体系,监控 k8s node 节点上的 cpu/memory 状况值,并记录到 tsdb 中。纷歧定要这个计划,我这边使用的是:node_exporter + thanos。由于 Node-annotator 那儿的计算公式是能够依据自己实践状况修正。
总体流程
node (cpu/memory) –> tsdb –> node-annotator –> node (CRD) –> crane-scheduler
看到这儿,是不是觉得 crane-scheduler 的功用实践没有那么杂乱,都是十分简单的,并不是高不可攀。
尽管我是这么说,我信任有小伙伴质疑:“为什么要这么项功用来做这一件作业,我有 xxx 种方法来处理”。我想说,咱们耐性的看,crane-scheduler 仍是有“亮点”来闭坑的,我想腾讯的工程师们不会那么“笨”,想不到。
原理分析
要说 crane-scheduler 这个调度器原理,我想仍是先看看 k8s scheduler 的逻辑。由于 crane-scheduler 遵守的 scheduler framework 标准。
k8s scheduler
这儿咱们将目光放在图中 Scheduling Cycle 这部分内容内。 在重视内容之前,我先要声明一个概念:
官方英文:Scheduling cycles are run serially, while binding cycles may run concurrently. 翻译中文:调度周期是串行,而绑定周期能够并行。
() 划要点:Scheduling cycles are run serially。 串行,串行,串行 重要的概念说三遍。 既然串行就会有一个 Queue。
看上图 Sort 就是让需求被调度的 pod 进入到一个 Queue 中。 官方英文:QueueSort These plugins are used to sort Pods in the scheduling queue. A queue sort plugin essentially provides a Less(Pod1, Pod2) function. Only one queue sort plugin may be enabled at a time.
咱们且先记下这儿是串行的,由于这儿有一个“坑”是要认真思考和注意的。
crane-scheduler 完成的图中的 Filter 模块,官方英文:These plugins are used to filter out nodes that cannot run the Pod. For each node, the scheduler will call filter plugins in their configured order. If any filter plugin marks the node as infeasible, the remaining plugins will not be called for that node. Nodes may be evaluated concurrently. 大致意思是说: 这些插件用于过滤掉无法运转 pod 的节点。关于每个节点,调度程序将按装备的顺序调用过滤器插件。假设任何过滤器插件将该节点标记为不可用的,则不会为该节点调用其余插件。节点能够并发求值。
() 划要点:
- 在 Filter 模块中,一切 plugins 依据装备顺序(也能够理解成编列),串行过滤,构成一个 chain。
- 在 Filter 模块中,在 chain 中有一个 plugin 返回 true(被过滤),chain 中其他 plugin 将不被执行。
- 在 Filter 模块中,假设 chain 中没有 plugin 返回 true(被过滤),那么维持原有的过滤成果。
QueueSort 的坑
既然一切的 pod 在进入 Scheduling Cycle 的时分都老老实实的排队了,那么咱们做这么一个假定:这个 Queue 中假设一个模块呈现拥塞,会怎样样?不敢想,后续的需求被调度的 pod 只能老老实实的等候,由于大家都在一条单行道上。
结合到事务上,假设自定义的 scheduler plugin 处理速度很慢或者拥塞了,那么关于拥有巨大 pod 数量的大 cluster 可谓是一个灾难性的状况。不但新发布事务的 pod 不能及时被调度运转,一起那也由于反常重启或者 HPA 触发扩容的 pod 都将被卡住,导致无法满意事务的实践要求。
crane-scheduler
结合上面 k8s scheduler 结构逻辑以及 QueueSort 中可能会碰到的“坑”,咱们再回过头去看 crane-scheduler 的架构图,是不是会发现,假设:crane-scheduler 在调度 pod 时分,去查询 tsdb 并计算负载值,再执行 filter。你觉得这个功率高吗? 发现这儿会存在卡点的人,恐怕不是我一个人。我还没有说,pod 在调度的时分 tsdb 反常无法查询数据呢?是不是很多都不敢去想。
提到这儿,细品,就发现了这个架构有意思的当地:
- Node-annotator/Prometheus 声明者。监控自己 Node 的 cpu/memory 负载状况,并声明这个值。由于自己最了解自己状况,自己对自己言行负责。
- Crane-scheduler 决议者。依据声明的值来做判别是否要执行行动。
各司其职,功用解构和鸿沟区分十分清晰。
躲藏的抗
既然声明者对自己负责,声明自己的状况值。的确存在声明者的程序反常或者无法连接到 tsdb 去读取数据,那该怎么处理?
处理这个问题实践比较简单,就是给声明者声明的状况值加上一个 timestamp。决议者看到这个 timestamp 就会跟现在时刻做比对,求差值。假设差值在一个周期内,那么就选用声明者所声明的状况值,反之则忽略这个值,执行默认战略。
由于决议者是作业在 k8s scheduler 内,选用上面的逻辑,就不会将 Queue 卡住。声明的值写在 Node CRD 中 metadata/annotations,不需求做查询 tsdb 和计算等候,而是直接调用声明成果做判别。
最终效果
左边是原生计划,右边选用了 Crane-scheduler 增强的计划
解读:负载越往中心会集越好。越往中心会集的部分(如图),之间差异越小越好。
参考资料
- kubernetes.io/docs/concep…
- github.com/gocrane/cra…