去年,公司让我牵头做云成本优化,也就是FinOps。

一开始我觉得这事儿不难,不就是看看哪里资源浪费了,缩一缩容,优化一下配置嘛。真正做起来才发现,云原生环境下的成本管理,比想象中复杂得多。Kubernetes的弹性、微服务的拆分、各种云服务的计费方式,让成本优化变成了一个系统工程。

这一年,踩了不少坑,熬了不少夜,也交了不少学费。这篇文章,我想把这些踩坑经历分享出来,从资源浪费、成本分摊、弹性伸缩到供应商谈判,聊聊云原生FinOps的真实体验。希望能给正在做或者打算做成本优化的朋友一些参考。

坑一:以为资源利用率低就是浪费

第一个大坑,是对资源利用率的误解。

刚开始做FinOps的时候,我第一件事就是看集群的资源利用率。一看,CPU平均利用率才15%,内存才30%,心想这浪费也太严重了,赶紧缩容。

结果缩容之后,问题来了。业务高峰期的时候,资源不够用了,服务响应变慢,甚至出现了OOM(内存溢出)。用户投诉,领导问责,我只好又把资源加回去,白忙活一场。

后来才明白,云原生环境下,资源利用率低不一定是浪费。原因有几个:

第一,Kubernetes的资源请求(requests)和实际使用是两回事。requests是调度的依据,实际使用可能远低于requests。但如果把requests设得太低,调度的时候会把太多Pod放到一个节点上,高峰期就会资源争抢。

第二,业务有波峰波谷。平均利用率低,不代表高峰期也低。比如电商业务,白天和大促的时候资源使用率很高,凌晨就很低。如果按平均利用率来缩容,高峰期肯定扛不住。

第三,有些资源是为了高可用预留的。比如多可用区部署、冗余副本,这些都会降低平均利用率,但能保证服务的稳定性。为了省成本把冗余去掉,一旦出问题,损失的钱可能比省下来的多得多。

正确的做法是,不能只看平均利用率,要看P95、P99的利用率,也就是高峰期的利用率。如果高峰期的利用率也很低,那才是真的浪费。还要区分不同的业务类型,无状态服务可以弹性伸缩,有状态服务和核心业务要预留足够的资源。

我们后来的做法是,按业务线和服务类型分类,分别看利用率。对于无状态、可弹性伸缩的服务,设置合理的requests和limits,配合HPA(水平Pod自动伸缩),高峰期自动扩容,低峰期自动缩容。对于核心的有状态服务,预留足够的资源,不盲目缩容。这样既保证了稳定性,又降低了成本。

坑二:成本分摊算不清楚

第二个大坑,是成本分摊。

公司有多个业务线,共用一个Kubernetes集群和云服务。老板想知道每个业务线花了多少钱,哪个业务线成本高,需要优化。这就需要做成本分摊。

一开始我以为成本分摊很简单,把总费用按资源使用量比例分一下就行了。实际做起来才发现,问题很多。

第一个问题是,共享资源怎么分。集群的基础设施(节点、网络、存储)是共用的,有些服务用得多,有些用得少,怎么公平分摊?还有一些公共服务(日志、监控、网关),是所有业务线都在用的,这些成本怎么分?

我们一开始按Pod的CPU和内存requests来分摊节点成本,按存储使用量分摊存储成本,按流量分摊网络成本。公共服务的成本,按各业务线的Pod数量平均分摊。

但这样分了之后,业务线的人不满意。他们说,公共服务他们用得少,凭什么按Pod数量平均分?还有,有些业务线的服务是IO密集型的,有些是CPU密集型的,统一按CPU和内存分,不公平。

第二个问题是,闲置资源算谁的。集群里总有一些闲置的资源,比如预留的缓冲、未分配的节点。这些闲置资源的成本,算到谁头上?如果算到平台团队头上,平台团队会觉得冤;如果分摊到业务线,业务线会说不是他们造成的。

第三个问题是,云服务的计费方式复杂。云服务的计费方式多种多样,有按量付费、包年包月、预留实例、竞价实例,还有各种折扣和优惠。不同的计费方式,成本差异很大。怎么把这些复杂的计费,准确地分摊到每个业务线,是个头疼的问题。

我们后来的解决方案是,引入了FinOps工具(用的是OpenCost,开源的Kubernetes成本监控工具),它能精确到每个Pod、每个服务的成本。共享资源按实际使用量分摊,公共服务按调用量或者资源占用分摊。闲置资源单独列出来,由平台团队负责优化,不分摊到业务线。云服务的费用,通过云厂商的账单API获取,按标签(Tag)分摊到业务线。

成本分摊做清楚之后,每个业务线都能看到自己的成本,有了优化的动力。之前大家对成本不敏感,现在看到自己业务线的账单,主动开始优化了。

坑三:弹性伸缩用不好反而更贵

第三个大坑,是弹性伸缩。

Kubernetes的HPA(水平Pod自动伸缩)和集群自动伸缩(Cluster Autoscaler),理论上能根据负载自动调整资源,高峰期扩容,低峰期缩容,节省成本。

但实际用起来,弹性伸缩如果配置不好,反而更贵。

第一个问题是,扩容不及时。HPA默认是根据CPU利用率来扩容的,当CPU达到阈值时才开始扩容。但Pod启动需要时间,尤其是Java应用,启动可能要几十秒甚至几分钟。这时候业务已经在高峰期了,资源还没加上来,用户体验就受影响了。

我们一开始用默认的HPA配置,结果大促的时候,流量上来了,Pod还在启动中,服务响应变慢。后来改成了基于自定义指标(比如QPS、队列长度)的扩容,并且设置了预热机制,在高峰期到来之前提前扩容,才解决了问题。

第二个问题是,缩容太激进。缩容的时候,如果一下子把太多Pod删掉,可能会导致剩余的Pod压力过大,甚至出现雪崩。我们有一次就是,低峰期HPA一下子缩了一半的Pod,结果突然来了一波流量,剩下的Pod扛不住,全挂了。

后来我们设置了缩容冷却时间,并且限制了每次缩容的比例(每次最多缩20%),避免缩容太激进。还设置了PodDisruptionBudget(PDB),保证缩容的时候至少有一定数量的Pod在运行。

第三个问题是,节点伸缩的成本。集群自动伸缩会根据Pod的调度需求,自动增加或减少节点。但云服务器的计费是按小时算的(即使按量付费,很多也是按小时计费),如果频繁地增加和删除节点,可能会产生更多的费用。

比如,一个节点用了40分钟就删掉了,还是要按1小时计费。如果一天内频繁地增删节点,成本可能比保持稳定的节点数还高。

我们后来的做法是,设置了节点的最小数量和最大数量,避免节点数波动太大。对于可预测的负载(比如白天高、凌晨低),用定时伸缩,在高峰期之前提前加节点,低峰期之后再减节点。对于不可预测的负载,用自动伸缩,但设置了冷却时间和伸缩步长。

第四个问题是,状态服务的伸缩。无状态服务伸缩很简单,加Pod减Pod就行。但有状态服务(数据库、消息队列、缓存)伸缩很复杂,涉及到数据迁移、主从切换、一致性保证等。我们一开始想对数据库做自动伸缩,结果差点把数据搞丢了,后来再也不敢对核心的有状态服务做自动伸缩了,都是手动操作,或者用云厂商的托管服务(比如RDS),让云厂商来处理伸缩。

经验总结:弹性伸缩是好东西,但要用好需要仔细配置。要考虑扩容的及时性、缩容的稳定性、节点的计费方式,以及服务的类型。不要盲目开自动伸缩,要根据业务特点来配置。

坑四:存储和网络成本被忽视

第四个大坑,是存储和网络成本。

做FinOps的时候,大家最关注的是计算成本(CPU和内存),因为这部分占比最大。但实际上,存储和网络成本也不容忽视,尤其是在云原生环境下。

第一个问题是,存储成本。Kubernetes里用了很多持久化存储(PVC),比如数据库的数据盘、日志盘、备份盘。这些存储是按容量和时间计费的,用得越久,费用越高。

我们一开始没太关注存储成本,直到有一次看账单,发现存储费用占了总费用的20%,才引起重视。仔细一查,发现很多PVC是之前测试的时候创建的,后来不用了,但没有删掉,一直在计费。还有一些日志盘,保留了半年的日志,但实际上只需要保留一个月。还有一些备份,存了很多份,其实不需要那么多。

我们后来做了几件事:清理无用的PVC,设置存储的生命周期策略(日志自动过期删除),优化备份策略(减少备份份数,用更便宜的存储类型),把冷数据从高性能存储迁移到低成本存储。这几项做下来,存储成本降了40%。

第二个问题是,网络成本。云服务的网络流量是收费的,尤其是跨可用区、跨地域的流量,费用不低。在云原生环境下,微服务之间的调用很多,如果服务部署在不同的可用区,跨可用区的流量费用会很高。

我们一开始没注意这个问题,后来看账单,发现网络费用占了15%。排查之后发现,主要是微服务之间的跨可用区调用产生的流量费。我们的服务是多可用区部署的,为了高可用,但服务之间的调用没有做可用区亲和性,导致很多调用是跨可用区的。

后来我们做了服务拓扑优化,在Kubernetes里设置了拓扑分布约束(Topology Spread Constraints),让相关的服务尽量部署在同一个可用区,减少跨可用区的流量。同时,用了服务网格(Istio)的流量管理,让调用优先在同可用区内完成。这样调整之后,网络费用降了30%。

还有一个问题是,外网流量。云服务的出网流量是收费的,而且不便宜。我们有一些服务,会从对象存储(OSS/S3)下载大量数据,产生了很多外网流量费。后来我们把服务和存储放在同一个地域,用内网访问,就不收流量费了。还有一些静态资源,用了CDN缓存,减少了回源的流量。

经验总结:做FinOps不能只看计算成本,存储和网络成本也要关注。定期清理无用的存储,优化存储类型和生命周期,减少跨可用区和外网流量,这些都能省不少钱。

坑五:预留实例和竞价实例用不好

第五个大坑,是预留实例和竞价实例。

云厂商为了吸引用户长期使用,提供了预留实例(RI/Reserved Instance)和节省计划(Savings Plan),价格比按量付费便宜很多,一般能省30%-50%。还有竞价实例(Spot Instance),价格更便宜,能省60%-90%,但随时可能被回收。

这些折扣实例如果用好了,能省很多钱;但用不好,反而会浪费钱。

第一个问题是,预留实例买多了。预留实例需要承诺使用1年或3年,提前付费或者按月付费。如果买多了,用不完,就浪费了。我们一开始为了省钱,买了很多预留实例,结果后来业务调整,有些服务下线了,预留实例用不完,又不能退,白白浪费了不少钱。

后来我们的做法是,先做容量规划,根据业务的增长预期,确定稳定的资源需求,只对这部分买预留实例。对于波动的、不确定的资源,用按量付费或者竞价实例。预留实例的购买也分批次,不要一次性买太多,留一些调整的空间。

第二个问题是,预留实例的灵活性。预留实例有很多限制,比如实例规格、地域、操作系统。如果买了特定规格的预留实例,后来想换实例规格,就用不上了。我们后来选了可转换的预留实例(Convertible RI),虽然折扣少一点,但可以在一定范围内转换实例规格,更灵活。

第三个问题是,竞价实例的稳定性。竞价实例价格便宜,但随时可能被云厂商回收(因为别人出更高的价格)。如果把核心业务跑在竞价实例上,被回收的时候服务就会受影响。

我们有一次就是,把一个不太重要的服务跑在竞价实例上,结果大促的时候,竞价实例被大量回收,服务实例数不够,影响了用户体验。后来我们规定,核心业务和有状态服务不用竞价实例,只有无状态的、可容错的服务(比如批处理、CI/CD、测试环境)才用竞价实例。

用竞价实例的时候,还要做好容错处理。比如,用多实例类型的竞价实例(一种被回收了,用另一种),设置好中断通知的处理(收到中断通知后,优雅地迁移服务),配合集群自动伸缩,在竞价实例被回收时自动补充按量实例。

经验总结:折扣实例是成本优化的重要手段,但要根据业务的稳定性和可预测性来合理使用。稳定的资源用预留实例,波动的资源用按量付费,可容错的非核心服务用竞价实例。不要为了省钱而牺牲稳定性。

坑六:优化了成本但影响了效率

第六个大坑,是成本优化和研发效率的平衡。

做FinOps的时候,很容易陷入一个误区:为了省钱,过度限制资源,结果影响了研发效率。

比如,我们一开始为了降低测试环境的成本,把测试环境的资源缩得很小,而且晚上和周末自动关机。结果开发人员加班的时候,测试环境用不了,或者环境太卡,测试跑不动,影响了开发进度。开发人员怨声载道,说为了省那点钱,耽误了项目进度,得不偿失。

还有,我们为了控制成本,设置了资源申请的审批流程,开发人员申请资源需要审批,有时候审批慢了,影响了项目上线。开发人员觉得流程太繁琐,效率太低。

后来我们反思,FinOps的目标不是把成本降到最低,而是在成本和效率之间找到平衡。成本优化不能以牺牲研发效率和业务发展为代价。

我们调整了策略:

第一,区分环境。生产环境严格控制成本,测试和开发环境在保证效率的前提下适当优化。测试环境的资源不用缩得太小,晚上和周末可以关机,但开发人员有需要的时候可以随时开机。

第二,简化流程。资源申请的审批流程简化,小额度的资源申请自动通过,大额度的才需要审批。而且审批要及时,不能耽误项目。

第三,建立成本意识。不是靠限制来控制成本,而是让开发人员有成本意识。比如,给每个团队看他们的成本账单,让他们知道自己用了多少钱,优化了有奖励,浪费了有提醒。这样大家会主动优化,而不是被动地被限制。

第四,用自动化来优化。很多成本优化可以用自动化来做,不需要人工干预。比如,自动清理无用的资源,自动调整不合理的配置,自动识别浪费。这样既能优化成本,又不影响研发效率。

经验总结:FinOps不是财务部门的事,也不是运维部门的事,而是整个团队的事。要在成本、效率、稳定性之间找到平衡,不能只看成本这一个维度。好的FinOps,是让大家在不影响效率的前提下,主动地、合理地使用资源。

踩坑之后的收获

踩了这么多坑,我们的FinOps工作也逐渐走上了正轨。

一年下来,云成本降了大概35%,而且是在业务量增长了50%的情况下实现的。也就是说,单位业务量的成本降了更多。更重要的是,团队的成本意识提高了,大家在做技术决策的时候,会考虑成本因素,不再是"先上了再说"。

总结一下,做好云原生FinOps,有几个关键点:

第一,数据先行。要先把成本数据搞清楚,每个业务线、每个服务、每个资源花了多少钱,都要能看到。没有数据,成本优化就是盲人摸象。

第二,分类施策。不同的业务、不同的环境、不同的资源类型,要用不同的优化策略。不能一刀切,要根据实际情况来。

第三,持续优化。成本优化不是一次性的项目,而是持续的工作。业务在变,资源在变,云服务的价格也在变,需要持续地监控、分析、优化。

第四,团队协作。FinOps需要研发、运维、财务、业务团队的协作。不是某一个部门的事,而是整个公司的事。要建立成本文化,让每个人都有成本意识。

第五,平衡取舍。成本、效率、稳定性,三者之间要找到平衡。不能为了省钱而牺牲效率和稳定性,也不能为了效率和稳定性而无视成本。

给新手的建议

最后,给刚开始做FinOps的朋友几个建议。

第一,从小处开始。不要一上来就想优化所有成本,先从一个业务线或者一个环境开始,积累经验,验证方法,然后再推广。

第二,用工具。FinOps涉及大量的数据收集和分析,靠人工是做不过来的。要用好工具,比如云厂商的成本管理工具、Kubernetes的成本监控工具(OpenCost、Kubecost)、可视化工具等。

第三,建立基线。优化之前,先建立成本基线,知道当前的成本是多少,由哪些部分组成。优化之后,对比基线,才能看到效果。

第四,快速迭代。成本优化是一个迭代的过程,不要追求一次到位。先做容易做的、见效快的,比如清理无用资源、调整不合理的配置,然后再做复杂的优化,比如预留实例、架构优化。

第五,沟通和汇报。FinOps的成果要及时向领导和团队汇报,让大家看到优化的效果,获得支持。同时,也要让大家知道成本优化的重要性,形成成本文化。

写在最后

云原生FinOps是一个既有挑战又有价值的工作。

踩坑的过程虽然痛苦,但每解决一个问题,省下一笔钱,都会有成就感。而且,通过FinOps,你能更深入地理解业务和技术,对系统的整体架构有更全面的认识。

如果你正在做FinOps,或者准备做,希望我的踩坑经历能给你一些参考。成本优化这条路没有捷径,需要耐心、细心和持续的努力。但只要方向对了,坚持下去,一定能看到效果。

如果你也有FinOps的经验或者问题,欢迎在评论区交流。让我们一起,把云成本管得更好。