Don’t Let Your Wallet Melt: Automate EC2 Shutdowns with AWS Lambda and Terraform!

Introduction

AWS EC2 instances are fantastic for running scalable applications, but forgetting to stop them can lead to sky-high bills. If you’re a beginner, you might accidentally leave an instance running, only to discover later that your wallet took a hit.

But don’t worry—I’ve got a fun and easy solution for you! In this article, I’ll walk you through setting up an automated system to stop your EC2 instances at a specific time using AWS Lambda and Terraform. This way, you’ll never have to worry about unnecessary costs again.

And the best part? You can grab the entire project from my public GitLab repository and run it in your environment!

Overview of the Solution

We’ll create an AWS Lambda function that checks if any EC2 instances are running at a specific time (6 PM IST in this example). If instances are running, the Lambda function will automatically stop them. We’ll use Terraform to deploy the Lambda function, its necessary IAM role, and a CloudWatch event rule to trigger the function.

Step 1: Install and Configure Terraform with AWS

Before we dive into creating and deploying the Lambda function, let’s make sure you have Terraform installed and configured with your AWS account.

Install Terraform:

For Windows: Download Terraform from the official Terraform website and follow the installation instructions.

For macOS: Use Homebrew:

brew tap hashicorp/tap
brew install hashicorp/tap/terraform

For other operating systems, including Windows and Linux, please refer to the installation instructions provided on the Terraform website linked above.

Step 2: Write the Lambda Function

First, we need a Python script that will be executed by the Lambda function. This script will:

• Check the local time in the specified time zone (IST in this case).

• If the time is 6 PM, it will stop all running EC2 instances.

Here’s the Python script :

import boto3
import pytz
import datetime
import os

def lambda_handler(event, context):
    # Define the time zone based on your local time or region
    time_zone = os.environ.get('TIME_ZONE', 'Asia/Kolkata')

    # Get the current time in the specified time zone
    tz = pytz.timezone(time_zone)
    now = datetime.datetime.now(tz)
    local_hour = now.hour

    # Check if the local time is 18:00 (6 PM)
    if local_hour == 18:
        ec2 = boto3.client('ec2')
        instances = ec2.describe_instances(Filters=[{'Name': 'instance-state-name', 'Values': ['running']}])
        instance_ids = [instance['InstanceId'] for reservation in instances['Reservations'] for instance in reservation['Instances']]

        if instance_ids:
            ec2.stop_instances(InstanceIds=instance_ids)
            print(f'Stopping instances: {instance_ids}')
        else:
            print('No running instances found.')

    return {
        'statusCode': 200,
        'body': 'Function executed successfully.'
    }

Step 3: Package the Lambda Function

Before deploying the Lambda function, you need to package the Python script along with its dependencies. To automate this, we’ll use a simple bash script.

#!/bin/bash

# Create a directory for the Lambda package
mkdir -p lambda_package

# Install necessary Python packages into the package directory
pip install pytz -t lambda_package/

# Copy the Lambda function file to the package directory
cp lambda_function.py lambda_package/

# Change to the package directory
cd lambda_package

# Zip the contents of the package directory into a deployment package
zip -r ../lambda_function.zip .

# Go back to the original directory
cd ..

# Print a message indicating that the package has been created
echo "Lambda deployment package created successfully as lambda_function.zip"

Run this script in your terminal:

./package_lambda.sh

Step 4: Deploy with Terraform

This section details the Terraform configuration to deploy an AWS Lambda function that automatically stops EC2 instances at a specified time. We’ll define the necessary AWS resources, including the Lambda function, IAM roles, CloudWatch events, and CloudWatch log groups.

1. Overview

Our Terraform setup consists of the following key files:

provider.tf: Configures the AWS provider.

variables.tf: Defines input variables for customization.

main.tf: Contains the main resource definitions for AWS.

2. Terraform Files

provider.tf

This file sets up the AWS provider with the region specified in the variables file:

provider "aws" {
  region = var.region # Use the region from variables.tf
}

variables.tf

Here, we define the variables used in the configuration, including the AWS region, timezone, and Lambda function name:

variable "region" {
  description = "The AWS region to deploy resources in"
  type        = string
  default     = "ap-south-1" # Default region, can be overridden
}

variable "timezone" {
  description = "The timezone in which the instances will be stopped"
  type        = string
  default     = "Asia/Kolkata" # IST as default timezone
}

variable "lambda_function_name" {
  description = "The name of the Lambda function"
  type        = string
  default     = "StopEC2Instances"
}

main.tf

This file contains the core Terraform resources for the Lambda function and related components:

# main.tf

resource "aws_iam_role" "lambda_role" {
  name = "LambdaEC2StopRole"

  assume_role_policy = jsonencode({
    Version = "2012-10-17",
    Statement = [
      {
        Effect = "Allow",
        Principal = {
          Service = "lambda.amazonaws.com"
        },
        Action = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_policy_attachment" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2FullAccess"
}
resource "aws_iam_role_policy_attachment" "lambda_logs_policy_attachment" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}


resource "aws_lambda_function" "stop_ec2_instances" {
  function_name = var.lambda_function_name
  role          = aws_iam_role.lambda_role.arn
  handler       = "lambda_function.lambda_handler"
  runtime       = "python3.8"
  filename      = "lambda_function.zip"

  environment {
    variables = {
      TIME_ZONE = var.timezone # Set to IST
    }
  }

  source_code_hash = filebase64sha256("lambda_function.zip")
   # Set the timeout for the Lambda function (e.g., 210 seconds)
  timeout = 210
}

resource "aws_cloudwatch_event_rule" "stop_ec2_instances_rule" {
  name                = "StopEC2InstancesRule"
  description         = "Trigger Lambda to stop EC2 instances at 6 PM IST"
  schedule_expression = "cron(30 12 * * ? *)" # Runs at 12:30 PM UTC which is 6 PM IST

  tags = {
    Name = "StopEC2InstancesRule"
  }
}

resource "aws_cloudwatch_event_target" "stop_ec2_instances_target" {
  rule = aws_cloudwatch_event_rule.stop_ec2_instances_rule.name
  arn  = aws_lambda_function.stop_ec2_instances.arn

  depends_on = [aws_lambda_permission.allow_cloudwatch]
}

resource "aws_lambda_permission" "allow_cloudwatch" {
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.stop_ec2_instances.function_name
  principal     = "events.amazonaws.com"
  statement_id  = "AllowExecutionFromCloudWatch"
  source_arn    = aws_cloudwatch_event_rule.stop_ec2_instances_rule.arn
}

resource "aws_cloudwatch_log_group" "lambda_log_group" {
  name              = "/aws/lambda/${aws_lambda_function.stop_ec2_instances.function_name}"
  retention_in_days = 30 # Adjust retention period as needed
}

3. Running Terraform

To deploy the configuration:

  1. Initialise Terraform:
terraform init
  1. Plan the Deployment:
terraform plan -out=tfplan
  1. Apply the Configuration:
terraform apply tfplan

This will create the necessary AWS resources and set up the automation to stop EC2 instances at 6 PM IST.

Step 5: Test the Setup

You can manually trigger the Lambda function from the AWS Console to ensure everything is working as expected. If everything is set up correctly, your EC2 instances should stop automatically at the specified time.

Conclusion

By automating the shutdown of EC2 instances with AWS Lambda and Terraform, you can prevent unnecessary costs and manage your cloud resources more effectively. This approach is particularly useful for beginners who might forget to manually stop their instances, helping them avoid unexpected bills.

Feel free to customize the Lambda function, Terraform configuration, or trigger times based on your needs. And don’t forget, you can clone the project directly from my public GitLab repository, so you can get started right away. Happy coding!

You can manually trigger the Lambda function from the AWS Console to ensure everything is working as expected. If everything is set up correctly, your EC2 instances should stop automatically at the specified time.

0
Subscribe to my newsletter

Read articles from Adharsh Shanmugam directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Adharsh Shanmugam
Adharsh Shanmugam