Simplify AWS Lambda deployment by using a CloudFormation Template

AWS Lambda CloudFormation Easy Button

There are a few ways to create a Lambda function, some of which include using the AWS Console, CLI, and as I will cover in this post, using a CloudFormation Template.

If you are unfamiliar with AWS CloudFormation, “CloudFormation is a service that helps you model and set up your Amazon Web Services resources so that you can spend less time managing those resources and more time focusing on your applications that run in AWS”.

Basically, you create a template that describes one or more of the AWS resources that you want (Lambda, EC2 instances, RDS DB instances), and AWS CloudFormation takes care of provisioning and configuring those resources for you.

You’ll spend little to no time individually creating and configuring those AWS resources and figuring out what’s dependent on what since AWS CloudFormation handles all of that for you.

So how can you do this for your Lambda function?

  1. Write a CloudFormation Template, declaring AWS resources — in this case a Lambda function. Templates can be written in JSON or YAML.
  2. Create the Stack with CloudFormation by using the AWS Console, or AWS CLI.
  3. Done!

Write a Cloud Formation Template

  1. At a minimum, an AWS CloudFormation template requires the Resources section containing the resources we want provisioned in AWS. But for this post, I will include two additional fields — AWSTemplateFormatVersion and Description.

Template.JSON

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "A description of the template",
"Resources" : {}
}

Template.YAML

AWSTemplateFormatVersion: '2010-09-09'
Description: A description of the template
Resources:

Now that we have the bare minimum for our template, we can start to define the resources we need. For the Lambda function that we are setting up, we need at least two resources:

  1. The IAM Role for the Lambda function.
  2. The Lambda function itself with the configurations and code.

The IAM Role for the Lambda Function

Template.JSON

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "A description of the template",
"Resources" : {
"LambdaExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "MyLambdaExecutionRole",
"AssumeRolePolicyDocument": {
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
}
}
}

Template.YAML

AWSTemplateFormatVersion: '2010-09-09'
Description: A description of the template
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: MyLambdaExecutionRole
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole

Let’s go over what we just added.

We added a resource with the logic name LambdaExecutionRole of type AWS::IAM::Role. For this Resource type, only the property AssumeRolePolicyDocument is required however, I recommend including the property RoleName and setting it to something that makes sense to you — here I set it to MyLambdaExecutionRole.

The Lambda function with the configurations and code.

Template.JSON

{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "A description of the template",
"Resources" : {
"LambdaExecutionRole": {
"Type": "AWS::IAM::Role",
"Properties": {
"RoleName": "MyLambdaExecutionRole",
"AssumeRolePolicyDocument": {
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
}
},
"LambdaFunction":{
"Type": "AWS::Lambda::Function,
"Properties": {
"Handler": "index.handler",
"Role": {
"Fn::GetAtt": ["LambdaExecutionRole","Arn"]
},
"Runtime": "nodejs12.x",
"Code": {
"ZipFile": "exports.handler = function(event,context) { console.log("Hello World") }"
}
},
}
}
}

Template.YAML

AWSTemplateFormatVersion: '2010-09-09'
Description: A description of the template
Resources:
LambdaExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: MyLambdaExecutionRole
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
LambdaFunction:
Type: AWS::Lambda::Function
Properties:
Code:
ZipFile: |
exports.handler = function(event, context)
console.log("Hello world");
}
Handler: index.handler
Role:
Fn::GetAtt: [ LambdaExecutionRole, Arn ]
Runtime: nodejs12.x

Let’s go over what we just added.

We added a Logic Resource named LambdaFunction of type AWS::Lambda::Function. For this resource type, there are two required properties Code and Role. However, I recommend also adding the Handler and Runtime properties to better define your function.

The Code property is used to define your function code, which can be an S3 Object, a container image, or if using NodeJS or Python, the code can be defined in-line within the template as in the example above.

Keep in mind that with with in-line code, AWS CloudFormation places it in a file named index and zips it to create a deployment package. For the Handler property, the first part of the handler identifier must be index. For example, index.handler.

The Role property can either be the ARN of an existing IAM Role or, as in this case, you can create a new role with the template and reference it issuing one of the available Intrinsic Function provided by CloudFormation Fn::GetAtt .

With the above template, we can now run it by using the AWS Console, or by using the following CLI command:

aws cloudformation create-stack --stack-name hello-lambda-stack \
--template-body file://template.yaml (or .json) \
--capabilities CAPABILITY_NAMED_IAM

Template sections

Metadata: Objects that provide additional information about the template.

Parameters : Values to pass to your template at runtime (when you create or update a stack). You can refer to parameters from the Resources and Outputs sections of the template.

Rules: Validates a parameter or a combination of parameters passed to a template during a stack creation or stack update.

Mappings: A mapping of keys and associated values that you can use to specify conditional parameter values, similar to a lookup table. You can match a key to a corresponding value by using the Fn::FindInMap intrinsic function in the Resources and Outputs sections.

Conditions: Conditions that control whether certain resources are created or whether certain resource properties are assigned a value during stack creation or update. For example, you could conditionally create a resource that depends on whether the stack is for a production or test environment.

Transform: For serverless applications (also referred to as Lambda-based applications), specifies the version of the AWS Serverless Application Model (AWS SAM) to use. When you specify a transform, you can use AWS SAM syntax to declare resources in your template. The model defines the syntax that you can use and how it is processed.

You can also use AWS::Include transforms to work with template snippets that are stored separately from the main AWS CloudFormation template. You can store your snippet files in an Amazon S3 bucket and then reuse the functions across multiple templates.

Outputs: Describes the values that are returned whenever you view your stack’s properties. For example, you can declare an output for an S3 bucket name and then call the aws cloudformation describe-stacks AWS CLI command to view the name.

Web and Application developer. Lately, AWS Lambda and AWS Amplify have taken a special place in my heart.