![]()
2021年,亚马逊云上的某个节点第47次触发PLEG超时告警。运维团队冲进控制台,发现CPU占用率0%,内存充足——但Docker守护进程已经僵死,像被按了暂停键的录像带。没人知道为什么。
这个Bug拖了3年,横跨AWS、Linux内核、Kubernetes三个技术栈,最终逼一家数据巨头自研了一套调度系统。今天的故事关于IO隔离,也关于为什么"重启试试"有时候是句废话。
01 一个设计假设,埋了3年的雷
Kubernetes的调度器有个盲区:它看得见CPU和内存,对磁盘IO和网络带宽完全失明。设计文档里没写,但代码逻辑很诚实——IO资源被假设为无限,或者由外部系统代管。
对普通Web应用,这个假设成立。但当平台开始承载TB级数据管道、分布式数据库时,问题变得尖锐。某次生产事故中,一个Spark批处理作业把节点磁盘吃满,隔壁Pod的MySQL查询从10ms飙到30秒。
更阴险的是连锁反应。高IO负载触发内核级阻塞,Docker守护进程卡住,Kubelet的PLEG(Pod生命周期事件生成器)检测超时,直接判定节点死亡。CPU明明空闲,节点却被驱逐——系统像被自己的反射弧绊倒。
运维手册写了"重启Docker"的SOP,但执行成功率不到四成。系统CPU飙高时,Docker进程连启动都完不成。 reactive recovery(被动恢复)成了掷骰子。
![]()
02 甩锅链:从AWS到Linux再到K8s
团队最初怀疑AWS的Xen虚拟化层。老实例类型(m4/c4/r4)的dom0用软件处理磁盘IO,可能产生严重的CPU steal time。迁移到Nitro架构的新机型后,问题频率下降,但没消失。
转向Linux内核。cgroups v1的blkio控制器能限制磁盘带宽,但实现粗糙——它按设备号统计,对现代存储栈(设备映射、多队列NVMe)形同虚设。网络侧更糟,tc(流量控制)规则与K8s的CNI插件打架,配置一次需要手工遍历节点。
2020年,团队做了一个重决策:押注cgroups v2。新版本的IO控制器支持io.max接口,能按cgroup精确限制读写带宽。代价是全面升级K8s v1.22,重构节点初始化流程,相当于给飞行中的飞机换引擎。
升级花了14个月。期间需要维护两套节点池,旧集群的PLEG超时仍在发生——第23次、第31次、第38次。每次事故报告都多一行备注:"等待cgroups v2就绪"。
03 与英特尔合作:把调度器改造成IO感知
内核层解决后,调度层仍是盲区。标准K8s scheduler extender(调度扩展器)只能过滤,不能精确预留IO容量。团队找到英特尔的开源团队,共建了一套定制方案:
![]()
调度器插件在打分阶段引入IO维度,读取节点agent上报的磁盘/网络剩余容量;节点agent实时采集cgroup v2的io.stat数据,动态计算可分配额度;当Pod申请突发IO时,agent用io.max和tc进行硬限制,而非软驱逐。
测试集群跑了8个月验证。设计文档经过4个跨职能团队签字:基础设施、数据平台、SRE、安全。回滚策略写明了3种触发条件和自动化脚本——这是生产系统的入场券,不是技术Demo的谢幕词。
验证完成时,平台已具备安全迁移有状态负载的技术条件。数据库、消息队列、缓存系统终于可以离开裸机,搬进K8s。
04 比技术更难的事
回顾这3年,作者在技术博客留下一段话:「最大的挑战不是写代码,是让四个团队相信这个问题值得3年投入,是说服领导层在'能跑就行'的压力下批准重构,是让每次事故复盘都指向根因而非临时补丁。」
IO隔离方案最终没有开源。它绑定特定内核版本、依赖英特尔硬件特性、嵌入公司内部调度逻辑——这是一套"只能我们用"的系统。但设计文档和踩坑记录被整理成内部知识库,作者希望"让努力不被遗忘"。
2023年,某次内部技术分享会上,一位SRE提问:如果重来一次,会不会选择直接买托管数据库服务,绕过整个K8s有状态化改造?作者停顿了几秒,没有回答。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.