容器编排:调度算法的“贪婪”与一致性的“妥协”——我为什么最终放弃了Docker Swarm

调度器:一个不可能三角的暴力美学

我第一次真正细看Kubernetes调度器的源码,是在凌晨三点排查一个集群雪崩。Pod无故Pending,节点资源充裕——但调度器就是不给放。后来发现是扩展资源(Extended Resource)的整数溢出。那个瞬间我对着屏幕骂了句脏话,然后苦笑。真的,调度器这个东西,说穿了就是在一个多维约束下找最优解,但工程实现里全是妥协。

Scheduler的Predicates阶段,本质是硬过滤。节点端口冲突、卷挂载限制、亲和性规则没过,直接 fail。什么?你说资源够?没用,规则是死的。然后Priorities阶段,一堆权重函数算分:LeastRequestedPriority给你资源最空的那个,BalancedResourceAllocation看CPU和内存配比——嘿,你以为这就公平了?实际生产里磁盘IO压力、网络带宽这些它根本不看。这就导致一种诡异场景:调度器自认为优选的节点,上去跑个数据库直接IOPS打满。我用实际集群做过压测,三台节点,每台NVMe,但有一台控制器固件有bug,4K随机写会衰减到200 IOPS。调度器一无所知,把CockroachDB Pod全调度上去了,结果你猜怎么着——整个集群延迟从2ms飙到900ms。

Kubernetes调度器Predicates和Priorities流程架构图
Kubernetes调度器Predicates和Priorities流程架构图

不过话说回来,这个框架的扩展性是真不错。你可以自己写Scheduler Extender,或者用调度框架的插件在Score阶段注入你的自定义指标。我们后来就接入了Prometheus的node_disk_io_time,写了个简单插件,让调度器能感知实时IO负载。这活儿不难,但很多团队想不到。哦对,要警惕一个坑:自定义Priorities权重如果设太高,会压过默认函数,导致集群资源严重不均衡。我有次手滑设了100,结果所有Pod全堆到一台节点上,差点搞崩集群。

状态一致性的代价:etcd不只是“高可用KV”

很多人以为容器编排的问题出在容器本身。错。本质是分布式状态管理。Kubernetes把所有对象——Pod、Service、ConfigMap——序列化成Protobuf存进etcd。说起来轻巧,但当集群到5000个节点、20万个Pod时,etcd的写放大能让你怀疑人生。我们有一次大规模发布,Deployment滚动更新,API Server疯狂往etcd写事件。etcd是Raft共识,每次写入要fsync到多数派磁盘。机械盘?别做梦了,SSD也扛不住。那次我们用的企业级SATA SSD,延迟p99瞬间从2ms飙升到800ms,API Server全堵住,kubectl get pods卡死30秒。

etcd Raft日志复制与压缩机制示意图
etcd Raft日志复制与压缩机制示意图

数据说话:事后分析,etcd的写吞吐从平稳3000 ops/s掉到500 ops/s,而当时实际写入需求是8000 ops/s。怎么解决的?首先,切分事件TTL,把Event对象保留时间从1小时降到15分钟,减少数据量。然后启用etcd的并发压缩和碎片整理。最关键是——把etcd挪到单独裸金属节点,用NVMe且RAID 0,打开-no-sync选项(别学我们,生产没敢开)。优化后写吞吐回到12000 ops/s。但在公有云用托管集群的,你得留意一下etcd存储类型,不少厂商默认SSD其实是远端网络盘,延时比本地SATA还差。

还有一个小道消息:如果你在用v1.21之前的版本,watch缓存有内存泄漏bug,长时间运行的集群内存被吃到swap,etcd响应时间直接爆炸。我给社区提过issue,修复patch在v1.21.2。

落地三坑:资源预留、CNI迷思、自动伸缩的幻觉

落地三坑:资源预留、CNI迷思、自动伸缩的幻觉
落地三坑:资源预留、CNI迷思、自动伸缩的幻觉

坑一:内存Limit不设或瞎设。Java应用的JVM堆默认1/4物理内存,容器没设Limit,以为自己有32G,结果节点实际只有16G。OOM Killer一来,Pod重启到你崩溃。解法?Requests和Limits必须同时设,且Java用-XX:MaxRAMPercentage=75.0动态感知容器内存。我看过太多事故报告,根因就是这。压测数据:一个Spring Boot服务,设了Limit=2Gi,内存实际占用稳定在1.6G,不设Limit时峰值能撑到3.8G,然后把同节点MySQL Pod干掉了。

Kubernetes Pod OOMKilled事件监控面板截图
Kubernetes Pod OOMKilled事件监控面板截图

坑二:CNI选型想当然。用Flannel的vxlan模式,以为简单可靠。结果跨节点Pod间TCP吞吐量从物理网卡限速9.4Gbps掉到2.3Gbps,因为vxlan封装带来30%以上的CPU开销和MTU问题。换成Calico BGP模式后,吞吐回到8.7Gbps。但Calico BGP对网络设备有要求,二层网络下的子网搭配不当会造成路由黑洞。我们曾在某次idc割接时,因为ToR交换机未开启BGP graceful restart,导致全网Pod IP不可达15分钟。真的,血泪教训。别迷信某种CNI,要基于你的宿主机网络拓扑实测iPerf3。

坑三:HPA基于CPU的坑。很多团队以为设了HPA就万事大吉,但CPU指标太粗糙。一个Go程序,GC瞬间CPU飙到100%,HPA立刻扩容,等Pod起来后CPU回落,又缩容,如此往复——抖动。我们用Custom Metrics适配Prometheus的请求延迟P99,平滑多了。还有一个细节:缩容策略默认5分钟稳定窗口太短,会导致频繁地变配,对数据库连接池友好吗?不。我们改成10分钟,并配合PodDisruptionBudget。

写到这,突然想起第一次在生产切K8s时的豪言壮语:“三个月搞定,半年优化完”。现在看,光调度器那部分就断续搞了八个月。容器编排不是银弹,它是工程的另一种泥潭。但看着200+个微服务在几千个节点上安静运行,还是很爽的。对吧?

免责声明:市场有风险,选择需谨慎!此文仅供参考,不作买卖依据。如有侵权请联系删除。
文章名称:容器编排:调度算法的“贪婪”与一致性的“妥协”——我为什么最终放弃了Docker Swarm
文章链接:https://lfdjt.com/info_23_7744.html