Scheduling Lambda Functions with AWS SAM
A few days ago, I spent some time learning how to use Amazon’s Serverless Application Model (SAM) to schedule the recurring execution of Lambda functions. To help better cement my understanding, I assembled an overview of all the SAM template components necessary to schedule the periodic execution of a Go-based Lambda function. I also made note of how I used the SAM CLI to package and deploy everything to AWS.
Serverless Application Model
Amazon’s Serverless Application Model is a specification for translating SAM templates into CloudFormation templates. Much like macro expansion, it works through a textual transformation of the input SAM template into a template the CloudFormation engine can make sense of.
There are several components that make up a SAM template, but in this example we only use four: Format Version, Description, Transform, and Resources.
- Format Version equates to
AWSTemplateFormatVersion
in the template, which identifies its capabilities - Description is optional, but provides a way to give the template a high-level description
- Transform can map to multiple things, but here it maps to the
AWS::Serverless-2016-10-31
transform, which is a version of the SAM specification
As far as Resources go, this template defines two: TestFunction
and TestRole
.
AWSTemplateFormatVersion: '2010-09-09'
Description: A scheduled Amazon Lambda function.
Resources:
TestFunction:
Properties:
CodeUri: .
Events:
Testy:
Properties:
Schedule: rate(1 hour)
Type: Schedule
Handler: main
Role: !GetAtt TestRole.Arn
Runtime: go1.x
Type: AWS::Serverless::Function
TestRole:
Properties:
AssumeRolePolicyDocument:
Statement:
- Action:
- sts:AssumeRole
Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Version: '2012-10-17'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
Type: AWS::IAM::Role
Transform: AWS::Serverless-2016-10-31
TestRole
is a resource of type AWS::IAM::Role
, which is a top-level CloudFormation resource. It creates an Identity and Access Management (IAM) role containing the permissions necessary for our Lambda function to do its thing. In this case, it simply encapsulates a canned IAM policy, AWSLambdaBasicExecutionRole
. This policy allows the Lambda function to use the following CloudWatch API calls to log function output to CloudWatch Logs.
logs:CreateLogGroup
logs:CreateLogStream
logs:PutLogEvents
The next resource, TestFunction
, is of type AWS::Serverless::Function
. This is not a top-level CloudFormation resource. Instead, it is a SAM resource that expands into multiple top-level CloudFormation resources. Based on our usage, it expands into three:
AWS::Lambda::Function
AWS::Lambda::Permission
AWS::Events::Rule
AWS::Lambda::Function
is the top-level CloudFormation resource to define an Amazon Lambda function. Because we want to schedule the function’s periodic execution, we include an Events
property on our AWS::Serverless::Function
resource. This allows us to define the function execution schedule within the context of the function’s properties. Behind-the-scenes, the Events
property expands into a AWS::Events::Rule
resource with an invocation rate of once per hour.
Lastly, in order for the CloudWatch Events API to invoke our function, it needs permissions to do so. AWS::Lambda::Permission
grants CloudWatch Events the permission to invoke our function.
Package and ship
The AWS SAM CLI builds on top of the SAM specification by providing a single tool to manage the packaging and deployment of serverless applications. Installation is a bit out-of-scope for this post, but once you’ve managed to install the sam
tool, the application deployment process occurs in three phases.
package main
import (
"context"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func HandleRequest(ctx context.Context, e events.CloudWatchEvent) (string, error) {
return fmt.Sprintf("Hello, world."), nil
}
func main() {
lambda.Start(HandleRequest)
}
First, compile your Go-based Lambda function into a Linux compatible binary.
GOOS=linux go build -o main main.go
Once the binary exists, use sam
to upload the binary to S3 and reference it in a newly created packaged.yaml
CloudFormation configuration.
$ sam package --s3-bucket test-global-config-us-east-1 \
--template-file template.yaml \
--output-template-file packaged.yaml
Uploading to 7001c68762c2fcda61de373e0a30563d 29187040 / 29187040.0 (100.00%)
Successfully packaged artifacts and wrote output template to file packaged.yaml.
Before using sam
to deploy using the contents of packaged.yaml
, run a quick diff
to see what changed.
$ diff template.yaml packaged.yaml
< CodeUri: .
---
> CodeUri: s3://test-global-config-us-east-1/7001c68762c2fcda61de373e0a30563d
Lastly, use sam
again to deploy the template through a CloudFormation stack named Test
.
$ sam deploy --template-file packaged.yaml \
--stack-name Test \
--capabilities CAPABILITY_IAM
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - Test
Within an hour or so (it only takes a few minutes to deploy—the wait is for the function schedule to trigger), you should see something like the following in your function’s CloudWatch Logs log stream.
START RequestId: 5886a0f4-50a1-1cca-10b2-67f512fd83b1 Version: $LATEST
"Hello, world."
END RequestId: 5886a0f4-50a1-1cca-10b2-67f512fd83b1
REPORT RequestId: 5886a0f4-50a1-1cca-10b2-67f512fd83b1
Duration: 1.59 ms Billed Duration: 100 ms Memory Size: 128 MB Max Memory Used: 5 MB