In my previous post, we looked at a Python script designed to automatically clean up idle EBS volumes. However, manually deploying that script, configuring IAM roles, and setting up EventBridge schedules via the AWS Management Console doesn't scale for production.
To achieve true enterprise operational efficiency, everything must be managed as Infrastructure as Code (IaC).
With over 12 years in IT and 6+ years managing scalable AWS infrastructure, I always recommend automating cloud governance architectures from day one. In this article, I will share the complete production-ready Terraform templates to package and deploy our serverless cost optimization engine.
📁 Project Directory Structure
To keep our infrastructure configurations maintainable, we will break our codebase into standard Terraform modules:
aws-cost-optimizer/
├── main.tf # Core Lambda & EventBridge infrastructure
├── iam.tf # Strict IAM roles and least-privilege policies
├── variables.tf # Customizable deployment variables
└── src/
└── lambda_function.py # Our optimization script from post #1
🛠️ 1. Defining Variables (variables.tf)
First, we establish our variables. This enables different environment setups (like Dev vs. Production) to utilize custom time frames for resource checks.
variable "aws_region" {
type = string
default = "us-east-1"
description = "The target AWS region for deployment"
}
variable "retention_days" {
type = number
default = 7
description = "Number of days an EBS volume must be idle before deletion"
}
🔒 2. Strict IAM Security Guardrails (iam.tf)
As an AWS Certified Solutions Architect, I cannot stress enough the importance of the Principle of Least Privilege. Our Lambda execution role should only have permissions to scan and delete what it absolutely needs.
# Create the IAM Role for Lambda Execution
resource "aws_iam_role" "lambda_cost_optimizer_role" {
name = "lambda-cost-optimizer-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [{
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = { Service = "lambda.amazonaws.com" }
}]
})
}
# Attach Strict Least-Privilege Custom Policies
resource "aws_iam_role_policy" "lambda_ec2_policy" {
name = "lambda-cost-optimizer-ec2-policy"
role = aws_iam_role.lambda_cost_optimizer_role.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = [
"ec2:DescribeVolumes",
"ec2:CreateSnapshot",
"ec2:DeleteVolume"
]
Resource = "*"
},
{
Effect = "Allow"
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
]
Resource = "arn:aws:logs:*:*:*"
}
]
})
}
🏗️ 3. Main Automation & Scheduling Engine (main.tf)
This file handles packaging the Python script on the fly, creating the Lambda resource, and setting up the weekly EventBridge cron scheduler.
provider "aws" {
region = var.aws_region
}
# Automatically package the Lambda source directory into a ZIP archive
data "archive_file" "lambda_zip" {
type = "zip"
source_dir = "${path.module}/src"
output_path = "${path.module}/lambda_function.zip"
}
# Deploy the Lambda Resource
resource "aws_lambda_function" "cost_optimizer" {
filename = data.archive_file.lambda_zip.output_path
function_name = "ebs-cost-optimizer-engine"
role = aws_iam_role.lambda_cost_optimizer_role.arn
handler = "lambda_function.lambda_handler"
runtime = "python3.11"
source_code_hash = data.archive_file.lambda_zip.output_base64sha256
timeout = 60
environment {
variables = {
RETENTION_DAYS = var.retention_days
}
}
}
# Setup the EventBridge Cron Trigger (Every Friday at 6:00 PM UTC)
resource "aws_cloudwatch_event_rule" "weekly_trigger" {
name = "ebs-cleanup-weekly-schedule"
description = "Triggers cost optimization engine at the end of the work week"
schedule_expression = "cron(0 18 ? * FRI *)"
}
# Link EventBridge to Lambda
resource "aws_cloudwatch_event_target" "trigger_lambda" {
rule = aws_cloudwatch_event_rule.weekly_trigger.name
target_id = "TriggerLambdaCostOptimizer"
arn = aws_lambda_function.cost_optimizer.arn
}
# Allow EventBridge to Invoke the Lambda Function
resource "aws_lambda_permission" "allow_eventbridge" {
statement_id = "AllowExecutionFromEventBridge"
action = "lambda:InvokeFunction"
function_name = aws_lambda_function.cost_optimizer.function_name
principal = "events.amazonaws.com"
source_arn = aws_cloudwatch_event_rule.weekly_trigger.arn
}
💡 Production Architecture Best Practices
When deploying this inside your organization, implement these two key practices:
-
Remote State Files: Never store your local
terraform.tfstatefile on your work machine. Store it safely inside an encrypted Amazon S3 bucket configured with state locking via an Amazon DynamoDB table to prevent concurrent updates. -
Environment Isolation: Use Terraform workspaces or distinct folder paths (
environments/devandenvironments/prod) to thoroughly test code changes in staging environments prior to executing deployments in production data structures.
🚀 Conclusion
By combining AWS serverless computing with Terraform, your organization shifts away from tedious manual monitoring dashboards into an elegant model of fully automated infrastructure lifecycle management.
Do you manage your serverless scripts via cloud consoles, or do you standardize everything through infrastructure as code pipelines? Let's discuss in the comments below!
Top comments (0)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.