Automating EC2 Deployment with Terraform and Lambda: A Guide to Stop Instances and Send Notification with SNS.

Automating EC2 Deployment with Terraform and Lambda: A Guide to Stop Instances and Send Notification with SNS.

I’d like to demonstrate how you can deploy EC2 instances using Terraform. And create a lambda function to stop these instances and trigger relative notification using SNS.

What is EC2?

  • Amazon EC2 (Elastic Compute Cloud) is a foundational service within Amazon Web Services (AWS) that offers resizable compute capacity in the cloud. Users deploy virtual servers (instances) easily, scaling capacity as needed. It provides diverse instance types and pricing options, integrates with other AWS services securely, and supports auto-scaling and load balancing. In essence, EC2 enables flexible, efficient cloud computing.

What is AWS Lambda?

  • AWS Lambda is a serverless computing service by Amazon Web Services. It lets you run code without managing servers. Lambda automatically scales based on triggers like HTTP requests or data changes. You're charged only for the compute time used.

What is AWS SNS?

  • AWS SNS (Simple Notification Service) is a fully managed messaging service by Amazon Web Services. It enables you to send notifications to distributed systems and applications through various communication protocols such as email, SMS, HTTP, or even to other AWS services like Lambda functions or SQS queues. It simplifies the process of sending notifications to a large number of subscribers or endpoints with high reliability and scalability.

What is Terraform and why use it?

  • Terraform is an open-source infrastructure as code (IaC) tool developed by HashiCorp. It allows users to define and provision infrastructure resources in a declarative configuration language.

  • There are several reasons to use Terraform:

    1. Infrastructure as Code (IaC): Simplifies infrastructure management through code.

    2. Multi-Cloud Support: Manages infrastructure across various cloud providers.

    3. Automation: Automates provisioning and lifecycle management of resources.

    4. State Management: Tracks and manages infrastructure state for efficient changes.

    5. Scalability: Scales from small environments to large deployments.

    6. Community and Ecosystem: Benefits from a robust community and ecosystem of reusable components and best practices.

Let's start:

  1. Write a code to deploy ec2 instances:

    • Here I used a module, which is a self-contained collection of Terraform configurations, that allows you to encapsulate and reuse infrastructure components, making it easier to manage and maintain your Terraform configurations. (module/ec2/main.tf)
    provider "aws" {
      region = "us-east-1"
    }

    #ec2 creation
    resource "aws_instance" "windows_instances" {
      count = var.count_value
      ami = var.ami_value
      instance_type = var.instance_type_value
      tags = {
        Name = "windowsec2-${count.index + 1}"
      }
    }
    output "instance_ids" {
      value = aws_instance.windows_instances[*].id
    }

variable.tf under module/ec2

    variable "ami_value" {
      description = "This is ami value of windows instance"
    }

    variable "instance_type_value" {
      description = "This is instance type"
    }

    variable "count_value" {
      description = "Number of ec2 instance creation"
    }
  1. Create a lambda function to stop these EC2 instances. And trigger a relative notification.

    • Create a Lambda function in Python(lambda.py) and make a zip file(lambda.zip). upload in the same directory where is yours main.tf.
  •     import boto3
    
        def lambda_handler(event, context):
            # Initialize EC2 client
            ec2_client = boto3.client('ec2')
    
            # Get list of running instances
            instances = ec2_client.describe_instances(
                Filters=[
                    {
                        'Name': 'instance-state-name',
                        'Values': ['running']
                    }
                ]
            )['Reservations']
    
            # Stop each running instance
            for reservation in instances:
                for instance in reservation['Instances']:
                    instance_id = instance['InstanceId']
                    ec2_client.stop_instances(InstanceIds=[instance_id])
                    print(f"Stopped instance {instance_id}")
    
            # Send notification
            sns_client = boto3.client('sns')
            sns_client.publish(
                TopicArn='your_sns_topic_are',
                Message='EC2 instances have been stopped successfully'
            )
    
  1. Write code to trigger a lambda function using AWS cloud watch (main.tf).

     provider "aws" {
       region = "us-east-1" 
     }
    
     # EC2 Instance Creation
     module "ec2_instance" {
       source = "./module/ec2"
       count_value = 10
       ami_value = "ami-0f9c44e98edf38a2b"
       instance_type_value = "t2.micro"
     }
    
     # IAM Role for Lambda Execution
     resource "aws_iam_role" "lambda_role" {
       name = "stopec2"
       assume_role_policy = jsonencode({
         "Version" : "2012-10-17",
         "Statement": [{
           "Effect": "Allow",
           "Principal": {
             "Service": "lambda.amazonaws.com"
           },
           "Action": "sts:AssumeRole"
         }]
       })
       tags = {
         tag-key = "lambda-stopec2"
       }
     }
    
     # IAM Policy for Lambda Execution
     resource "aws_iam_policy" "lambda_policy" {
       name        = "STOPEC2"
       description = "Policy for Lambda to stop EC2 instances and send SNS notification"
       policy = jsonencode({
         "Version" : "2012-10-17",
         "Statement": [{
           "Effect": "Allow",
           "Action": [
             "ec2:StopInstances",
             "sns:Publish",
             "logs:CreateLogGroup",
             "logs:CreateLogStream",
             "logs:PutLogEvents"
           ],
           "Resource": "*"
         }]
       })
     }
    
     # Attach Policy to Role
     resource "aws_iam_user_policy_attachment" "lambda_policy_attachment" {
       user       = "your_iam_user_name"
       policy_arn = aws_iam_policy.lambda_policy.arn
     }
    
     # Lambda Function
     resource "aws_lambda_function" "stop_ec2_lambda" {
       filename      = "lambda.zip"  # Path to your Lambda function code
       function_name = "StopEC2Instances"
       handler       = "lambda.lambda_handler"
       runtime       = "python3.8"
       timeout       = 900
       role          = aws_iam_role.lambda_role.arn
       source_code_hash = filebase64sha256("lambda.zip")
     }
    
     # Trigger for Lambda Function
     resource "aws_cloudwatch_event_rule" "trigger_lambda_rule" {
       name        = "TriggerLambdaOnSchedule"
       description = "Trigger Lambda function to stop EC2 instances"
       schedule_expression = "cron(5 18 4 3 ? *)" #mention schedule time 
     }
    
     resource "aws_cloudwatch_event_target" "lambda_target" {
       rule      = aws_cloudwatch_event_rule.trigger_lambda_rule.name
       target_id = "lambda"
       arn       = aws_lambda_function.stop_ec2_lambda.arn
     }
    
     # SNS Topic
     resource "aws_sns_topic" "ec2_stop_notification" {
       name = "EC2_Stop_Notification"
     }
    
     #SNS Subscription
     resource "aws_sns_topic_subscription" "snssubscription" {
       topic_arn = aws_sns_topic.ec2_stop_notification.arn
       protocol = "email"
       endpoint = "Enter email address"
     }
    
     resource "aws_sns_topic_subscription" "snssubscription_lambda" {
       topic_arn = aws_sns_topic.ec2_stop_notification.arn
       protocol = "lambda"
       endpoint = aws_lambda_function.stop_ec2_lambda.arn
     }
    
     # Lambda Permission to Execute from CloudWatch Events
     resource "aws_lambda_permission" "cloudwatch_permission" {
       statement_id  = "AllowExecutionFromCloudWatch"
       action        = "lambda:InvokeFunction"
       function_name = aws_lambda_function.stop_ec2_lambda.function_name
       principal     = "events.amazonaws.com"
       source_arn    = aws_cloudwatch_event_rule.trigger_lambda_rule.arn
     }
    
     # Lambda Permission to Publish SNS
     resource "aws_lambda_permission" "sns_publish_permission" {
       statement_id  = "AllowExecutionToSNS"
       action        = "lambda:InvokeFunction"
       function_name = aws_lambda_function.stop_ec2_lambda.function_name
       principal     = "sns.amazonaws.com"
       source_arn    = aws_sns_topic.ec2_stop_notification.arn
     }
    
  2. Save the above code and run the command "terraform init" to initialize the terraform configuration directory then "terraform plan" to preview the changes and finally "terraform apply" to apply the changes described by the execution plan.

  3. You will receive a notification on the mentioned email.