Shutdown EC2 periodically to save cost

Many times, you might forget to shut down EC2 after late-night learning and forget to turn off your EC2 instances. This article focuses on to Shutdown EC2 periodically to save costs
Task
Create a Lambda function that shuts down EC2 instances periodically using AWS CloudWatch Events.
This task is achieved in two ways
- Manually (Recommended for first-time practice)
- Automated using CloudFormation (Recommended for saving time)
2. Automated using CloudFormation IaaC Template
I like automating things. So here is how to achieve this
- Create an IAM Role for CloudFormation to deploy stacks and create resources. You can just skip if you already have a role.
- Go to the AWS Console. Go to ClouFormation and Create a Stack.
- Pass Params to CloudFormation
- Select IAM Role for CloudFormation to create stacks.
- Checkbox CAPABILITY IAM as Ticked
- Deploy Stack. Wait for Stack to deploy.
- Check for EC2 instances if they are running or stopped.
import boto3
import json
ec2 = boto3.client('ec2')
def get_instance_ids():
all_instances = ec2.describe_instances()
instance_ids = []
for reservation in all_instances['Reservations']:
for instance in reservation['Instances']:
print("InstanceID",instance['InstanceId'])
if instance["State"]["Name"]=="running":
instance_ids.append(instance['InstanceId'])
return instance_ids
def handler(event, context):
instance_ids = get_instance_ids()
if len(instance_ids) != 0:
ec2.stop_instances(InstanceIds=instance_ids)
response = "Successfully stopped instances: " + str(instance_ids)
return {'statusCode': 200,'body': json.dumps(response)}
lambda_file.py
AWSTemplateFormatVersion: "2010-09-09"
Description: Cloudformation IaaC code to ShutDown all ec2 at Time t. It solves the problem that you forget to shutdown ec2 and get an unwanted bill. Please be aware of what you are doing and do not use this in your company account even by mistake.
Parameters:
CronExpression:
Description: "Enter a cron expression to schedule your event. For Example cron(30 1 * * ? *) means that your lambda will run everyday at 1:10 am. The Time Zone here refers to the time zone in which your AWS region lies."
Type: String
Default: "cron(30 1 * * ? *)"
LogsParam:
Description: Do you want to store logs. Storing logs is chargeble.
Type: String
Default: "No"
AllowedValues: ["Yes", "No"]
Conditions:
UseAWSLambdaBasicExecutionRole: !Equals [!Ref LogsParam, "Yes"] # If LogsParam equals Yes then set UseAWSLambdaBasicExecutionRole as True
Resources:
# Create IAM Role for Lambda. Lamba needs some permissions to shutdown VMs
LambdaStopEC2Rule:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: sts:AssumeRole
# If UseAWSLambdaBasicExecutionRole is True then attach the Lambda Basic Execution Role else attach nothing
ManagedPolicyArns: !If [UseAWSLambdaBasicExecutionRole, arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole, !Ref "AWS::NoValue"]
Path: /
Policies:
- PolicyName: LambdaStopEC2Policy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- ec2:DescribeInstances
- ec2:StopInstances
- ec2:DescribeInstanceStatus
Resource: '*'
# Create Lambda that will shutdowm VMs
StopAllEC2Lambda:
Type: AWS::Lambda::Function
Properties:
Description: Start starts build for a code build project
Handler: index.handler
Runtime: python3.9
Role: !GetAtt "LambdaStopEC2Rule.Arn"
# Timeout: 20
MemorySize: 128
Code:
ZipFile: !Sub |
import boto3
import json
ec2 = boto3.client('ec2')
def get_instance_ids():
all_instances = ec2.describe_instances()
instance_ids = []
for reservation in all_instances['Reservations']:
for instance in reservation['Instances']:
print("InstanceID",instance['InstanceId'])
if instance["State"]["Name"]=="running":
instance_ids.append(instance['InstanceId'])
return instance_ids
def handler(event, context):
instance_ids = get_instance_ids()
if len(instance_ids) != 0:
ec2.stop_instances(InstanceIds=instance_ids)
response = "Successfully stopped instances: " + str(instance_ids)
return {'statusCode': 200,'body': json.dumps(response)}
# Create CloudWatch Events Rule. This rule will execute Lambda periodically
ScheduledRule:
Type: AWS::Events::Rule
Properties:
Description: "ScheduledRule"
ScheduleExpression: !Ref CronExpression
State: "ENABLED"
Targets:
- Arn: !GetAtt StopAllEC2Lambda.Arn
Id: "StopAllEC2Lambda"
# CloudWatch Events Rule needs permission to invoke Lambda. So attaching lambda invoke permission.
PermissionForEventsToInvokeLambda:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref StopAllEC2Lambda
Action: "lambda:InvokeFunction"
Principal: "events.amazonaws.com"
SourceArn:
Fn::GetAtt:
- "ScheduledRule"
- "Arn"
Outputs:
MyStacksRegion:
Value: !Ref "AWS::Region"
Description: Your AWS Region
template.yaml