回答三个灵魂拷问

2026-05-21 21:53 回答三个灵魂拷问已关闭评论

回答三个灵魂拷问

一句话结论:通过标签治理 + 资源调度 + 自动化策略,我把一个月的云成本从 12 万砍到 7.3 万,核心操作只有 3 个脚本和一套标签规范。

为什么我要写这篇

去年我开始接手公司的 AWS 账单管理。一看数字惊了——月均 12 万人民币,还在涨。

财务问"钱花在哪了",我说不上来。技术问"哪些能砍",我也说不清。

这不是个例。大多数团队上云时的常态是:建环境一时爽,成本治理火葬场。云成本不是某一天突然爆炸的,而是每个月多 5%-10% 慢慢涨上去的,等你看账单时才惊觉失控。

我花了两周时间梳理资源、分析账单、写自动化脚本,最终把月成本降到 7.3 万。这篇文章就是这次实战的全过程记录。


在碰任何资源之前,我先问自己三个问题:

钱花在哪了?——按服务、按环境、按团队拆分

哪些是浪费?——闲置资源、过度配置、无人认领

怎么自动化?——不能靠人盯,得让系统自己管

AWS 的账单体系很强大,但前提是你得会用。我踩的第一个坑就是"看了 Cost Explorer 以为懂了"——实际上光看总账单没用,你需要按维度切分。

账单分析实操

# 使用 AWS CLI 导出月度成本,按服务聚合
aws ce get-cost-and-usage \
  --time-period Start=2025-01-01,End=2025-01-31 \
  --granularity MONTHLY \
  --metrics "UnblendedCost" \
  --group-by Type=DIMENSION,Key=SERVICE \
  --output table

但 CLI 只能看个大概。真正有用的分析要用 Cost Explorer 的标签维度。问题是——我们没打标签。这是大多数团队的现状:资源一堆,标签为零。

第一个教训:没有标签,成本分析就像蒙眼摸象。打标签是 FinOps 的第一步,省不了。


标签治理——最枯燥但最重要的一步

我把打标签策略做成强制规范,不是建议。

标签规范

Env: prod / staging / dev / test
Team: platform / data / backend / frontend
Project: <项目名>
Owner: <负责人花名>
AutoStop: true / false     # 是否允许非工作时间关机

定完规范后,我用脚本扫描所有资源,先找出没打标签的"黑户":

# scan_untagged.py — 扫描未打标签的资源
import boto3

ec2 = boto3.client('ec2')
rds = boto3.client('rds')

# 扫描 EC2
instances = ec2.describe_instances()
for reservation in instances['Reservations']:
    for instance in reservation['Instances']:
        tags = {t['Key']: t['Value'] for t in instance.get('Tags', [])}
        if 'Env' not in tags or 'Owner' not in tags:
            print(f"EC2 {instance['InstanceId']} — 缺少必要标签")
            print(f"  现有标签: {tags}")

# 扫描 RDS
dbs = rds.describe_db_instances()
for db in dbs['DBInstances']:
    tags = {t['Key']: t['Value'] for t in db.get('TagList', [])}
    if 'Env' not in tags:
        print(f"RDS {db['DBInstanceIdentifier']} — 缺少 Env 标签")

运行结果让我倒吸一口凉气——47% 的资源没有 Owner 标签。将近一半的资源出了事没人知道该找谁。

标签补全策略

我没有要求一次性补完,而是分两轮:

  1. 第一轮:脚本自动推断(根据 VPC 环境、安全组命名等 hints 猜 Env)
  2. 第二轮:列出无法推断的资源列表,发到团队群@所有人认领
  3. # 用 AWS Resource Group Tagging API 批量打标签
    aws resourcegroupstaggingapi tag-resources \
      --resource-arn-list arn:aws:ec2:ap-northeast-1:123456789:instance/i-0abcd1234 \
      --tags Env=prod,Team=platform,Owner=zhangsan
    

揪出五大浪费源

标签打好后,我在 Cost Explorer 按 EnvOwner 切分,发现了五个典型的浪费模式:

1. 周末不关的 Dev 实例

Dev 环境的 EC2 和 RDS,下班后和周末照常运行。一周 168 小时,真正用到的不到 50 小时。

算账

  • 1 台 t3.large($0.0832/h)在 dev 环境
  • 每周浪费:118h × $0.0832 = $9.82
  • 一个月浪费:约 $42
  • 整个 dev 环境 20+ 台实例 → 月浪费 $800+

2. 过度配置的生产实例

生产环境也有问题——大部分实例的 CPU 利用率不到 10%,但配置了 m5.4xlarge(16 vCPU,64GB)。

不是所有服务都需要那么大。很多历史配置是"当时随便选的"或者"怕不够直接上大规格",之后再也没人审视。

3. 僵尸资源

已经没人用的负载均衡器、EIP、EBS 快照,每个月还在产生费用。特别是 EBS 快照——很多人打了快照就忘了删,几个月下来积了上百 GB。

4. 未预留的持续负载

对于一些 7×24 运行的服务,按需付费比 Reserved Instance 贵 40%-60%。但团队没人去算这个账。

5. 跨区域数据传输

一些服务配置了跨区域复制,但实际没人消费这些副本。我们有一个新加坡区域的 RDS 只读副本,每个月产生 $300+ 的跨区域传输费,结果没人用它。

第二个教训:重复数据比没数据更烧钱。


自动化降本策略

发现问题只是第一步,关键是怎么持续地管

我不相信"加强管理"这回事——人盯人永远盯不住。唯一解是自动化。

策略一:非工作时间自动停机

# stop_dev_instances.py — 每天 20:00 停 Dev 实例,9:00 启动
import boto3
from datetime import datetime, timezone

ec2 = boto3.client('ec2')
rds = boto3.client('rds')

def get_dev_resources():
    """获取所有 Env=dev 且 AutoStop=true 的资源"""
    instances = []
    paginator = ec2.get_paginator('describe_instances')
    for page in paginator.paginate():
        for reservation in page['Reservations']:
            for inst in reservation['Instances']:
                tags = {t['Key']: t['Value'] for t in inst.get('Tags', [])}
                if tags.get('Env') == 'dev' and tags.get('AutoStop', 'true') == 'true':
                    instances.append(inst['InstanceId'])
    return instances

def stop_instances():
    dev_ids = get_dev_resources()
    if not dev_ids:
        print("没有需要关闭的 Dev 实例")
        return
    ec2.stop_instances(InstanceIds=dev_ids)
    print(f"已停止实例: {dev_ids}")

def start_instances():
    dev_ids = get_dev_resources()
    if not dev_ids:
        print("没有需要启动的 Dev 实例")
        return
    ec2.start_instances(InstanceIds=dev_ids)
    print(f"已启动实例: {dev_ids}")

if __name__ == '__main__':
    hour = datetime.now(timezone.utc).hour
    if hour >= 11 or hour < 1:   # UTC 11:00–次日 1:00(北京时间 19:00–次日 9:00)
        stop_instances()
    elif 1 <= hour < 9:          # UTC 1:00–9:00(北京时间 9:00–17:00)
        start_instances()

注意时区问题。我第一版直接用北京时间写死了,结果夏令时切换时脚本在错误的时间运行,周末没关机被财务质问。

这个脚本配合 EventBridge 定时触发:

# 部署到 EventBridge Scheduler(北京时间每天 20:00)
aws scheduler create-schedule \
  --name dev-stop-schedule \
  --schedule-expression "cron(0 12 * * ? *)" \
  --target "arn:aws:lambda:ap-northeast-1:xxx:function:stop-dev-instances" \
  --flexible-time-window Mode=OFF

策略二:RDS 实例按需停止

RDS 不能直接关,得先处理一个坑——RDS 停止后 7 天会自动重启,这是 AWS 的硬限制。解决方案:7 天内重新停止。

# 用 Lambda + CloudWatch Events 每周检查一次
# handler.py
def lambda_handler(event, context):
    rds = boto3.client('rds')
    dbs = rds.describe_db_instances()
    for db in dbs['DBInstances']:
        if db['DBInstanceStatus'] == 'stopped':
            rds.start_db_instance(DBInstanceIdentifier=db['DBInstanceIdentifier'])
            # 等几秒再停掉(绕开 7 天限制)
            time.sleep(5)
            rds.stop_db_instance(DBInstanceIdentifier=db['DBInstanceIdentifier'])

第三个教训:AWS 的服务都有自己的"小脾气",RDS 的 7 天限制让我第一次自动化脚本上线后一周才发现又跑起来了。

策略三:自动降配 + 弹性伸缩

对于 CPU 利用率长期低于 20% 的生产实例,我分两步处理:

  1. 降配m5.4xlargem5.2xlarge(先观察两周确认无性能影响)
  2. 配置自动伸缩策略:如果之后负载上升,让 ASG 自动扩容
  3. # 修改实例类型(需要先停止实例)
    aws ec2 modify-instance-attribute \
      --instance-id i-0abcd1234 \
      --instance-type "{\"Value\": \"m5.2xlarge\"}"
    

对于弹性伸缩,我设置了基于 CPU 和内存的双指标策略:

# autoscaling_policy.yaml
SimpleScalingPolicy:
  ScaleUpThreshold: 70   # CPU > 70% 持续 5 分钟 → +1 台
  ScaleDownThreshold: 30 # CPU < 30% 持续 10 分钟 → -1 台
  Cooldown: 300          # 冷却时间 5 分钟

策略四:存储生命周期管理

EBS 快照和 S3 存储的"僵尸数据"最容易被忽略,因为它们不产生直接的告警。

# cleanup_orphan_snapshots.py — 清理 30 天以上的孤立快照
import boto3
from datetime import datetime, timedelta, timezone

ec2 = boto3.client('ec2')

snapshots = ec2.describe_snapshots(OwnerIds=['123456789'])['Snapshots']
cutoff = datetime.now(timezone.utc) - timedelta(days=30)

for snap in snapshots:
    if snap['StartTime'] < cutoff:
        description = snap.get('Description', '')
        # 不删由 AWS Backup 创建的快照
        if 'Created by AWS Backup' in description:
            continue
        print(f"删除快照 {snap['SnapshotId']},创建于 {snap['StartTime']}")
        ec2.delete_snapshot(SnapshotId=snap['SnapshotId'])

用 Dashboard 让成本可视化

自动化做完了,还差最后一步——让每个人能看到自己的成本

我搭了一个简单的成本看板,不需要 Grafana 那么重的东西,直接用 AWS Cost Explorer API + 一个简单脚本每天发报告:

# cost_report.py — 每天生成团队成本报告
import boto3
from datetime import date, timedelta

ce = boto3.client('ce')

response = ce.get_cost_and_usage(
    TimePeriod={
        'Start': (date.today() - timedelta(days=1)).strftime('%Y-%m-%d'),
        'End': date.today().strftime('%Y-%m-%d')
    },
    Granularity='DAILY',
    Metrics=['UnblendedCost'],
    GroupBy=[{'Type': 'TAG', 'Key': 'Team'}]
)

for group in response['ResultsByTime'][0]['Groups']:
    team = group['Keys'][0].replace('Team$', '')
    cost = group['Metrics']['UnblendedCost']['Amount']
    print(f"{team}: ${cost}")

这份报告每天发到企业微信,谁花了多少一目了然。自从公开成本数据后,团队主动优化的意愿明显提高——谁也不想自己的名字出现在"月度浪费榜"前三。


最终成果

两个月的持续优化,账单从 $17,200/月(约 ¥12 万)降到了 $10,100/月(约 ¥7.3 万),节省约 41%

优化项 月节省金额 说明
Dev 环境定时启停 $2,400 20+ 实例,停机时间 ~65%
生产实例降配 $1,800 8 台实例从 4xlarge 降到 2xlarge
清理僵尸资源 $900 快照 + EIP + 闲置 LB
RI 覆盖持续负载 $1,200 3 年期全预付,覆盖 70% 的基线负载
取消无用跨区域复制 $300 删除了未使用的只读副本
其他(S3 生命周期等) $500 标准→冷存储、删除过期版本

几个值得延伸思考的方向

FinOps 这件事做下来,我觉得最难的其实不是技术,是让团队形成成本意识。自动化脚本我可以写,但每个人创建资源时主动打标签、主动评估规格,这才是持续省钱的根本。

如果你也想做,我建议从这三件事开始:

  1. 立刻开始打标签——不用追求完美,先打 EnvOwner 两个就够了
  2. 挑一个浪费最大的动作自动化——通常是非生产环境的定时关机,收益最明显
  3. 让成本透明——谁花了多少发到群里,peer pressure 比任何管理手段都有效

至于更进阶的方向,比如基于 Kubernetes 的自动扩缩容、Spot Instance 的覆盖率优化、以及多云成本对比,就是下一篇文章的话题了。

你可能感兴趣的文章

来源:每日教程每日一例,深入学习实用技术教程,关注公众号TeachCourse
转载请注明出处: https://teachcourse.cn/4160.html ,谢谢支持!

资源分享

分类:Android 标签:
结合实例讲解Glide图片变换(Transformation)的常见场景应用 结合实例讲解Glide图片变换(T
ArrayMap方法解析 ArrayMap方法解析
Rust 异步编程与 Tokio 运行时深度实战 Rust 异步编程与 Tokio 运行时
Open Claw模型切换过程 Open Claw模型切换过程

评论已关闭!