Cost Optimization¶
What You'll Learn¶
- How to see where money goes and who owns it
- The biggest levers: rightsizing, commitment discounts, Spot, and Graviton
- The hidden costs that surprise most teams: data transfer, NAT, logs, and idle resources
- How to make cost part of normal engineering work instead of a quarterly scramble
Visibility First¶
You can't optimize what you can't attribute.
Cost allocation tags¶
Define a small, enforced tag set and activate the tags in Billing → Cost allocation tags:
| Tag | Example | Answers |
|---|---|---|
Owner or Team |
payments |
Who do we ask about this spend? |
Service |
orders-api |
What does this cost to run? |
Environment |
prod, staging |
How much does non-production cost? |
CostCenter |
cc-4410 |
Which budget pays? |
Enforce tags with Terraform default_tags, tag policies in AWS Organizations, and Config rules. Remember that some costs — data transfer, shared NAT gateways, support — can't be tagged and need allocation rules.
provider "aws" {
region = "eu-west-1"
default_tags {
tags = {
Team = "orders"
Service = "orders-api"
Environment = "prod"
ManagedBy = "terraform"
}
}
}
Tools¶
| Tool | Use for |
|---|---|
| Cost Explorer | Spend by service, account, tag, and usage type over time; forecasts |
| Budgets | Alerts on actual or forecasted spend per account, team, or service |
| Cost Anomaly Detection | Machine-learned alerts on unusual spend |
| Cost and Usage Report (CUR 2.0 / Data Exports) | Line-item detail for analysis in Athena or a FinOps tool |
| Compute Optimizer | Rightsizing recommendations for EC2, EBS, Lambda, ECS on Fargate, and RDS |
| Cost Optimization Hub | One place for recommendations across accounts, with estimated savings |
In Cost Explorer, group by usage type, not just service. "EC2 – Other" hides NAT gateway hours, data transfer, EBS volumes, and snapshots.
The Big Levers¶
1. Turn off and delete what isn't needed¶
The fastest savings come from resources nobody uses:
# Unattached EBS volumes
aws ec2 describe-volumes --filters Name=status,Values=available \
--query 'Volumes[].[VolumeId, Size, CreateTime]' --output table
# Elastic IPs not associated with anything
aws ec2 describe-addresses --query 'Addresses[?AssociationId==null].[PublicIp, AllocationId]' --output table
# Load balancers with no registered targets — check target group health
aws elbv2 describe-target-groups --query 'TargetGroups[].[TargetGroupName, LoadBalancerArns[0]]' --output table
# Old snapshots
aws ec2 describe-snapshots --owner-ids self \
--query 'Snapshots[?StartTime<=`2025-09-01`].[SnapshotId, VolumeSize, StartTime]' --output table
Automate cleanup carefully, with dry runs and protection tags — see the boto3 cleanup tool. Schedule non-production environments to scale to zero outside working hours.
2. Rightsize¶
Most instances are provisioned for peak load that never arrives.
- Use Compute Optimizer recommendations, based on at least two weeks of CloudWatch metrics (enable memory metrics through the CloudWatch agent for accurate recommendations).
- Rightsize before buying commitments — otherwise you commit to paying for waste.
- For Kubernetes, set requests from real usage; see Kubernetes Cost Optimization.
3. Commitment discounts¶
| Option | Commitment | Flexibility | Applies to |
|---|---|---|---|
| Compute Savings Plans | A dollar amount per hour for 1 or 3 years | Any instance family, size, region, OS; also Fargate and Lambda | Most flexible — the usual default |
| EC2 Instance Savings Plans | Per hour, one instance family in one region | Any size and OS within the family | Higher discount, less flexible |
| Reserved Instances | Specific capacity | Limited | Still the model for RDS, ElastiCache, OpenSearch, Redshift |
Commit only to your steady baseline — the usage that runs 24/7 even at the quietest time — and cover peaks with On-Demand and Spot. Review coverage and utilization monthly.
4. Spot¶
For fault-tolerant workloads — stateless services behind load balancers, CI runners, batch jobs, and Kubernetes worker nodes with Karpenter — Spot offers the deepest discounts. Diversify instance types and AZs, and handle two-minute interruption notices. See Spot Instances.
5. Graviton¶
ARM-based Graviton instances usually cost less than comparable x86 instances for the same workload, and often perform better. Build multi-architecture container images (docker buildx --platform linux/amd64,linux/arm64) and migrate stateless services first. Managed services such as RDS, ElastiCache, OpenSearch, and Lambda offer Graviton options too.
The Hidden Costs¶
Data transfer¶
| Traffic | Relative cost |
|---|---|
| Into AWS from the internet | Free |
| Within one AZ, over private IPs | Free |
| Between AZs in the same region | Charged in both directions |
| Between regions | Charged, more than cross-AZ |
| Out to the internet | Charged, tiered by volume |
| Through a NAT gateway | NAT processing charge plus normal transfer |
Reduce it:
- Add S3 and DynamoDB gateway endpoints — free, and they remove NAT processing charges for that traffic.
- Pull container images through ECR interface endpoints or a pull-through cache instead of through NAT.
- Serve static assets and downloads through CloudFront.
- Keep chatty service pairs in the same AZ where availability requirements allow (topology-aware routing in Kubernetes).
Public IPv4 addresses¶
AWS charges hourly for every public IPv4 address, whether attached or idle. Put instances in private subnets behind load balancers, release unused Elastic IPs, and consider IPv6 where clients support it.
Logs and metrics¶
- Set retention on every CloudWatch log group.
- Don't log at
DEBUGin production by default. - Watch custom metric counts — high-cardinality dimensions like per-customer or per-request IDs multiply metric costs.
- Archive long-term logs to S3 with lifecycle rules.
Storage¶
- Migrate gp2 to gp3 volumes.
- Delete snapshots and AMIs from old deployments, and expire noncurrent S3 versions.
- Use S3 Intelligent-Tiering for data with unknown access patterns.
Idle managed resources¶
NAT gateways, load balancers, EKS control planes, RDS instances, OpenSearch domains, and interface endpoints bill hourly with zero traffic. Consolidate low-traffic environments, share NAT gateways in non-production, and delete lab stacks promptly.
Make Cost an Engineering Practice¶
- Show teams their own costs in a weekly or monthly report by tag.
- Estimate cost in pull requests for infrastructure changes with tools such as Infracost, so a
db.r7g.8xlargegets questioned before it's merged. - Track unit costs — cost per order, per active user, per GB processed. Total spend should grow slower than the business metric.
- Review anomalies within a day, while the change that caused them is still fresh.
- Treat cost like performance: a non-functional requirement, reviewed continuously, owned by the teams that create it.
Common Mistakes¶
- Buying Savings Plans before rightsizing, and locking in waste for three years.
- No tags, so every cost conversation starts with "who owns this?"
- Large S3, ECR, or API traffic through NAT gateways instead of VPC endpoints.
- Non-production environments running 24/7 at production size.
- Log groups with infinite retention and debug logging in production.
- Treating Cost Explorer's "EC2 – Other" as unexplainable instead of breaking it down by usage type.
- Optimizing tiny line items while ignoring the top three services that make up most of the bill.
Interview Questions¶
- How would you find out why an AWS bill increased 40% last month?
- When would you choose Compute Savings Plans over EC2 Instance Savings Plans or Reserved Instances?
- What are the most common sources of unexpected AWS data transfer costs, and how do you reduce them?
- How would you make engineering teams accountable for the cost of their services?
- What should you do before committing to a Savings Plan?
Next¶
You've finished the AWS track. Continue to SRE Practices to run these systems reliably, or to Security.