AWS Compute Blog
Continuous Deployment for Serverless Applications
With a continuous deployment infrastructure, developers can quickly and safely release new features and bug fixes for their applications without manually triggering any deployment scripts. Amazon Web Services offers a number of products that make the creation of deployment pipelines easier:
- AWS CodePipeline
- AWS CodeCommit
- AWS CodeBuild – newly launched
A typical serverless application consists of one or more functions triggered by events such as object uploads to Amazon S3, Amazon SNS notifications, or API actions. Those functions can stand alone or leverage other resources such as Amazon DynamoDB tables or S3 buckets. The most basic serverless application is simply a function.
This post shows you how to leverage AWS services to create a continuous deployment pipeline for your serverless applications. You use the Serverless Application Model (SAM) to define the application and its resources, CodeCommit as your source repository, CodeBuild to package your source code and SAM templates, AWS CloudFormation to deploy your application, and CodePipeline to bring it all together and orchestrate your application deployment.
Creating a pipeline
Pipelines pick up source code changes from a repository, build and package the application, and then push the new update through a series of stages, running integration tests to ensure that all features are intact and backward-compatible on each stage.
Each stage uses its own resources; for example, if you have a “dev” stage that points to a “dev” function, they are completely separate from the “prod” stage that points to a “prod” function. If your application uses other AWS services, such as S3 or DynamoDB, you should also have different resources for each stage. You can use environment variables in your AWS Lambda function to parameterize the resource names in the Lambda code.
To make this easier for you, we have created a CloudFormation template that deploys the required resources. If your application conforms to the same specifications as our sample, this pipeline will work for you:
- The source repository contains an application SAM file and a test SAM file.
- The SAM file called
app-sam.yaml
defines all of the resources and functions used by the application. In the sample, this is a single function that uses the Express framework and theaws-serverless-express
library. - The application SAM template exports the API endpoint generated in a CloudFormation output variable called
ApiUrl
. - The SAM file called
test-sam.yaml
defines a single function in charge of running the integration tests on each stage of the deployment. - The test SAM file exports the name of the Lambda function that it creates to a CloudFormation output variable called
TestFunction
.
You can find the link to start the pipeline deployment at the end of this section. The template asks for a name for the service being deployed (the sample is called TimeService) and creates a CodeCommit repository to hold the application’s source code, a CodeBuild project to package the SAM templates and prepare them for deployment, an S3 bucket to store build artifacts along the way, and a multi-stage CodePipeline pipeline for deployments.
The pipeline picks up your code when it’s committed to the source repository, runs the build process, and then proceeds to start the deployment to each stage. Before moving on to the next stage, the pipeline also executes integration tests: if the tests fail, the pipeline stops.
This pipeline consists of six stages:
- Source – the source step picks up new commits from the CodeCommit repository. CodePipeline also supports S3 and GitHub as sources for this step.
- Build – Using CodeBuild, you pull down your application’s dependencies and use the AWS CLI to package your app and test SAM templates for deployment. The
buildspec.yml
file in the root of the sample application defines the commands that CodeBuild executes at each step. - DeployTests – In the first step, you deploy the updated integration tests using the
test-sam.yaml
file from your application. You deploy the updated tests first so that they are ready to run on all the following stages of the pipeline. - Beta – This is the first step for your app’s deployment. Using the SAM template packaged in the Build step, you deploy the Lambda function and API Gateway endpoint to the beta stage. At the end of the deployment, this stage run your test function against the beta API.
- Gamma – Push the updated function and API to the gamma stage, then run the integration tests again.
- Prod – Rinse, repeat. Before proceeding with the prod deployment, your sample pipeline has a manual approval step.
Running the template
- Choose Launch Stack below to create the pipeline in your AWS account. This button takes you to the Create stack page of the CloudFormation console with the S3 link to the pre-populated template.
- Choose Next and customize your StackName and ServiceName.
- Skip the Options screen, choose Next, acknowledge the fact that the template can create IAM roles in your account, and choose Create.
Running integration tests
Integration tests decide whether your pipeline can move on and deploy the app code to the next stage. To keep the pipeline completely serverless, we decided to use a Lambda function to run the integration tests.
To run the test function, the pipeline template also includes a Lambda function called <YourServiceName>_start_tests
. The start_tests function reads the output of the test deployment CloudFormation stack as well as the current stage’s stack, extracts the output values from the stacks (the API endpoint and the test function name), and triggers an asynchronous execution of the test function. The test function is then in charge of updating the CodePipeline job status with the outcome of the tests. The test function in the sample application generates a random success or failure output.
In the future, for more complex integration tests, you could use AWS Step Functions to execute multiple tests at the same time.
The sample application
The sample application is a very simple API; it exposes time
and time/{timeZone}
endpoints that return the current time. The code for the application is written in JavaScript and uses the moment-timezone
library to generate and format the timestamps. Download the source code for the sample application.
The source code includes the application itself under the app
folder, and the integration tests for the application under the test
folder. In the root directory for the sample, you will find two SAM templates, one for the application and one for the test function. The buildspec.yml
file contains the instructions for the CodeBuild container. At the moment, the buildspecs use npm
to download the app’s dependencies and then the CloudFormation package command of the AWS CLI to prepare the SAM deployment package. For a sophisticated application, you would run your unit tests in the build step.
After you have downloaded the sample code, you can push it to the CodeCommit repository created by the pipeline template. The app-sam.yaml
and test-sam.yaml
files should be in the root of the repository. Using the CodePipeline console, you can follow the progress of the application deployment. The first time the source code is imported, the deployment can take a few minutes to start. Keep in mind that for the purpose of this demo, the integration tests function generates random failures.
After the application is deployed to a stage, you can find the API endpoint URL in the CloudFormation console by selecting the correct stack in the list and opening the Outputs tab in the bottom frame.
Conclusion
Continuous deployment and integration are a must for modern application development. It allows teams to iterate on their app at a faster clip and deliver new features and fixes in customers’ hands quickly. With this pipeline template, you can bring this automation to your serverless applications without writing any additional code or managing any infrastructure.
You can re-use the same pipeline template for multiple services. The only requirement is that they conform to the same structure as the sample app with the app-sam.yaml
and test-sam.yaml
in the same repository.