AWS Serverless Application Model
I was recently preparing for my AWS DevOps Engineer exam and I wanted to give AWS Serverless Application Model a try. AWS Serverless Application Model is a framework to build and deploy serverless application on AWS using Lambda, API Gateway and DynamoDB.
Under the hood it is a AWS CloudFormation transform, which expands the CloudFormation syntax and adds additional resources under the AWS::Serverless
namespace. During the template provisioning those resources get expanded to basic CloudFormation resources.
The sam
CLI has commands to build, test, package and deploy your serverless applications. It makes deploying serverless application much easier than packaging your application by yourself and using generic CloudFormation templates.
Initialize the project
To initialize the project you use the sam init
command. It will ask a few questions and prepare a boilerplate project.
$ sam init
Which template source would you like to use?
1 - AWS Quick Start Templates
2 - Custom Template Location
Choice: 1
Which runtime would you like to use?
1 - nodejs12.x
2 - python3.8
3 - ruby2.7
4 - go1.x
5 - java11
6 - dotnetcore3.1
7 - nodejs10.x
8 - python3.7
9 - python3.6
10 - python2.7
11 - ruby2.5
12 - java8.al2
13 - java8
14 - dotnetcore2.1
Runtime: 4
Project name [sam-app]:
Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git
AWS quick start application templates:
1 - Hello World Example
2 - Step Functions Sample App (Stock Trader)
Template selection: 1
-----------------------
Generating application:
-----------------------
Name: sam-app
Runtime: go1.x
Dependency Manager: mod
Application Template: hello-world
Output Directory: .
Next steps can be found in the README file at ./sam-app/README.md
$ tree
.
├── Makefile
├── README.md
├── hello-world
│ ├── go.mod
│ ├── main.go
│ └── main_test.go
└── template.yaml
1 directory, 6 files
The project has a CloudFormation template in template.yaml
and an example Lambda function handler in hello-world/main.go
. You can notice the Transform: AWS::Serverless-2016-10-31
field in the template, which means we are using AWS SAM:
|
|
Local development
Now to build the Lambda deployment packages run sam build
.
$ sam build
Building function 'HelloWorldFunction'
Running GoModulesBuilder:Build
Build Succeeded
Built Artifacts : .aws-sam/build
Built Template : .aws-sam/build/template.yaml
Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
A nice feature of SAM CLI is that you can run the Lambda function locally or even run a server, which simulates whole API in the template. For this you will need to have Docker installed.
To invoke a single function use sam local invoke <function-name>
:
$ sam local invoke HelloWorldFunction
Invoking hello-world (go1.x)
Failed to download a new amazon/aws-sam-cli-emulation-image-go1.x:rapid-1.1.0 image. Invoking with the already downloaded image.
Mounting /home/damian/Projects/sam-test/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: 861e0e67-1567-104f-55ba-5ee5b7c63eeb Version: $LATEST
END RequestId: 861e0e67-1567-104f-55ba-5ee5b7c63eeb
REPORT RequestId: 861e0e67-1567-104f-55ba-5ee5b7c63eeb Init Duration: 49.63 ms Duration: 549.73 ms Billed Duration: 600 ms Memory Size: 128 MB Max Memory Used: 51 MB
{"statusCode":200,"headers":null,"multiValueHeaders":null,"body":"Hello, 178.43.131.97\n"}
You can also start a server, which simulates the AWS API Gateway:
$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-09-17 17:54:42 * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)
# in other terminal
$ curl http://127.0.0.1:3000/hello
Hello, 178.43.131.97
Deploy the application
To deploy Lambda functions you need to package and upload your code to S3. AWS SAM will handle this for you, but it requires an additonal configuration file samconfig.toml
to know what bucket it should use.
|
|
You can either provision the S3 bucket and create the samconfig.toml
file manually or use the --guided
flag in the sam deploy
command, so SAM will create it for you.
What sam deploy
does is:
- Detect, which functions must be updated
- Upload the CloudFormation template and Lambda deployment packages to S3
- Generate the ChangeSet and deploy it
$ sam deploy --guided
Configuring SAM deploy
======================
Looking for samconfig.toml : Not found
Setting default arguments for 'sam deploy'
=========================================
Stack Name [sam-app]:
AWS Region [us-east-1]: eu-west-1
#Shows you resources changes to be deployed and require a 'Y' to initiate deploy
Confirm changes before deploy [y/N]: y
#SAM needs permission to be able to create roles to connect to the resources in your template
Allow SAM CLI IAM role creation [Y/n]: Y
HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
Save arguments to samconfig.toml [Y/n]:
Looking for resources needed for deployment: Not found.
Creating the required resources...
Successfully created!
Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-128t3n8a6nlhy
A different default S3 bucket can be set in samconfig.toml
Saved arguments to config file
Running 'sam deploy' for future deployments will use the parameters saved above.
The above parameters can be changed by modifying samconfig.toml
Learn more about samconfig.toml syntax at
https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html
Uploading to sam-app/4a708650ed51c1f21e152bcc6440cf00 1325 / 1325.0 (100.00%)
Deploying with following values
===============================
Stack name : sam-app
Region : eu-west-1
Confirm changeset : True
Deployment s3 bucket : aws-sam-cli-managed-default-samclisourcebucket-128t3n8a6nlhy
Capabilities : ["CAPABILITY_IAM"]
Parameter overrides : {}
Initiating deployment
=====================
HelloWorldFunction may not have authorization defined.
Uploading to sam-app/cbc7be7eaba81f76b3cd7e012f847498.template 1155 / 1155.0 (100.00%)
Waiting for changeset to be created..
CloudFormation stack changeset
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Operation LogicalResourceId ResourceType
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Add HelloWorldFunctionCatchAllPermissionProd AWS::Lambda::Permission
+ Add HelloWorldFunctionRole AWS::IAM::Role
+ Add HelloWorldFunction AWS::Lambda::Function
+ Add ServerlessRestApiDeployment47fc2d5f9d AWS::ApiGateway::Deployment
+ Add ServerlessRestApiProdStage AWS::ApiGateway::Stage
+ Add ServerlessRestApi AWS::ApiGateway::RestApi
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Changeset created successfully. arn:aws:cloudformation:eu-west-1:146986152083:changeSet/samcli-deploy1600354796/6dc83be8-7e11-48c9-b6b0-8df847a66ac8
Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y
2020-09-17 17:00:07 - Waiting for stack create/update to complete
CloudFormation events from changeset
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
ResourceStatus ResourceType LogicalResourceId ResourceStatusReason
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole -
CREATE_IN_PROGRESS AWS::IAM::Role HelloWorldFunctionRole Resource creation Initiated
CREATE_COMPLETE AWS::IAM::Role HelloWorldFunctionRole -
CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction -
CREATE_COMPLETE AWS::Lambda::Function HelloWorldFunction -
CREATE_IN_PROGRESS AWS::Lambda::Function HelloWorldFunction Resource creation Initiated
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_COMPLETE AWS::ApiGateway::RestApi ServerlessRestApi -
CREATE_IN_PROGRESS AWS::ApiGateway::RestApi ServerlessRestApi Resource creation Initiated
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f9d -
CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionCatchAllPermissionProd Resource creation Initiated
CREATE_IN_PROGRESS AWS::Lambda::Permission HelloWorldFunctionCatchAllPermissionProd -
CREATE_COMPLETE AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f9d -
CREATE_IN_PROGRESS AWS::ApiGateway::Deployment ServerlessRestApiDeployment47fc2d5f9d Resource creation Initiated
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_IN_PROGRESS AWS::ApiGateway::Stage ServerlessRestApiProdStage Resource creation Initiated
CREATE_COMPLETE AWS::ApiGateway::Stage ServerlessRestApiProdStage -
CREATE_COMPLETE AWS::Lambda::Permission HelloWorldFunctionCatchAllPermissionProd -
CREATE_COMPLETE AWS::CloudFormation::Stack sam-app -
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
CloudFormation outputs from deployed stack
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Outputs
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Key HelloWorldFunctionIamRole
Description Implicit IAM Role created for Hello World function
Value arn:aws:iam::146986152083:role/sam-app-HelloWorldFunctionRole-URQ6NEJVAE9X
Key HelloWorldAPI
Description API Gateway endpoint URL for Prod environment for First Function
Value https://xzv8lq1611.execute-api.eu-west-1.amazonaws.com/Prod/hello/
Key HelloWorldFunction
Description First Lambda Function ARN
Value arn:aws:lambda:eu-west-1:146986152083:function:sam-app-HelloWorldFunction-DMK403RR7HEW
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Successfully created/updated stack - sam-app in eu-west-1
Enhance the template and add canary deployment
The nice thing in AWS SAM is, that it’s just an extension of CloudFormation templates, so you can define other resources, export output values or reference values in other stacks. It also gives an option to define the deployment strategy for Lambda functions and set triggers for rollbacks. You can perform canary deployment on the AWS Lambda level by using Lambda aliases. Let’s change the HelloWorld function, add DeploymentPreference
and AutoPublishAlias
parameters and define all CloudWatch alarm to rollback the deployment in case your function does not work during the CodeDeploy AllowTraffic
phase.
|
|
Now, let’s make a small change in the source code, rebuild the package and deploy it. This can take a bit, cause the command waits, till the CodeDeploy deployment is finished:
$ sam build
...
$ sam deploy
...
Successfully created/updated stack - sam-app in eu-west-1
More advanced example
A more advanced example is available on my Github aws-dev-ops-preparation repo. It shows also how to use and implement an lifecycle hook function to validate the deployment.
Additional notes about SAM
A few things I would like to mention, because I lost a few hours by not knowing them:
- Hook functions must start with prefix
CodeDeployHook_
or you have to provide an custom IAM role for the CodeDeploy in DeploymentPreference - The hook functions must call the AWS API
codedeploy:PutLifecycleEventHookExecutionStatus
to inform CodeDeploy, if the hook passed or failed. In other case it will wait for 1 hour and fail - The Alarms in DeploymentPreference can be used to rollback the deployment on Cloudwatch Alarm. The AWS docs suggest the other way - that they are triggered by a failed deployment