暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

AWS月账单降低60%,揭秘我们的FinOps革新之旅

k8s技术圈 2024-08-12
323

随着经济形势越发动荡,企业比以往任何时候都更加关注降本增效,在一家公司的IT预算中,云成本是除了人力成本外,最大的一项支出,笔者所在的公司今年的IT预算对比去年几乎腰斩(老板潜台词:“要么降低云成本,要么我就裁员”),于是今年我们团队(基础设施和SRE团队)最大的KPI就是,尽一切可能降低云成本。

通过分析业务,调研开源解决方案和自研相关组件,在保证稳定性的前提下,实现EC2成本从 约10万美金/月下降到了约4万美金/月,节省比例高达60%

01 云成本分析

首先就是要完成云成本的洞察,弄清楚钱究竟花在哪些地方了,我们是一家跨境电商公司,所以选择了AWS作为唯一云厂商,为了追求资源弹性、系统韧性和开发敏捷性,我们在2022年就完成了全部业务容器化改造,所有的应用都部署在EKS(Elastic Kubernetes Service,AWS托管版Kubernetes)上,是典型的云原生架构。

在我们的AWS账单中,成本构成 EC2占绝对大头,大概68%,剩下的存储占12%,网络占10%,其他PaaS层云服务占比不到10%。

02 算力分析 & 优化

集中力量办大事,于是我们决定从最大的EC2成本着手,要解决的第一个问题。对于成本优化,在业界通常有两种方向:利用率优化(使用更少的机器机器,即Using Less)和费率优化(利用云厂商的折扣优惠,即Paying Less),下面我们从这两个方面展开分析。

(Credit to @ProsperOps)

2.1 利用率优化

2.1.1 节点利用率优化

首先我们定义一下节点利用率:(所有Pod的资源request) (所有EC2的资源总和)。

通过分析我们的业务,存在白天业务负载高,HPA扩容业务Pod的实例数,晚上业务负载低,HPA缩容实例数。整体业务负载类似如下图。但是我们的EC2数量是根据峰值业务规模购买的,并没有根据业务负载实时增加或者释放,造成在非高峰时间段,整体EC2的节点利用率只有10%左右,造成非常大的浪费

那么此点的解决方案就非常显而易见了,通过根据业务的资源请求,实时伸缩节点,在业务高峰时增加节点,业务低谷是减少节点。

通过查找业界开源解决方案,有 Cluster Autoscaler(CA) 和Karpenter[1],大致有如下区别:

能力Cluster AutoscalerKarpenter
Node级别的autoscaling✅(伸缩速度快三倍)
支持优雅处理Spot中断
支持node rightsizing
节点扩容支持调度属性感知(如Topology spread)
支持资源碎片整理(binpack)
支持多种EC2选型❌,受限于Node Group的节点类型
node伸缩性能较慢,多层接口调用(managed node group->autoscaling group->ec2创建)较快(ec2 fleet接口->ec2创建)

从上表可以发现,Karpenter从节点选择策略和性能等多方面,都完全超越了Cluster Autoscaler,完美符合我们的应用场景。这里重点介绍一下Karpenter三大提升利用率的核心能力:节点智能选型、node rightsizing和binpack碎片整理。

在开始前,首先介绍一下Karpenter的NodePool,通过配置NodePool,能够限制选择实例类型,如我们使用的NodePool配置如下:

apiVersion: karpenter.sh/v1beta1
kind: NodePool
metadata:
  name: default
spec:
  template:
    spec:
      requirements:
        - key: karpenter.k8s.aws/instance-category
          operator: NotIn
          values:
          - p
          - g
          - gr
        - key: kubernetes.io/arch
          operator: In
          values:
          - amd64
        - key: kubernetes.io/os
          operator: In
          values:
          - linux
        - key: karpenter.sh/capacity-type
          operator: In
          values:
          - spot
          - on-demand


其代表在选择节点时,考虑AWS上无GPU(p,g,gr代表GPU机型)的所有x86架构机型,弄更多配置请查看:https://karpenter.sh/preview/concepts/nodepools/

因为我们本次实施的集群均为CPU节点,所以先排除了GPU节点,Karpenter也可以用来优化GPU成本,而且效果非常好,后面我们会分享更多GPU算力的优化,敬请关注!

节点选型如上图所示,假设多个Pending Pod,总资源request为1.5C 2GiB,Karpenter在内存中缓存有AWS所有机型的规格和价格相关信息,最后会从容量大于1.5C 2GiB,同时匹配NodePool要求的机型中,选择有余量并且价格最低的机器。当然你也可以通过Karpenter NodePool做一些节点限制,比如只能选择某些高性能芯片(如c7a,芯片为AMD EPYC 9R14)的机型。

binpack碎片整理如上图所示,在多台较大节点利用率不高时,Karpenter会将其合并到一台规格较小的节点,同时删除之前的2台较大节点,达到节省成本的目的。

Node righsizing如上图所示,在一个节点利用率较低时,Karpenter会将其驱逐到一个规格更小的节点,同时删除之前的较大节点,达到节省成本的目的。

2.1.2 Workload利用率优化

首先我们定义一下workload利用率:(workload的资源usage) (workload的资源request)。

目前业界比较通用的解决方案是VPA或类似解决方案(如crane),但是如果和HPA使用不当,可能会造成冲突,所以目前我们没有采用。

比如我一个deployment请求资源为100MiB,HPA设置在70%时扩容。因为业务量较少,deployment长期处于40MiB的使用量,VPA根据历史usage将资源request设置为50MiB,此时利用率大于70%,会触发HPA持续扩容,这个不是我们期待的。要设计一个通用的解决方案去适配使用HPA(包括不同metrics)的业务和未使用HAP的业务难度较大。

而且在集群中,我们存在部分Java业务,此类业务启动时耗费的资源是启动后使用资源的好几倍,VPA是根据历史数据推荐,并不能很好的适配此类场景。

2.2 费率优化

云厂商对同一种节点实例,有不同类型,同一类型又有不同的计费模式,如在aws有按需计费(on-demand),预留实例计费/Savings Plan计费(承诺用量后获得折扣,一般是按需7折左右),Spot计费(一般是按需计费的2折左右)。

这里着重介绍一下spot实例,AWS全球有500万台服务器,不可能每时每刻都有人用,所以一定有闲置资源,AWS就会把这个闲置资源拿出来“甩卖”,价格通常是按需实例的1-3折,非常便宜,但是AWS随时会把Spot实例回收掉,并且回收机制完全黑盒,也就意味着应用面临随时中断的风险,如下是m6i.xlarge在us-east-1(N. Virginia)不同计费模式的价格对比:

实例类型On Demand 价格Spot价格和折扣Savings Plan价格和折扣
m6i.xlarge(4C16G)$140.16/月$42.559/月,3折$102.97/月,7折

可以发现Spot相比on-demand和savings plan,有更大的折扣价,而且Savings Plan有一个缺点,即使深夜服务器我没使用,也要为此付费,还有1-3年的合同锁定期,完全丧失了云的“弹性”,等于把云当成物理机房使用,可想而知,成本是非常高昂的。所以综合下来,我们希望直接使用Spot实例,当AWS回收Spot实例时,至少会提前2min发送中断通知[2],需要在这2min之内,把业务迁移到其他节点,Karpenter处理逻辑如下:

根据如上处理逻辑,对于一个业务的所有Pod,如果能在收到中断信号后,在2min之内迁移到其他节点,理论上这种业务是完美适合spot节点的。

对于我们的业务,我们分析并对其做了如下划分:

对于如上表格中的具体设计,我们将在下面章节展开。

03 架构 & 实现

根据如上分析,我们的整体架构实现如下:

用户部署应用时(Deployment/StatefulSet/Job),1)mutating admission webhook对应用的Pod进行操作,控制应用级别的Pod分布;2)Karpenter根据Pod资源要求,创建/删除节点;3)同时在从AWS端收到spot中断通知时,迁移spot节点上面的Pod,其中我们主要实现如下功能:

  1. 实现了一个mutating admission webhook,监听对于服务(Deployment/StatefulSet/Job)的Pod创建和分布,对其进行patch affinity,亲和spot/on-demand节点;
  2. 修改了Karpenter对单replica deployment Pod的驱逐逻辑,走滚动升级接口而不是驱逐接口;
  3. Karpenter在收到中断通知时,默认是先驱逐此节点的Pod(出现Pending Pod),然后由Pending pod触发新节点创建,在Node新创建出来之前,业务可能出现稳定性问题;我们修改为先拉起替代节点(耗时40s左右),然后驱逐此节点(耗时40s左右),能在2min后的节点销毁到来之前完成,稳定性得到提升

这里详细描述 a 点。首先,spot节点存在label: karpenter.sh/capacity-type=spot
,on-demand节点存在label: karpenter.sh/capacity-type=on-demand
。所以,对于需要调度到spot节点的Pod,通过mutating admission webhook自动添加如下affinity:

...
spec:
  affinity:
    nodeAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          preference:
            matchExpressions:
              - key: node.kubernetes.io/capacity
                operator: In
                values:
                  - spot
...

使用 preferredDuringSchedulingIgnoredDuringExecution
而不使用 requiredDuringSchedulingIgnoredDuringExecution
是为了避免没有spot资源导致最后无法调度。对于需要调度到on-demand的Pod,通过mutating admisssion webhook自动添加如下affinity:

...
spec:
  affinity:
    nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
          - key: node.kubernetes.io/capacity
            operator: In
            values:
              - on-demand
 ...

同时,我们需要考虑缩容场景,如果不对缩容做相关控制,可能导致on-demand节点上面的Pod全部被缩容导致只有在spot节点,从而因为spot节点同时中断导致断服。所以在缩容时,优先缩容在spot节点上的Pod,此处通过 controller.kubernetes.io/pod-deletion-cost
annotation 实现:

  • 对于亲和spot节点的Pod,mutating admission webhook对其添加 controller.kubernetes.io/pod-deletion-cost=-1
    的annotation;
  • 对于亲和on-demand节点的Pod,mutating admission webhook对其添加 controller.kubernetes.io/pod-deletion-cost=1
    的annotation;

Karpenter在创建节点去承载这些Pod时,会考虑如上设置的affinity,创建on-demand/spot节点,通过对业务应用混合部署(on-demand/spot)的形式,达到即稳定,又低成本。

最后综合以上描述,对于我们4种类型业务,我们的 mutating admission webhook/增强Karpenter 做如下操作:

通过如上手段,我们多个集群的EC2成本从 约10万美金/月 下降到了 约4万美金/月,节省比例高达60%,同时在开发和生产集群,2个月以来稳定性都没有出现问题。

04 Serverless真的便宜?

为了应对此类波峰波谷业务,我们也调研了AWS Lambda,AWS Fargate,以一个程序(2Core/2GiB)运行一小时为例,消耗成本(us-east-2)对比如下,发现Lamda成本是EC2的1.5倍

AWS Lambda[3]EKS Fargate[4]EKS/EC2[5]
0.1198$0.0896$0.077$,使用c5a.large(2Core/4GiB),还有2GiB的剩余

最后我们还是选择了 EKS + EC2 的使用形式,结合我们修改后的Karpenter,即享受到了极致的折扣(按需伸缩节点,使用Spot并且无业务中断),也避免了后续被Savings Plan绑定的风险。Karpenter + EKS 完全重塑了我们底层基础设施的使用方式,让我们的底层基础设施具备极速弹性同时,具备最高的性价比,已经成为我们企业底层基础设施的最佳实践。

05 展望

Karpenter开源版本目前只能根据Pod的资源request,负责节点的选择、创建和删除,未对业务稳定性做深入设计,后续我们将会贡献此篇文章中的mutating admission webhook到开源社区中。同时,Karpenter现在只支持了Azure和AWS,后续我们将适配国内云,如阿里云[6]。欢迎添加我们的小助手(请备注:Karpenter),加入我们的Karpenter中文社区:

参考资料

  • [1] - https://github.com/aws/karpenter-provider-aws
  • [2] - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/spot-instance-termination-notices.html , https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/rebalance-recommendations.html
  • [3] - https://aws.amazon.com/lambda/pricing/
  • [4] - https://aws.amazon.com/fargate/pricing/
  • [5] - https://aws.amazon.com/ec2/pricing/on-demand/
  • [6] - https://github.com/cloudpilot-ai/karpenter-provider-alicloud

文章转载自k8s技术圈,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论