Back to Blog
    Automation

    Automating AWS Cost Monitoring with CloudWatch and Lambda

    Kostiantyn DementievJuly 15, 202510 min read

    What you will build: a cost monitoring loop that compares today against yesterday, breaks spend down by service, and pages you on an anomaly, before the monthly invoice explains it for you.

    Checking the billing console by hand is a losing game. A misconfigured job that starts on a Friday evening has all weekend to run, and you find out on the first of the month. The fix is a small automated loop that watches the numbers when nobody is looking.

    Architecture overview

    System components

    • CloudWatch billing metrics for near real-time cost data
    • Lambda for processing and comparison
    • SNS for notification fan-out
    • DynamoDB for historical tracking
    • EventBridge for scheduled execution

    Step 1: enable billing metrics

    Billing metrics publish to us-east-1 only, regardless of where your workloads run. Set the alarm there.

    AWS CLI

    aws cloudwatch put-metric-alarm \
      --alarm-name "Daily-Spend-Alarm" \
      --alarm-description "Alert when daily spend exceeds threshold" \
      --metric-name EstimatedCharges \
      --namespace AWS/Billing \
      --statistic Maximum \
      --period 86400 \
      --threshold 100 \
      --comparison-operator GreaterThanThreshold

    Step 2: the monitoring Lambda

    A daily comparison catches the majority of cost incidents. Anything that jumps more than 20% day over day is worth a look.

    Lambda handler (Python)

    import boto3
    import json
    from datetime import datetime, timedelta
    
    def lambda_handler(event, context):
        cloudwatch = boto3.client('cloudwatch')
        sns = boto3.client('sns')
    
        # Get current and previous day costs
        current_cost = get_daily_cost(cloudwatch, 0)
        previous_cost = get_daily_cost(cloudwatch, 1)
    
        # Calculate percentage change
        if previous_cost > 0:
            change_percent = ((current_cost - previous_cost) / previous_cost) * 100
        else:
            change_percent = 0
    
        # Alert if increase > 20%
        if change_percent > 20:
            send_alert(sns, current_cost, change_percent)
    
        return {
            'statusCode': 200,
            'body': json.dumps(f'Cost monitoring completed. Change: {change_percent:.2f}%')
        }
    
    def get_daily_cost(cloudwatch, days_ago):
        end_time = datetime.utcnow() - timedelta(days=days_ago)
        start_time = end_time - timedelta(days=1)
    
        response = cloudwatch.get_metric_statistics(
            Namespace='AWS/Billing',
            MetricName='EstimatedCharges',
            Dimensions=[{'Name': 'Currency', 'Value': 'USD'}],
            StartTime=start_time,
            EndTime=end_time,
            Period=86400,
            Statistics=['Maximum']
        )
    
        return response['Datapoints'][0]['Maximum'] if response['Datapoints'] else 0

    Going further

    Break the number down by service

    Knowing the bill went up 30% is not actionable. Knowing that NAT Gateway data processing went up 30% is. Group the Cost Explorer query by service so the alert arrives with a suspect already named.

    def get_service_costs(ce_client):
        response = ce_client.get_cost_and_usage(
            TimePeriod={
                'Start': (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d'),
                'End': datetime.now().strftime('%Y-%m-%d')
            },
            Granularity='DAILY',
            Metrics=['UnblendedCost'],
            GroupBy=[{'Type': 'DIMENSION', 'Key': 'SERVICE'}]
        )
    
        services = {}
        for result in response['ResultsByTime']:
            for group in result['Groups']:
                service = group['Keys'][0]
                cost = float(group['Metrics']['UnblendedCost']['Amount'])
                services[service] = cost
    
        return services

    Let AWS do the anomaly detection

    Before you write your own forecasting, turn on AWS Cost Anomaly Detection. It builds a baseline per service and flags deviations against it, which handles the seasonal patterns that a naive day over day comparison keeps false-alarming on. Use your Lambda for the checks it does not cover, not as a replacement.

    Pro tip

    Set separate thresholds per team or project using cost allocation tags, and route each alert to the channel that owns the spend. A single alert to a shared inbox gets muted within a month.

    Monitoring tells you when spend jumps. Our AWS cost audit finds what is driving it, at a fixed price.