Container Cost Optimization on ECS and EKS
Optimize container costs on ECS and EKS through right-sizing, Spot instances, Fargate Spot, and resource efficiency strategies.
Container workloads offer unique optimization opportunities through right-sizing, Spot capacity, and resource efficiency strategies on ECS and EKS.
Resource Right-Sizing
Container Insights Analysis
import boto3
cloudwatch = boto3.client('cloudwatch')
def analyze_container_utilization(cluster, service, days=7):
end = datetime.utcnow()
start = end - timedelta(days=days)
metrics = {}
for metric in ['CpuUtilized', 'CpuReserved', 'MemoryUtilized', 'MemoryReserved']:
response = cloudwatch.get_metric_statistics(
Namespace='ECS/ContainerInsights',
MetricName=metric,
Dimensions=[
{'Name': 'ClusterName', 'Value': cluster},
{'Name': 'ServiceName', 'Value': service}
],
StartTime=start,
EndTime=end,
Period=3600,
Statistics=['Average', 'Maximum']
)
if response['Datapoints']:
metrics[metric] = {
'avg': sum(d['Average'] for d in response['Datapoints']) / len(response['Datapoints']),
'max': max(d['Maximum'] for d in response['Datapoints'])
}
# Calculate utilization percentages
cpu_utilization = (metrics['CpuUtilized']['avg'] / metrics['CpuReserved']['avg']) * 100
memory_utilization = (metrics['MemoryUtilized']['avg'] / metrics['MemoryReserved']['avg']) * 100
return {
'cpu_utilization_avg': cpu_utilization,
'memory_utilization_avg': memory_utilization,
'recommendation': generate_recommendation(cpu_utilization, memory_utilization)
}
def generate_recommendation(cpu_util, mem_util):
if cpu_util < 30 and mem_util < 30:
return 'Significantly over-provisioned. Consider reducing by 50%'
elif cpu_util < 50 and mem_util < 50:
return 'Over-provisioned. Consider reducing by 25%'
elif cpu_util > 80 or mem_util > 80:
return 'Near capacity. Consider increasing resources'
return 'Appropriately sized'
Fargate Spot
Task Definition with Spot
SpotTaskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
Family: spot-service
Cpu: '256'
Memory: '512'
NetworkMode: awsvpc
RequiresCompatibilities:
- FARGATE
ContainerDefinitions:
- Name: app
Image: !Sub ${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/app:latest
Essential: true
PortMappings:
- ContainerPort: 8080
SpotService:
Type: AWS::ECS::Service
Properties:
Cluster: !Ref Cluster
TaskDefinition: !Ref SpotTaskDefinition
DesiredCount: 5
CapacityProviderStrategy:
- CapacityProvider: FARGATE_SPOT
Weight: 4
Base: 0
- CapacityProvider: FARGATE
Weight: 1
Base: 2 # 2 On-Demand tasks for stability
Graviton Migration
Cost Comparison
def compare_graviton_savings():
comparisons = {
'Fargate': {
'x86': {'cpu': 0.04048, 'memory': 0.004445}, # per vCPU-hour, per GB-hour
'arm64': {'cpu': 0.03238, 'memory': 0.003556} # 20% cheaper
},
'EC2': {
'm6i.large': 0.096,
'm6g.large': 0.077 # Graviton, 20% cheaper
}
}
# Calculate monthly savings for 10 tasks, 2 vCPU, 4 GB each
tasks = 10
vcpu = 2
memory_gb = 4
hours = 730
x86_cost = tasks * hours * (vcpu * comparisons['Fargate']['x86']['cpu'] +
memory_gb * comparisons['Fargate']['x86']['memory'])
arm_cost = tasks * hours * (vcpu * comparisons['Fargate']['arm64']['cpu'] +
memory_gb * comparisons['Fargate']['arm64']['memory'])
return {
'x86_monthly_cost': x86_cost,
'graviton_monthly_cost': arm_cost,
'monthly_savings': x86_cost - arm_cost,
'savings_percentage': ((x86_cost - arm_cost) / x86_cost) * 100
}
Multi-Architecture Build
# buildspec.yml for multi-arch images
version: 0.2
phases:
pre_build:
commands:
- aws ecr get-login-password | docker login --username AWS --password-stdin $ECR_REPO
- docker buildx create --use
build:
commands:
- |
docker buildx build \
--platform linux/amd64,linux/arm64 \
--tag $ECR_REPO:latest \
--push .
EKS Cost Optimization
Karpenter for Efficient Scaling
apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
name: cost-optimized
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot", "on-demand"]
- key: kubernetes.io/arch
operator: In
values: ["amd64", "arm64"]
- key: node.kubernetes.io/instance-type
operator: In
values:
- m5.large
- m5a.large
- m5.xlarge
- m6g.large
- m6g.xlarge
limits:
resources:
cpu: 1000
memory: 2000Gi
provider:
subnetSelector:
karpenter.sh/discovery: my-cluster
securityGroupSelector:
karpenter.sh/discovery: my-cluster
consolidation:
enabled: true
Pod Resource Requests
apiVersion: apps/v1
kind: Deployment
metadata:
name: cost-optimized-app
spec:
template:
spec:
containers:
- name: app
resources:
requests:
cpu: "100m" # Start small
memory: "128Mi"
limits:
cpu: "500m" # Allow bursting
memory: "256Mi"
Working with Warqline
We are a cloud engineering consultancy and an official AWS and Google Cloud partner. If you are running this in production and want a second pair of eyes, we scope work in a free 45-minute technical call: you describe what you are running and what worries you, and we tell you what we would look at first.
Conclusion
Container cost optimization combines right-sizing based on actual utilization, Spot capacity for fault-tolerant workloads, and Graviton migration for consistent savings. Monitor utilization continuously and adjust resources accordingly.