云原生成本治理实战:账单拆解与资源优化,月账单降低30%

发布时间:2026/10/6 8:33:31
云原生成本治理实战:账单拆解与资源优化,月账单降低30%
做过云原生运维的朋友应该都有这种感受每到月底盯着云厂商账单里那一串数字CPU利用率却常年趴在个位数心里就特别不是滋味。微服务上了Kubernetes之后成本结构从“一台台虚拟机”变成了“一堆计算、存储、网络、负载均衡和托管服务杂费”哪里烧钱、哪里浪费光靠控制台首页根本看不清楚。今年我们团队专门做了一轮云原生成本治理没有砍功能、没有降SLA硬是把月账单压低了30%。这篇文章就是把这套从账单拆解、资源画像、弹性伸缩、存储网络再整治到自动化防反弹的完整过程记录下来包括每一步背后的思考以及踩过的坑。不管你是运维工程师、开发负责人还是刚接触FinOps的同事这轮实战里用到的工具、命令和配置都可以直接抄到自己的环境里试。1. 先搞清楚云原生账单里的钱到底花在哪1.1 账单结构拆解不是只有节点费用那点事从传统虚拟机迁到Kubernetes之后账单结构会一下子复杂很多。传统云主机无非就是计算、带宽、磁盘三大项而云原生环境至少分成四个层面第一个是基础设施层包括节点Node的计算资源、系统盘、数据盘、公网IP、负载均衡器第二个是集群控制面费用如果你用托管K8s会有一笔固定的管理费第三个是云上生态服务比如日志、监控、镜像仓库、对象存储、托管数据库第四个是业务层面一个Pod跑在节点上节点背后是云主机Pod里又挂PVC和Ingress但账单上不会直接写“这个Deployment花了多少钱”。我当时做的第一件事不是去找新的降本工具而是把过去三个月的账单按“计算、存储、网络、负载均衡、其他服务”五个维度全部拉出来先看占比。结果很典型计算占了60%左右存储和网络各占15%剩下10%是SLB、NAT、日志之类的杂费。计算费用是大头但注意存储和网络加在一起也有30%这两块如果不去治理就算把节点全部换成Spot账单也降不下来。拆完大类之后必须继续往下拆到工作负载。我们用的是OpenCost它能把Kubernetes里的命名空间、Deployment、Pod消耗的资源按云厂商的定价换算成金额再按标签归集。没有这个粒度你只能看到一个集群总价根本不知道是谁在烧钱。实际做完标签映射之后发现费用最大的几个服务不是我们想象中流量最高的核心业务而是几个常年没人管的后台任务、每天都自动创建一个SLB的测试环境、以及一批从不清理的快照。所以这里给第一个实用建议千万别上来就调节点规格先把账单按成本对象拆开。如果连自己的成本构成都说不清后面优化就是在瞎猜。1.2 隐藏浪费每个团队都在“多申请一点”云原生环境里最典型的浪费不是某一次操作失误而是所有人的“多申请一点”心态。开发同学写Deployment时普遍习惯把requests设成4C8G但实际业务可能只需要0.5C1G。这样做的根源有两个一是大家潜意识里觉得云资源是无限的申请多了总比不够好二是不太清楚Kubernetes里requests和limits的含义以为Pod申请了4C8G节点上就真占用了4C8G。实际上Pod的requests只是调度器的“占位符”它告诉调度器“这个Pod最少需要这么多资源请你派到一个剩余容量足够的节点上”。limits才是硬限制进程最多只能用这么多。如果requests虚高调度器会以为节点资源已经满了开始疯狂自动扩节点而真正跑在上面的Pod CPU和内存利用率却低得可怜。这种“隐性浪费”特别可怕因为每个服务看起来都不算过分合在一起却导致集群不断膨胀。我们当时统计过全部服务平均CPU利用率只有8%内存利用率不到30%但集群已经因为requests堆积而自动扩出了不少新节点。要改变这个现象必须把“申请量”和“实际用量”两套数据拉出来对比。Prometheus里现成的指标就是container_cpu_usage_seconds_total和container_memory_working_set_bytes。用这两个指标除以节点的可分配量每天算一次“集群资源利用率日报”发给各服务负责人。这一步根本不需要商业工具一行PromQL加一个表格就能做。很多团队卡就卡在“不知道实际用了多少”那成本优化自然无从谈起。2. 成本优化的三板斧规格、副本、伸缩策略2.1 给每个Deployment做资源画像重新设置requests和limits有了真实水位数据接下来就是一轮细致的资源画像工作。我们给所有核心服务重新确定了资源参数原则很简单requests取过去7天P95实际用量再乘以1.2倍limits取过去7天P99用量再乘以1.5倍。为什么要留这个余量因为直接贴着实际用量设置遇到流量毛刺很容易OOMKilled或CPU throttling。但也不能像原来那样留3到4倍余量那样优化等于白做。举一个真实案例。订单服务原来请求量是4C8G但实际P95只有1C2.5G。我们把它调整为requests1C3Glimits2C5G。很多人担心调低limits之后Pod会被杀其实要看哪个指标内存方面如果容器实际工作集超过limits会触发OOMKillCPU方面limits超了不会杀Pod只是把CPU时间片限制在配额内多余的请求会被限流延迟可能升高。所以内存limits要稍微保守一些而CPU limits可以按P99留足。改完之后盯着Prometheus观察了两周OOMKilled次数为零CPU限流次数从每秒几百次直接掉到零。这里的关键经验是不要一口气把所有服务全改了。我们使用Kustomize管理不同环境的资源覆盖先改测试环境做一轮压力测试再通过GitOps流程发布到生产。生产环境的覆盖单独放在一个overlay里每次只改两三个服务观察至少一周。如果同时调整的服务太多出了问题根本没法回溯是哪个参数引起的。2.2 副本数和自动伸缩HPA加VPA的配合副本数也是一个巨大的成本点。我们看过一批服务明明QPS只有个位数却为了“高可用”常年维持3到5个副本。这种情况下副本数跟本不需要那么高先降到2再把弹性交给HPA。HPA配置有几个关键参数容易踩坑。第一个是scaleDown的stabilizationWindowSeconds默认300秒会让缩容显得很慢但如果设得太短又容易在流量抖动时反复横跳。我们后来通过behavior字段分别控制扩容时等待窗口短一点60秒缩容时等待窗口长一点300秒。第二个容易被忽略的点是HPA必须基于容器的requests才能计算利用率。如果Deployment里没有写resources.requestsHPA采集到的CPU利用率数据就是空的配置了等于白配。再进阶一点我们用VPA来辅助判断“单Pod到底该给多少资源”。VPA的recommender模式只出建议不自动修改适合团队还没有完全信任自动化的阶段。它根据历史用量给出Recommended Requests我们每周看一次建议手工调整Deployment配置。VPA和HPA并不冲突VPA管单Pod资源HPA管副本数但官方不建议让VPA和HPA同时基于同一个指标否则两者会互相打架。我们后来把VPA建议应用到资源上之后HPA只单独管CPU内存不参与HPA效果稳定很多。2.3 用好竞价实例Spot和弹性节点池如果集群里所有节点都是按量付费成本优化永远谈不上彻底。云厂商的竞价实例Spot Instances折扣通常可以达到50%到70%但风险在于随时可能被回收。哪些负载适合跑在Spot上无状态Web服务、异步任务Worker、CI构建节点、大数据批处理这些都可以。不适合的包括有状态数据库、依赖本地持久化磁盘的服务、以及需要长期连续运行的关键业务。我们当时先把测试环境全部迁移到了Spot节点池节点费用直接省了近一半。生产环境也建了一个Spot节点池配合PodDisruptionBudget和拓扑分布约束让无状态服务混部上去。具体做法是给节点池打spottrue的标签在Deployment里用nodeSelector选择再用podAntiAffinity确保同一服务的副本分布在不同节点。如果某个服务是消息消费者Spot回收时可能会中断处理需要单独评估是否能接受。还有一点很重要Spot节点的价格在不同可用区之间是波动的最好使用云厂商原生的节点组混合模式让它自动选择最低价的可用区去调度。生产环境不要把所有节点都换成Spot至少要保留一个小的按量节点池做兜底。这样当Spot池被大规模回收或者资源不足时HPA扩容出去的Pod会落到按量池里虽然成本会高但至少服务不会悬空。3. 存储、网络和负载均衡的隐藏成本3.1 存储优化别让快照和云盘成了隐形黑洞云原生的存储成本往往比想象中高很多。我们之前每个Deployment都挂独立的云盘有些只是用来写日志而且每个环境都开启了每小时快照保留7天。月底一看快照费用居然跟云盘正本差不多。后来我们做了三件事效果立竿见影。第一调整持久化方案。日志、临时文件这类数据尽量不要持久化到云盘改为上报到对象存储或者ElasticsearchPod本身使用emptyDir就够了。第二按实际IO需求选云盘类型。普通业务用通用型SSD甚至HDD只有高频数据库才需要高性能SSD不要所有盘都默认最高配置。第三重新设计快照策略保留3天内的每小时快照、7天内的每日快照、30天内的每周快照把那些没用的长期快照全部清理掉。这几项做完存储账单缩水了40%。这里有一个Kubernetes的固有坑PVC扩容只能大不能小。一旦PVC创建时指定了容量后面想缩小就必须重建PVC并迁移数据非常麻烦。所以我们要求所有新建PVC先做需求评估不要图省事直接给100GB。如果需要缩减只能通过新PVC加数据迁移的方式处理这会涉及业务中断一定要提前规划好维护窗口。3.2 网络流量和负载均衡SLB不是白菜价网络费用的两大来源分别是负载均衡器和公网流量。很多团队忽视了云厂商负载均衡器的计费方式通常按LCULoad Balancer Capacity Unit计费也就是按并发连接数、流量、规则数综合计算。如果你给每个微服务单独创建一个SLB费用会成倍上升而且这些SLB很多还只是给内部调用用完全没必要。我们做的第一件事就是合并入口。Kubernetes集群内统一使用Nginx Ingress而不是给每个服务单独挂云负载均衡器。Nginx Ingress部署在集群内部前面只需要挂一个SLB之后通过Ingress资源把不同域名或路径转发到不同Service。这样虽然多了一层Nginx转发但省掉了大量的SLB实例费。如果你特别担心Nginx成为瓶颈可以给Ingress Controller单独做HPA或者直接使用云厂商的ALB Ingress Controller但千万不要回到“每个服务一个SLB”的老路。公网流量部分我们规定内部调用一律走内网DNS数据库连接串全部改成内网地址。有些外部调用必须经过公网时尽量在出口侧做统一NAT网关并且给大文件下载走对象存储的CDN而不是从应用服务器转发。另外那些流量型服务建议开启云厂商的TCP优化和流量压缩能有效降低出网流量费用。这块优化完成后网络费用整体下降了25%。4. 成本可视化、预算告警和自动化治理4.1 用OpenCost/Kubecost把成本分账到业务线光看云厂商账单最多只能定位到云主机级别没法关联到具体Deployment。我们需要的是让每个业务负责人打开面板就能看到自己这个月烧了多少。开源工具OpenCost正好做这件事它从Prometheus拉取容器资源指标再按照配置好的云定价表计算成本最终按命名空间、Deployment和标签汇总。我们在集群里部署了一套OpenCost配合Grafana做成本大盘每几个小时刷新一次能看到每个命名空间、每个Deployment在24小时内的成本排行。后来也试过Kubecost它在网络成本分摊和多集群聚合上更强但核心思路完全一致。最关键的是我们给每个命名空间设了一个预算基线比如某业务线每月预算8000块超过基线的20%就触发告警。这个动作带来的心理效应非常明显以前“没人觉得自己用了多少”现在每个服务负责人都开始主动盯自己的成本曲线。这里有个很现实的坑成本数据允许有一定误差但不能失真。OpenCost里的节点单价默认是目录价而企业实际购买通常有折扣。我们要在配置里把折扣率填进去或者定期用真实账单校准。否则优化一段时间后你会发现工具上显示的成本和云厂商账单对不上团队会立刻对工具失去信任后面的治理就推不动了。4.2 用Prometheus和Alertmanager做成本异常告警成本告警不能等到月底看账单必须实时感知。我们自己在Prometheus里构造了一个监控指标比如每个命名空间的日成本估算然后设置两类告警。第一类是相对告警当天成本比近7天日均成本增长超过50%并且持续2小时触发warning。第二类是绝对告警某个命名空间的周成本超过了预算的80%就发出通知。通知统一走Alertmanager推到运维值班群关键成本项直接电话告警。告警触发后我们一般会看两条线。一是流量是不是真的上涨了比如拉了促销活动二是是否有异常的Pod副本数。通过PromQL查一下Deployment的期望副本数和当前副本数如果发现副本数暴增很可能是HPA配置的指标被某个突发流量打爆或者某个服务在大量重试。我们后来制作了一个“成本巡检单”把常见嫌疑项列出来比如新发布的服务没写资源限制、某条HPA被误删、上一周的Pod还卡在Pending状态导致额外节点被拉起。按这个清单过一圈基本能快速定位问题。4.3 用Ansible和Kustomize统一管理资源配置防止反弹成本优化最怕的不是优化不动而是优化完之后“反弹”。今天把某个服务从4C8G降到1C2G明天开发为了排查问题又临时改成8C16G月底一看还涨了。要从机制上防住反弹必须把资源配置纳入标准发布流程。我们用Kustomize管理多环境配置生产环境的overlay固定了每个服务的requests和limits不允许在仓库外手工改。CI流水线里加了一个资源校验脚本扫描待发布的YAML文件如果发现没有resources字段或者requests明显大于历史P95用量就直接打回让开发重新填写。这个脚本用Python写大概几十行本质是读YAML加比对Prometheus数据。另外还用Ansible写了一套巡检剧本定期在集群里扫描各种“僵尸资源”没有设置resources的Pod、超过30天未使用的PVC、副本数已经为0但仍占用负载均衡器资源的Service。Ansible直接调用Kubernetes API获取信息再把结果汇总成周报告。这套流程坚持了一个月之后新上线的服务默认自带合理资源配置几乎没有再出现大规模的规格反弹。5. 实战中的坑与排查经验速查表5.1 调整limits后服务变得很慢怎么办这是降配之后最常遇到的问题。当你把limits从高的值压下来时CPU限流throttling会把Pod可使用的CPU时间片切断表现就是服务延迟明显上升、吞吐量下降。排查时看Prometheus指标container_cpu_cfs_throttled_periods_percentage如果长期高于5%说明limits设得太紧了。解决方法有两条一是把limits往上调留出更多余量二是去掉CPU limits只保留requests。去掉limits要谨慎因为它会破坏QoS等级还可能导致某个Pod跑满节点影响邻居。我们的建议是分批次调整第一周只降内存limits第二周再降CPU limits。如果同时降两个维度出了问题很难定位是哪一个引起的。对Java类服务要额外注意。JVM默认认为容器内存就是物理机内存如果不显式设置堆大小它可能会根据节点内存自动计算堆的初始值导致容器OOMKilled。解决办法是在启动参数里显式配置-Xmx比如容器内存limits是4G就把-Xmx设为2G或3G留一部分给元空间和线程栈。我们当时有个服务连续OOM了三次排查半天才发现是JVM自适应堆导致的。5.2 HPA扩缩容抖动怎么处理HPA抖动通常表现为副本数忽多忽少不仅影响服务稳定还会让云节点费用产生不必要的波动。常见原因有三个一是指标采集周期太短Prometheus默认15秒抓一次而HPA默认每15秒评估一次指标一波动就会误判二是突发流量触发了扩容但稳定后又被立即缩容形成震荡三是Pod启动时间本身长副本还没就绪就被认为闲置触发了缩容。应对方案有三个。第一把HPA评估周期调长比如--horizontal-pod-autoscaler-sync-period设为60秒。第二对业务指标先做rate聚合窗口拉到5分钟均值再作为HPA指标过滤掉秒级抖动。第三配置behavior参数扩容时等待窗口可以短一些60秒缩容时等待窗口长一些300秒并且限制每次缩容的副本数量比如最多缩1个。这样即使流量突然下去副本也不会疯狂缩水下一波高峰到来时还能有足够余量。5.3 Spot实例回收导致Pod重建风暴Spot节点被云厂商回收之前通常会提前两分钟发出中断通知。如果没处理这个通知Pod会被直接强杀严重时会有一大片Pod同时重建集群内Pod数量瞬间翻倍造成极大的调度压力。我们当时部署了一个DaemonSet来监听Spot中断通知收到之后立刻对节点打污点taint同时给Pod设置优雅终止宽限期让服务有足够时间完成请求处理并退出。另一个问题是Spot节点池如果资源不足新调度出来的Pod会落到按量节点池成本瞬间反弹。要能从监控上区分这个现象我们对节点池打了标签在成本大盘里单独展示按量池上的Pod数量。看到按量池Pod数量异常上涨就知道该扩容Spot池了。我们不建议为省钱完全关闭按量池生产环境至少保留一个很小的按量池给关键Pod做兜底否则一次大规模Spot回收就会拖垮一套服务。6. 我个人的一点体会折腾完整轮成本优化之后给我留下最深印象的其实不是那30%的数字而是整个协作方式的改变。最初我们只是把成本报表发给各业务线对方第一反应是“这是不是算错了”。等我们把OpenCost的拆分逻辑和Prometheus真实用量截图一起发过去并告诉他们“你这个服务请求量是实际使用量的4倍”时业务负责人才开始认真对待。后来好几个团队主动跑来问怎么调整参数因为看到了自己服务的成本排行也体会到了降本带来的预算空间。关于优化节奏我想多说一句宁慢勿快。我们每两周做一轮每轮只触碰一部分服务改完至少观察一周。虽然整体花了两个月时间但没有因资源调整引起过一次故障。这种“慢”换来的信任比账面上那30%更珍贵。如果谁想一口气把所有服务全部降配大概率会在某个繁忙时段出问题反而让团队对成本优化产生抵触。最后再分享一个小技巧。如果你还没搭建Kubecost或OpenCost可以先写几个PromQL挂到Grafana上把每个命名空间的CPU和内存请求量与使用量趋势展示出来。当一个数字开始变得可测量、可比较时所有人都会自动去关注它。这是一种机制上的成本意识比任何一条优化命令都管用。