My colleague, Ryan Manikowski, recently wrote a great blog giving an overview of AWS Lambda.  His article mentioned that AWS Lambda Functions can be triggered by a number of potential events and services.  In this 4-part blog series I am going to cover the Lambda “Scheduled Event” in-depth.

 

Background

Lambda was originally designed to be an “event driven” service, meaning that it was reactionary in nature.  Lambda functions were intended to be written to handle some event (e.g. SNS notification, Kinesis, DynamoDB) and perform some unit of work based on that event.  But what if you want to use Lambda in a non-reactionary way?  What if you want to invoke your Lambda function at some regular interval to perform whatever task you see fit?  At AWS re:Invent in October 2015, AWS announced (at the same time they announced Python function support. Woohoo!) that they were adding support for Scheduled Events to Lambda.

So you can now create Lambda Scheduled Events, via CloudWatch Events Schedule Rules, using either a fixed rate or a cron expression to define your schedule.  Pretty sweet.

Some Technical Details

Schedule Expressions

As I just mentioned above, you can use either of two methods to define a schedule for invoking a Lambda function – rate and cron expressions.

Rate Expressions

The rate expression uses the form: rate(Value Unit)

  • The Value must be a positive integer, and the Unit must be either minute(s), hour(s), or day(s).
  • The rate frequency must be at least 1 minute.
  • A singular value (e.g. 1) must use the singular tense of the unit and, likewise, plural values (e.g. 2 or more) must use the plural tense of the unit.

ex: rate(1 hour) would trigger a Lambda function every 1 hour (perpetually)
ex: rate(5 minutes) would trigger a function every 5 minutes (perpetually)

 

Cron Expressions

The cron expression uses the form: cron(Minutes Hours Day-of-month Month Day-of-week Year)

  • All time is referenced against UTC.
  • All fields are required.
  • One of the day-of-month or day-of-week values must be a question mark (?)

ex: cron(0/15 * * * ? *) would trigger a Lambda function at 0, 15, 30, and 45 minutes past the hour, every hour of every day.
ex: cron(0 23 ? * MON-FRI *) would trigger a Lambda function at 11:00PM UTC Monday through Friday.

ProTip:

If you need your function to execute at very specific points in time (e.g. every 15 minutes starting at the top of the hour), use the cron schedule expression.  The rate schedule expression will start when you create the Scheduled Event rule and then run at the rate defined thereafter.  Meaning if you used rate(15 minutes) as your schedule expression and create that Scheduled Event at 9:53AM, it would start at 9:53 and then kick-off every 15 minutes after that (10:08, 10:23, 10:38, …).  That may or may not be an issue depending on your use-case.

Input data (i.e. parameters)

Along with the rate expression, a scheduled event can also provide a Lambda function with Input data (in a JSON formatted string), which will be handed to the event handler function as a data object (a dictionary in the case of python).  This can be useful if you need to pass your function parameters or arguments, as opposed to “hard-coding” values in your function.  I’ll include an example of this below.  While supplying Input data is not necessarily required, it is something that can and should be used in a number of applications.

Down to the “Nitty-Gritty”

Now that we have an understanding of how AWS Lambda scheduled events can be expressed, we can dive into a real-world scenario and examine how to set that up using either the API (python and boto3) or CloudFormation.  Because what fun would it be doing it in the web console after all?  And this is a deep-dive, so using the web console would be a distasteful choice anyway.  Also…automation.  Enough said.

Suffice to say, creating the scheduled event for your function in the AWS web console can be done quite easily by selecting “Scheduled Event” from the “Event source type” drop-down list and defining your expression.

The Use Case

Let’s assume we’ve written a nice little Lambda function that will search the EC2 API and find all of our instances running in a region, or multiple regions, and manage EBS snapshot backups for all EBS volumes on any instances with a specific tag.

We could hard-code our parameters in the Lambda function or derive them a number of ways, but let’s assume we’ve done the right thing and have specified them in the Scheduled Event Input parameter.  You might say, “Yeah, but I can just derive the current AWS region during the Lambda function execution, so why bother even providing that input?”  To which I would say:

Lambda is only currently available in the us-east-1, us-west-2, ap-northeast-1, eu-west-1, and eu-central-1 regions. What if you want to manage EC2 backups in ap-southeast-2 or another region where lambda isn’t yet available?

Defining our Inputs

backup_tag (dictionary): A single key/value pair used by the backup function to identify which EC2 instance (any instances with a matching tag) to manage backups on.
Ex. { “Key”: “Environment”, “Value”: “Prod” }

regions (list of strings): A list of region(s) to manage EC2 backups against.
Ex. [ “us-west-2”, “us-west-1”, “us-east-1” ]

support_email (string): An email address to send backup reports, alerts, etc. to.
Ex. “[email protected]

Those three inputs would be captured as a JSON string like so:

Img_1

Lambda Function IAM Role Requirement

Lambda functions require an IAM Role be specified at time of creation.  The Role must have the lambda service added as a “Trusted Entity” so that it can assume the Role.  The Trusted Relationship Policy Document (called AssumeRolePolicyDocument in CloudFormation) should look like this:

Img_2

In addition to the Trusted Entity Policy Document, the Role should have a policy, inline or managed, assigned to it that will allow the Lambda function all of the access it needs to AWS resources and APIs (e.g. EC2 describe instances, create snapshots, delete snapshots).  For our use-case the following policy is a good place to start:

Img_3

Creating the Lambda Function IAM Role

In our last article, we looked at how to set up scheduled events using either the API (python and boto3) or CloudFormation, including the required Trusted Entity Policy Document and IAM Role. This role and policy can be created manually using the AWS web console (not recommended), scripted using the IAM API (e.g. Python and boto3), or using a templating tool (e.g. CloudFormation. Hashicorp’s Terraform).  For this exercise we will cover both the scripted and the template tool approaches.

The IAM policy rights required for creating the Lambda function role and policy are:

  • iam:CreateRole
  • iam:PutRolePolicy

Creating the Lambda IAM role via the IAM API using Python and boto3

Note: For the following example you must either have your AWS credentials defined in a supported location (e.g. ENV variables, ~/.boto, ~/.aws/configuration, EC2 meta-data) or you must specify credentials when creating your boto3 client (or alternatively ‘session’).  The User/Role associated with the AWS credentials must also have the necessary rights, defined by policy, to perform the required operations against AWS IAM API.

The following python script will produce our desired IAM role and policy:

lambda execution role cloudformation

That will create the necessary lambda function role and its inline policy.

Creating the Lambda IAM role using AWS CloudFormation Template

The following block of JSON can be added to a CloudFormation template’s “Resource” section to create the Lambda function role and its inline policy:

boto3 iam role example

Registering the Lambda Function

Now that we’ve created the Lambda function IAM role, for the sake of simplicity, let’s assume the function itself is already written and packaged sitting in an S3 bucket that your Lambda function Role will have access to.  For this example, let’s assume our S3 URL for this backup function is: s3://my-awesome-lambda-functions/ec2_backup_manager.zip

The IAM policy rights required for creating the Lambda function and the Scheduled Event are:

  • lambda:CreateFunction
  • lambda:AddPermission
  • events:PutRule
  • events:PutTargets

Registering the Lambda Function and Scheduled Event via the AWS Lambda API using Python and boto3

Note: Like the previous boto3 example, you must either have your AWS credentials defined in a supported location (e.g. ENV variables, ~/.boto, ~/.aws/configuration, EC2 meta-data) or you must specify credentials when creating your boto3 client (or alternatively ‘session’).  The User/Role associated with the AWS credentials must also have the necessary rights, defined by policy, to perform the required operations against the AWS Lambda API.

A few notes on the creation_function function arguments
  • The Runtime is the language and version being used by our function (i.e. python2.7)
  • The Role is the ARN of the role we created in the previous exercise
  • The Handler is the function within your code that Lambda calls to begin execution. For our python example the value is: {lambda_function_name}.{handler_function_name}
  • The Code is either a base64 encoded zip file or the s3 bucket and key
An important Warning about IAM role replication delay

In the previous step we created an IAM role that we reference in the code below when creating our Lambda function.  Since IAM is an AWS region independent service it takes some time – usually less than a minute – for create/update/delete actions against the IAM API to replicate to all AWS regions.  This means that when we perform the create_function operation in the script below, we may very well receive an error from the Lambda API stating that the role we specified does not exist. 

This is especially true when scripting an operation where the create_function operation happens only milliseconds after the create_role function.  Since there is really no way of querying the Lambda API to see if the role is available in the region yet prior to creating the function, the best option is to use exception handling to catch the specific error where the role does not yet exist and wrap that exception handler in a retry loop with an exponential back-off algorithm (though sleeping for 10-15 seconds will work just fine too).

Let’s pick up in the python script where we previously left off in the last example:

Img_6

Registering the Lambda Function and Scheduled Event using AWS CloudFormation Template

Note: The S3Bucket MUST exist in the same region you are launching your CFT stack in.  To support multi-region templates I generally will have a Lambda S3 bucket for each Lambda region and append a .{REGION_NAME} suffix to the bucket name (e.g. my-awesome-lambda-functions.us-west-2).  Since CloudFormation provides us with a psuedo-parameter of the region you are launching the stack in (AWS::Region), you can utilize that to ensure you are referencing the appropriate bucket (see my example below).

The following block of JSON can be used in conjunction with our previous CloudFormation snippet by being added to the template’s “Resource” section to create the Lambda function, CloudWatch Event, and Input:

Img_7

If you implement your Lambda functions using either of the two examples provided you should be able to reliably create, , manage, automate and scale them to whatever extent and whatever schedule you need.  And the best part is you will only be charged for the ACTUAL compute time you use, in 100ms increments.

Now go have fun, automate everything you possibly can, and save your organization thousands of dollars in wasted compute costs!  Oh, and conserve a bunch of energy while you’re at it.

-Ryan Kennedy, Sr Cloud Consultant

 

rss
Facebooktwitterlinkedinmail