回答三个灵魂拷问
一句话结论:通过标签治理 + 资源调度 + 自动化策略,我把一个月的云成本从 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 标签。将近一半的资源出了事没人知道该找谁。
标签补全策略
我没有要求一次性补完,而是分两轮:
- 第一轮:脚本自动推断(根据 VPC 环境、安全组命名等 hints 猜 Env)
- 第二轮:列出无法推断的资源列表,发到团队群@所有人认领
# 用 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 按 Env 和 Owner 切分,发现了五个典型的浪费模式:
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% 的生产实例,我分两步处理:
- 降配:
m5.4xlarge→m5.2xlarge(先观察两周确认无性能影响) - 配置自动伸缩策略:如果之后负载上升,让 ASG 自动扩容
# 修改实例类型(需要先停止实例)
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 这件事做下来,我觉得最难的其实不是技术,是让团队形成成本意识。自动化脚本我可以写,但每个人创建资源时主动打标签、主动评估规格,这才是持续省钱的根本。
如果你也想做,我建议从这三件事开始:
- 立刻开始打标签——不用追求完美,先打
Env和Owner两个就够了 - 挑一个浪费最大的动作自动化——通常是非生产环境的定时关机,收益最明显
- 让成本透明——谁花了多少发到群里,peer pressure 比任何管理手段都有效
至于更进阶的方向,比如基于 Kubernetes 的自动扩缩容、Spot Instance 的覆盖率优化、以及多云成本对比,就是下一篇文章的话题了。

评论已关闭!