云上资源"只增不减"是大多数团队的通病。本篇从实战角度出发,覆盖成本画像、资源优化、自动化治理全链路,帮你把每一分钱花在刀刃上。
一、成本优化概述
1.1 云成本构成
一张典型的企业云账单,通常由以下几大类构成:
关键洞察:计算和存储通常占总成本的 70% 以上,是优化的主战场。
1.2 FinOps 理念
FinOps(Financial Operations)不是"砍预算",而是一套让工程、财务、业务三方协作的云财务管理框架:
核心循环:Inform → Optimize → Operate
Inform:让每个人看到自己花了多少钱(可见性)
Optimize:找到浪费并采取行动(右sizing、折扣、清理)
Operate:建立持续治理机制(预算、告警、自动化)FinOps 三大原则:
团队需要为自己的云消费负责 —— 用标签把成本归属到团队/项目
集中管理与去中心化执行并存 —— 平台团队制定规则,业务团队自主优化
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
done2.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 AverageAgentless 方案(通过系统命令):
# 在目标机器上执行
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 闲着、内存不够。
实例规格族对照表(阿里云):
# 查询可用实例规格
aliyun ecs DescribeInstanceTypes \
--RegionId cn-hangzhou \
--output cols=InstanceType,CpuCoreCount,MemorySize \
rows=InstanceTypes.InstanceType[] | head -303.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-xxxAWS 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
done3.3 预留实例与节省计划
预留实例利用率检查:
# 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_ID3.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 存储类型选择
对象存储生命周期配置(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.json4.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
doneAWS 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 table4.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 -105.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 864006.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: true6.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 --bigkeys6.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: Container7.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: 8Gi7.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
done7.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
done8.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)']"写在最后:成本优化不是一次性的运动,而是持续的习惯。把巡检脚本化、把告警自动化、把分摊透明化,让"花钱如流水"变成"花钱有数"。下一期我们将聊聊灾备与高可用架构设计,敬请期待。