
雲端費用評估與優化:帳單不該是月底的驚喜
雲端最大的優勢是「用多少付多少」,但這也是最大的陷阱。沒有實體伺服器擺在機房裡提醒你成本的存在,資源的開通只要幾秒鐘,卻沒有人告訴你它每小時都在燒錢。很多團隊在上雲初期只關注功能和架構,等第一個月帳單寄來才發現金額遠超預期——NAT Gateway 的資料傳輸費比 EC2 本身還貴、忘記刪除的 EBS snapshot 累積了幾百 GB、測試環境的 RDS 24 小時跑著卻沒人在用。
成本意識不是等帳單來了才開始建立的,它應該從架構設計的第一天就融入決策。這篇文章從估算、監控、分析到優化,建立完整的雲端成本管理流程。
架構概覽
flowchart LR A["1. 監控 Monitor\nCost Explorer\nBudgets"] --> B["2. 分析 Analyze\nAnomaly Detection\nKubecost"] B --> C["3. 調整 Rightsize\nCompute Optimizer\n降級閒置資源"] C --> D["4. 預留 Reserve\nRI / Savings Plans\nSpot Instances"] D --> E["5. 檢視 Review\n清理未用資源\n驗證優化效果"] E --> A A -.->|"即時追蹤\n設定告警"| A B -.->|"找出異常\n定位浪費"| B C -.->|"CPU < 20%?\n降級規格"| C D -.->|"穩定負載?\n買 RI 省 30-72%"| D E -.->|"EBS / EIP / ALB\n未用即刪"| E
成本優化生命週期
flowchart LR Estimate[估算 Estimate\n預測成本基準線] --> Monitor[監控 Monitor\n即時追蹤實際花費] Monitor --> Analyze[分析 Analyze\n找出異常與浪費] Analyze --> Optimize[優化 Optimize\n採取降本措施] Optimize --> Estimate Estimate -.->|AWS Pricing Calculator\nInfracost| Estimate Monitor -.->|Cost Explorer\nBudgets| Monitor Analyze -.->|Cost Anomaly Detection\nKubecost| Analyze Optimize -.->|Right-sizing\nRI/SP\nSpot| Optimize
成本管理不是一次性的任務,而是持續循環的過程:估算建立基準線、監控追蹤實際花費、分析找出差異和浪費、優化採取行動降低成本、再重新估算驗證效果。每個階段都有對應的工具和方法。
核心概念
-
雲端計價模型:雲端服務的計價方式主要分為四種。(1)On-Demand(隨需計費):最彈性也最貴,按小時或秒計費,隨時開隨時關,適合短期測試或流量不可預測的場景。(2)Reserved Instance(RI,預留執行個體):承諾使用 1 年或 3 年,換取 30%-72% 的折扣。適合穩定運行的生產環境。1 年期約省 30-40%,3 年期約省 60-72%,但中途不能退。(3)Spot / Preemptible Instance(競價型執行個體):使用雲端供應商的閒置容量,價格最低(省 60-90%),但隨時可能被回收(通常會有 2 分鐘的通知)。適合可中斷的工作負載,例如 batch processing、CI runner、資料分析。(4)Savings Plans(節省方案):AWS 特有的方案,承諾每小時固定消費金額(例如 $10/hr),不綁定特定執行個體類型,比 RI 更有彈性,折扣力度類似。
-
主要成本組成:雲端帳單的大頭通常來自四個類別。(1)Compute(運算):EC2 / GCE / Azure VM 是最大的支出項,費用取決於執行個體類型、數量和運行時間。一台
t3.medium(2 vCPU / 4GB)在us-east-1的 On-Demand 價格約 30。(2)Storage(儲存):S3 / GCS / Azure Blob 的儲存費用按 GB-月計算。S3 Standard 約 0.08/GB-月。(3)Network(網路):資料傳輸費是最容易被忽略的成本。從雲端傳出到網際網路(egress)收費、跨 AZ 傳輸收費、NAT Gateway 的資料處理費每 GB 50/月(Multi-AZ 翻倍)。 -
隱藏成本陷阱:以下是最常讓人帳單爆炸的隱藏成本。(1)NAT Gateway 資料處理費:每個 NAT Gateway 固定費用 32/月),加上每 GB 資料處理費 0.01。微服務架構下如果 service A 在 AZ-a、service B 在 AZ-b,兩者頻繁通訊的流量費會很可觀。(3)EBS Snapshots:每 GB-月 0.005(約 16/月,即使沒有流量通過。測試環境的 ALB 常常被遺忘。
-
FinOps 原則:FinOps(Financial Operations)是一套雲端財務管理的方法論,核心原則包含:(1)Cost Visibility(成本可視化):每筆費用都必須能追溯到具體的團隊、專案或環境。透過 tagging 策略(例如
team=backend、env=prod、project=api)讓成本分攤清晰。(2)Accountability(責任歸屬):每個團隊要對自己的雲端花費負責,而不是全公司共用一張帳單、沒人在意。設定每個團隊的預算上限和告警。(3)Optimization(持續優化):定期 review 成本報表,找出浪費並採取行動。不是一次優化就結束,而是持續循環的過程。
成本估算方法論
使用 AWS Pricing Calculator
AWS Pricing Calculator(calculator.aws)可以在上雲前估算月費。操作流程:選擇 Region → 加入服務(EC2、RDS、S3 等)→ 設定規格和用量 → 檢視月費估算。估算時要注意包含所有相關成本,不要只算 EC2 就覺得夠了。
估算公式
月費估算 = Compute + Storage + Network + Database + Misc
Compute = Σ (instance 單價 × 數量 × 運行時數)
Storage = Σ (儲存 GB × 單價) + (請求次數 × 單價)
Network = egress 流量 GB × 單價 + NAT 處理 GB × 單價 + 跨 AZ 流量 GB × 單價
Database = DB instance 單價 + 儲存 + 備份 + 讀取副本
Misc = ALB + Route53 + CloudWatch + EIP + 其他
實戰估算:典型 Web 應用的月費
假設要部署一個中型 Web 應用,架構如下:2 台 EC2 (t3.medium) + 1 台 RDS (db.t3.medium, Multi-AZ) + S3 (100GB) + ALB + NAT Gateway。Region 選 us-east-1。
| 項目 | 規格 | 月費估算 |
|---|---|---|
| EC2 × 2 | t3.medium, On-Demand | 60 |
| RDS | db.t3.medium, Multi-AZ, 100GB gp3 | 8 = $108 |
| S3 | 100GB Standard + 100 萬次 GET | 0.4 = $2.7 |
| ALB | 固定費 + LCU (低流量) | 5 = $21 |
| NAT Gateway | 固定費 + 50GB 處理 | 2.25 = $34.25 |
| Data Transfer | 100GB egress | $9 |
| EBS | 2 × 30GB gp3 | $4.8 |
| Route 53 | 1 Hosted Zone + 100 萬次 query | 0.4 = $0.9 |
| CloudWatch | 基本監控 | $0 (免費) |
| 合計 | ~$240/月 |
注意幾個重點:(1)RDS Multi-AZ 讓資料庫費用翻倍,如果 staging 環境不需要高可用可以用 Single-AZ。(2)NAT Gateway 的固定費用 32 × AZ 數量。(3)Data Transfer 的 egress 費用會隨流量線性增長,如果有大量對外回應(例如影片串流),這一項會爆增。
如果把 EC2 改成 1 年 RI(All Upfront),Compute 成本從 38,節省 37%。再加上 RDS 的 RI,整體可以從 180 左右,年省約 $720。
優化策略
-
Right-sizing(調整規格):最簡單也最有效的優化方式。很多團隊在初期為了「安全」選了太大的執行個體,但實際 CPU 使用率長期低於 20%。AWS 的 Compute Optimizer 和 Cost Explorer 的 Right Sizing Recommendations 可以根據過去的使用量建議更合適的執行個體類型。例如把長期 CPU < 10% 的
m5.xlarge(0.096/hr),直接省 50%。 -
Reserved Instances / Savings Plans(預留或承諾):對於穩定運行 24/7 的生產環境工作負載,RI 或 Savings Plans 是最直接的降本手段。建議策略:先用 On-Demand 運行 1-2 個月,觀察實際用量和穩定性。確認工作負載穩定後,對核心 Compute 和 Database 購買 1 年期 RI。Savings Plans 比 RI 更靈活,如果團隊會頻繁調整執行個體類型,優先選擇 Savings Plans。3 年期折扣更大,但只建議對非常確定的長期負載使用。
-
Spot Instances(競價型執行個體):適合以下場景:(1)CI/CD Pipeline runner:build job 跑完就結束,被中斷就重跑。(2)Batch processing:大量資料處理、影片轉碼,可以分批進行。(3)開發/測試環境:不需要 100% 可用性。(4)無狀態的 Web worker:搭配 Auto Scaling Group 和 On-Demand 混合使用。Spot 的價格通常是 On-Demand 的 10-30%,但要設計好中斷處理機制。
-
Auto Scaling(自動擴縮):根據實際負載自動調整執行個體數量。白天流量高時擴展到 4 台,凌晨流量低時縮減到 1 台。如果平均流量只有尖峰的 30%,Auto Scaling 理論上可以省下 50-60% 的 Compute 成本。搭配 Scheduled Scaling(例如工作日 8:00 擴展、22:00 縮減)可以更精準控制。
-
Storage Tiering(儲存分層):S3 提供多種儲存等級,價格差異巨大。S3 Standard 約 0.0125/GB-月(每月存取 1-2 次的資料)、S3 Glacier Instant Retrieval 約 0.00099/GB-月(歸檔資料,取回需要 12 小時)。用 Lifecycle Policy 自動將老舊資料降級到低成本等級。
-
清理未使用的資源:這是最容易被忽略但效果顯著的優化。常見的浪費:(1)未掛載的 EBS volumes:EC2 刪了但 EBS 沒刪。(2)過期的 EBS snapshots:備份策略沒有設定保留期限。(3)閒置的 NAT Gateway:VPC 還在但裡面沒有服務了。(4)未使用的 Elastic IP:分配了但沒掛到 EC2。(5)測試環境的 RDS / ALB:週末和晚上沒人用但 24 小時在跑。
成本監控工具
-
AWS Cost Explorer:AWS 內建的成本分析工具,可以按服務、Region、Tag、Linked Account 等維度查看花費。支援過去 12 個月的歷史資料和未來 12 個月的預測。建議每週至少看一次,觀察趨勢變化。
-
AWS Budgets:設定月預算上限和告警閾值。當實際花費或預測花費超過閾值時,透過 Email 或 SNS 通知。建議為每個環境(prod / staging / dev)分別設定 Budget。
-
AWS Cost Anomaly Detection:用機器學習偵測異常花費。例如某個服務突然比過去 30 天的平均值高出 3 倍,系統會自動發出告警。適合偵測忘記關閉的資源或意外的流量暴增。
-
Infracost(IaC 成本估算):在 Terraform plan 階段就估算基礎設施的月費變化。整合到 CI/CD pipeline,每次 PR 自動附上成本影響。例如加一台 RDS 時,PR comment 會顯示「月費增加 $108」。
-
Kubecost(Kubernetes 成本):專門分析 Kubernetes cluster 的成本分攤。可以看到每個 namespace、deployment、pod 的實際資源消耗和對應費用。幫助找出 over-provisioned 的 deployment 和 idle 的資源。
實作範例 / 設定範例
AWS Budget 告警設定(CloudFormation)
# cloudformation-budget.yml
AWSTemplateFormatVersion: '2010-09-09'
Description: Monthly cost budget with alert notifications
Resources:
MonthlyBudget:
Type: AWS::Budgets::Budget
Properties:
Budget:
BudgetName: monthly-total-cost
BudgetType: COST
TimeUnit: MONTHLY
BudgetLimit:
Amount: 500
Unit: USD
CostFilters: {}
CostTypes:
IncludeTax: true
IncludeSubscription: true
UseBlended: false
NotificationsWithSubscribers:
# 實際花費達到 80% 時通知
- Notification:
NotificationType: ACTUAL
ComparisonOperator: GREATER_THAN
Threshold: 80
ThresholdType: PERCENTAGE
Subscribers:
- SubscriptionType: EMAIL
Address: team@example.com
- SubscriptionType: SNS
Address: !Ref BudgetAlertTopic
# 預測花費超過 100% 時通知
- Notification:
NotificationType: FORECASTED
ComparisonOperator: GREATER_THAN
Threshold: 100
ThresholdType: PERCENTAGE
Subscribers:
- SubscriptionType: EMAIL
Address: team@example.com
BudgetAlertTopic:
Type: AWS::SNS::Topic
Properties:
TopicName: budget-alerts
DisplayName: Budget Alert Notifications
# 每個環境的 Budget
ProductionBudget:
Type: AWS::Budgets::Budget
Properties:
Budget:
BudgetName: production-env-cost
BudgetType: COST
TimeUnit: MONTHLY
BudgetLimit:
Amount: 300
Unit: USD
CostFilters:
TagKeyValue:
- 'user:env$production'
NotificationsWithSubscribers:
- Notification:
NotificationType: ACTUAL
ComparisonOperator: GREATER_THAN
Threshold: 90
ThresholdType: PERCENTAGE
Subscribers:
- SubscriptionType: EMAIL
Address: team@example.com也可以用 AWS CLI 快速建立 Budget:
# 用 AWS CLI 建立 Budget 告警
aws budgets create-budget \
--account-id 123456789012 \
--budget '{
"BudgetName": "monthly-limit",
"BudgetType": "COST",
"TimeUnit": "MONTHLY",
"BudgetLimit": {
"Amount": "500",
"Unit": "USD"
}
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{
"SubscriptionType": "EMAIL",
"Address": "team@example.com"
}
]
}
]'Terraform + Infracost 整合
# main.tf - 附上 Infracost 的成本註解
# Infracost 會自動解析 Terraform 並估算費用
# 執行 infracost breakdown --path . 即可看到成本明細
# EC2 instance
# Infracost 估算:~$30/月 (On-Demand)
resource "aws_instance" "web" {
ami = "ami-0abcdef1234567890"
instance_type = "t3.medium"
root_block_device {
volume_size = 30 # Infracost 估算:~$2.4/月 (gp3)
volume_type = "gp3"
}
tags = {
Name = "web-server"
env = "production"
team = "backend"
project = "api"
}
}
# RDS instance
# Infracost 估算:~$108/月 (Multi-AZ)
resource "aws_db_instance" "main" {
identifier = "main-db"
engine = "postgres"
engine_version = "15.4"
instance_class = "db.t3.medium"
allocated_storage = 100
storage_type = "gp3"
multi_az = true # Multi-AZ 費用翻倍
backup_retention_period = 7 # 超過免費額度的備份會收費
skip_final_snapshot = false
tags = {
env = "production"
team = "backend"
}
}
# NAT Gateway — 容易被忽略的成本大戶
# Infracost 估算:~$32/月 (固定費用) + 資料處理費
resource "aws_nat_gateway" "main" {
allocation_id = aws_eip.nat.id
subnet_id = aws_subnet.public.id
tags = {
Name = "main-nat"
env = "production"
}
}
# 在 CI/CD pipeline 中整合 Infracost
# .github/workflows/infracost.yml 可以在 PR 自動附上成本影響:
#
# - name: Infracost breakdown
# run: |
# infracost diff \
# --path . \
# --format json \
# --out-file /tmp/infracost.json
#
# - name: Post PR comment
# run: |
# infracost comment github \
# --path /tmp/infracost.json \
# --repo $GITHUB_REPOSITORY \
# --pull-request $PR_NUMBER \
# --github-token $GITHUB_TOKENS3 Lifecycle Policy 自動降級儲存等級
{
"Rules": [
{
"ID": "archive-old-logs",
"Status": "Enabled",
"Filter": {
"Prefix": "logs/"
},
"Transitions": [
{
"Days": 30,
"StorageClass": "STANDARD_IA"
},
{
"Days": 90,
"StorageClass": "GLACIER_IR"
},
{
"Days": 365,
"StorageClass": "DEEP_ARCHIVE"
}
],
"Expiration": {
"Days": 730
}
},
{
"ID": "cleanup-incomplete-uploads",
"Status": "Enabled",
"Filter": {
"Prefix": ""
},
"AbortIncompleteMultipartUpload": {
"DaysAfterInitiation": 7
}
},
{
"ID": "archive-old-backups",
"Status": "Enabled",
"Filter": {
"Prefix": "backups/"
},
"Transitions": [
{
"Days": 7,
"StorageClass": "STANDARD_IA"
},
{
"Days": 30,
"StorageClass": "GLACIER_IR"
}
],
"Expiration": {
"Days": 180
}
}
]
}用 AWS CLI 套用這個 Lifecycle Policy:
aws s3api put-bucket-lifecycle-configuration \
--bucket my-app-bucket \
--lifecycle-configuration file://lifecycle-policy.json自動清理未使用資源的腳本
#!/bin/bash
# cleanup-unused-resources.sh
# 定期執行(cron: 每週一 9:00)尋找並列出未使用的 AWS 資源
# 0 9 * * 1 /opt/scripts/cleanup-unused-resources.sh
set -euo pipefail
REPORT_FILE="/tmp/unused-resources-$(date +%Y%m%d).txt"
echo "=== 未使用資源報告 $(date) ===" > "$REPORT_FILE"
# 1. 找出未掛載的 EBS Volumes
echo -e "\n--- 未掛載的 EBS Volumes ---" >> "$REPORT_FILE"
aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'Volumes[*].{ID:VolumeId,Size:Size,Created:CreateTime,AZ:AvailabilityZone}' \
--output table >> "$REPORT_FILE"
UNATTACHED_EBS=$(aws ec2 describe-volumes \
--filters Name=status,Values=available \
--query 'length(Volumes)')
echo "未掛載 EBS 數量: $UNATTACHED_EBS" >> "$REPORT_FILE"
# 2. 找出未使用的 Elastic IPs
echo -e "\n--- 未使用的 Elastic IPs ---" >> "$REPORT_FILE"
aws ec2 describe-addresses \
--query 'Addresses[?AssociationId==`null`].{IP:PublicIp,AllocationId:AllocationId}' \
--output table >> "$REPORT_FILE"
# 3. 找出超過 30 天的 EBS Snapshots
echo -e "\n--- 超過 30 天的 EBS Snapshots ---" >> "$REPORT_FILE"
THIRTY_DAYS_AGO=$(date -d '30 days ago' +%Y-%m-%dT%H:%M:%S 2>/dev/null \
|| date -v-30d +%Y-%m-%dT%H:%M:%S)
aws ec2 describe-snapshots \
--owner-ids self \
--query "Snapshots[?StartTime<='${THIRTY_DAYS_AGO}'].{ID:SnapshotId,Size:VolumeSize,Date:StartTime}" \
--output table >> "$REPORT_FILE"
# 4. 找出閒置的 Load Balancers(過去 7 天無流量)
echo -e "\n--- 可能閒置的 ALB ---" >> "$REPORT_FILE"
for ALB_ARN in $(aws elbv2 describe-load-balancers --query 'LoadBalancers[*].LoadBalancerArn' --output text); do
REQUEST_COUNT=$(aws cloudwatch get-metric-statistics \
--namespace AWS/ApplicationELB \
--metric-name RequestCount \
--dimensions Name=LoadBalancer,Value="$(echo "$ALB_ARN" | cut -d'/' -f2-)" \
--start-time "$(date -d '7 days ago' +%Y-%m-%dT%H:%M:%S 2>/dev/null || date -v-7d +%Y-%m-%dT%H:%M:%S)" \
--end-time "$(date +%Y-%m-%dT%H:%M:%S)" \
--period 604800 \
--statistics Sum \
--query 'Datapoints[0].Sum // `0`' \
--output text 2>/dev/null || echo "0")
if [ "$REQUEST_COUNT" = "0" ] || [ "$REQUEST_COUNT" = "None" ]; then
ALB_NAME=$(aws elbv2 describe-load-balancers \
--load-balancer-arns "$ALB_ARN" \
--query 'LoadBalancers[0].LoadBalancerName' --output text)
echo "閒置 ALB: $ALB_NAME ($ALB_ARN)" >> "$REPORT_FILE"
fi
done
# 5. 找出 stopped 超過 7 天的 EC2 instances
echo -e "\n--- 長期 Stopped 的 EC2 ---" >> "$REPORT_FILE"
aws ec2 describe-instances \
--filters Name=instance-state-name,Values=stopped \
--query 'Reservations[*].Instances[*].{ID:InstanceId,Type:InstanceType,Name:Tags[?Key==`Name`]|[0].Value,StopTime:StateTransitionReason}' \
--output table >> "$REPORT_FILE"
# 發送報告
echo -e "\n=== 報告結束 ===" >> "$REPORT_FILE"
echo "報告已產生: $REPORT_FILE"
# 可以透過 SNS 發送通知
# aws sns publish \
# --topic-arn arn:aws:sns:us-east-1:123456789012:cost-alerts \
# --subject "Weekly Unused Resources Report" \
# --message file://"$REPORT_FILE"常見問題與風險
-
帳單爆炸才發現問題:上雲第一個月帳單 500。原因通常是:(1)忘記 NAT Gateway 和 Data Transfer 的費用。(2)測試環境的資源沒有關閉。(3)Multi-AZ 和備份費用沒算進去。避免方式:上雲前用 Pricing Calculator 完整估算所有項目(不要漏掉 Network 和 Misc),第一天就設定 Budget Alert。
-
Reserved Instance 買錯規格:花了 $5000 買 3 年 RI,結果半年後發現需要換執行個體類型或搬到其他 Region。RI 不能跨 Region 使用、部分類型不能換。避免方式:先用 1 年期試水溫,不要一開始就買 3 年期。優先考慮 Savings Plans(不綁定執行個體類型)。只對非常確定的核心負載購買長期承諾。
-
Spot Instance 被回收導致服務中斷:Spot Instance 被 AWS 回收(2 分鐘通知),如果沒有設計好 graceful shutdown 和 failover,使用者會看到服務中斷。避免方式:Spot 只用在可中斷的工作負載。Production 的 Web 服務用 On-Demand + Spot 混合(例如 ASG 中 70% On-Demand + 30% Spot)。設計好中斷處理(checkpoint、job retry)。
-
Tag 策略沒有落實:沒有統一的 tagging 標準,Cost Explorer 看到的都是 untagged 的資源,無法分辨哪些費用屬於哪個團隊或專案。避免方式:用 AWS Organizations 的 SCP(Service Control Policy)或 Config Rules 強制要求 tag。在 Terraform 的 provider 層級設定 default tags。定期稽核 untagged 資源。
-
忽略非生產環境的成本:Staging 和 Dev 環境照搬 Production 的規格,加起來的費用甚至超過 Production。避免方式:非生產環境用更小的執行個體、Single-AZ 的 RDS、關閉不必要的 NAT Gateway。設定排程(weekday 8:00 開機、22:00 關機)。詳見 Environment Separation。
-
Data Transfer 費用失控:大量資料從 S3 透過 CloudFront 對外服務、跨 Region 複製資料、微服務之間跨 AZ 呼叫,這些都會產生顯著的網路傳輸費。避免方式:使用 CloudFront 可以降低 S3 egress 費用(CloudFront 的 data transfer 費率比直接從 S3 傳出便宜)。盡量讓相關服務部署在同一個 AZ。用 VPC Endpoint 取代 NAT Gateway 存取 AWS 服務。
優點
- 雲端的彈性計費讓小團隊可以從低成本開始,隨業務增長擴展
- RI / Savings Plans / Spot 提供多種降本選擇,適合不同場景
- 完善的成本監控工具(Cost Explorer、Budgets)讓花費透明可追蹤
缺點 / 限制
- 計價模型複雜,不同服務的計費方式差異大,容易漏算隱藏成本
- RI 和 Savings Plans 需要提前承諾,預測不準會造成浪費
- 跨團隊的成本分攤需要嚴格的 tagging 策略,執行成本不低
- 優化是持續性的工作,需要投入人力定期 review 和調整