作者
蒋彪,腾讯云高级工程师,10+ 年专一于操作系统相干技术,Linux 内核资深发烧友。目前负责腾讯云原生 OS 的研发,以及 OS/ 虚拟化的性能优化工作。
导语
混部,通常指在离线混部 (也有离在线混部之说),意指通过将在线业务(通常为提早敏感型高优先级工作) 和离线工作 (通常为 CPU 消耗型低优先级工作) 同时混合部署在同一个节点上,以期晋升节点的资源利用率。其中的要害难点在于底层资源隔离技术,重大依赖于 OS 内核,而现有的原生 Linux kernel 提供的资源隔离能力在面对混部需要时,再次显得有些顾此失彼(或至多说不够完满),仍需深度 Hack,方能满足生产级别的需要。
(云原生)资源隔离技术次要包含 CPU、memory、IO 和网络,4 个方面。本文聚焦于 CPU 隔离技术和相干背景,后续 (系列) 再循序渐进,逐渐开展到其余方面。
背景
无论在 IDC,还是在云场景中,资源利用率低 都相对是大部分用户 / 厂商面临的独特难题。一方面,硬件老本很高 (大家都是靠买,而且绝大部分硬件(核心技术) 把握于他人手中,没有定价权,议价能力也通常很弱),生命周期还很短(过几年还得换新);另一方面,极度难堪的是这么贵的货色无奈充分利用,拿 CPU 占用率来说,绝大部分场景的均匀占用率都很低(如果我拍不超过 20%(这里指日均值,或周均值),置信大部分同学都不会有意见,这就意味着,贼贵的货色理论只用了不到五分之一,如果你还想正经的居家过日子,想必都会感觉疼爱。
因而,晋升主机 (节点) 的资源利用率是一项十分值得摸索,同时收益非常明显的工作。解决思路也十分间接,
惯例的思维模式:多跑点业务。说起来容易,谁没试过呢。外围艰难在于:通常的业务都有显著的峰谷特色
你心愿的样子可能是这样的:
但事实的样子多半是这样的:
而在为业务做容量布局是,须要按 Worst Case 做 (假如所有业务的优先级都一样),具体来说,从 CPU 层面的话,就须要按 CPU 峰值(可能是周峰值、甚至月 / 年峰值) 的来布局容量(通常还得留肯定的余量,应答突发),
而事实中大部分状况是:峰值很高,但理论的均值很低。因而导致了绝大部分场景中的 CPU 均值都很低,理论 CPU 利用率很低。
后面做了个假如:“所有业务的优先级都一样”,业务的 Worst Case 决定了整机的最终体现 (资源利用率低)。如果换种思路,但业务辨别优先级时,就有更多的施展空间了,能够通过就义低优先级业务的服务质量(通常能够容忍) 来保障高优先级业务的服务质量,如此能部署在适量高优先级业务的同时,部署更多的业务(低优先级),从而整体上晋升资源利用率。
混部 (混合部署) 因而应运而生。这里的“混”,实质上就是“辨别优先级”。广义上,能够简略的了解为“在线 + 离线”(在离线)混部,狭义上,能够扩大到更广的利用范畴:多优先级业务混合部署。
其中波及的核心技术包含两个层面:
- 底层资源隔离技术。(通常)由操作系统 (内核) 提供,这是本 (系列) 文章的外围关注点。
- 下层的资源调度技术。(通常)由下层的资源编排 / 调度框架 (典型如 K8s) 提供,打算另做系列文章开展,仍请期待。
混部也是业界十分热门的话题和技术方向,以后支流的头部大厂都在继续投入,价值不言而喻,也有较高的技术门槛 (壁垒)。相干技术起源甚早,颇有渊源,赫赫有名的 K8s(前身 Borg) 其实源于 Google 的混部场景,而从混部的历史和成果看,Google 算是行业内的标杆,号称 CPU 占用率 (均值) 能做到 60%,具体可参考其经典论文:
https://dl.acm.org/doi/pdf/10…
https://storage.googleapis.co…
当然,腾讯 (云) 在混部方向的摸索也很早,也经验了几次大的技术 / 计划迭代,至今已有不错的落地规模和成绩,详情又需起另外的话题,不在本文探讨。
技术挑战
如后面所说,混部场景中,底层资源隔离技术至关重要,其中的“资源”,整体上分为 4 个大类:
- CPU
- Memory
- IO
- 网络
本文聚焦于 CPU 隔离技术,次要剖析在 CPU 隔离层面的技术难点、现状和计划。
CPU 隔离
后面说的 4 类资源中,CPU 资源隔离能够说是最根底的隔离技术。一方面,CPU 是可压缩 (可复用) 资源,复用难度绝对较低,Upstream 的解决方案可用性绝对较好;另一方面,CPU 资源与其余资源关联性较强,其余资源的应用 (申请 / 开释) 往往依赖于过程上下文,间接依赖于 CPU 资源。举例来说,当 CPU 被隔离 (压抑) 后,其余如 IO、网络的申请可能 (大部分状况) 因为 CPU 被压抑(得不到调度),从而也随之被压抑。
因而,CPU 隔离的成果也会间接影响其余资源的隔离成果,CPU 隔离是最外围的隔离技术。
内核调度器
具体来说,落地到 OS 中,CPU 隔离实质上齐全依赖于 内核调度器 实现,内核调度器是负载调配 CPU 资源的内核基本功能单元(很官网的说法),具体来说(广义说),能够对应到咱们接触最多的 Linux 内核的默认调度器:CFS 调度器(实质上是一个调度类,一套调度策略)。
内核调度器决定了何时、选取什么工作 (过程) 到 CPU 上执行,因而决定了混部场景中在线和离线工作的 CPU 运行工夫,从而决定了 CPU 隔离成果。
Upstream kernel 隔离成果
Linux 内核调度器默认提供了 5 个调度类,理论业务能用的基本上只有两种:
- CFS
- 实时调度器(rt/deadline)
混部场景中,CPU 隔离的实质在于须要:
- 当在线工作须要运行时,尽力 压抑 离线工作
- 当在线工作不运行时,离线工作利用闲暇 CPU 运行
对于“压抑”,基于 Upstream kernel(基于 CFS),有如下几种思路(计划):
优先级
能够升高离线工作的优先级,或晋升在线工作的优先级实现。在不批改调度类的状况下(基于默认的 CFS),能够动静调整的优先级范畴为:[-20, 20)
工夫片的具体表现为单个调度周期内可分得的工夫片,具体来说:
- 一般优先级 0 与最低优先级 19 之间的工夫片调配权重比为:1024/15,约为:68:1
- 最高优先级 -20 与一般优先级 0 之间的工夫片调配权重比为:88761/1024,约为:87:1
- 最高优先级 -20 和最低优先级 19 之间的工夫片调配权重比为:88761/15,约为:5917:1
看起来压抑比还比拟高,退出通过设置离线工作的优先级为 20,在线放弃默认 0(通常的做法),此时在线和离线的工夫片调配权重为 68:1。
假如单个调度周期长度为 24ms(大部分零碎的默认配置),看起来(粗略估算),单个调度周期中离线能调配到的工夫片约为 24ms/69=348us,可占用约 1 /69=1.4% 的 CPU。
理论的运行逻辑还有点差别:CFS 思考吞吐,设置了单次运行的最小工夫粒度爱护 (过程单次运行的最小工夫):sched_min_granularity_ns,少数状况设置为 10ms,意味着离线一旦产生抢占后,能够继续运行 10ms 的工夫,也就意味着在线工作的调度提早(RR 切换提早) 可能达 10ms。
Wakeup 时也有最小工夫粒度爱护 (Wakeup 时,被抢占工作的最小运行工夫保障):sched_wakeup_granularity_ns,少数状况设置为 4ms。意味着离线一旦运行后,在线工作的 wakeup latency(另一种典型的调度提早) 也可能达 4ms。
此外,调整优先级并不能优化抢占逻辑,具体来说,在施行抢占时(wakeup 和周期性),并不会参考优先级,并不会因为优先级不同,而实时不同的抢占策略(不会因为离线工作优先级低,而压抑其抢占,缩小抢占机会),因而有可能导致离线产生不必要的抢占,从而导致烦扰。
Cgroup(CPU share)
Linux 内核提供了 CPU Cgroup(对应于容器 pod),能够通过设置 Cgroup 的 share 值来管制容器的优先级,也就是说能够通过调低离线 Cgroup 的 share 值来实现“压抑 ” 目标。对于 Cgroup v1 来说,Cgroup 的默认 share 值为 1024,Cgruop v2 的默认 share(weight) 值为 100(当然还能够调),如果设置离线 Cgroup 的 share/weight 值为 1(最低值),那么,在 CFS 中,相应的工夫片调配权重比别离为:1024:1 和 100:1,对应的 CPU 占用别离约为 0.1% 和 1%。
理论运行逻辑依然受限于 sched_min_granularity_ns 和 sched_wakeup_granularity_ns。逻辑跟优先级场景相似。
与优先级计划相似,抢占逻辑未依据 share 值优化,可能存在额定的烦扰。
非凡 policy
CFS 中还提供了一个非凡的调度 policy:SCHED_IDLE,专用于运行优先级极低的工作,看起来是专为”离线工作“设计的。SCHED_IDLE 类工作实质上是有一个权重为 3 的 CFS 工作,其与一般工作的工夫片调配权重比为:1024:3,约为 334:1,此时离线工作的 CPU 占用率约为 0.3%。工夫片调配如:
理论运行逻辑依然受限于 sched_min_granularity_ns 和 sched_wakeup_granularity_ns。逻辑跟优先级场景相似。
CFS 中对于 SCHED_IDLE 工作做了非凡的抢占逻辑优化 (压抑 SCHED_IDLE 工作对其余工作的抢占,缩小抢占机会),因而,从这个角度看,SCHED_IDLE 为”适配“(尽管 Upstream 本意并非如此) 混部场景迈进了一小步。
此外,因为 SCHED_IDLE 是 per-task 的标记,并无 Cgroup 级别的 SCHED_IDLE 标记能力,而 CFS 调度时,须要先选 (task)group,而后再从 group 当选 task,因而对于云原生场景 (容器) 混部来说,单纯应用 SCHED_IDLE 并不能施展理论作用。
整体看,尽管 CFS 提供了优先级 (share/SCHED_IDLE 原理上相似,实质都是优先级),并可依据优先级对低优先级工作进行肯定水平的压抑,然而,CFS 的外围设计在于”偏心“,实质上无奈做到对离线的”相对压抑“,即便设置”优先级“(权重) 最低,离线工作仍能取得固定的工夫片,而取得的工夫片不是闲暇的 CPU 工夫片,而是从在线工作的工夫片中抢到的。也就是说,CFS 的”偏心设计“,决定了无奈完全避免离线工作对在线的烦扰,无奈达到完满的隔离成果。
除此之外,通过 (极限) 升高离线工作的优先级 (上述几种计划实质都是如此) 的形式,实质上,还压缩了离线工作的优先级空间,换句话说,如果还想进一步在离线工作之间辨别优先级(离线工作之间也可能有 QoS 辨别,理论可能有这样的需要),那就无能为力了。
另,从底层实现的角度看,因为在线和离线均应用 CFS 调度类,理论运行时,在线和离线共用运行队列 (rq),叠加计算 load,共用 load balance 机制,一方面,离线在做共用资源(比方运行队列) 操作时须要做同步操作(加锁),而锁原语自身是不辨别优先级的,不能排除离线烦扰;另一方面,load balance 时也无奈辨别离线工作,对其做非凡解决(比方激进 balance 避免饥饿、晋升 CPU 利用率等),对于离线工作的 balance 成果无法控制。
实时优先级
此时,你可能会想,如果须要相对抢占 (压抑离线),为何不必实时调度类(RT/deadline) 呢?实时调度类相比于 CFS,刚好达到”相对压抑“的成果。
的确如此。然而,这种思路下,只能将在线业务设置为实时,离线工作放弃为 CFS,如此,在线能相对抢占离线,同时如果放心离线被饿死的话,还有 rt_throttle 机制来保障离线不被饿死。
看起来”完满“,其实不然。这种做法的实质,会压缩在线工作的优先级空间和生存空间(与之前调低离线工作优先级的后果相同),后果是在线业务只能用实时调度类(只管大部分在线业务并不满足实时类型的特色),再无奈利用 CFS 的原生能力(比方偏心调度、Cgroup 等,而这恰好是在线工作的刚需)。
简略来看,问题在于:实时类型并不能满足在线工作本身运行的须要,实质上看在线业务本身并不是实时工作,如此强扭为实时后,会有比较严重的副作用,比方零碎工作 (OS 自带的工作,比方各种内核线程和零碎服务) 会呈现饥饿等。
总结一下,对于实时优先级的计划:
- 认可实时类型对于 CFS 类型的”相对压抑“能力(这正是咱们想要的)
- 但以后 Upstream kernel 实现中,只能将在线工作设置为比 CFS 优先级更高的实时类型,这是理论利用场景中无奈承受的。
优先级反转
说到这,你心里可能还有一个微小的问号:”相对压抑“后,会有优先级反转问题吧?怎么办?
答案是:确实存在优先级反转问题
解释下这种场景下的优先级反转的逻辑:如果在线工作和离线工作之间有共享资源 (比方内核中的一些公共数据,如 /proc 文件系统之类),当离线工作因访问共享资源而拿到锁(形象一下,不肯定是锁) 后,如果被”相对压抑“,始终无奈运行,当在线工作也须要拜访该共享资源,而期待相应的锁时,优先级反转呈现,导致死锁(长时间阻塞也可能)。优先级反转是调度模型中须要思考的一个经典问题。
粗略总结下优先级反转产生的条件:
- 在离线存在共享资源。
- 存在共享资源的并发拜访,且应用了睡眠锁爱护。
- 离线拿到锁后,被齐全相对压抑,没有运行的机会。这句话能够这样了解:所有的 CPU 都被在线工作 100% 占用,导致离线没有任何运行机会。(实践上,只有有闲暇 CPU,离线工作就可能通过 load balance 机制利用上)
在云原生混部场景中,对于优先级反转问题的解决办法(思路),取决于对待该问题的角度,咱们从如下几个不同的角度来看,
- 优先级反转产生可能性有多大?这取决于理论的利用场景,实践上如果在线业务和离线业务之间不存在共享资源,其实就不会产生优先级反转。在云原生的场景中,大体上分两种状况:
(1)平安容器场景。此场景中,业务理论运行于”虚拟机“(形象了解)中,而虚拟机本身保障了绝大部分资源的隔离性,这种场景中,根本能够防止产生优先级反转(如果的确存在,能够特事特办,独自解决)
(2)一般容器场景。此场景中,业务运行于容器中,存在一些共享资源,比方内核的公共资源,共享文件系统等。如后面剖析,在存在共享资源的前提下,呈现优先级反转的条件还是比拟严苛的,其中最要害的条件是:所有 CPU 都被在线工作 100% 占用,这种状况在事实的场景中,是十分少见的,算是十分极其的场景,事实中能够独自解决这样的”极其场景“
因而,在 (绝大部分) 实在云原生场景中,咱们能够认为,在调度器优化 /hack 足够好的前提下,能够躲避。
- 优先级反转如何解决?尽管优先级反转仅在极其场景呈现,但如果肯定要解决的话(Upstream 肯定会思考),该怎么解决?
(1)Upstream 的想法。原生 Linux kernel 的 CFS 实现中,为最低优先级 (能够认为是 SCHED_IDLE) 也保留了肯定的权重,也就意味着,最低优先级工作也能失去肯定的工夫片,因而能够 (根本) 防止优先级反转问题。这也是社区始终的态度:通用,即便是极度极其的场景,也须要完满 cover。这样的设计也恰好是不能实现”相对压抑“的起因。从设计的角度看,这样的设计并无不妥,但对于云原生混部场景来说,这样的设计并不完满:并不感知离线的饥饿水平,也就是说,在离线并不饥饿的状况下,也可能对在线抢占,导致不必要的烦扰。
(2)另一种想法。针对云原生场景的优化设计:感知离线的饥饿和呈现优先级反转的可能性,但离线呈现饥饿并可能导致优先级反转时 (也就是无可奈何时),才进行抢占。如此一方面能防止不一样的抢占(烦扰),同时还能防止优先级反转问题。达到(绝对) 完满的成果。当然,不得不抵赖,这样的设计不那么 Generic,不那么 Graceful,因而 Upstream 也根本不太可能承受。
超线程烦扰
至此,还漏了另一个关键问题:超线程烦扰。这也是混部场景的顽疾,业界也始终没有针对性的解决方案。
具体的问题是,因为同一个物理 CPU 上的超线程共享外围的硬件资源,比方 Cache 和计算单元。当在线工作和离线工作同时运行在一对超线程上时,相互之间会因为硬件资源争抢,而呈现互相烦扰的状况。而 CFS 在设计时也齐全没有思考这个问题
导致后果是,在混部场景中,在线业务的性能受损。理论测试应用 CPU 密集型 benchmark,因超线程导致的性能烦扰可达 40%+。
注:Intel 官网的数据:物理 core 性能差不多只能 1.2 倍左右的单核性能。
超线程烦扰问题是混部场景中的关键问题,而 CFS 在最后设计时是 (简直) 齐全没有思考过的,不能说是设计缺失,只能说是 CFS 并不是为混部场景而设计的,而是为更通用的、更宏观的场景而生。
Core scheduling
说到这,业余 (搞内核调度) 的同学可能又会冒出一个疑难:难道没听说过 Core scheduling 么,不能解决超线程烦扰问题么?
听到这,不得不说这位同学的确很业余,Core Scheduling 是内核调度器模块 Maintainer Perter 在 2019 年提交的一个新 feature(基于更早之前的社区中曾提出的 coscheduling 概念),次要的指标在于解决(应该是 mitigation 或者是 workaround) L1TF 破绽(因为超线程之间共享 cache 导致数据泄露),次要利用场景为:云主机场景中,防止不同的虚拟机过程运行于同一对超线程上,导致数据泄露。
其核心思想是:防止不同标记的过程运行于同一对超线程上。
现状是:Core scheduling patchset 在通过长达 v10 的版本的迭代,近 2 年的探讨和 improve/rework 之后,终于,就在最近(2021.4.22),Perter 收回了看似可能进入(何时能进入还不好说) master 的版本(还不太残缺):
https://lkml.org/lkml/2021/4/…
对于这个话题,值得一个独自的深刻的分享,不在这里开展。也请期待 …
这里间接抛 (集体) 观点(轻拍):
- Core scheduling 的确能用来解决超线程烦扰问题。
- Core scheduling 设计初衷是解决安全漏洞 (L1TF),并非为混部超线程烦扰而设计。因为须要保障平安,须要实现相对隔离,须要简单(开销大) 的同步原语(比方 core 级别的 rq lock),重量级的 feature 实现,如 core 范畴的 pick task,过重的 force idle。另外,还有配套的中断上下文的并发隔离等。
- Core scheduling 的设计和实现太重、开销太大,开启后性能 regression 重大,并不能辨别在线和离线。不太适宜 (云原生) 混部场景。
实质还是:Core scheduling 亦非为云原生混部场景而设计。
论断
综合后面的剖析,能够形象的总结下以后现有的各种计划的长处和问题。
基于 CFS 中的优先级 (share/SCHED_IDLE 相似) 计划,长处:
- 通用。能力强,能全面 hold 住大部分的利用场景
- 能 (根本) 防止优先级反转问题
问题:
- 隔离成果不完满(没有相对压抑成果)
- 其余各种小毛病(不完满)
基于实时工作类型的计划,长处:
- 相对压抑,隔离成果完满
- 有机制防止优先级反转(rt_throttle)
问题:
- 不实用。在线工作不能 (大部分状况) 用实时工作类型。
- 有机制 (rt_throttle) 防止优先级反转,但开启后,隔离成果就不完满了。
基于 Core scheduling 解决超线程烦扰隔离,长处:
- 完满超线程烦扰隔离成果
问题:
- 设计太重,开销太大
结语
Upstream Linux kernel 为思考通用性,设计的优雅,难以满足特定场景 (云原生混部) 中的极致需要,若想谋求卓越和极致,还须要深度 Hack,而 TencentOS Server 始终在路上。(听着耳熟?的确以前也这么说过!
对于 Linux kernel 的内核调度器的具体实现和代码剖析(基于 5.4 内核(Tkernel4)),咱们后续会陆续推出相应的解析系列文章,在探讨云原生场景的痛点的同时,联合相应的代码剖析,以期升高 Linux 内核神秘感,探讨更广大的 Hack 空间。敬请期待。
思考
- 如果想要让在线业务应用 CFS (利用 CFS 的弱小能力),同时又想具备”相对压抑“的能力,现实的做法应该怎么办?(感觉答案就要跃然纸上了!
- 如果不须要完满隔离成果(相对压抑),同时须要解决优先级反转,还须要”靠近完满“的隔离成果,还想尽量利用现有机制(不想太大的调度器 Hack,危险更小),那又该怎么办?(认真看看后面的各种现有计划的剖析总结,感觉也快靠近答案了)
【腾讯云原生】云说新品、云研新术、云游新活、云赏资讯,扫码关注同名公众号,及时获取更多干货!!