This runbook provides guidance for monitoring, optimizing, and managing cloud costs for the Oshun infrastructure.
Cost Overview#
Monthly Cost Breakdown (Estimated)#
| Service | Staging | Production | % of Total |
|---|---|---|---|
| ECS (Fargate) | $200-400 | $800-1500 | 25-30% |
| RDS (PostgreSQL) | $100-200 | $400-800 | 15-20% |
| RunPod (GPU) | $100-300 | $500-2000 | 20-40% |
| S3 Storage | $20-50 | $50-150 | 2-5% |
| CloudWatch | $50-100 | $100-300 | 5-8% |
| ALB | $20-40 | $40-100 | 2-4% |
| Data Transfer | $50-100 | $200-500 | 5-10% |
| Other | $50-100 | $100-300 | 5-10% |
| Total | $600-1300 | $2200-5600 |
Monitoring Costs#
AWS Cost Explorer#
Monthly cost by service:
aws ce get-cost-and-usage \
--time-period Start=$(date -d 'first day of this month' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
--granularity MONTHLY \
--metrics BlendedCost \
--group-by Type=DIMENSION,Key=SERVICE \
--query 'ResultsByTime[0].Groups[*].{Service:Keys[0],Cost:Metrics.BlendedCost.Amount}' \
--output table
Daily cost trend:
aws ce get-cost-and-usage \
--time-period Start=$(date -d '7 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
--granularity DAILY \
--metrics BlendedCost \
--query 'ResultsByTime[*].{Date:TimePeriod.Start,Cost:Total.BlendedCost.Amount}' \
--output table
Cost by tag (environment):
aws ce get-cost-and-usage \
--time-period Start=$(date -d 'first day of this month' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
--granularity MONTHLY \
--metrics BlendedCost \
--group-by Type=TAG,Key=Environment \
--query 'ResultsByTime[0].Groups[*].{Environment:Keys[0],Cost:Metrics.BlendedCost.Amount}'
AWS Budgets#
Create monthly budget alert:
aws budgets create-budget \
--account-id $(aws sts get-caller-identity --query Account --output text) \
--budget '{
"BudgetName": "oshun-monthly",
"BudgetLimit": {"Amount": "5000", "Unit": "USD"},
"BudgetType": "COST",
"TimeUnit": "MONTHLY"
}' \
--notifications-with-subscribers '[
{
"Notification": {
"NotificationType": "ACTUAL",
"ComparisonOperator": "GREATER_THAN",
"Threshold": 80,
"ThresholdType": "PERCENTAGE"
},
"Subscribers": [
{"SubscriptionType": "EMAIL", "Address": "ops@oshun.ai"}
]
}
]'
RunPod Costs#
Check RunPod spending:
curl -X POST https://api.runpod.io/graphql \
-H "Authorization: Bearer $RUNPOD_API_KEY" \
-d '{"query": "{ myself { spending { totalSpend currentMonthSpend } } }"}'
Cost Optimization Strategies#
ECS Optimization#
Use Fargate Spot (Staging)#
Fargate Spot provides up to 70% discount for interruptible workloads.
# In Terraform
capacity_provider_strategy {
capacity_provider = "FARGATE_SPOT"
weight = 4
base = 0
}
capacity_provider_strategy {
capacity_provider = "FARGATE"
weight = 1
base = 1 # At least 1 on-demand task
}
Right-Size Tasks#
Analyze CPU/memory utilization:
# Average CPU utilization over 7 days
aws cloudwatch get-metric-statistics \
--namespace AWS/ECS \
--metric-name CPUUtilization \
--dimensions Name=ClusterName,Value=oshun-production Name=ServiceName,Value=oshun-api \
--start-time $(date -d '7 days ago' -u +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--period 86400 \
--statistics Average Maximum p99
# If average CPU < 30%, consider reducing task size
# If max CPU > 80%, consider increasing
Task sizing recommendations:
| Avg CPU | Avg Memory | Recommendation |
|---|---|---|
| < 20% | < 40% | Reduce by 50% |
| 20-40% | 40-60% | May reduce by 25% |
| 40-60% | 60-80% | Well-sized |
| > 70% | > 80% | Increase or scale out |
Scheduled Scaling#
Reduce costs during off-peak hours:
# Scale down at night (10 PM UTC)
aws application-autoscaling put-scheduled-action \
--service-namespace ecs \
--scheduled-action-name night-scale-down \
--resource-id service/oshun-production/oshun-api \
--scalable-dimension ecs:service:DesiredCount \
--schedule "cron(0 22 ? * * *)" \
--scalable-target-action MinCapacity=1,MaxCapacity=4
# Scale up in morning (8 AM UTC)
aws application-autoscaling put-scheduled-action \
--service-namespace ecs \
--scheduled-action-name morning-scale-up \
--resource-id service/oshun-production/oshun-api \
--scalable-dimension ecs:service:DesiredCount \
--schedule "cron(0 8 ? * MON-FRI *)" \
--scalable-target-action MinCapacity=2,MaxCapacity=10
RDS Optimization#
Reserved Instances#
For predictable database workloads, RIs save 30-60%:
# Check current on-demand usage
aws ce get-reservation-coverage \
--time-period Start=$(date -d 'first day of last month' +%Y-%m-%d),End=$(date -d 'first day of this month' +%Y-%m-%d) \
--filter '{"Dimensions": {"Key": "SERVICE", "Values": ["Amazon Relational Database Service"]}}'
# Purchase RI via console or CLI
aws rds purchase-reserved-db-instances-offering \
--reserved-db-instances-offering-id <offering-id> \
--reserved-db-instance-id oshun-production-ri
Storage Optimization#
Enable storage autoscaling to avoid over-provisioning:
aws rds modify-db-instance \
--db-instance-identifier oshun-production \
--max-allocated-storage 500 # GB
Delete old snapshots:
# List snapshots older than 30 days
aws rds describe-db-snapshots \
--db-instance-identifier oshun-production \
--query "DBSnapshots[?SnapshotCreateTime<='$(date -d '30 days ago' +%Y-%m-%d)'].DBSnapshotIdentifier"
# Delete old manual snapshots (automated retained per policy)
aws rds delete-db-snapshot --db-snapshot-identifier <snapshot-id>
S3 Optimization#
Lifecycle Policies#
Move infrequently accessed data to cheaper storage:
aws s3api put-bucket-lifecycle-configuration \
--bucket oshun-production-assets \
--lifecycle-configuration '{
"Rules": [
{
"ID": "MoveToIA",
"Status": "Enabled",
"Filter": {"Prefix": "exports/"},
"Transitions": [
{"Days": 30, "StorageClass": "STANDARD_IA"},
{"Days": 90, "StorageClass": "GLACIER"}
]
},
{
"ID": "DeleteTemp",
"Status": "Enabled",
"Filter": {"Prefix": "temp/"},
"Expiration": {"Days": 7}
}
]
}'
Intelligent-Tiering#
For unpredictable access patterns:
# Enable Intelligent-Tiering by default
aws s3api put-bucket-intelligent-tiering-configuration \
--bucket oshun-production-assets \
--id entire-bucket \
--intelligent-tiering-configuration '{
"Id": "entire-bucket",
"Status": "Enabled",
"Tierings": [
{"Days": 90, "AccessTier": "ARCHIVE_ACCESS"},
{"Days": 180, "AccessTier": "DEEP_ARCHIVE_ACCESS"}
]
}'
RunPod Optimization#
Idle Timeout Tuning#
| Traffic Pattern | Recommended Timeout | Impact |
|---|---|---|
| Continuous | 120-300s | Low cold starts |
| Bursty | 60-120s | Balance |
| Infrequent | 30-60s | Lower cost |
| Very Infrequent | 0-30s | Lowest cost |
# Reduce idle timeout for low-traffic endpoint
curl -X POST https://api.runpod.io/graphql \
-H "Authorization: Bearer $RUNPOD_API_KEY" \
-d '{
"query": "mutation { updateEndpoint(input: { id: \"ENDPOINT_ID\", idleTimeout: 30 }) { id idleTimeout } }"
}'
GPU Selection#
| Use Case | Recommended GPU | Price/Hour | Notes |
|---|---|---|---|
| SD1.5 | RTX 4090 | $0.50 | Fast, cheap |
| SDXL | A40 | $0.86 | Good balance |
| Flux | A100 40GB | $1.58 | Required for Flux |
| Training/Fine-tune | A100 80GB | $2.00 | Max VRAM |
CloudWatch Optimization#
Reduce Log Retention#
# Set 14-day retention for staging
aws logs put-retention-policy \
--log-group-name /ecs/oshun-staging/api \
--retention-in-days 14
# Set 30-day retention for less critical production logs
aws logs put-retention-policy \
--log-group-name /ecs/oshun-production/api \
--retention-in-days 30
Optimize Metrics Collection#
Disable high-cardinality metrics if not needed:
# Review custom metrics
aws cloudwatch list-metrics --namespace Oshun/Custom
# Delete unused custom metrics (stop publishing)
Data Transfer Optimization#
Use VPC endpoints to avoid NAT costs:
# S3 Gateway Endpoint (free)
aws ec2 create-vpc-endpoint \
--vpc-id vpc-xxx \
--service-name com.amazonaws.us-east-1.s3 \
--route-table-ids rtb-xxx
# ECR, CloudWatch Interface Endpoints
aws ec2 create-vpc-endpoint \
--vpc-id vpc-xxx \
--service-name com.amazonaws.us-east-1.ecr.api \
--vpc-endpoint-type Interface \
--subnet-ids subnet-xxx
Cost Alerts#
Setting Up Alerts#
80% budget threshold:
aws cloudwatch put-metric-alarm \
--alarm-name oshun-cost-warning \
--alarm-description "AWS costs exceeding 80% of budget" \
--metric-name EstimatedCharges \
--namespace AWS/Billing \
--statistic Maximum \
--period 21600 \
--threshold 4000 \
--comparison-operator GreaterThanThreshold \
--dimensions Name=Currency,Value=USD \
--evaluation-periods 1 \
--alarm-actions arn:aws:sns:us-east-1:123456789:billing-alerts
Anomaly Detection#
aws cloudwatch put-anomaly-detector \
--namespace AWS/Billing \
--metric-name EstimatedCharges \
--stat Maximum \
--dimensions Name=Currency,Value=USD
Monthly Cost Review#
Review Checklist#
- Review AWS Cost Explorer for unexpected spikes
- Check RunPod spending dashboard
- Review underutilized resources
- Check for orphaned resources (unused EBS, old snapshots)
- Verify Reserved Instance coverage
- Review data transfer costs
- Update budget forecasts if needed
Cost Reporting#
Generate monthly report:
#!/bin/bash
# Monthly cost report script
echo "=== Oshun Monthly Cost Report ==="
echo "Period: $(date -d 'first day of last month' +%B\ %Y)"
echo ""
echo "=== AWS Costs by Service ==="
aws ce get-cost-and-usage \
--time-period Start=$(date -d 'first day of last month' +%Y-%m-%d),End=$(date -d 'first day of this month' +%Y-%m-%d) \
--granularity MONTHLY \
--metrics BlendedCost \
--group-by Type=DIMENSION,Key=SERVICE \
--query 'ResultsByTime[0].Groups[*].{Service:Keys[0],Cost:Metrics.BlendedCost.Amount}' \
--output table
echo ""
echo "=== Costs by Environment ==="
aws ce get-cost-and-usage \
--time-period Start=$(date -d 'first day of last month' +%Y-%m-%d),End=$(date -d 'first day of this month' +%Y-%m-%d) \
--granularity MONTHLY \
--metrics BlendedCost \
--group-by Type=TAG,Key=Environment \
--query 'ResultsByTime[0].Groups[*].{Environment:Keys[0],Cost:Metrics.BlendedCost.Amount}' \
--output table
echo ""
echo "=== Total ==="
aws ce get-cost-and-usage \
--time-period Start=$(date -d 'first day of last month' +%Y-%m-%d),End=$(date -d 'first day of this month' +%Y-%m-%d) \
--granularity MONTHLY \
--metrics BlendedCost \
--query 'ResultsByTime[0].Total.BlendedCost.Amount'
Emergency Cost Reduction#
If costs are spiking unexpectedly:
1. Immediate Actions#
# Scale down non-critical services
aws ecs update-service --cluster oshun-staging --service oshun-api --desired-count 0
# Reduce RunPod max workers
curl -X POST https://api.runpod.io/graphql \
-H "Authorization: Bearer $RUNPOD_API_KEY" \
-d '{"query": "mutation { updateEndpoint(input: { id: \"ENDPOINT_ID\", workersMax: 1 }) { id } }"}'
2. Identify Cost Source#
# Check for cost spikes by service
aws ce get-cost-and-usage \
--time-period Start=$(date -d '3 days ago' +%Y-%m-%d),End=$(date +%Y-%m-%d) \
--granularity DAILY \
--metrics BlendedCost \
--group-by Type=DIMENSION,Key=SERVICE \
--output table
3. Common Causes#
| Symptom | Likely Cause | Fix |
|---|---|---|
| ECS costs doubled | Auto-scaling spike | Check scaling policies |
| Data transfer spike | Large file uploads/downloads | Review CloudFront/CDN |
| RunPod costs high | Workers staying alive | Reduce idle timeout |
| S3 costs increased | Lifecycle not applied | Check lifecycle policies |
| RDS costs increased | Storage autoscale | Review storage usage |