wave-ebad.jpg

Riptide Blog

​Auto-on/off with AWS Lambda

Posted by Jose O'farril on July 7, 2016

Scheduled tasks and Cron jobs have always been a staple in a Systems Administrator toolkit. While workloads move to cloud platform, so do our tools. Below I’ll show one way of tackling the promise of being able to turn schedule EC2 resources to auto start and auto shutdown with AWS Lambda service.

AWS Lambda service

As of late 2015 AWS added a Scheduled Event feature in their Lambda service. This included a Cron feature. At the same time they also included support for Python, which may be more familiar to a SysAdmin/Engineer. At least is was in my case. So this example will use the AWS Python SDK (boto3).

Here’s the code we’ll be working with. The configurable fields are in the filters, here we add the tag values of our EC2 instance. In this use case, I’ve added the following tag to each of the EC2 instances I want to automate. Auto-on = True


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):
# We'll use boto's filtering method to find
# all stopped EC2 instances with our autotag value
filters = [{
'Name': 'tag:Auto-on',
'Values': ['True']
},
{
'Name': 'instance-state-name',
'Values': ['stopped']
}
]

# Start the filtering
instances = ec2.instances.filter(Filters=filters)

# Get all the instances that are off
StoppedInstances = [instance.id for instance in instances]

# print these for the log
print StoppedInstances

# Check to see if there are any stopped instances.
if len(StoppedInstances) > 0:
#Start'em Up
startingUp = ec2.instances.filter(InstanceIds=StoppedInstances).start()
print startingUp
else:
print "Nothing to see here"

Setting up our Lambda function

When you create your lambda function you’ll see an option to select a template. In our use case we’re going to skip these, but they are a great resource to see basic usage for lambda.
Lambda function

In the Configure Function screen we’re going to give our function the following options

  • Name (something easily recognizable
  • Description (Anything that helps tell you what’s here
  • Runtime (Python 2.7)

And we’ll copy/paste our code into the inline editor.

configure function

We’ll leave the Handler name as is since we’re using the default lambda_hander name.
Under Role we’ll create a new Basic Execution Role (A new screen will open).

lambda role

In the new screen create the new IAM role and name it something you’ll understand. While this is the default Lambda function to be able to write into CloudWatch Logs, we need to add in permission to interact with EC2. We’ll add the following policy to the new role.

{
"Sid": "Stmt1467215252000",
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:StartInstances",
"ec2:StopInstances"
],
"Resource": [
"*"
]
}
]
}

role policy

Back in the lambda configuration we’ll select our new policy. We’ll increase the timeout to 1 min or so since we’re interacting with the AWS API’s. We can leave the No VCP option since the API isn’t an internal resource to AWS.

no vcp

Event Source

Now that we have the lambda function all loaded, lets set up the event source. In our case I want to start the EC2 instances at 11am UTC. Fill in the following options.

  • Event Source Type (Scheduled)
  • Rule Name (Custom Name)
  • Description of the Rule
  • Schedule Expression (Lambda uses more of the Java Expression of cron found here)

event source

Testing it all out

If you have a test instance or 2 in a stopped state in your environment we can initiate a test. Make sure your instances have a custom tag of Auto-on = True or whatever your chose to call your tag.
In the input test even we’ll select Scheduled Event from the the drop down. In this case the parameters in the can remain as the default since we’re not pulling any of the event message data into our function. Save the Test and run.

lambda test

If all goes well you should have an output similar to this.

lambda results

Where the log output shows that instances have been started.

START RequestId: 45897b28-3e12-11e6-a4cd-bd2a1889586e Version: $LATEST
[INFO] 2016-06-29T15:58:11.259Z 45897b28-3e12-11e6-a4cd-bd2a1889586e Calling paginated ec2:describe_instances with {'Filters': [{'Values': ['True'], 'Name': 'tag:Auto-on'}, {'Values': ['stopped'], 'Name': 'instance-state-name'}]}
[INFO] 2016-06-29T15:58:11.325Z 45897b28-3e12-11e6-a4cd-bd2a1889586e Starting new HTTPS connection (1): ec2.us-east-1.amazonaws.com
['i-4a14b3da', 'i-4b14b3db']
[INFO] 2016-06-29T15:58:12.0Z 45897b28-3e12-11e6-a4cd-bd2a1889586e Calling paginated ec2:describe_instances with {'InstanceIds': ['i-4a14b3da', 'i-4b14b3db']}
[INFO] 2016-06-29T15:58:12.199Z 45897b28-3e12-11e6-a4cd-bd2a1889586e Calling ec2:start_instances with {u'InstanceIds': ['i-4a14b3da', 'i-4b14b3db']}
[{u'StartingInstances': [{u'InstanceId': 'i-4b14b3db', u'CurrentState': {u'Code': 0, u'Name': 'pending'}, u'PreviousState': {u'Code': 80, u'Name': 'stopped'}}, {u'InstanceId': 'i-4a14b3da', u'CurrentState': {u'Code': 0, u'Name': 'pending'}, u'PreviousState': {u'Code': 80, u'Name': 'stopped'}}], 'ResponseMetadata': {'HTTPStatusCode': 200, 'RequestId': '84e54c5f-d5c1-44d1-a935-ab24d37dddf0'}}]
END RequestId: 45897b28-3e12-11e6-a4cd-bd2a1889586e
REPORT RequestId: 45897b28-3e12-11e6-a4cd-bd2a1889586e Duration: 1450.06 ms Billed Duration: 1500 ms Memory Size: 128 MB Max Memory Used: 61 MB

Adding a Lambda function to stop instances follows the same steps expect change the tag and the code has a couple changes to stop instead of start the instances.

import boto3
import logging

# setup simple logging for INFO
logger = logging.getLogger()
logger.setLevel(logging.INFO)

# Connection
ec2 = boto3.resource('ec2')

def lambda_handler(event, context):

# all running EC2 instances.
filters = [{
'Name': 'tag:AutoOff',
'Values': ['True']
},
{
'Name': 'instance-state-name',
'Values': ['running']
}
]

# filter the instances
instances = ec2.instances.filter(Filters=filters)

# locate all running instances
RunningInstances = [instance.id for instance in instances]

# print the instances for logging purposes
print RunningInstances

# make sure there are actually instances to shut down.
if len(RunningInstances) > 0:
#perform the shutdown
shuttingDown = ec2.instances.filter(InstanceIds=RunningInstances).stop()
print shuttingDown
else:
print "Nothing to see here"

If you are looking for AWS solutions or need advice from our AWS Cloud Architect give us a shout!

Topics: AWS, AWS Lambda service, AWS Solution Architect, Cloud/IoT

Written by Jose O'farril

Request a Free Consultation

Subscribe to Email Updates

Posts by Topic

see all