我的第一张AWS账单金额为23,000美元。那时我在这家公司工作才三周。

没有人提醒我这一点。在我为自己开发的那个功能感到自豪的同时,这笔费用却在不知不觉中不断增加——那个功能就是一个Lambda函数,它会在每次用户操作发生时调用外部API来处理相关数据。代码简洁明了,测试也做得非常到位。那个月共有3200万次用户操作,而每次API调用的成本仅为0.0007美元。

我的工程经理把这张账单转发给我,并只写了两个字:“请解释一下。”

就在那一刻,我认识到了“财务与运营管理”的重要性——并不是通过听讲座或参加认证课程,而是因为自己编写了那些成本高昂的代码,却直到造成损失后才意识到这一点。

正是这份指南在那时帮助了我。它为我提供了一份全面而诚实的指导,帮助我从一个只会开发功能正常的系统的人,转变为一个既能确保系统正常运行,又能使其成本控制在合理范围内的工程师。读完这份指南后,你将掌握所需的技能、脚本和术语,从而能够以 CFO和CTO都愿意听的方式来讨论云服务相关的开支问题。

目录

你将学到的内容

  • 如何以工程师的角度来解读AWS账单,而不仅仅是作为一个被动观察者。

  • 哪种标签策略才能使成本分配变得可行。

  • 如何利用现有的CloudWatch数据来合理调整EC2和RDS实例的配置。

  • 购买节省计划时正确的顺序是什么——为什么顺序比折扣率更重要。

  • 如何构建自动清理系统,以处理那些被遗忘的资源。

    如何利用数据为工程团队提供决策依据,从而有效展示云服务成本情况。

    哪些退费和成本核算机制能够确保成本管理的有效性。

  • 让我们开始吧。

    先决条件

    在开始执行这个路线图之前,你需要具备一些必要的技能并准备好相应的工具。

    所需知识:

    • 你能够将应用程序部署到AWS上(使用EC2、Lambda或容器技术)

    • 你了解基本的AWS服务:S3、RDS、EC2、VPC、IAM

    • 你能熟练阅读Python代码并编写简单的bash脚本

    • 你知道什么是拉取请求,并且至少参与过一次代码审查过程

    所需权限:

    • 拥有对AWS计费控制台及Cost Explorer的只读访问权限

    • 已配置好AWS CLI v2,且至少启用了ReadOnlyAccess策略

    • 需要安装Python 3.9或更高版本,以便运行本指南中的审计脚本

    所需心态:你不需要成为财务专家,但你必须愿意去审视那些可能让人感到不适的数字。我曾经合作过的每一位在FinOps领域取得优异成就的工程师都有一个共同点:他们都愿意在别人都不愿提问的时候,主动去问“但这到底要花费多少成本?”

    预计耗时:这个路线图涵盖了为期24个月的系统化技能提升过程。你可以用几个晚上的时间阅读相关资料,但真正的实践需要持续24个月的时间。

    四个阶段的整体规划

    在深入展开学习之前,先来看看整个学习过程的总体框架:

    阶段1 — 成为具有成本意识的工程师(第1至3个月)
    ├── 阅读并理解你的云服务账单
    ├── 为每一项资源添加有意义的元数据标签
    ├│ 确定导致你成本最高的5个因素
    └── 通过提供成本说明来阻止那些高成本的拉取请求被提交
    
    阶段2 — 优化专家(第4至8个月)
    ├ ├── 根据实际需求调整所有过度配置的资源
    ├ ├── 实施存储生命周期管理策略
    ├ ├── 将非生产环境中的资源迁移至Spot实例
    └── 按照正确的顺序购买Savings Plan服务
    
    阶段3 — 自动化架构师(第9至15个月)
    ├ ├── 为那些被遗忘或不再使用的资源建立自动清理机制
    ├ ├── 在CI/CD流程中加入成本估算功能
    ├ ├── 创建能够体现成本变动情况的自动扩展规则
    └── 部署一个自助式的FinOps管理仪表板
    
    阶段4 — 云服务财务管理者(第16至24个月)
    ├ ├── 与工程团队领导一起定期开展FinOps评审会议
    ├ ├── 为各个部门制定费用报销方案
    ├ ├── 与AWS协商企业级合作协议
    └── 确保云服务支出的预测误差控制在5%以内

    为什么这个学习过程需要持续24个月,而不是一个周末就能完成的项目呢?因为每个阶段都是建立在前一个阶段的基础之上的。那些直接跳过前期准备、直接购买Savings Plan的工程师,最终可能会为不必要的资源支付折扣价;而那些在给资源添加标签之前就先构建仪表板的工程师,虽然会得到一些看起来很美观的图表,但这些图表中实际上并没有可供操作的数据。因此,这个学习顺序绝非随意安排的。

    阶段1:成为具有成本意识的工程师——第1至3个月

    1.1 像工程师而不是会计师一样阅读账单

    默认的AWS成本管理工具视图会显示服务层面的总费用数据,但这些信息属于会计层面的统计结果。你真正需要的是从工程角度进行详细分析:哪些具体资源会产生费用,它们服务于哪些业务功能,以及每一笔开支是否合理。

    首先,获取一份详细的费用分解报告:

    # 获取上个月按服务分类的费用明细
    # 在进行任何优化操作之前先运行此命令,以便获得基准数据
    aws ce get-cost-and-usage \
      --time-period Start=\((date -d 'last month' +%Y-%m-01),End=\)(date +%Y-%m-01) \
      --granularity MONTHLY \
      --group-by Type=DIMENSION,Key=SERVICE \
      --metrics UnblendedCost \
      --query 'ResultsByTime[0].Groups[*].{Service:Keys[0],Cost:Metrics.UnblendedCost.Amount}' \
      --output table | sort -k3 -rn
    

    将输出结果保存下来,文件名命名为`aws-baseline-YYYY-MM.txt`。之后每个月的费用数据都可以与这个基准值进行对比。如果没有基准数据,就无法衡量进展情况;而如果没有可量化的进展,也就无法向领导层证明这项工作确实值得投入精力去优化。

    对于你排名前五的服务项目,需要问这三个问题:

    大多数工程师只会停留在“这个服务是用来做什么的?”这种问题上,而从未深入思考更有意义的问题。在我初次审核某个账户时,我会使用以下框架来进行分析:

    第一个问题是:你是否清楚这项服务具体承担着哪些业务功能?这里指的是功能本身,而不是产品名称。“S3”并不能作为一个有效的答案;而“用于存储那些在90天内无人观看的未处理视频文件”才是正确的描述。

    第二个问题是:观察过去三个月的数据,这项服务的费用是在增加、保持稳定还是有所下降?如果每月的费用始终为12,000美元,那么这与六个月前这个数字仅为4,000美元的情况是完全不同的。

    第三个问题是:这项服务占你总账单的百分比是多少?如果在某个项目上花费了1%的资源来进行优化,而另一个占40%的项目却得不到关注,那么这种做法无疑是在浪费时间。

    1.2 真正能够长期有效使用的标签策略

    关于标签的使用,有一个事实需要承认:大多数标签策略在六个月内就会失效,因为它们最初设计的目的只是为了满足报告需求,并非为工程师提供实用的帮助。当工程师们工作节奏很快时,他们很难准确地为各种资源添加标签。解决办法不是要求他们更加严格遵守规则,而是应该在基础设施层面强制实施标签机制。

    以下是一组最基本的、能够满足90%标识需求的标签:

    # 这六种标签可以用于实现成本分配、责任追踪以及自动问题修复功能
    # 请为AWS账户中的所有资源添加这些标签——无论是EC2、RDS、S3还是Lambda等等
    Environment: "production" | "staging" | "dev"
    Team: "platform" | "backend" | "data" | "ml"
    Service: "payment-api" | "fraud-detection" | "user-service"
    Owner: "ayo@cloudfrugal.com"     # 负责管理该资源的人员
    CostCenter: "engineering"         # 用于退费申请的相关记录
    AutoShutdown: "true" | "false"    # 是否启用自动问题修复功能
    

    在Terraform层面强制要求使用这些标签,以确保它们不会被忽略:

    # variables.tf
    # 将此代码添加到您的Terraform主模块中
    # 任何没有使用这些标签来创建资源的方案都会在验证阶段失败
    
    variable "required_tags" {
      description = "此账户中所有资源都必须使用的标签"
      type = map(string)
    
      validation {
        condition = contains(keys(var.required_tags), "Environment") && \
                    contains(keys(var.required_tags), "Team") && \
                    contains(keys(var.required_tags), "Owner")
        error_message = "required_tags必须包含Environment、Team和Owner这三个标签。"
      }
    }
    
    # 在所有资源中应用这些标签
    resource "aws_instance" "app_server" {
      ami           = data.aws_ami.amazon_linux.id
      instance_type = "t3.medium"
    
      tags = merge(var.required_tags, {
        Name    = "app-server-${var.environment}"
        Service = "payment-api"
      })
    }
    

    找出当前所有没有标签的资源:

    # 列出缺少Team标签的EC2实例
    # 每周运行此命令,直到结果数量变为零
    aws ec2 describe-instances \
      --query "Reservations[].Instances[?!not_null(Tags[?Key='Team'].Value | [0])].[InstanceId, InstanceType, State.Name]" \
      --output table
    

    一旦开始查找没有标签的资源,你就会发现一个规律:账户中创建最早的资源往往标签最少,而且这些资源通常也是最昂贵的。例如,那些在2021年创建的EC2实例,由于在那时还没有实施标签管理政策,因此就会导致每月产生3,000美元的费用,而这种费用根本无法得到合理解释。

    1.3 成本意识强的代码审查

    在工程团队中,最常被忽视的FinOps实践就是在代码变更被合并之前,先评估这些变更所带来的成本影响。养成这个习惯后,每次处理一个代码提交请求只需要花费30秒钟的时间,而这就能避免那些在产品发布后才被发现、导致高昂成本的问题的发生。

    将以下内容添加到您的代码提交模板中:

    ## 成本影响(针对基础设施和数据相关的变更)
    
    - [ ] 此次变更不会影响云资源的使用情况
    - [ ] 是否引入了新的API调用:每次调用的预计成本为____美元,每月的调用次数为____次
    - [ ] 是否使用了新的数据存储方式:预计每月产生的费用差异为____美元
    - [ ] 是否进行了跨区域的数据传输:是/否
    - [ ] 是否引入了需要按次计费的新外部服务:是/否
    
    如果除了第一个选项之外还有其他选项被选中,请在提交代码审查请求之前提供相应的成本估算结果。
    

    关键在于要将成本估算作为代码审查的重要环节,而不是等到每月15日才由财务部门来处理的事情。

    阶段1的成果

    到第3个月底,您应该已经掌握了各项资源的基准成本信息,确保所有活跃资源都配备了标签,确定了导致成本最高的5个因素并制定了具体的降低措施,同时还成功阻止了至少一项因成本问题而无法通过代码审查的提交请求。

    阶段2:优化专家——第4到8个月

    2.1 优化资源配置:云节省中的“80/20法则”

    在我审计的每一个账户中,我发现导致云资源浪费的最常见原因就是计算资源的过度配置。

    这种现象具有普遍性:工程师会按照能够应对预期高峰负载的大小来配置实例,但实际的高峰负载从未达到预期的规模,而且由于没有自动提示机制来显示“这台机器的利用率仅为75%”,因此也没有人会重新调整实例的大小。

    在做出任何更改之前,请务必先核实实际的资源使用情况:

    # rightsize_analyzer.py
    # 用于查找那些平均CPU使用率低于20%且已运行14天的EC2实例
    # 这些实例都是需要优化配置的对象,但不会被自动删除
    
    import boto3
    from datetime import datetime, timedelta
    
    def find_oversized_instances(region='us-east-1'):
        """
        返回过去14天内平均CPU使用率低于20%的EC2实例。
        单纯看CPU使用率低并不能确定是否需要调整资源配置——如果安装了CloudWatch代理,还需要检查内存使用情况。
        """
        ec2 = boto3.client('ec2', region_name=region)
        cw = boto3.client('cloudwatch', region_name=region)
    
        reservations = ec2.describe_instances(
            Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]
        )['Reservations']
    
        candidates = []
    
        for r in reservations:
            for inst in r['Instances']:
                iid = inst['InstanceId']
                itype = inst['InstanceType']
                tags = {t['Key']: t['Value'] for t in inst.get('Tags', [])}
    
                # 从CloudWatch获取过去14天的平均CPU使用率
                stats = cw.get MetricStatistics(
                    Namespace='AWS/EC2',
                    MetricName='CPUUtilization',
                    Dimensions=[{'Name': 'InstanceId', 'Value': iid}],
                    StartTime.datetime.utcnow() - timedelta(days=14),
                    EndTime(datetime.utcnow(),
                    Period=1209600,   # 14天的时间跨度
                    Statistics=['Average']
                )['Datapoints']
    
                avg_cpu = stats[0]['Average'] if stats else 0.0
    
                if avg_cpu < 20.0:
                    candidates.append({
                        'instance_id': iid,
                        'instance_type': itype,
                        'avg_cpu pct': round(avg_cpu, 1),
                        'environment': tags.get('Environment', 'unknown'),
                        'owner': tags.get('Owner', 'unknown'),
                        'team': tags.get('Team', 'unknown'),
                    })
    
        return sorted(candidates, key=lambda x: x['avg_cpupct'])
    
    if __name__ == '__main__':
        results = find_oversized_instances()
        print(f"\n找到了{len(results)}个需要优化配置的实例:\n")
        for r in results:
            print(f"  {r['instance_id']} ({r['instance_type']}) — "
                  f"{r['avg_cpu pct}%的平均CPU使用率 — "
                  f"所有者: {r['owner']}")
    

    需要提醒的是:CPU使用率低于20%只是一种提示,并不代表最终结论。某些工作负载对内存的需求较高,或者依赖于I/O操作,因此即使它们的资源配置是合理的,CPU使用率也可能较低。在根据这些建议调整资源配置之前,请同时检查内存使用情况(这需要启用CloudWatch代理)以及网络I/O流量状况,再结合CPU使用率来做出决策。

    2.2 存储分层:避免为冷数据支付过高费用

    S3 Standard的费用为每月0.023美元/GB,而S3 Glacier Deep Archive的费用仅为每月0.00099美元/GB,两者之间的价格差异高达23倍。如果你有一些六个月前才被访问过的数据,却因为没有设置生命周期策略而将其保存在S3 Standard中,那么你实际上支付的是远高于实际需要的费用。

    适用于工程团队的完整S3生命周期策略:

    {
      "Rules": [
        {
          "ID": "application-logs-lifecycle",
          "Status": "Enabled",
          "Filter": {"Prefix": "logs/"},
          "Transitions": [
            {"Days": 30,  "StorageClass": "STANDARD_IA"},
            {"Days": 90,  "StorageClass": "GLACIER_IR"},
            {"Days": 365, "StorageClass": "DEEP_ARCHIVE"}
          ],
          "Expiration": {"Days": 2555},
          "AbortIncompleteMultipartUpload": {"DaysAfterInitiation": 7}
        },
        {
          "ID": "training-checkpoints-lifecycle",
          "Status": "Enabled",
          "Filter": {"Prefix": "ml-checkpoints/"},
          "Transitions": [
            {"Days": 7,  "StorageClass": "STANDARD_IA"},
            {"Days": 30, "StorageClass": "GLACIER_IR"}
          ],
          "Expiration": {"Days": 90}
        }
      ]
    }
    
    # 将生命周期策略应用到某个桶上
    aws s3api put-bucket-lifecycle-configuration \
      --bucket your-logs-bucket \
      --lifecycle-configuration file://lifecycle.json
    
    # 验证策略是否正确应用
    aws s3api get-bucket-lifecycle-configuration \
      --bucket your-logs-bucket
    

    2.3 节省计划:顺序至关重要

    节省计划是一种承诺:在一年内或三年内,你每小时在AWS计算资源上的支出至少为某个固定金额,作为交换,你可以获得30%到70%的按需费用折扣。这种折扣是实实在在存在的,但问题在于不能在优化资源配置之前就购买节省计划。

    错误的操作顺序:假设你的每月EC2费用为50,000美元,你购买了每小时费用为35,000美元的节省计划。随后你调整了资源配置,并使用了按需实例,结果实际支出降到了22,000美元/月。然而你却已经承诺在12个月内每月支付35,000美元,而实际上你的需求仅为22,000美元/月。这意味着你多花了13,000美元来购买那些你并不需要的计算资源,而这些资源实际上还可以享受30%的折扣。真是“优惠中的浪费”啊……

    正确的操作顺序:

    第1-2个月:利用VPA和CloudWatch的数据来调整所有实例的资源配置
    第3个月:将测试环境和开发环境迁移到按需实例上
    第4个月:将兼容的工作负载迁移至Graviton平台,其成本可降低20%
    第5个月:添加VPC终端节点以消除NAT Gateway的费用
    第6个月:然后分析你的稳定状态下的按需费用支出情况
    第6个月后:购买能够覆盖这些优化后费用70%的节省计划

    计算需要投入多少资源:

    # 获取过去30天的按需EC2使用费用
    # 这个数值就是你应该投入的资源量
    aws ce get-cost-and-usage \
      --time-period Start=\((date -d '30 days ago' +%Y-%m-%d),End=\)(date +%Y-%m-%d) \
      --granularity DAILY \
      --filter '{
        "And": [
          {"Dimensions": {"Key": "SERVICE",       "Values": ["Amazon Elastic Compute Cloud - Compute"]}},
          {"Dimensions": {"Key": "PURCHASE_TYPE", "Values": ["On-Demand"]}}
        ]
      }' \
      --metrics UnblendedCost \
      --query 'ResultsByTime[*].{Date:TimePeriod.Start,Cost:Total.UnblendedCost.Amount}' \
      --output table
    
    # 获取AWS推荐的投入资源量
    aws savingsplans get-savings-plans-purchase-recommendation \
      --savings-plans-type COMPUTE_SP \
      --term-in-years ONE_YEAR \
      --payment-option NO_UPFRONT \
      --lookback-period-in-days THIRTY DAYS
    

    阶段3:自动化架构师——第9到15个月

    3.1 孤立资源问题——以及为何它永远不会自行得到解决

    孤立资源就相当于你忘记取消的健身会员资格。它们存在,会持续产生费用,但直到年度审计时才会有人注意到这些问题。

    造成这一现象的根本原因并非懒惰,而是基础设施层缺乏生命周期管理机制。当一名工程师为了一周的实验创建了一个EC2实例,然后离开公司后,就没有任何自动提示来表明该实例已经变成了“孤立资源”。这个实例会一直存在,每月产生140美元的费用,直到有人去发现它并处理掉。

    解决这个问题的方法是进行每周一次的自动化审计,找出需要删除的资源,并通知相应的负责人。而不是依赖工程师们自觉地去清理这些资源。

    # orphan_reporter.py
    # 每周日通过EventBridge触发Lambda函数来运行此脚本
    # 生成关于孤立资源的报告,供人工审核
    # 不会自动删除资源——删除操作需要人工决策
    
    import boto3
    import json
    import urllib.request
    from datetime import datetime, timedelta, timezone
    
    SLACK_WEBHOOK = 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL'
    UNATTACHED_VOLUME_AGE_days = 14
    SNAPSHOT_AGE_days = 90
    
    
    def find_orphaned_resources():
        ec2 = boto3.client('ec2')
        report = {'monthly_waste_usd': 0, 'items': []}
    
        # 未关联的EBS卷
        for vol in ec2.describe_volumes(
            Filters=[{'Name': 'status', 'Values': ['available']}]
        )['Volumes']:
            age = (datetime.now(timezone.utc) - vol['CreateTime']).days
            if age >= UNATTACHED_VOLUME_AGE_days:
                cost = round(vol['Size'] * 0.08, 2)  # gp3类型的费用计算方式
                tags = {t['Key']: t['Value'] for t in vol.get('Tags', [])}
                report['items'].append({
                    'type':  'Unattached EBS Volume',
                    'id':    vol['VolumeId'],
                    'detail': f"{vol['Size']}GB {vol['VolumeType']} — 已存在{age}天",
                    'owner': tags.get('Owner', 'unknown'),
                    'monthly_cost_usd': cost,
                })
                report['monthly_waste_usd'] += cost
    
        # 未关联的Elastic IP地址
        for addr in ec2.describe_addresses]['Addresses']:
            if 'AssociationId' not in addr:
                report['items'].append({
                    'type':  'Unassociated Elastic IP',
                    'id':    addr.get('AllocationId', addr['PublicIp')),
                    'detail': addr['PublicIp'],
                    'owner': 'unknown',
                    'monthly_cost_usd': 3.60,
                })
                report['monthly_waste_usd'] += 3.60
    
        # 过期的快照
        cutoff = (datetime.now(timezone.utc) - timedelta(days=SNAPSHOT_AGE_days)).isoformat()
        for snap in ec2.describe_snapshots(OwnerIds=['self'])['Snapshots']:
            if snap['StartTime'].isoformat() < cutoff:
                cost = round(snap.get('VolumeSize', 0) * 0.05, 2)
                report['items'].append({
                    'type':  f'Snapshot ({SNAPSHOT_AGE_days}+ 天前创建')',
                    'id':    snap['SnapshotId'],
                    'detail': f"创建于 {snap['StartTime'].strftime('%Y-%m-%d')}",
                    'owner': 'unknown',
                    'monthly_cost_usd': cost,
                })
                report['monthly_waste_usd'] += cost
    
        return report
    
    
    def post_to_slack(report):
        lines = [
            f":money_with_wings: *每周孤立资源报告*",
            f"共发现*{len.report['items'])*个孤立资源",
            f"这些资源的每月费用为*${report['monthly_waste_usd']:.2f}美元*\n",
        ]
        for item in report['items'][:20]:  # 限制显示20条记录以保持可读性
            lines.append(
                f"• {item['type']}` {item['id']} — {item['detail']}",
                f"— 每月费用为*${item['monthly_cost_usd']:.2f}美元* — 所有者:{item['owner']}"
            )
        lines.append("\n请审核并删除不再需要的资源。")
    
        req = urllib.request.Request(
            SLACK_WEBHOOK,
            data=json.dumps({'text': '\n'.join(lines)}).encode(),
            headers={'Content-Type': 'application/json'}
        )
        urllib.request.urlopen(req)
    
    
    def lambda_handler(event, context):
        report = find_orphaned_resources()
        post_to_slack(report)
        return {
            'items_found': len.report['items]),
            'monthly_waste': report['monthly_waste_usd'],
        }
    

    3.2 在您的CI/CD管道中进行成本估算

    我们的目标是在PR阶段发现那些会带来高昂成本的基础设施变更——在这些变更被部署之前,也在进行费用结算之前及时发现它们。

    # .github/workflows/cost-check.yml
    # 适用于任何涉及基础设施文件的PR请求
    # 使用Infracost来估算每月的成本变化幅度
    
    name: 基础设施成本检查
    
    on:
      pull_request:
        paths:
          - 'terraform/**'
          - 'infrastructure/**'
          - '*.tf'
    
    jobs:
      cost-estimate:
        name: 估算每月成本变化
        runs-on: ubuntu-latest
    
        steps:
          - uses: actions/checkout@v4
    
          - name: 设置Infracost
            uses: infracost/actions/setup@v3
            with:
              api-key: ${{ secrets.INFRACOST_API_KEY }}
    
          - name: 生成成本估算报告
            run: |
              infracost breakdown \
                --path terraform/ \
                --format json \
                --out-file /tmp/infracost.json
    
          - name: 将成本变化信息添加到PR请求中
            uses: infracost/actions/comment@v3
            with:
              path: /tmp/infracost.json
              behavior: update
    
          - name: 如果每月成本增加超过阈值,则阻止PR请求继续处理
            run: |
              MONTHLY_DELTA=$(cat /tmp/infracost.json | \
                jq '.projects[0].diff.totalMonthlyCost' | tr -d '"')
    
              echo "估算的每月成本变化幅度为:\$$MONTHLY_delta"
    
              # 如果成本增加超过500美元/月,则拒绝该PR请求
              python3 -c "
              import sys
              delta = float('$MONTHLY_DELTA')
              if delta > 500:
                  print(f'PR请求被阻止:估算的每月成本增加幅度为\\({delta:.2f}/月,超过了500美元的阈值')
                  sys.exit(1)
              else:
                  print(f'成本检查通过:估算的每月成本增加幅度为\${delta:.2f}/月')
              "
    

    阶段4:云财务经理——第16至24个月

    4.1 与高管们进行财务运营审查

    到了第16个月,你们已经掌握了足够的数据。在阶段4,受众发生了变化——你们不再向那些了解实例类型和NAT Gateway定价的工程师们汇报,而是要向CTO们说明基础设施投资是否与其产生的业务价值成正比,同时也要向CFO们解释成本何时会停止上升。

    这种表达方式的转变虽然简单,但却非常重要。你们不会再说“我们调整了EC2实例的配置”,而会改为说“我们在保持相同请求处理能力的情况下,将基础设施的单位成本降低了28%”;也不会再说“我们消除了NAT Gateway的费用”,而是会说“我们弥补了实际支付费用与所需费用之间6,400美元/月的差距”。

    在所有针对高管的财务运营讨论中,关键的指标都是“每个业务单位的成本”。而不是总账单金额(比如每次API调用的成本、每用户的成本、每笔交易的成本,或者每种模型推理所需的成本)。这个比例能够反映随着业务规模的增长,你们的基础设施使用效率是否也在不断提高。

    # unit_economics.py
    # 计算每笔交易的成本——这一指标对管理层来说非常重要
    
    import boto3
    from datetime import datetime, timedelta
    
    def calculate_cost_per_transaction(service_name, transaction_count, days_back=30):
        """
        返回指定服务在过去N天内的每笔交易成本。
        transaction_count:同一时期内的总交易数量(数据来源于你的统计指标)
        """
    
        ce = boto3.client('ce')
    
        response = ce.get_cost_and_usage(
            TimePeriod={
                'Start': (datetime.now() - timedelta(days=days_back)).strftime('%Y-%m-%d'),
                'End':   datetime.now().strftime('%Y-%m-%d'),
            },
            Granularity='MONTHLY',
            Metrics=['UnblendedCost'],
            Filter={
                'Tags': {
                    'Key':    'Service',
                    'Values': [service_name]
                }
            }
        )
    
        total_cost = sum(
            float(period['Total']['UnblendedCost']['Amount'])
            for period in response['ResultsByTime']
        )
    
        cost_per_txn = total_cost / transaction_count if transaction_count > 0 else 0
    
        return {
            'service':           service_name,
            'period_days':       days_back,
            'total_cost_usd':    round(total_cost, 2),
            'transactions':      transaction_count,
            'cost_pertxn_usd':  round(cost_per_txn, 6),
        }
    
    
    # 示例:某支付服务本月处理了420万笔交易
    result = calculate_cost_per_transaction('payment-api', 4_200_000)
    print(f"每笔交易的成本:{result['cost_pertxn_usd']:.6f}")
    print(f"总基础设施成本:{result['total_cost_usd']:,.2f}")
    

    4.2 退款与费用回溯模型

    退款意味着实际上是要向各部门收取他们的云服务使用费用;而费用回溯则是指直接向各部门展示他们的使用成本,而不进行内部财务结算。这两种方式最终都会达到同一个效果:工程师们会开始关注自己的消费情况,因为他们的同事正在密切关注这些数据。

    # showback_report.py
    # 生成按团队划分的月度费用报告,供工程团队负责人查阅

    import boto3
    from datetime import datetime

    def generate_team_showback():
    ce = boto3.client('ce')

    response = ce.get_cost_and_usage(
    TimePeriod={
    'Start': datetime.now().replace(day=1).strftime('%Y-%m-%d'),
    'End': datetime.now().strftime('%Y-%m-%d'),
    },
    Granularity='MONTHLY',
    Metrics=['UnblendedCost'],
    GroupBy=[
    {'Type': 'TAG', 'Key': 'Team'},
    {'Type': 'DIMENSION', 'Key': 'SERVICE'},
    ]
    )

    by_team = {}
    for group in response['ResultsByTime'][0].get('Groups', []):
    team = group['Keys')[0].replace('Team$', '') or 'untagged'
    service = group['Keys'][1]
    cost = float(group['Metrics']['UnblendedCost']['Amount'])

    if team not in by_team:
    by_team[team] = {'total': 0, 'services': {}}
    by_team[team]['total'] += cost
    by_team[team]['services'][service] = round(cost, 2)

    # 按总成本降序排序后打印报告
    print(f"\n{'='*52}")
    print(f" 各团队的月度云服务使用费用")
    print(f" 生成时间:{datetime.now().strftime('%Y-%m-%d')}")
    print(f"{'='*52}\n")

    for team, data in sorted(by_team.items(), key=lambda x: x[1]['total'], reverse=True):
    print(f" {team:<20} {data['total']:>10,.2f}/月")
    top_services = sorted(data['services'].items(), key=lambda x: x[1], reverse=True)[:3]
    for svc, cost in top-services:
    print(f" └─ {svc:<30} ${cost:>8,.2f}")
    print()

    generate_team_showback()

    必备工具与认证

    在本路线图的每个阶段,以下工具都至关重要:

    阶段 工具 重要性说明
    1 AWS成本管理工具 免费且内置,是所有成本分析的起点
    1 AWS CLI的ce命令 可通过脚本进行成本查询——而仪表板无法自动完成此类操作
    2 AWS计算优化工具 基于机器学习为EC2和RDS提供资源优化建议
    2 VPA(Kubernetes版) 根据实际使用情况为Pod提供资源优化建议
    3 Infracost 用于对Terraform变更进行成本估算的工具
    3 AWS预算管理工具 可主动发出警报,在月度账单生成前发现问题
    4 AWS成本与使用情况报告 + Athena 可进行任意细粒度的SQL级账单分析
    4 CloudHealth或Vantage 支持多账户、多云环境的成本管理

    值得你投入时间的认证:FinOps基金会的“FinOps认证从业者”认证。准备这个认证需要20个小时,考试费用为300美元。获得这一认证后,招聘经理和客户会知道你系统地掌握了这一领域的相关知识——当你需要在高层管理层推动FinOps相关事宜时,这一点尤为重要。

    你的90天行动计划

    第1个月 — 基础阶段:

    如果尚未启用AWS成本管理工具,请立即将其激活。按照第1.1节中的指令执行基线数据采集操作,并保存结果。然后运行第1.2节中的未标记资源查询命令,记录哪些资源缺少标签信息。找出导致成本最高的三大因素,并将这些分析结果呈报给你的工程经理——不要把它们视为问题,而要将其视为能够带来实际效益的机会,并附上具体的成本数据。

    第2个月 — 快速见效的措施:

    对你的EC2实例群运行第2.1节中的资源优化分析工具,缩减那些被评估为最需要优化的资源的规模。为你的两个最大容量存储桶应用S3生命周期策略。为S3、ECR和DynamoDB创建VPC端点。估算每项优化措施所能带来的成本节省额,并将实际节省金额与基线数据进行对比记录。

    第3个月 — 自动化与习惯养成:

    在每周日自动运行用于报告资源使用情况的Lambda函数。将成本检查功能添加到你的基础设施代码仓库中。定期召开FinOps审查会议——即使最初只有一两个人参与也可以。在真正需要更多人参与之前,先养成这种定期回顾的习惯。

    最佳实践总结

    务必做到:在任何优化操作之前,首先确定一个成本基线。没有对比基准,这个数字就毫无意义。

    务必做到:在购买节省计划之前,先进行资源规模优化。这一点绝对不可忽视,顺序的不同会直接影响最终结果。

    应该做: 在基础设施层强制实施标签管理——使用Terraform或CloudFormation来实现,而不要将其仅仅作为流程提醒。

    应该做: 将测试环境和开发环境迁移到Spot实例上。虽然这种迁移会导致一定的中断,但这种中断是可以控制的;然而,70%的成本差异却无法被忽视。

    应该做: 在评估数据传输成本之前,为S3、ECR和DynamoDB添加VPC端点。这个操作只需花费30分钟,却能带来数千美元的成本节省。

    应该做: 在展示成本分析结果时,应按照“每项业务指标对应的成本”来进行呈现,而不是以总账单的形式来显示。“我们将每笔交易的成本从0.0021美元降低到了0.0013美元”——这才是真正的业务成果;而“每月可节省38,000美元”则只是一种会计上的数据而已。

    不要做: 在没有对基础配置进行优化之前就购买节约计划。这样只会让自己陷入不必要的成本浪费中。

    不要做: 在完成标签管理工作之前就构建FinOps仪表板。那些没有关联数据支持的精美图表根本无法解决任何实际问题。

    不要做: 在没有经过人工审核的情况下就直接清理那些“孤立存在的资源”。应该先以仅生成报告的模式运行这些清理脚本两周,确认这些资源确实属于需要被删除的范畴,然后再添加相应的删除逻辑。

    资源

    • FinOps基础框架 —— 这是一个实践指南所依据的框架,它定义了“信息收集、优化配置及运营管理”这三个核心环节。

    • AWS成本管理工具API参考 —— 本指南中使用的所有成本查询命令的完整参考资料。

    • AWS计算优化器 —— AWS提供的自动资源配置优化服务,能够补充第二阶段中的手动分析工作。

    • Infracost文档 —— 第三阶段中使用的成本估算工具的安装指南。

    • FinOps认证专家考试 —— 工具部分中提到的认证考试。

    • AWS节约计划文档 —— 关于节约计划的类型、适用规则及购买策略的权威说明资料。

    • 配套代码库 —— 本指南中包含的所有脚本,包括资源配置优化分析工具、孤立资源检测工具以及效果报告生成器等。

    Ayobami Adejumo 是一位资深平台工程师兼FinOps咨询专家。他曾经为20多家处于A轮或B轮融资阶段的公司审核过他们的AWS基础设施配置,并且也是FinOps基础框架的积极支持者。

Comments are closed.