My ready-to-use Cloudformation template for automatically stopping and restarting your EC2 AWS instances. You'd like to save some money on AWS, by automatically shutting down your EC2 development instances at night? Install this Cloudformation template and spend less money!
One of the recommendations of AWS is to automatically shut down your EC2 development instances at night and on weekends. By doing this, you reduce the cost of your AWS bill significantly. With this Cloudformation template, you can easily create and maintain your own daily schedule which will automatically stop or start your instances at the time you specified.
So first install this template in your Cloudformation console, and adjust the start and stop times, via the 2 parameters that start with 'cron(...)'. These are Cron Unix type expressions, modified to the Amazon sauce. All instances tagged with 'hibernate = True' will be then stopped or started according to your schedule, by two simple Python Lambda functions.
The general format of the AWS cron expression is:
cron( minute hour day month weekday year )
.
Beware, the times are expressed in the GMT timezone! The full documentation with examples of cron expression are here. For example cron ( 5 12 ? * MON-FRI * )
means, every day at 12:05 GMT from Monday to Friday.
In the template below, you may notice that I target the new Arm64/Graviton 2 processors because they are faster and cheaper than Lambda running on x86_64.
Last tip, don't forget to enable hibernation on your EC2 instances for faster shutdown and wake up! 😉
If you have some questions or other tips, feel free to comment below the code.
#
AWSTemplateFormatVersion: 2010-09-09
Description: Stop start tagged EC2 instances with specified schedule
Parameters:
StartTime:
Type: String
Default: "cron(5 12 ? * MON-FRI *)"
Description: "Start Instances cron expression GMT time! Bonus: If you leave this empty instances won't be started"
StopTime:
Type: String
Default: "cron(5 23 ? * MON-FRI *)"
Description: "Stop Instances cron expression GMT time!"
Conditions:
DoNotStart: !Equals [ !Ref StartTime, "" ]
DoStart: !Not [ Condition: DoNotStart ]
Resources:
# Role for the lambda function
Role:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Sid: ""
Path: "/"
Policies:
- PolicyName: "AllowXRay"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- xray:PutTraceSegments
- xray:PutTelemetryRecords
Resource:
- "*"
- PolicyName: "CreateLogs"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- logs:CreateLogStream
- logs:PutLogEvents
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*"
- PolicyName: "CreateLogsGroup"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- logs:CreateLogGroup
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:*"
- PolicyName: "EC2"
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action:
- ec2:DescribeInstances
- ec2:DescribeTags
- ec2:StopInstances
- ec2:StartInstances
Resource:
- "*"
# LAMDBA Python Code for auto start/stop instances
LambdaStopInstances:
Type: AWS::Lambda::Function
Properties:
Handler: "index.lambda_handler"
Role: !GetAtt Role.Arn
Runtime: python3.9
MemorySize: 128
Architectures:
- arm64
Timeout: 60
Description: Stop or hibernate tagged instances
TracingConfig:
Mode: Active
Code:
ZipFile: |
import boto3
import logging
# setup simple logging for INFO
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# define the connection
ec2 = boto3.resource('ec2')
def lambda_handler(event, context):
# Use the filter() method of the instances collection to retrieve
# all running EC2 instances.
filters = [{
'Name': 'tag:hibernate',
'Values': ['True', 'true']
},
{
'Name': 'instance-state-name',
'Values': ['running']
}
]
# filter the instances
instances = ec2.instances.filter(Filters=filters)
RunningInstances = [instance.id for instance in instances]
# make sure there are actually instances to shut down.
if len(RunningInstances) > 0:
action = ec2.instances.filter(InstanceIds=RunningInstances).stop()
print(action)
LambdaStartInstances:
Type: AWS::Lambda::Function
Properties:
Handler: "index.lambda_handler"
Role: !GetAtt Role.Arn
Runtime: python3.9
MemorySize: 128
Architectures:
- arm64
Timeout: 60
Description: Start tagged instances
TracingConfig:
Mode: Active
Code:
ZipFile: |
import boto3
import logging
# setup simple logging for INFO
logger = logging.getLogger()
logger.setLevel(logging.INFO)
# define the connection
ec2 = boto3.resource('ec2')
def lambda_handler(event, context):
# Use the filter() method of the instances collection to retrieve
# all running EC2 instances.
filters = [{
'Name': 'tag:hibernate',
'Values': ['True', 'true']
},
{
'Name': 'instance-state-name',
'Values': ['stopped']
}
]
# filter the instances
instances = ec2.instances.filter(Filters=filters)
StoppedInstances = [instance.id for instance in instances]
# make sure there are actually instances to start.
if len(StoppedInstances) > 0:
action = ec2.instances.filter(InstanceIds=StoppedInstances).start()
print(action)
# Lambdas scheduling
EventLambdaStopRule:
Type: AWS::Events::Rule
Properties:
Description: EventLambdaStopRule
ScheduleExpression: !Sub "${StopTime}"
State: ENABLED
Targets:
- Arn: !GetAtt LambdaStopInstances.Arn
Id: "LambdaStop"
EventLambdaStartRule:
Type: AWS::Events::Rule
Properties:
Description: EventLambdaStartRule
ScheduleExpression: !Sub "${StartTime}"
State: ENABLED
Targets:
- Arn: !GetAtt LambdaStartInstances.Arn
Id: "LambdaStart"
Condition: DoStart
PermissionForEventsToInvokeLambdaStop:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref LambdaStopInstances
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt EventLambdaStopRule.Arn
PermissionForEventsToInvokeLambdaStart:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref LambdaStartInstances
Action: lambda:InvokeFunction
Principal: events.amazonaws.com
SourceArn: !GetAtt EventLambdaStartRule.Arn
Condition: DoStart
Download the start-stop CF template
Of course, you could also use the serverless framework to do the same if you don't like CF templates :)
Have fun!
Speak your mind: