Automated EC2 Control using Lambda and Events

Automated EC2 Control using Lambda and Events

This exercise demonstrates how Lambda can be used to start, stop, and protect EC2 instances. This is first demonstrated through manually triggering the Lambda functions using the AWS console and then through AWS Eventbridge as part of an event-driven architecture.

This lab is part of Adrian Cantrill's Solutions Architect course.

Part One

The lab starts by using a CloudFormation template which simply launches two EC2 instances that are going to be the targets of the Lambda functions.

For the Lambda functions to have the ability to manipulate the EC2 instances, a policy and an execution role have to be created.

The policy should allow for the logging of information into Cloudwatch logs and also to start and stop instances.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "ec2:Start*",
        "ec2:Stop*"
      ],
      "Resource": "*"
    }
  ]
}

Next, the IAM role has to be created for Lambda which uses the policy to perform the necessary actions. This is simply named as EC2StartStopLambdaRole in the demo.


At this point, it is useful to note the instance IDs of the EC2 instances for the demonstration of using Lambda to stop an instance.


With the instance IDs on hand, the Lambda functions created and setup with the role.

import boto3
import os
import json

region = 'us-east-1'
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    instances=os.environ['EC2_INSTANCES'].split(",") #lists instances from ENVIRONMENT VARIABLE
    ec2.stop_instances(InstanceIds=instances)
    print('stopped instances: ' + str(instances))

Once the code is placed in the Lambda function, an environment variable called EC2_INSTANCES has to be created with the IDs of the EC2 instances. This is how the Lambda function knows which instances to stop.

A simple test will show that both EC2 instances are stopped.


Similarly, a Lambda function to start the EC2 instances are created in the same steps in the above which will start the EC2 instances when the Lambda function is tested.

import boto3
import os
import json

region = 'us-east-1'
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    instances=os.environ['EC2_INSTANCES'].split(",")
    ec2.start_instances(InstanceIds=instances)
    print('started instances: ' + str(instances))

Part Two

The steps shown above were highly manual; requiring user intervention to start or stop the instances. In a real-world scenario, it would be more often that functions would depend on certain actions happening within the application system.

To demonstrate how a Lambda function can be used in an event-driven approach, Eventbridge is used to monitor service events and to get lambda to react based on those events.

The following function is different in that it receives an event. It accepts event data and uses that to determine which EC2 instance was stopped and is then started again.

import boto3
import os
import json

region = 'us-east-1'
ec2 = boto3.client('ec2', region_name=region)

def lambda_handler(event, context):
    print("Received event: " + json.dumps(event))
    instances=[ event['detail']['instance-id'] ]
    ec2.start_instances(InstanceIds=instances)
    print ('Protected instance stopped - starting up instance: '+str(instances))

The Lambda function itself does not watch for the event to happen, rather, Eventbridge is set to monitor an EC2 instance if it was stopped and it sends information into the target Lambda function. The Lambda function then uses that information to start the instance.


The following image from CloudWatch shows the event that is sent to the Lambda function.


Final Thoughts

The lab exercise shows a fairly simple implementation of using Lambda for an event-driven approach. In more complex approaches, this method is truly valuable in that it improves the responsiveness systems and decouples different components of a system which allows for improved scaling and maintainability. This concept is essential to creating well-architected systems and I am looking forward to more complex exercises in the future.