云上资源"只增不减"是大多数团队的通病。本篇从实战角度出发,覆盖成本画像、资源优化、自动化治理全链路,帮你把每一分钱花在刀刃上。


一、成本优化概述

1.1 云成本构成

一张典型的企业云账单,通常由以下几大类构成:

成本类别

占比(典型)

主要项目

计算

40-55%

ECS/EC2、容器节点、函数计算

存储

15-25%

块存储、对象存储、NAS、备份

数据库

10-20%

RDS、Redis、MongoDB、数仓

网络

5-15%

公网带宽、CDN、NAT网关、跨区流量

其他

5-10%

日志、监控、安全、消息队列

关键洞察:计算和存储通常占总成本的 70% 以上,是优化的主战场。

1.2 FinOps 理念

FinOps(Financial Operations)不是"砍预算",而是一套让工程、财务、业务三方协作的云财务管理框架:

核心循环:Inform → Optimize → Operate

Inform:让每个人看到自己花了多少钱(可见性)
Optimize:找到浪费并采取行动(右sizing、折扣、清理)
Operate:建立持续治理机制(预算、告警、自动化)

FinOps 三大原则

  1. 团队需要为自己的云消费负责 —— 用标签把成本归属到团队/项目

  2. 集中管理与去中心化执行并存 —— 平台团队制定规则,业务团队自主优化

  3. FinOps 是持续过程,不是一次性项目 —— 成本治理需要常态化


二、资源画像:摸清家底

优化的前提是"看得清"。先给所有资源画个像。

2.1 CPU 利用率统计

阿里云 CLI 查看 ECS CPU 使用率

# 查询过去7天的CPU平均使用率
aliyun cms DescribeMetricLast \
  --Namespace acs_ecs_dashboard \
  --MetricName CPUUtilization \
  --Period 86400 \
  --Dimensions '[{"instanceId":"i-xxx"}]' \
  --StartTime $(date -d '7 days ago' +%s000) \
  --EndTime $(date +%s000)

AWS CloudWatch 批量统计

# 获取所有EC2实例的CPU利用率
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name CPUUtilization \
  --dimensions Name=InstanceId,Value=i-xxx \
  --start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 86400 \
  --statistics Average

批量导出脚本(适用于多实例)

#!/bin/bash
# batch_cpu_report.sh - 批量统计ECS CPU利用率
echo "InstanceId,InstanceType,AvgCPU7d" > cpu_report.csv

for instance_id in $(aliyun ecs DescribeInstances --RegionId cn-hangzhou \
  --output cols=Instances.Instance[].InstanceId rows=Instances.Instance[]); do
  avg=$(aliyun cms DescribeMetricLast \
    --Namespace acs_ecs_dashboard \
    --MetricName CPUUtilization \
    --Period 604800 \
    --Dimensions "[{\"instanceId\":\"$instance_id\"}]" \
    --output rows=Datapoints[].Average | head -1)
  echo "$instance_id,,$avg" >> cpu_report.csv
done

2.2 内存利用率

# 通过CloudWatch Agent采集内存(AWS需先安装Agent)
aws cloudwatch get-metric-statistics \
  --namespace CWAgent \
  --metric-name mem_used_percent \
  --dimensions Name=InstanceId,Value=i-xxx \
  --start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 86400 \
  --statistics Average

Agentless 方案(通过系统命令)

# 在目标机器上执行
free -m | awk '/Mem:/ {printf "内存使用率: %.1f%%\n", $3/$2*100}'

2.3 磁盘利用率

# 磁盘空间使用率
df -h | awk '$5+0 > 70 {print "⚠️ 高水位:", $0}'

# 磁盘IOPS监控(iostat)
iostat -dx 1 5 | awk '/^[sv]d/ {print $1, "读IOPS:", $2, "写IOPS:", $6}'

2.4 网络流量统计

# 带宽使用峰值
sar -n DEV 1 10 | grep -E "eth0|ens" | awk '{print $1, "收:", $5, "KB/s", "发:", $6, "KB/s"}'

# 阿里云流量统计
aliyun cms DescribeMetricLast \
  --Namespace acs_ecs_dashboard \
  --MetricName InternetOutRate \
  --Period 3600 \
  --Dimensions '[{"instanceId":"i-xxx"}]'

2.5 资源画像汇总模板

#!/bin/bash
# resource_profile.sh - 一键生成资源画像
HOST=$(hostname)
echo "=== 资源画像: $HOST ==="
echo "--- CPU ---"
lscpu | grep -E "^CPU\(s\)|^Model name|^Thread"
echo "--- 内存 ---"
free -h | head -2
echo "--- 磁盘 ---"
lsblk -d -o NAME,SIZE,TYPE | grep disk
df -h / | tail -1
echo "--- 网络 ---"
cat /proc/net/dev | awk 'NR>2 && $2>0 {print $1, "接收:", $2/1024/1024/1024, "GB", "发送:", $10/1024/1024/1024, "GB"}'
echo "--- 负载 ---"
uptime
echo "--- 运行服务 ---"
systemctl list-units --type=service --state=running --no-pager | head -20

三、计算资源优化

计算成本占大头,优化空间也最大。

3.1 实例类型选择

常见误区:上来就选通用型,结果 CPU 闲着、内存不够。

工作负载特征

推荐实例族

说明

CPU 密集(编译、转码)

计算型(c 系列)

CPU:内存 ≈ 1:2

内存密集(缓存、大数据)

内存型(r 系列)

CPU:内存 ≈ 1:8

均衡型(Web、应用)

通用型(g 系列)

CPU:内存 ≈ 1:4

突发流量(测试、开发)

突发型(t 系列)

基线+积分模式

GPU 计算(AI训练)

GPU 型(gn 系列)

按需选卡型

实例规格族对照表(阿里云)

# 查询可用实例规格
aliyun ecs DescribeInstanceTypes \
  --RegionId cn-hangzhou \
  --output cols=InstanceType,CpuCoreCount,MemorySize \
  rows=InstanceTypes.InstanceType[] | head -30

3.2 竞价实例(Spot Instance)

核心思想:用"随时可能被回收"换"最高 90% 折扣"。

适合场景:无状态服务、批处理、CI/CD、大数据计算、机器学习训练。

不适合场景:数据库、有状态核心服务、单点应用。

阿里云竞价实例

# 创建竞价实例(出价不超过按需价格的30%)
aliyun ecs CreateInstance \
  --RegionId cn-hangzhou \
  --InstanceType ecs.c6.xlarge \
  --ImageId centos_7_9_x64_20G \
  --SpotStrategy SpotAsPriceGo \
  --SpotPriceLimit 0.30 \
  --SecurityGroupId sg-xxx \
  --VSwitchId vsw-xxx

AWS Spot Fleet 配置

{
  "SpotFleetRequestConfig": {
    "IamFleetRole": "arn:aws:iam::role/aws-ec2-spot-fleet-role",
    "TargetCapacity": 10,
    "SpotPrice": "0.05",
    "AllocationStrategy": "diversified",
    "LaunchSpecifications": [
      {
        "InstanceType": "c5.xlarge",
        "ImageId": "ami-xxx",
        "KeyName": "my-key"
      },
      {
        "InstanceType": "c5a.xlarge",
        "ImageId": "ami-xxx",
        "KeyName": "my-key"
      }
    ]
  }
}

Spot 中断处理最佳实践

# 监听Spot中断通知(通过CloudWatch Events / EventBridge)
# 在实例上运行中断处理脚本
#!/bin/bash
while true; do
  # 检查是否收到中断通知(2分钟预警)
  if curl -s http://169.254.169.254/latest/meta-data/spot/instance-action | grep -q "terminate"; then
    echo "$(date) 收到Spot中断通知,开始优雅退出..."
    # 1. 从负载均衡摘除
    aws elbv2 deregister-targets --target-group-arn arn:xxx --targets Id=$(curl -s http://169.254.169.254/latest/meta-data/instance-id)
    # 2. 等待请求排空
    sleep 30
    # 3. 保存状态
    /opt/scripts/save_state.sh
    exit 0
  fi
  sleep 5
done

3.3 预留实例与节省计划

购买方式

折扣幅度

灵活性

适用场景

按需

无折扣

最高

短期测试、突发流量

预留实例(1年)

30-40%

稳定基线负载

预留实例(3年)

50-60%

最低

核心长期服务

Savings Plan

20-40%

中高

混合工作负载

竞价实例

60-90%

最高

容错型负载

预留实例利用率检查

# AWS: 检查RI利用率
aws ce get-reservation-utilization \
  --time-period Start=$(date -d '30 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
  --group-by Type=SUBSCRIPTION_ID

3.4 自动伸缩

HPA 基于 CPU 的自动伸缩(Kubernetes)

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: web-app-hpa
  namespace: production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  minReplicas: 2
  maxReplicas: 20
  metrics:
  - type: Resource
    resource:
      name: cpu
      target:
        type: Utilization
        averageUtilization: 60
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 70
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
      - type: Percent
        value: 25
        periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
      - type: Percent
        value: 100
        periodSeconds: 15

定时伸缩策略(阿里云 ESS)

# 工作日早高峰扩容
aliyun ess CreateScheduledTask \
  --ScheduledAction "ari:xxx" \
  --ScheduledTaskName "workday-scale-up" \
  --Recurrence "0 0 8 ? * MON-FRI" \
  --LaunchTime "2026-06-11T08:00Z" \
  --Description "工作日早8点扩容"

# 晚上缩容
aliyun ess CreateScheduledTask \
  --ScheduledAction "ari:yyy" \
  --ScheduledTaskName "night-scale-down" \
  --Recurrence "0 0 22 ? * *" \
  --LaunchTime "2026-06-11T22:00Z" \
  --Description "每晚10点缩容"

四、存储优化

4.1 存储类型选择

存储类型

单价

延迟

适用场景

SSD 云盘

<1ms

数据库、高频读写

高效云盘/通用SSD

1-3ms

系统盘、一般应用

普通云盘/HDD

5-10ms

日志存储、冷数据

对象存储标准

最低

50-100ms

图片、视频、静态资源

对象存储归档

极低

分钟级恢复

合规归档、备份

对象存储生命周期配置(AWS S3)

{
  "Rules": [
    {
      "ID": "LifecycleRule",
      "Filter": { "Prefix": "logs/" },
      "Status": "Enabled",
      "Transitions": [
        {
          "Days": 30,
          "StorageClass": "STANDARD_IA"
        },
        {
          "Days": 90,
          "StorageClass": "GLACIER"
        },
        {
          "Days": 365,
          "StorageClass": "DEEP_ARCHIVE"
        }
      ],
      "Expiration": {
        "Days": 730
      }
    }
  ]
}

阿里云 OSS 生命周期规则

# 创建生命周期配置
cat > lifecycle.json << 'EOF'
<LifecycleConfiguration>
  <Rule>
    <ID>log-archive</ID>
    <Prefix>logs/</Prefix>
    <Status>Enabled</Status>
    <Transition>
      <Days>30</Days>
      <StorageClass>IA</StorageClass>
    </Transition>
    <Transition>
      <Days>90</Days>
      <StorageClass>Archive</StorageClass>
    </Transition>
    <Expiration>
      <Days>365</Days>
    </Expiration>
  </Rule>
</LifecycleConfiguration>
EOF

# 通过CLI设置
ossutil lifecycle --method put oss://my-bucket lifecycle.json

4.2 冷数据归档

# 查找超过90天未访问的文件
find /data -type f -atime +90 -exec ls -lh {} \; | awk '{total+=$5} END {printf "可归档数据: %.2f GB\n", total/1024/1024}'

# 批量迁移冷数据到对象存储
aws s3 sync /data/logs s3://my-bucket/logs/ \
  --storage-class STANDARD_IA \
  --exclude "*.tmp" \
  --expected-size $(du -sb /data/logs | cut -f1)

4.3 快照清理

过期快照是隐形账单大户

#!/bin/bash
# snapshot_cleanup.sh - 清理过期快照
DAYS_KEEP=30
REGION="cn-hangzhou"

# 阿里云:清理超过30天的快照
for snap_id in $(aliyun ecs DescribeSnapshots \
  --RegionId $REGION \
  --Filter '[{"Key":"CreationTime","Value":"before:'$(date -d "$DAYS_KEEP days ago" +%Y-%m-%dT%H:%M:%SZ)'"}]' \
  --output cols=SnapshotId rows=Snapshots.Snapshot[]); do
  echo "删除快照: $snap_id"
  aliyun ecs DeleteSnapshot --SnapshotId $snap_id --RegionId $REGION
done

AWS EBS 快照清理

# 列出超过30天的快照
aws ec2 describe-snapshots \
  --owner-ids self \
  --filters "Name=status,Values=completed" \
  --query "Snapshots[?StartTime<='$(date -d '30 days ago' -u +%Y-%m-%dT%H:%M:%S.000Z)'].[SnapshotId,StartTime,VolumeSize]" \
  --output table

4.4 存储用量审计

#!/bin/bash
# storage_audit.sh - 存储用量审计
echo "=== 存储用量审计 $(date) ==="

echo "--- 块存储 ---"
for vol in $(aws ec2 describe-volumes --query "Volumes[].VolumeId" --output text); do
  size=$(aws ec2 describe-volumes --volume-ids $vol --query "Volumes[0].Size" --output text)
  state=$(aws ec2 describe-volumes --volume-ids $vol --query "Volumes[0].State" --output text)
  if [ "$state" = "available" ]; then
    echo "⚠️ 未挂载卷: $vol (${size}GB)"
  fi
done

echo "--- S3用量 ---"
for bucket in $(aws s3api list-buckets --query "Buckets[].Name" --output text); do
  size=$(aws s3api list-objects-v2 --bucket $bucket --query "sum(Contents[].Size)" --output text 2>/dev/null || echo 0)
  count=$(aws s3api list-objects-v2 --bucket $bucket --query "length(Contents[])" --output text 2>/dev/null || echo 0)
  echo "$bucket: $(echo "scale=2; $size/1024/1024/1024" | bc) GB, $count objects"
done

五、网络优化

5.1 流量分析

# 分析入站/出站流量(按小时统计)
aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name NetworkOut \
  --dimensions Name=InstanceId,Value=i-xxx \
  --start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 3600 \
  --statistics Sum \
  --unit Bytes

# 找出流量最大的实例
for instance in $(aws ec2 describe-instances --query "Reservations[].Instances[].InstanceId" --output text); do
  out=$(aws cloudwatch get-metric-statistics \
    --namespace AWS/EC2 \
    --metric-name NetworkOut \
    --dimensions Name=InstanceId,Value=$instance \
    --start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S) \
    --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
    --period 604800 \
    --statistics Sum \
    --query "Datapoints[0].Sum" --output text 2>/dev/null)
  echo "$instance: $(echo "scale=2; ${out:-0}/1024/1024/1024" | bc) GB out"
done | sort -t: -k2 -n -r | head -10

5.2 CDN 优化

CDN 回源流量是隐性成本

# 检查CDN缓存命中率
# 阿里云CDN
aliyun cdn DescribeDomainHitRateData \
  --DomainName cdn.example.com \
  --StartTime "2026-06-01T00:00:00Z" \
  --EndTime "2026-06-10T00:00:00Z" \
  --Interval 86400

# 如果命中率低于90%,说明大量请求回源了
# 优化措施:
# 1. 增加缓存时间
# 2. 启用Range回源
# 3. 预热热门资源

CDN 回源优化配置

# Nginx 源站缓存配置
location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
    expires 30d;
    add_header Cache-Control "public, immutable";
    add_header X-Cache-Status $upstream_cache_status;
}

# API 响应缓存(短缓存 + stale)
location /api/v1/public/ {
    proxy_cache api_cache;
    proxy_cache_valid 200 5m;
    proxy_cache_use_stale error timeout updating;
    proxy_cache_lock on;
}

5.3 内网流量优化

# 检查是否有服务走公网通信(应该用内网)
# AWS VPC Flow Logs 分析
aws logs filter-log-events \
  --log-group-name "vpc-flow-logs" \
  --start-time $(date -d '1 day ago' +%s)000 \
  --filter-pattern "{ srcAddr != 10.* && dstAddr != 10.* && dstPort = 3306 }" \
  --limit 20

# 阿里云:查看是否有ECS通过公网IP访问RDS
# 检查安全组规则,确保数据库只允许内网访问

5.4 带宽包与计费模式

# 比较带宽计费模式
# 按流量计费 vs 按带宽计费

# 计算日均流量
DAILY_GB=$(aws cloudwatch get-metric-statistics \
  --namespace AWS/EC2 \
  --metric-name NetworkOut \
  --dimensions Name=InstanceId,Value=i-xxx \
  --start-time $(date -u -d '30 days ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period 2592000 \
  --statistics Sum \
  --query "Datapoints[0].Sum" --output text)

# 按带宽1Mbps = 约324GB/月
# 如果月流量 < 324GB * 带宽单价/流量单价,按流量计费更划算

六、数据库优化

6.1 RDS 规格优化

# 查看RDS当前规格和使用率
aliyun rds DescribeDBInstancePerformance \
  --DBInstanceId rm-xxx \
  --Key MySQL_Sessions

# 检查慢查询
aliyun rds DescribeSlowLogRecords \
  --DBInstanceId rm-xxx \
  --StartTime "2026-06-01T00:00:00Z" \
  --EndTime "2026-06-10T00:00:00Z" \
  --PageSize 20

# AWS RDS Performance Insights
aws pi get-resource-metrics \
  --service-type RDS \
  --identifier db-xxx \
  --metric-queries '[{"Metric":"db.load.avg","GroupBy":{"Group":"db.wait_event"}}]' \
  --start-time $(date -u -d '7 days ago' +%Y-%m-%dT%H:%M:%S) \
  --end-time $(date -u +%Y-%m-%dT%H:%M:%S) \
  --period-in-seconds 86400

6.2 读写分离

# AWS RDS Proxy + Aurora 读写分离
# Aurora 自动扩展 Reader 节点
# CloudFormation 配置示例
Resources:
  AuroraCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      Engine: aurora-mysql
      EngineVersion: "8.0.mysql_aurora.3.04.0"
      ScalingConfiguration:
        AutoPause: true
        MinCapacity: 2        # ACU 最小值
        MaxCapacity: 16       # ACU 最大值
        SecondsUntilAutoPause: 300
      EnableHttpEndpoint: true

6.3 数据库缓存

# Redis 缓存命中率检查
redis-cli INFO stats | grep -E "keyspace_hits|keyspace_misses"
# hits / (hits + misses) > 95% 为健康

# 慢查询日志
redis-cli SLOWLOG GET 10

# 内存使用分析
redis-cli MEMORY DOCTOR
redis-cli --bigkeys

6.4 Serverless 数据库

# Aurora Serverless v2 - 按需计费
# 适合开发测试、低流量场景
Resources:
  ServerlessCluster:
    Type: AWS::RDS::DBCluster
    Properties:
      Engine: aurora-postgresql
      ServerlessV2ScalingConfiguration:
        MinCapacity: 0.5    # ACU,最低可到0.5
        MaxCapacity: 16

七、容器与 K8s 成本治理

7.1 资源配额(ResourceQuota)

apiVersion: v1
kind: ResourceQuota
metadata:
  name: team-quota
  namespace: team-a
spec:
  hard:
    requests.cpu: "20"
    requests.memory: 40Gi
    limits.cpu: "40"
    limits.memory: 80Gi
    persistentvolumeclaims: "10"
    pods: "50"
    services.loadbalancers: "2"
---
apiVersion: v1
kind: LimitRange
metadata:
  name: default-limits
  namespace: team-a
spec:
  limits:
  - default:
      cpu: "500m"
      memory: 512Mi
    defaultRequest:
      cpu: "100m"
      memory: 128Mi
    type: Container

7.2 VPA(Vertical Pod Autoscaler)

apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
  name: web-app-vpa
spec:
  targetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: web-app
  updatePolicy:
    updateMode: "Off"  # 先用Off模式只看建议,不自动调整
  resourcePolicy:
    containerPolicies:
    - containerName: "*"
      minAllowed:
        cpu: 50m
        memory: 64Mi
      maxAllowed:
        cpu: 4
        memory: 8Gi

7.3 闲置 Pod 清理

#!/bin/bash
# k8s_cost_cleanup.sh - 清理闲置资源

echo "=== 闲置Pod检查 ==="
# CPU使用率低于5m的Pod(近1小时)
kubectl top pods --all-namespaces --sort-by=cpu | awk 'NR>1 && $3 ~ /m/ && $3+0 < 5 {print "⚠️ 低CPU Pod:", $1, $2, $3}'

echo "=== 未使用的PVC ==="
for pvc in $(kubectl get pvc -A -o jsonpath='{range .items[*]}{.metadata.namespace}/{.metadata.name} {.status.phase}{"\n"}{end}' | grep Bound | awk '{print $1}'); do
  ns=$(echo $pvc | cut -d/ -f1)
  name=$(echo $pvc | cut -d/ -f2)
  # 检查是否有Pod挂载
  mount_count=$(kubectl get pods -n $ns -o json | jq -r ".items[] | select(.spec.volumes[]?.persistentVolumeClaim.claimName==\"$name\") | .metadata.name" | wc -l)
  if [ "$mount_count" -eq 0 ]; then
    echo "⚠️ 未挂载PVC: $ns/$name"
  fi
done

echo "=== 空Namespace(无工作负载)==="
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}' | tr ' ' '\n' | grep -vE '^(kube-system|kube-public|default)$'); do
  pod_count=$(kubectl get pods -n $ns --no-headers 2>/dev/null | wc -l)
  if [ "$pod_count" -eq 0 ]; then
    echo "⚠️ 空Namespace: $ns"
  fi
done

7.4 Namespace 成本分摊

#!/bin/bash
# k8s_cost_allocation.sh - 按Namespace统计资源消耗

echo "Namespace,CPU_Request(m),CPU_Limit(m),Mem_Request(Mi),Mem_Limit(Mi),Pod_Count"
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
  read cpu_req cpu_lim mem_req mem_lim pod_count <<< $(kubectl get pods -n $ns -o json | jq -r '
    [.items[] | {
      cpu_req: (.spec.containers[].resources.requests.cpu // "0"),
      cpu_lim: (.spec.containers[].resources.limits.cpu // "0"),
      mem_req: (.spec.containers[].resources.requests.memory // "0"),
      mem_lim: (.spec.containers[].resources.limits.memory // "0")
    }] | {
      cpu_req: ([.[].cpu_req] | map(gsub("m";"") | gsub("";"0") | tonumber) | add),
      cpu_lim: ([.[].cpu_lim] | map(gsub("m";"") | gsub("";"0") | tonumber) | add),
      mem_req: ([.[].mem_req] | map(gsub("Mi";"") | gsub("Gi";"000") | gsub("";"0") | tonumber) | add),
      mem_lim: ([.[].mem_lim] | map(gsub("Mi";"") | gsub("Gi";"000") | gsub("";"0") | tonumber) | add),
      count: length
    } | [.cpu_req, .cpu_lim, .mem_req, .mem_lim, .count] | @csv' 2>/dev/null)
  echo "$ns,$cpu_req,$cpu_lim,$mem_req,$mem_lim,$pod_count"
done

八、自动化成本治理

8.1 预算告警

# AWS Budget 创建月度预算告警
aws budgets create-budget \
  --account-id 123456789012 \
  --budget '{
    "BudgetName": "monthly-production",
    "BudgetLimit": {"Amount": "50000", "Unit": "CNY"},
    "TimeUnit": "MONTHLY",
    "BudgetType": "COST",
    "CostTypes": {"IncludeTax": true, "IncludeSubscription": true}
  }' \
  --notifications-with-subscribers '[
    {
      "Notification": {
        "NotificationType": "ACTUAL",
        "ComparisonOperator": "GREATER_THAN",
        "Threshold": 80,
        "ThresholdType": "PERCENTAGE"
      },
      "Subscribers": [
        {"SubscriptionType": "EMAIL", "Address": "ops@company.com"},
        {"SubscriptionType": "SNS", "Topic": "arn:aws:sns:region:123456789012:cost-alerts"}
      ]
    },
    {
      "Notification": {
        "NotificationType": "FORECASTED",
        "ComparisonOperator": "GREATER_THAN",
        "Threshold": 100,
        "ThresholdType": "PERCENTAGE"
      },
      "Subscribers": [
        {"SubscriptionType": "EMAIL", "Address": "cfo@company.com"}
      ]
    }
  ]'

8.2 自动关机策略

#!/bin/bash
# auto_shutdown.sh - 自动关停非生产环境资源
# 建议通过 crontab 每天晚上10点执行

TAG_KEY="Environment"
TAG_VALUE="dev,staging"

echo "=== $(date) 开始关停非生产环境 ==="

# 关停开发/测试ECS
for instance_id in $(aws ec2 describe-instances \
  --filters "Name=tag:$TAG_KEY,Values=$TAG_VALUE" \
            "Name=instance-state-name,Values=running" \
  --query "Reservations[].Instances[].InstanceId" --output text); do
  echo "关停实例: $instance_id"
  aws ec2 stop-instances --instance-ids $instance_id
done

# 关停RDS(开发环境)
for db in $(aws rds describe-db-instances \
  --query "DBInstances[?contains(DBInstanceIdentifier,'dev') && DBInstanceStatus=='available'].DBInstanceIdentifier" \
  --output text); do
  echo "关停RDS: $db"
  aws rds stop-db-instance --db-instance-identifier $db
done

定时启动脚本

#!/bin/bash
# auto_start.sh - 早上自动启动(工作日执行)
DOW=$(date +%u)
if [ "$DOW" -gt 5 ]; then
  echo "周末不启动"
  exit 0
fi

echo "=== $(date) 启动非生产环境 ==="
for instance_id in $(aws ec2 describe-instances \
  --filters "Name=tag:Environment,Values=dev,staging" \
            "Name=instance-state-name,Values=stopped" \
  --query "Reservations[].Instances[].InstanceId" --output text); do
  echo "启动实例: $instance_id"
  aws ec2 start-instances --instance-ids $instance_id
done

8.3 标签管理

#!/bin/bash
# tag_enforcement.sh - 标签合规检查
# 检查未打标签的资源

REQUIRED_TAGS="Owner Team Environment CostCenter Project"

echo "=== 未打标签的ECS实例 ==="
for instance in $(aws ec2 describe-instances \
  --query "Reservations[].Instances[].[InstanceId,Tags]" --output json | \
  jq -r '.[][] | @base64'); do
  id=$(echo $instance | base64 -d | jq -r '.[0]')
  tags=$(echo $instance | base64 -d | jq -r '.[1] // [] | map(.Key) | join(",")')
  for req_tag in $REQUIRED_TAGS; do
    if ! echo "$tags" | grep -q "$req_tag"; then
      echo "⚠️ $id 缺少标签: $req_tag"
    fi
  done
done

自动打标签(Lambda + CloudTrail)

# lambda_tag_enforcer.py
import boto3

REQUIRED_TAGS = {
    'Environment': 'unknown',
    'Team': 'unknown',
    'CostCenter': 'unknown',
    'Owner': 'unknown'
}

def handler(event, context):
    ec2 = boto3.client('ec2')
    
    # 获取所有未打完整标签的实例
    instances = ec2.describe_instances(
        Filters=[{'Name': 'instance-state-name', 'Values': ['running']}]
    )
    
    for reservation in instances['Reservations']:
        for instance in reservation['Instances']:
            existing_tags = {t['Key']: t['Value'] for t in instance.get('Tags', [])}
            missing_tags = []
            
            for key, default in REQUIRED_TAGS.items():
                if key not in existing_tags:
                    missing_tags.append({'Key': key, 'Value': default})
            
            if missing_tags:
                ec2.create_tags(
                    Resources=[instance['InstanceId']],
                    Tags=missing_tags
                )
                print(f"已为 {instance['InstanceId']} 补充标签: {[t['Key'] for t in missing_tags]}")
    
    return {'statusCode': 200}

8.4 成本报告自动生成

#!/bin/bash
# cost_report.sh - 月度成本报告生成

MONTH=$(date -d 'last month' +%Y-%m)
REPORT_FILE="cost_report_${MONTH}.html"

cat > $REPORT_FILE << 'HEADER'
<html><head><style>
body { font-family: -apple-system, sans-serif; max-width: 900px; margin: 0 auto; padding: 20px; }
h1 { color: #1a73e8; }
table { border-collapse: collapse; width: 100%; margin: 16px 0; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background: #f5f5f5; }
.warn { color: #e65100; font-weight: bold; }
.ok { color: #2e7d32; }
</style></head><body>
HEADER

echo "<h1>☁️ 月度云成本报告 - ${MONTH}</h1>" >> $REPORT_FILE

# AWS Cost Explorer
echo "<h2>服务维度成本</h2><table><tr><th>服务</th><th>本月</th><th>上月</th><th>变化</th></tr>" >> $REPORT_FILE

aws ce get-cost-and-usage \
  --time-period Start=${MONTH}-01,End=$(date -d "${MONTH}-01 + 1 month" +%Y-%m-%d) \
  --granularity MONTHLY \
  --metrics "BlendedCost" \
  --group-by Type=DIMENSION,Key=SERVICE \
  --query "ResultsByTime[].Groups[].{Service:Keys[0],Cost:Metrics.BlendedCost.Amount}" \
  --output json | jq -r '.[] | "<tr><td>\(.Service)</td><td>¥\(.Cost)</td></tr>"' >> $REPORT_FILE

echo "</table>" >> $REPORT_FILE

# Top 10 最贵资源
echo "<h2>💰 Top 10 成本标签</h2><table><tr><th>标签</th><th>金额</th></tr>" >> $REPORT_FILE

aws ce get-cost-and-usage \
  --time-period Start=${MONTH}-01,End=$(date -d "${MONTH}-01 + 1 month" +%Y-%m-%d) \
  --granularity MONTHLY \
  --metrics "BlendedCost" \
  --group-by Type=TAG,Key=CostCenter \
  --filter '{"Dimensions":{"Key":"SERVICE","Values":["Amazon Elastic Compute Cloud - Compute"]}}' \
  --query "ResultsByTime[].Groups[].[Keys[0],Metrics.BlendedCost.Amount]" \
  --output json | jq -r 'sort_by(.[1] | tonumber) | reverse | .[0:10][] | "<tr><td>\(.[0])</td><td>¥\(.[1])</td></tr>"' >> $REPORT_FILE

echo "</table></body></html>" >> $REPORT_FILE

echo "报告已生成: $REPORT_FILE"

九、账单分析

9.1 账单结构解析

典型云账单结构:

总账单
├── 计算服务
│   ├── ECS 按需实例
│   ├── ECS 预留实例(分摊)
│   ├── 竞价实例
│   ├── 容器服务
│   └── 函数计算
├── 存储服务
│   ├── 云盘
│   ├── 对象存储
│   ├── 文件存储
│   └── 快照
├── 数据库
│   ├── RDS
│   ├── Redis
│   └── 数仓
├── 网络
│   ├── 公网带宽
│   ├── CDN
│   ├── NAT 网关
│   └── VPN
└── 其他
    ├── 日志服务
    ├── 监控
    └── 安全

9.2 成本归因

# AWS: 按标签查询成本
aws ce get-cost-and-usage \
  --time-period Start=2026-06-01,End=2026-06-10 \
  --granularity DAILY \
  --metrics "UnblendedCost" \
  --group-by Type=TAG,Key=Team \
  --filter '{"Tags":{"Key":"Environment","Values":["production"]}}'

# 按资源ID查询(找出最贵的单个资源)
aws ce get-cost-and-usage \
  --time-period Start=2026-06-01,End=2026-06-10 \
  --granularity MONTHLY \
  --metrics "UnblendedCost" \
  --group-by Type=DIMENSION,Key=RESOURCE_ID \
  --filter '{"Dimensions":{"Key":"SERVICE","Values":["Amazon Elastic Compute Cloud - Compute"]}}' \
  --query "ResultsByTime[].Groups[] | sort_by(@, &to_number(Metrics.UnblendedCost.Amount)) | reverse | .[0:20]"

9.3 成本分摊模型

# 成本分摊规则配置示例
cost_allocation:
  shared_infrastructure:
    # 共享基础设施按业务线流量比例分摊
    - resource: "NAT网关"
      method: "traffic_ratio"
      tags: ["Team"]
    
    - resource: "监控平台"
      method: "host_count"
      tags: ["Team"]
    
    - resource: "日志平台"
      method: "log_volume"
      tags: ["Team"]
  
  direct_costs:
    # 直接成本直接归属
    - resource: "ECS"
      method: "tag"
      tag: "Team"
    
    - resource: "RDS"
      method: "tag"
      tag: "Team"

分摊计算脚本

#!/bin/bash
# cost_allocation.sh - 按团队分摊成本
echo "=== 成本分摊报告 ==="

# 直接归属成本
echo "--- 直接成本 ---"
aws ce get-cost-and-usage \
  --time-period Start=2026-06-01,End=2026-06-10 \
  --granularity MONTHLY \
  --metrics "UnblendedCost" \
  --group-by Type=TAG,Key=Team \
  --output json | jq -r '.ResultsByTime[].Groups[] | "\(.Keys[0]): ¥\(.Metrics.UnblendedCost.Amount)"'

# 共享成本(按比例分摊)
TOTAL_SHARED=$(aws ce get-cost-and-usage \
  --time-period Start=2026-06-01,End=2026-06-10 \
  --granularity MONTHLY \
  --metrics "UnblendedCost" \
  --filter '{"Dimensions":{"Key":"SERVICE","Values":["Amazon Virtual Private Cloud","AmazonCloudWatch"]}}' \
  --query "ResultsByTime[0].Total.UnblendedCost.Amount" --output text)

echo "--- 共享成本总额: ¥$TOTAL_SHARED ---"
echo "(需按各团队资源使用比例进一步分摊)"

十、成本优化 Checklist

10.1 月度巡检清单

## 成本优化月度巡检

### 计算资源
- [ ] 检查 CPU 平均利用率 < 30% 的实例 → 考虑降配
- [ ] 检查内存利用率 < 40% 的实例 → 考虑降配
- [ ] 确认开发/测试环境非工作时间自动关机
- [ ] 评估预留实例覆盖率(目标 > 70%)
- [ ] 检查竞价实例中断率和成本节省

### 存储
- [ ] 清理超过 N 天的快照
- [ ] 检查未挂载的云盘/卷
- [ ] 配置对象存储生命周期规则
- [ ] 归档超过 90 天未访问的数据
- [ ] 检查存储桶中孤立的 multipart uploads

### 数据库
- [ ] 检查 RDS CPU/内存利用率
- [ ] 评估读写分离效果
- [ ] 清理慢查询和死连接
- [ ] 检查 Redis 命中率(目标 > 95%)
- [ ] 评估 Serverless 数据库适用场景

### 网络
- [ ] 检查 CDN 缓存命中率(目标 > 90%)
- [ ] 评估带宽计费模式是否最优
- [ ] 检查是否有服务走公网通信
- [ ] 清理闲置的 NAT 网关和弹性 IP

### K8s/容器
- [ ] 清理闲置 Pod 和未挂载 PVC
- [ ] 检查资源 Request/Limit 配置合理性
- [ ] 评估 VPA 建议并执行
- [ ] 按 Namespace 统计成本

### 账单
- [ ] 核对本月账单是否有异常增长
- [ ] 检查标签覆盖率(目标 > 95%)
- [ ] 生成成本分摊报告
- [ ] 更新预算和告警阈值

10.2 自动化巡检脚本

#!/bin/bash
# cost_audit.sh - 一键成本巡检
# 用法: ./cost_audit.sh [region] [output_file]

REGION="${1:-cn-hangzhou}"
OUTPUT="${2:-cost_audit_$(date +%Y%m%d).txt}"

echo "=== 成本巡检报告 ===" > $OUTPUT
echo "生成时间: $(date)" >> $OUTPUT
echo "区域: $REGION" >> $OUTPUT
echo "" >> $OUTPUT

# 1. 低利用率ECS
echo "【1. 低CPU利用率ECS】" >> $OUTPUT
for instance in $(aliyun ecs DescribeInstances --RegionId $REGION \
  --output cols=InstanceId rows=Instances.Instance[]); do
  # 获取7天CPU均值
  avg=$(aliyun cms DescribeMetricLast \
    --Namespace acs_ecs_dashboard \
    --MetricName CPUUtilization \
    --Period 604800 \
    --Dimensions "[{\"instanceId\":\"$instance\"}]" \
    --output rows=Datapoints[].Average 2>/dev/null | head -1)
  if [ -n "$avg" ] && (( $(echo "$avg < 20" | bc -l) )); then
    echo "⚠️ $instance: CPU均值 ${avg}%" >> $OUTPUT
  fi
done

# 2. 孤立资源
echo "" >> $OUTPUT
echo "【2. 孤立资源】" >> $OUTPUT
# 未挂载云盘
for vol in $(aliyun ecs DescribeDisks --RegionId $REGION \
  --Status Available \
  --output cols=DiskId rows=Disks.Disk[] 2>/dev/null); do
  size=$(aliyun ecs DescribeDisks --RegionId $REGION --DiskIds "[\"$vol\"]" \
    --output rows=Disks.Disk[].Size | head -1)
  echo "⚠️ 未挂载云盘: $vol (${size}GB)" >> $OUTPUT
done

# 3. 弹性IP检查
echo "" >> $OUTPUT
echo "【3. 未绑定弹性IP】" >> $OUTPUT
for eip in $(aliyun ecs DescribeEipAddresses --RegionId $REGION \
  --Status Available \
  --output cols=AllocationId rows=EipAddresses.EipAddress[] 2>/dev/null); do
  echo "⚠️ 闲置EIP: $eip" >> $OUTPUT
done

# 4. 快照统计
echo "" >> $OUTPUT
echo "【4. 快照统计】" >> $OUTPUT
snap_count=$(aliyun ecs DescribeSnapshots --RegionId $REGION \
  --output rows=TotalCount | head -1)
echo "快照总数: $snap_count" >> $OUTPUT

echo "" >> $OUTPUT
echo "=== 巡检完成 ===" >> $OUTPUT
echo "报告已保存: $OUTPUT"

10.3 成本优化自动化流水线

# .gitlab-ci.yml 或 GitHub Actions 工作流
# cost-optimization-pipeline

stages:
  - audit
  - analyze
  - optimize
  - report

cost-audit:
  stage: audit
  script:
    - ./scripts/cost_audit.sh $AWS_REGION audit_result.json
  artifacts:
    paths: [audit_result.json]

cost-analyze:
  stage: analyze
  script:
    - python3 scripts/cost_analyze.py audit_result.json --threshold 20
  artifacts:
    paths: [analysis_report.html]

auto-optimize:
  stage: optimize
  script:
    - ./scripts/auto_shutdown.sh dev  # 关停开发环境
    - ./scripts/snapshot_cleanup.sh 30  # 清理30天前快照
    - ./scripts/tag_enforcement.sh    # 补充标签
  only:
    - schedules  # 仅定时任务触发

cost-report:
  stage: report
  script:
    - ./scripts/cost_report.sh
    - curl -X POST $WEBHOOK_URL -d @cost_report.json  # 推送到飞书/钉钉

附录:实用命令速查

# === 成本查询 ===
# AWS
aws ce get-cost-and-usage --time-period Start=2026-06-01,End=2026-06-10 \
  --granularity MONTHLY --metrics "BlendedCost"

# 阿里云
aliyun bssopenapi QueryBill --BillingCycle 2026-06

# === 资源发现 ===
# AWS 未使用资源
aws support describe-trusted-advisor-checks --query "checks[?category=='cost_optimizing']"
aws trustedadvisor describe-check-summaries --check-ids <check-id>

# === 存储审计 ===
# S3 存储用量
aws s3api list-objects-v2 --bucket my-bucket --query "[sum(Contents[].Size), length(Contents[])]"

# === 快照清理 ===
# 列出过期快照
aws ec2 describe-snapshots --owner-ids self \
  --filters "Name=status,Values=completed" \
  --query "Snapshots[?StartTime<='$(date -d '30 days ago' -u +%Y-%m-%dT%H:%M:%S.000Z)']"

写在最后:成本优化不是一次性的运动,而是持续的习惯。把巡检脚本化、把告警自动化、把分摊透明化,让"花钱如流水"变成"花钱有数"。下一期我们将聊聊灾备与高可用架构设计,敬请期待。