Starting and Stopping EC2 Instances using a Lambda – and cut your AWS bill in half!
|Cutting your AWS EC2 Bill with Lambda Functions
When running a large training program for an investment bank, we needed over 30 EC2 instances, but only between certain hours of the day. This simple Lambda Function, cut our AWS bill by around 65% on the normal cost of running those instances all day every day.
As a CTO and Cofounder of a food delivery business, I was able to cut our AWS bill substantially by running our servers in the evening when deliveries were taking place. Again, a simple Lambda function could cut the bill as we would no longer be running them all the time.
How many of your servers are really needed all the time? If you want to shave your AWS bill, then Lambda’s make it easy to schedule the starting and stopping of your instances.
How to Create the Lambda Function
Part 1 Create the IAM Role with Permission to access EC2
Any Lambda expression will run with a set of permissions. Those permissions are configured as an IAM role. If you don’t have an IAM role already with permission to access EC2 you will need to create one first.
- In the AWS Administration Console, visit the IAM service.
- In the left pane of the IAM service, click Roles.
- Then click, Create New Role.
- At the Set Role Name dialog, enter a name, something like Ec2AccessRole.
- At the Select Role Type dialog, click Select by the EC2 Role option.
- You are now presented with a list of policies. Locate and select the EC2FullAccess and click Next Step.
- At the Review screen, click Create Role.
Part 2 Create the Lambda Function
- In the AWS Administration Console, visit the Lambda service.
- Click the Create new Lambda Function button.
- At the Select Blueprint dialog, select the first option Blank Function.
- At the Configure Triggers dialog, click the grey checked box and at the drop down, select CloudWatch Events Schedule.
- In the Configure Triggers form, enter a suitable name for your trigger, something like: StartServersAt8AM
- In the Configure Triggers form, enter a suitable description, something like: Start instances at 8am.
- In the Configure Triggers form, enter a Schedule Expression. These are in the form of Cron, which is a scheduling command found on Unix boxes. It has a standard format for times and dates which is used by AWS. So for example, to start at 8AM Monday to Friday the expression would be: cron(00 08 ? * MON-FRI *). An excellent utility to help you can be found here: http://www.cronmaker.com/. This simple Web site will give you the required cron expression for the time you require. IMPORTANT: Note that the time must be in UTC!
- Check the Enable trigger checkbox and click Next.
Part 3 Create the Code for the Function
Now you will need to set up the actual Function itself to start the servers. This will be written in Python.
- At the Configure Function dialog, enter a name, something like startMyServers.
- At the Configure Function dialog, enter a description, something like Start the servers.
- At the Configure Function dialog, set the Runtime to Python.
- In the code box below, enter the following code. In our example, we are setting it to start servers with a specific Tag on them. You could change this to be anything you like. Some way of identifying the servers you wish to start and stop.
import boto3
import logging
ec2 = boto3.resource('ec2')
def lambda_handler(event, context):
filters = [{
'Name': 'tag:Role', // you might change this tag name. Our servers had a tag called Role
'Values': ['MyRoleTagValue'] // this was the value of the tag called Role. You can change this also. Just make sure you add the Tag called Role to your own instances
},
{
'Name': 'instance-state-name',
'Values': ['stopped']
}
]
instances = ec2.instances.filter(Filters=filters)
stoppedInstances = [instance.id for instance in instances]
if len(stoppedInstances) > 0:
startingUp = ec2.instances.filter(InstanceIds=stoppedInstances).start()
- In the Lambda function handler and role section, select Choose an Existing Role.
- In the drop down that appears, select the role created in Part 1. We suggested the name of Ec2AccessRole.
- The remaining fields can be left as they are. Click Next.
- At the Review dialog, click Create Function.
That’s it! You’re done. To create one that stops the servers, the process is pretty much the same. Create another Lambda, but just change the code slightly to check for started instances, and then call stop() on them instead of start. A simple example of the code is below.
import boto3
ec2 = boto3.resource('ec2')
def lambda_handler(event, context):
filters = [{
'Name': 'tag:MyTag',
'Values': ['MyTagValue']
},
{
'Name': 'instance-state-name',
'Values': ['running']
}
]
instances = ec2.instances.filter(Filters=filters)
runningInstances = [instance.id for instance in instances]
if len(runningInstances) > 0:
shuttingDown = ec2.instances.filter(InstanceIds=runningInstances).stop()
Hi
Ive just tried this and am waiting for the function to run. For the IAM role, surely you meant to chose an AWS LAMBDA type role and not an AMAZON EC2 role?
When you come to chose the role in the lambda function config, the role doesn’t show up in the drop down box, even if you enter the arn.
Also, you dont mention about the role having cloudwatch events access?
Can you please clarify the role type and polices that need to be applied?
thanks!
Hi Steve
I have these functions running on my account and the only permission that they have is a Role configured with the AWS managed EC2FullAccess policy. Nothing regarding Cloudwatch since they are not listening for cloudwatch events since they are running on a schedule. So if you create a role in IAM with full access to EC2 enabled, and then use the role in your Lambda you will be good to go.
Thanks for the great tutorial! Clean and simple, no databases or pipelines required, only Lambda.
I adapted it for use with Auto Scaling Groups: https://gist.github.com/veuncent/3e8ea08b0b593620451e887daef1595a