干K8s平台运维这么多年最让我糟心的场景之一就是业务方天天喊要扩容器财务盯着多云账单喊降本而我打开Grafana一看集群平均CPU利用率常年只有13%上下。不是机器不够是K8s资源调度这套机制天然就按声明分配、不按实际用量分配——每个Pod都往高里报Requests节点账面满满当当实际却大半时间在空转。要破这个局混部技术是目前云原生社区公认最有效的路子让在线服务和离线任务在同一批节点上错峰共生把预留却闲置的算力重新利用起来。这篇文章我会从资源浪费的根因讲起说清楚混部的核心原理、调度器和节点Agent的落地细节、资源隔离方案再晒一组真实改造数据和踩坑经历适合正在做K8s成本优化、资源治理的SRE和平台工程师。1. 资源利用率困局集群平均负载撑不过15%的真相要谈混部得先想明白一件事集群算力到底浪费在哪了。很多人一开始以为要优化的是机器不够用真正上了监控平台才发现问题恰恰相反是账面不够用实际很空闲。1.1 从一台物理机看资源浪费Requests背后的虚高账单K8s调度器的调度依据从来不是Pod实际用多少CPU和内存而是Pod声明要多少Requests。Pod能占多大资源Limits只是配额上限调度时看的只是Requests。这个机制本身没错但集群规模大了以后它就会形成一种典型的账面挤压效应。举一个我经常用来跟业务方解释的例子。一台4核8G的节点上面跑了一个requests4C8G、limits4C8G的业务Pod。实际运行一周后你看监控它的CPU平均使用率只有0.3核内存峰值也只有600M。因为账面上这个Pod已经把整台节点的资源都包圆了其他任何Pod都没法再调度上来——哪怕实际上这台机器有九成算力在闲着。这种情况非常普遍。业务方为了给流量高峰留缓冲通常会把Requests按峰值负载的1.5到2倍去报。再加上多个团队各自拿着业务指标酌情加缓冲集群层面的requests总量就被层层放大。算下来集群平均CPU利用率能到20%都算健康多数生产集群常年趴在10%到15%。我们内部做过统计线上80个Deployment的集群按requests计算的虚拟利用率能达到75%按实际用量算的真实利用率只有13%。这就是混部技术要撬动的最大空间requests与真实用量之间的差值。资源利用率在K8s里并没有一个现成的仪表盘通常我们用Prometheus和Kubelet的容器指标来算公式很简单节点CPU实际使用量除以节点CPU总量再做集群聚合平均。算出来的数字越难看说明可优化空间越大。1.2 在线服务的潮汐效应与缓冲策略在线服务的负载天然是潮汐式的。电商平台白天流量高凌晨流量低直播业务在开播和活动节点是高峰支付系统在整点和小周期内有明显脉冲。但Requests声明是静态的不会跟着潮汐起落。于是出现一个很矛盾的现象夜间在线服务基本闲置但整个集群的资源被requests锁死Spark批处理任务等不到节点。这并非某个团队的配置失误而是保障SLO的系统性代价。对在线服务来说P99延迟一旦被击穿就是事故。业务方宁可把requests拍到波峰值的两倍也不愿接受任何看起来有风险的调度。结果是每个团队都在自己的Pod上留了缓冲多个团队的缓冲叠在一起集群层面形成了一座巨大的虚拟资源山。这座山上没有任何实际负载但调度器看不见新Pod就是上不去。要解决这个问题光靠宣导大家把requests填准一点没有用。业务方不可能为了省成本放弃自身稳定性。正确的思路是在线服务保留它的缓冲声明但在调度和资源管理层面能够识别这些缓冲其实是闲置的并允许离线任务在运行中借用这部分闲置算力。这正是混部技术的出发点。2. 混部技术的核心逻辑在线和离线任务如何共处一室2.1 混部到底在混什么利用错峰与超卖回收闲置算力混部Colocation简单说就是把在线服务和离线任务放在同一个K8s集群、同一批节点上运行。在线服务包括Web应用、微服务API、事务型数据库等特点是延迟敏感要求P99稳定离线任务包括Spark批处理、Flink作业、AI模型训练、数据回填等特点是计算密集且对延迟不敏感晚跑几分钟甚至几小时都能接受。这两类负载放一起就能实现错峰共生在线服务波谷期离线任务把闲置的CPU和内存用起来在线服务波峰期离线任务让出资源或主动压制自己保证在线任务SLO不受影响。绕开requests账面的限制、利用闲置算力跑批处理本质上是给K8s资源调度引入了一层动态超卖逻辑。打个比方。一台服务器就像一套房子在线服务是常驻住户它的房间requests需要有保证但多数时间其实空着。混部相当于给这套房子装了智能门禁住户不在房间时允许临时租客离线任务进去用空间住户一回来门禁必须立刻把房间腾出来。关键是立刻归还这步如果做不到整个混部就是灾难。2.2 为什么K8s原生调度做不了这件事从Requests分配到真实供需博弈K8s原生调度器解决不了混部有以下几条硬伤。第一调度器只看静态声明。它把节点可分配资源Allocatable减去所有已调度Pod的Requests之和来判断节点是否还有余量。它永远不知道实际利用率是多少自然无法判断哪些Requests是虚高的。第二缺少跨QoS的资源压制能力。原生虽然把Pod分为Guaranteed、Burstable、BestEffort但一旦调度上去CPU和内存的竞争基本是谁抢到算谁的。原生机制允许高优Pod挤压低优Pod但混部需要的是双向动态调整在线任务资源用不完时离线任务可以把资源拿走在线任务负载上来时又随时把资源收回。这种动态借用关系原生调度器完全hold不住。第三缺少回收机制。在线任务释放出来的闲置资源谁来发现、谁来统计、谁来显式分配给离线任务kubelet只知道按cgroup约束让Pod不超限不会主动利用空闲资源。要落地混部必须在调度器和节点代理两层同时扩展。所以混部方案通常是一个控制面组件加一个节点Agent的组合控制面负责把离线任务调度到有闲置资源的节点节点Agent负责动态感知真实负载并执行压制和回收。Koordinator是阿里开源后捐给CNCF的混部项目也是目前社区里这套能力最完整的实现Volcano也能做类似的批调度。后面我会以Koordinator为例讲落地细节因为它在资源隔离和干扰检测上的设计更贴近混部场景。3. 落地混部调度QoS分级、超卖控制与节点Agent3.1 QoS分层设计从Guaranteed到Batch的优先级阶梯先明确一点K8s原生QoS只有三级混部场景远远不够用。你没法只靠Guaranteed/Burstable/BestEffort来表达这个在线服务是核心中的核心必须永远不被挤压和这个在线服务可以容忍偶尔被挤一下之间的差异。混部组件的第一个核心能力就是提供更细粒度、支持抢占和压制逻辑的QoS体系。以Koordinator为例它把Pod的QoS扩展成了四档System系统组件例如kube-system下的关键Pod。LSELatency Sensitive Exclusive独享资源的延迟敏感应用可以独占某些物理核或重资源。LSLatency Sensitive普通在线服务允许与其他在线服务共享但不允许被BE抢占。BEBatch离线批处理任务只能使用在线服务闲置出来的资源。调度器分配资源时严格按照System LSE LS BE的优先级排序。同一节点上如果BE任务和LS任务争抢CPUBE必须让路。这个让路不是靠BE任务自觉而是节点Agent强制执行的后面细说。实际操作中给Pod打标很简单在metadata里加一个label即可apiVersion: apps/v1 kind: Deployment metadata: name: order-service labels: koordinator.sh/qosClass: LS --- apiVersion: batch/v1 kind: Job metadata: name: spark-etl-job labels: koordinator.sh/qosClass: BE这里有个经验不是所有在线服务都适合标LSE。我的建议是核心API、消息队列、交易链路这部分标LSE普通内部服务标LS别一上来全标LSE——LSE太多会把混部空间挤没因为LSE倾向独享节点资源。我见过有团队把所有Deployment全标成LSE混部收益直接归零。QoS分层的目的是区分优先级而不是让所有业务都抢最高档。3.2 节点资源超卖与弹性限额如何安全地多卖CPU和内存混部能不能跑起来关键在超卖模型。这里的超卖不是盲目地让节点放很多Pod而是通过节点Agent动态计算节点真实可分配资源让调度器以此为依据而不是死死盯着requests账面值。CPU超卖相对安全因为CPU是可压缩资源挤了顶多拉长调度队列最多让任务变慢不会直接杀掉进程。通常混部会把CPU超卖率控制在1.5倍到2倍之间。Koordinator里的关键参数是超卖系数节点Agent会根据在线Pod的实际使用量动态算出可超卖容量再把结果作为扩展资源上报给调度器。公式大致是可超卖CPU 节点Allocatable CPU 超卖系数 × 在线Pod的Requests CPU - 在线Pod实际使用CPU。这个值不是固定的每分钟都会刷新。内存就不一样了内存是不可压缩资源。一用完就是OOM轻则杀掉BE Pod重则把同节点的在线Pod也拖下水。所以混部对内存极其谨慎默认策略是让BE Pod只能捡在线任务实际没用完的内存超卖率最多1.2倍还必须配合完整的回收和保护机制。极端情况下宁可淘汰BE任务也绝不让它挤走在线任务。建议的初始参数如下表这是我跑了多轮压测后相对稳妥的组合资源类型默认超卖系数安全上限关键保护机制CPU1.5~2.02.5CPU Suppress、CPU Burst内存1.0~1.21.3memory.high、远程内存回收、OOM优先级调整网络带宽不超卖-带宽限流磁盘IO不超卖-IO权重控制这里特别提醒一句磁盘IO和网络带宽经常被忽略但实际对在线服务干扰最明显的恰恰是这两个。BE任务在大量读写数据时如果IO权重不限在线服务的存储延迟会直线上升。混部完整方案里一定要把IO调度纳入进来。3.3 节点Agent的作用从静态调度走向动态回收K8s原生kubelet只管按requests和limits给Pod建cgroup它没有能力回应实际剩余多少资源这种问题。混部的第二个核心组件就是节点AgentKoordinator对应的是Koordlet它做三件事。第一采集真实用量。Agent会周期性地读取cgroup和系统指标算出每个在线Pod的实际CPU使用、内存使用、网络和存储IO整理成节点级的真实剩余资源。第二上报给调度器。Agent把可超卖容量和动态剩余容量以扩展资源的形式上报比如koordinator.sh/batch-cpu、koordinator.sh/batch-memory调度器调度BE Pod时只看这些扩展资源的余量。第三执行压制和回收。当在线任务负载上升、节点压力变大时Agent会降低BE Pod的CPU配额或者触发内存回收。这个动作不需要重新调度秒级生效。这第三点特别重要——在线任务负载飙升的瞬间指望调度器把BE Pod迁走是不现实的太慢。只有节点Agent能快速压制BE任务给在线任务让出路径。所以混部系统的架构本质是控制面管计算节点Agent管执行。K8s资源调度的优化到这一步实际上已经演变成控制面调度 节点级动态治理的协同架构了。4. 性能保障与稳定性控制压得住离线护得住在线混部改造最怕的不是技术方案选型而是上线后一个午高峰P99延迟直接飙红。压得住离线任务、护得住在线任务比任何漂亮的调度策略都重要。这一章我把CPU、内存、干扰检测三块分开讲。4.1 CPU资源隔离为什么不能靠cgroup限流了事很多人觉得限制离线任务CPU很简单给它设个CPU limits超出就throttle。真这么做在线任务的延迟反而会受伤。原因是cgroup的CFS quota限流是启动/停止式的容器用完配额后会被强制挂起等下一周期再跑。这种反复挂起和换入换出会让CPU调度队列变得拥挤增加上下文切换。在线任务如果和BE任务在同一核上竞争P99延迟会显著恶化。混部方案里更有效的CPU隔离是组合拳绑核CPUSet把BE Pod绑定到指定物理核上不与在线任务抢核。绑核适合CPU密集型的BE任务比如AI训练、大数据计算效果最直接。优先级权重cgroup v2的cpu.weight可以设置在线和离线的CPU权重比例在线任务权重远高于BE调度器在竞争时优先满足在线任务。CPU Burst允许在线任务在预留配额之外临时借用闲置CPU弥补CFS周期性的短板。CPU Suppress节点Agent发现CPU整体压力超过阈值时直接压低BE Pod的CFS配额把CPU让出来。举个例子一个运行电商核心API的Podrequests2Climits2C平时可能只用0.5C高峰期瞬时冲到2.5C。如果没有CPU Burst2C的limit会直接限住它有了Burst它可以临时借用BE任务占用的CPU节点Agent同时压制BE任务保障这个API不被卡住。我踩过的一个坑是绑核策略和NUMA的交互。BE任务如果绑在某个NUMA节点上同时内存分配也落在同一NUMA性能很稳定但如果跨NUMA频繁访问内存离线任务性能会暴跌还会拖累同节点的在线任务。所以配置绑核一定要看拓扑别只盯着CPU核数匹配。提示绑核之前务必先检查机器的NUMA拓扑用lscpu或numactl --hardware确认物理核分布避免BE任务绑定到跨NUMA的核组。4.2 干扰检测与自愈轻微抖动时的主动规避即使做了上述所有隔离BE任务仍然可能通过共享的L3缓存、内存带宽、网络队列等方式干扰在线任务。经典场景BE任务在Spark shuffle阶段突然大量读本地磁盘又写远端网络队列被占满同节点在线服务的接口延迟从50ms涨到300ms。静态隔离拦不住这种看不见的干扰。因此成熟的混部方案都带干扰检测模块围绕几个关键指标CPU调度延迟runqueue latency在线Pod的线程在可运行队列里等待的时间。缓存缺失率LLC miss在线Pod的L3缓存命中率是否异常下降。内存带宽占用BE任务是否吃掉大量内存带宽。网络和磁盘延迟在线服务的包传输和存储IO延迟是否抬升。Koordinator的干扰检测策略可以配置成类似这样的逻辑如果连续30秒检测到在线Pod的调度延迟超过基线阈值就判定BE任务构成干扰自动执行压制或驱逐。实际配置通过slo-config这个ConfigMap管理里面可以定义针对不同QoS等级的干扰阈值和处置动作。指标基线怎么定很重要。我的做法是混部改造前先采集在线服务一两周的指标算出P99基线和波动区间再以基线作为干扰检测的参照。如果直接拍脑袋定一个绝对阈值比如调度延迟大于10ms就报警很容易误报因为在线任务本身偶发业务抖动也正常。自愈动作的梯度也要设计好别一步到位直接驱逐。我一般按三级处理第一级压低BE Pod配额第二级给BE Pod打上已被压制标记并降级第三级才驱逐重调度。给BE任务留出慢慢完成任务的时间总比在高峰期突然把BE Pod全部驱逐、造成节点资源骤变更可控。4.3 内存回收与缓存限制别让离线任务吃光Page Cache内存这块是混部翻车的重灾区。BE任务尤其大数据和AI训练会大量读文件把Page Cache吃得很厉害。在线任务如果也访问同样的数据集本来该命中Page Cache结果因为缓存被BE撑爆只能重新读盘延迟瞬间飙升。这个现象非常隐蔽因为CPU占用率看着不高但业务已经慢了。解决办法有几层设置memory.high作为BE Pod的软内存上限超过之后内核会积极回收该cgroup的内存不给硬杀进程的机会。远程内存回收把BE Pod的匿名内存换出或压缩把空闲内存还给系统。限制Page Cache占用比例不让BE Pod无限制膨胀缓存。调整OOM优先级确保系统内存紧张时最先被OOM掉的是BE Pod而不是在线Pod。Koordinator有专门的内存回收能力在检测到节点内存压力高时主动把BE Pod的匿名内存换出或者把Page Cache清掉。K8s原生没有这种机制这也是混部组件不可替代的原因之一。我强烈建议对BE任务做短时大内存申请压测模拟Spark计算中段突然请求10G内存的场景。不压一次你永远不知道节点Agent的回收策略反应有多快、是否可靠。5. 从方案到实践混部改造的路径、收益和风险前面讲完了原理最后这部分是真实项目里最值钱的经验怎么落地、收益怎么算、踩了哪些坑。5.1 一个真实的混部改造案例资源利用率从13%提升到38%我在上一家公司推进过一次混部改造背景是线上有80多个Deployment离线有零散的Spark和训练任务集团要求IDC成本同比降低20%。方案分三步走。第一步资源盘点与打标。把在线服务按重要性分成LSE和LS两档核心交易链路和支付网关标LSE普通内部服务标LS离线任务全部标BE。这一步花了大概两周主要是跟各业务方对齐你的服务真的需要LSE吗讨论过程占了大部分时间。第二步部署Koordinator控制面和Koordlet节点代理先在一个16台机器的节点组里试点。这个节点组我特意挑的是业务流量相对稳定的支付后端集群而不是流量峰谷最大的电商主站目的是控制试错影响面。试点期间只容纳Spark任务不开干扰检测的自动驱逐只记录不处置让数据先说话。第三步验证稳定后全量推广。逐步放开超卖比例、开启干扰检测的自动压制同时把监控面板共享给业务方让每个业务都能看到自己的P99有没有波动。推广节奏按节点组滚动进行每批观察两天。最终数据试点节点组CPU利用率从13%提到38%内存利用率从41%提到69%。集群整体物理机数量从30台压缩到18台IDC成本年化下降约18%。更难得的是大促流量高峰期间核心在线服务的P99没有出现过明显抬升。这个数据在行业里不算极致有些互联网大厂能做到利用率50%以上但对中型业务来说已经是显著改善了。5.2 关键收益明细与回本计算混部改造的收益不能只盯着利用率数字要算综合账。假设一台物理机以2路32核、256G内存为例年综合成本是X元混部省下12台机器一年就是12X的纯节省。再算投入Koordinator本身是开源组件没有软件采购成本主要投入是人力需要一个专门的混部运维角色或至少半个SRE的持续投入加上监控系统扩展干扰指标、每周复盘BE任务的资源画像。这个成本不低但相比省下的硬件开销通常一个季度以内就能回本。什么样的集群最适合上混部我总结了几条在线负载有明显波峰波谷夜间或低峰期有大量空闲窗口。有稳定的离线计算需求比如定时ETL、数据训练、报表跑批而不只是临时任务。多个业务线负载高峰不重叠。有足够的监控和告警能力支撑干扰检测。反过来如果集群负载已经长期维持在70%以上或者在线服务属于一点抖动都不能容忍的极实时业务比如高频交易撮合混部就别碰了。混部是给有闲置但账面满的集群准备的不是万金油。5.3 混部最容易被忽视的三个坑第一个坑BE任务突发大内存引发节点雪崩。我们踩过一次一个数据回填任务在计算中段突然一次性申请8G内存节点Agent回收速度没跟上直接触发OOM把同节点的在线Redis实例杀了。事后复盘根因是BE Pod没有设置内存limits且节点内存回收阈值配置过保守。解法是给所有BE Pod强制设置内存limits并把回收触发阈值从90%下调到80%留出足够缓冲。第二个坑压测数据掩盖真实业务波动。试点期间压测跑得很完美CPU利用率超过30%、P99毫无感知。结果正式流量一上来午高峰在线P99在连续几天里持续波动。排查发现压测流量是匀速分布的真实流量有突发性和周期共性BE任务在高峰期存活过多在线任务偶发资源需求时来不及腾地方。后来把超卖系数下调了20%并开启高峰时段BE任务自动降级策略才稳定下来。第三个坑业务方不信任导致方案推不动。技术问题都是小事组织问题才是大事。有业务方看到自己的节点上多了BE Pod情绪很大生怕被影响。我们后来做了两件事把BE Pod状态、压制事件、干扰检测记录全部透明化开一个只读监控面板让业务方随时自查同时明确写了一键暂停混部的应急预案。有了看得见的监控和可回退的方案业务方配合度大幅提升。5.4 可选的简化路径Volcano、云厂商托管方案与GPU扩展如果不想自己维护Koordinator也有简化路径。Volcano更偏批处理调度增强在在线离线混合的QoS管控上比Koordinator弱一些但对任务多、调度复杂的场景依然很实用。云厂商一般也提供托管式的Serverless混部或者节点池级混部方案适合不想投入太多运维人力的情况。我的态度是要么认真上混部组件并持续治理要么就别用节点池硬隔离。最怕的是半混部——只装了调度器没配节点Agent的压制和回收那种方案线上跑一周就会出问题。另外如果集群里有GPU资源混部还涉及显存隔离和GPU共享调度Koordinator也有对应的GPU方案。但通常建议先把CPU内存的混部跑通再考虑GPU混部因为GPU的显存不可压缩容错空间比内存还小。如果让我总结混部改造的经验我想说这件事的技术难度其实没有传说中那么大真正的门槛在于团队对资源利用率的理解是否形成了共识。Koordinator这类组件已经把调度、压制、回收、干扰检测都集成好了剩下的是运维流程和业务对齐。我个人实操中的体会是先别追求激进的超卖系数也别一上来全量推广用一个节点组跑三到四周把监控基线、告警规则、回收策略调到让业务方完全无感再逐步放量。另外确保任何时刻都保留一个一键全量驱逐BE任务的开关这个开关比任何监控都更让业务安心。混部这条路一旦跑通对成本优化的贡献是立竿见影的而它背后撬动的K8s资源调度逻辑升级也值得每位平台工程师深入理解。