AWS Cost Guard 💰

Automated Billing Alerts & Daily Recaps

A cloud cost management solution built with Infrastructure as Code (IaC).

🔎 Project Overview

This project demonstrates the creation of an automated cloud cost management system on AWS. It proactively notifies users of potential overspending via custom budget alerts and provides a daily summary of cloud expenditure directly to their inbox, all provisioned using Terraform.

📧 Get A Daily Email

A key feature of this project is the automated daily email, providing a clear and timely summary of recent spending. This proactive alert helps to immediately identify cost anomalies.

A screenshot of the daily AWS Cost Recap email.

📌 The Problem

Managing cloud costs effectively can be challenging. Manual monitoring is time-consuming and reactive, often leading to budget overruns before they are noticed. Lack of granular, timely insights into daily spending makes it difficult to track trends and optimize cloud usage.

🛠️ The Solution

To address these challenges, I developed the AWS Cost Guard, an automated system that:

  • Proactive Budget Alerts: Notifies stakeholders via email when monthly spending approaches defined thresholds (e.g., 90% and 100% of budget).
  • Daily Cost Recap: Sends a concise daily email summarizing the previous day's AWS expenditure.
  • Infrastructure as Code (IaC): Ensures consistent, repeatable, and version-controlled deployment using Terraform.

💻 Technologies Used

  • AWS Services:
    • ▸ AWS Budgets (for cost monitoring and alerts)
    • ▸ AWS Lambda (serverless function for daily cost recap)
    • ▸ AWS Simple Notification Service (SNS) (for email notifications)
    • ▸ AWS CloudWatch Events / EventBridge (for scheduling Lambda function)
    • ▸ AWS Identity and Access Management (IAM) (for secure permissions)
    • ▸ AWS Cost Explorer (accessed via Lambda's Boto3 client)
  • Programming Language: Python 3.9 (for Lambda)

🔑 Key Features

  • Automated Monthly Budget Alerts: Configurable budget limit, triggers email alerts at 90% and 100% of the budget.
  • Daily Cost Recap Emails: Provides a summary of the previous day's spending, helping to quickly identify anomalies.
  • Serverless & Cost-Effective: Leverages AWS Lambda and SNS, incurring costs only when executed or notifications are sent.
  • Automated Deployment: Full infrastructure provisioning via Terraform, ensuring reproducibility and easy updates.
  • Secure Access: Least-privilege IAM roles for Lambda, ensuring secure interaction with AWS services.

Implementation Details & Code Highlights

Terraform Configuration (`main.tf` excerpts)

Key Terraform blocks demonstrate the provisioning of core resources:

AWS Lambda Function


resource "aws_lambda_function" "cost_recap_lambda" {
  filename          = archive_file.lambda_zip.output_path
  function_name     = "DailyCostRecapLambda"
  role              = aws_iam_role.lambda_cost_recap_role.arn
  handler           = "lambda_function.handler"
  runtime           = "python3.9"
  source_code_hash  = archive_file.lambda_zip.output_base64sha256
  timeout           = 60
  environment {
    variables = {
      SNS_TOPIC_ARN = aws_sns_topic.daily_recap_topic.arn
    }
  }
}
                

AWS Budgets Budget & Alert


resource "aws_budgets_budget" "monthly_cost_budget" {
  budget_type       = "COST"
  limit_amount      = var.budget_limit_euro
  limit_unit        = "USD" # AWS Budgets always uses USD internally for limit unit
  time_unit         = "MONTHLY"
  time_period_start = formatdate("YYYY-MM-DD_HH:mm", timeadd(timestamp(), "24h")) # Corrected format
  # ... other attributes
  notification {
    comparison_operator  = "GREATER_THAN"
    threshold            = 90
    threshold_type       = "PERCENTAGE"
    notification_type    = "ACTUAL"
    subscriber {
      address = aws_sns_topic.billing_alarm_topic.arn
      type    = "SNS"
    }
  }
  # ... more notifications if needed
}
                

Python Lambda Function (`lambda_function.py` excerpt)

The Python script queries the AWS Cost Explorer API and formats the daily spending data:


import boto3
import os
import json
from datetime import datetime, timedelta

def handler(event, context):
    sns_topic_arn = os.environ['SNS_TOPIC_ARN']
    ce_client = boto3.client('ce') # Region automatically picked up by Lambda runtime
    sns_client = boto3.client('sns')

    end_date = datetime.utcnow().date()
    start_date = end_date - timedelta(days=1) # Get yesterday's cost

    try:
        response = ce_client.get_cost_and_usage(
            TimePeriod={
                'Start': start_date.strftime('%Y-%m-%d'),
                'End': end_date.strftime('%Y-%m-%d')
            },
            Granularity='DAILY',
            Metrics=['UnblendedCost']
        )

        cost_data = response['ResultsByTime'][0]['Total']['UnblendedCost']
        amount = float(cost_data['Amount'])
        unit = cost_data['Unit']

        message = f"Daily AWS Cost Recap for {start_date.strftime('%Y-%m-%d')}:\n" \
                  f"Total Unblended Cost: {amount:.2f} {unit}\n" \
                  f"-----------------------------------------\n" \
                  f"This is an automated notification."

        sns_client.publish(
            TopicArn=sns_topic_arn,
            Message=message,
            Subject=f"Daily AWS Cost Recap - {start_date.strftime('%Y-%m-%d')}"
        )
        print(f"SNS notification sent successfully for {start_date}.")

    except Exception as e:
        print(f"Error getting cost data or sending SNS: {e}")
        raise e # Re-raise for CloudWatch logs to capture errors
    return {
        'statusCode': 200,
        'body': json.dumps('Cost recap processed!')
    }
                

Challenges & Solutions

  • Challenge: `InvalidParameterValueException: AWS_REGION` Environment Variable Conflict

    Initially attempted to set `AWS_REGION` as a Lambda environment variable in Terraform, which is a reserved key.

    Solution: Removed `AWS_REGION` from the Terraform `environment` block. AWS Lambda automatically injects this variable into the runtime environment, making explicit definition unnecessary and conflicting. This reinforced understanding of Lambda's inherent environment variables.

  • Challenge: `time_period_start` Format Error in AWS Budgets

    Encountered an error where the `time_period_start` for `aws_budgets_budget` couldn't parse the expected timestamp format (e.g., `YYYY-MM-DD_HH:mm:ssZ`).

    Solution: Modified the `formatdate` function in Terraform to specifically match the AWS Budgets API's unique expected format: `YYYY-MM-DD_HH:mm`. This highlighted the importance of API-specific documentation for resource attributes.

  • Challenge: AWS Console Region/Account Mismatch

    Resources created successfully by Terraform were not visible in the AWS Console.

    Solution: The issue was a common one: the AWS Console was set to a different region (e.g., `us-east-1`) or logged into a different AWS account than where Terraform deployed the resources (`eu-west-3`). Verifying and correcting the console's region and account ID immediately resolved the visibility issue. This reinforced the regional nature of most AWS services.

  • Challenge: SNS Subscription Confirmation Emails Not Arriving

    Confirmation emails for SNS topic subscriptions were not always reaching the specified `admin_email`.

    Solution: Checked spam/junk folders thoroughly. Utilized the "Request confirmation" feature directly in the AWS SNS console. In some cases, using an alternative email provider (e.g., personal Gmail) or checking email server settings (for corporate emails) was necessary to overcome aggressive spam filters. This underscored the importance of confirming SNS subscriptions for notification delivery.