Learning CloudFormation and SAM
It's great that we've created these placeholder AWS Lambdas for handling expense receipts, but our client "Sauf Pompiers" wants to speed up the process and hire more engineers which will have to collaborate. Now, we can't possibly imagine a team of engineers to work collaboratively, write and edit the code from the UI. We need to enable the engineers to use their favorite tools, version the codebase.
Luckily, in the AWS ecosystem, there is a tool called CloudFormation. Simply put, its infrastructure-as-code. A YAML/JSON templating language that defines what infrastructure you need. Write it locally in your favorite editor, version it, edit and change and when you want to create, update or delete the specified infrastructure you just use the CloudFormation CLI to (re)deploy. An service / application in CloudFormation is defined as a Stack. Think of it as a recipe, where you write what ingredients you need and how to they interact and then you give it to CloudFormation,your chef, who makes it for you. If you write your recipe wrongly or with bad ingredients, it' not the chef's fault.
Now how do we define an AWS Lambda using CloudFormation? For example, our "enter-expense" AWS Lambda consists of two parts:
- the hardware, the provisioned AWS Lambda infrastructure
- its software, the codebase ran on the AWS Lambda infrastructure
That's what we need to define with CloudFormation. First, let's start with the hardware, where the code is going to run.
YourLambdaFunction:
Type: AWS::Lambda::Function
Properties:
Runtime: nodejs12.x
Code: src/
Handler: index.handler
Let's explain this. This YAML code snippet represents an AWS resource, and in this case, an AWS Lambda. When you "send" this resource to AWS, it will create an AWS Lambda Function in your AWS account. The first line is the name of the resource, you can write whatever you want there. The next line, defines the type of the AWS resource you want to create. The Type
property follows a certain naming structure. The first, AWS
, represents its namespace. In this case its AWS
, which means it belongs to native AWS resources. The next one is Resource Domain, which in this case is Lambda
, and then you have Function
, which represents the exact Resource Domain Type. Beside the Function
type it can also be a Permission
for example, and so on.
The next line are the resource's Properties
. A resource can have many properties, most of which are optional and the few are required. You can see all the properties for AWS Lambda here.
The next three: Handler
, Code
and Runtime
are the only required ones (as you could presume). The Runtime
is simply the function's runtime, based on the programming language you want your function to run. The Code
represents the function code, and it can be defined either as the folder path, like here in the example, or even inline. The Handler
is the name of the file and its method within its code that Lambda calls to execute your function. For more, open the link to the AWS Lambda documentation.
Now, as AWS Lambda is run only when an event triggers it, with CloudFormation you will also need to define which kind of trigger, in form of a permission, AWS::Lambda::Permission
. For example here is one which triggers a Lambda when something happens in an S3 bucket:
BucketPermission:
Type: AWS::Lambda::Permission
Properties:
Action: 'lambda:InvokeFunction'
FunctionName: !Ref BucketWatcher
Principal: s3.amazonaws.com
SourceAccount: !Ref "AWS::AccountId"
SourceArn: !Sub "arn:aws:s3:::${BucketName}"
As you can see just by reading this piece of code, it allows the S3 service of a Source Bucket from an AWS Account to invoke an AWS Lambda Function named BucketWatcher
.
And that's not all. You also need to write a Lambda Execution Role (a AWS::IAM::Role
resource type). So, imagine, for a simple Lambda, you have to write both an AWS Lambda resourceSome of you may be seeing this for the first time, and as you guessed it, its a bit to verbose, right?
For that reason, AWS has created tool, which is 100% based on AWS CloudFormation and a lot less verbose, called AWS SAM.
AWS SAM
AWS SAM, or AWS Serverless Application Model, is an AWS-built open-source framework for creating serverless application. Based 100% on AWS CloudFormation, AWS SAM is the most dominant way of defining serverless applications.
Let's go straight to the example:
YourSAMLambdaFunction
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs12.x
CodeUri: src/
Handler: index.handler
Doesn't seem to different, right? The only change is in the Resource Domain, which instead of Lambda is Serverless? The Serverless resource domain means that it will create a resource from the AWS SAM domain.
Again, not a lot.
It's main difference lies in its definition of the AWS Lambda triggers. Instead of manually defining the AWS::Lambda::Permission
and the AWS Execution Role, you will be just adding a property Events
inside the Properties
for your Serverless Function, like in the following example:
YourSAMLambdaFunction
Type: AWS::Serverless::Function
Properties:
Runtime: nodejs12.x
CodeUri: src/
Handler: index.handler
Events:
S3ObjectCreatedEvent:
Type: S3
Properties:
Bucket: !Ref SrcBucket
Events: s3:ObjectCreated:*
SrcBucket:
Type: AWS::S3::Bucket
The Events
property specifies all the possible events that can trigger your function, and it abstracts by defaulting the AWS Lambda Permission and the AWS Lambda Role into a single Lambda Event definition. You can see that you need to:
- Name the event, in this case
S3ObjectCreatedEvent
. You can name it how you prefer. - Define its Event Type, its
S3
. - Define the Event properties based on the Event Type, in this case its
Bucket
andEvents
, which specify the event trigger source bucket and the S3 events which will invoke the Lambda, respectively.
All event triggers follow the similar rule as the definition for AWS resource types. It's a lot easier, as you can see!
Lambda Code
For either of these cases, AWS CloudFormation and AWS SAM, the way you will write the code is the same. For example, here is the Hello World code from the previous example:
exports.handler = async (event) => {
// TODO implement
const response = {
statusCode: 200,
body: JSON.stringify('Hello from Lambda!'),
};
return response;
};
This code should now be inside a file called index.js
. That file should be in a folder called src
within your serverless project. That way, when AWS wants to execute your Lambda function, it will search within the folder src
, because of the CodeUri
property. Then it will search for a file named index
with a method handler
.
Deployment
This is the main benefit of AWS SAM. It has a really greate CLI tool called AWS SAM CLI ( who would've guessed?! )
Within the tool there is a command sam deploy
. You can try it out yourself, it's slightly magical, as it tries to detect your current setup and will create a samconfig.toml file with all your configurations. If it doesn't it will do a "guided deployment", where it will ask you for the missing parameters.
It's parameters are:
- stack name, the name of the CloudFormation stack you are creating,
- template name, the name of the CloudFormation template file (which defines the AWS infrastructure you want to create),
- region, the AWS region in which you want to deploy,
- deployment bucket, where it packages your whole application before deploying it,
- capabilities, which represent the IAM (Identity Access Management), which you can guess, just give its adequate permissions to deploy,
- parameter overrides, if your CloudFormation stack accepts some paramters before being created you can override their default state using this parameter
You can see more about the sam deploy
here.
Now that you know the basics on how to create a Lambda function, it's time for your tasks!
Task
- Create a project folder for your Expense Receipts
- Setup your Serverless project (per your chosen language) and copy the code into separate handlers.
- Inside the project folder, create the
template.yml
file for your CloudFormation stack - Define your three AWS Lambda functions infrastructure within the
template.yml
. No need to define your Lambda Function trigger events, soEvents
property isn't needed. - Deploy it using SAM CLI
- Try the code from the UI
Hints
Feel free to simply copy and paste the function template and just change the name, but it is recommended to type it in yourself, to get the "feeling" of writing serverless resources under your belt.
Try a
sam deploy -- guided
first. Feel free to remove the samconfig.toml file, as much as you want. But remember, it does deploy your whole stack.To delete the CloudFormation stack without leaving console, just run
aws cloudformation delete-stack --stack-name xyz
.#1As this is a very easy exercise, there are no more hints now. Feel free to ask me, if you have any questions.
Interesting fact #1 . There is an ongoing PR for
sam destroy
- see here