Managing Serverless staging and production deployments with CodePipeline
This blog is the third in a three-part tutorial covering CI/CD pipelines for apps built with the Serverless framework. In the first post, we created a CodeBuild project to lint and unit test our code and added it to CodePipeline. The second post covered integration testing using Jest and CodeBuild, as well as IAM permissions for deploying serverless apps from CodeBuild. In this final installment, we’ll deploy to staging and production environments. All of the code is available in our repo.
This blog takes us through four steps:
- Creating deployment packages
- Deploying to staging
- Adding an approval gate before our production deployment
- Deploying to production
Let’s get started!
Creating Deployment Packages
Once our tests pass, we want to create a packaged version of our app that can be deployed by later stages in our pipeline. To do this, we’re going to take advantage of the packaging tools that come with the Serverless framework, as well as the ability of CodeBuild to save artifacts for later use.
First up: creating the package. Like deployments, packages are specific to a certain Serverless stage—or CloudFormation stack. We need to do two things for each stage; first we’ll create a destination directory for that package then we’ll tell Serverless to create the package and put it in that directory. We’ll add these commands to the build
section of our buildspec.yml
, after the integration tests are run:
build:
commands:
# linting and unit tests
- npm run-script lint
- npm test
# deploy integration testing environment
- serverless deploy --stage test -v
# integration tests
- npm run-script integration
# create directory for deployment packages
- mkdir artifacts
# create staging deployment package
- mkdir artifacts/stg
- serverless package --package artifacts/stg --stage stg -v
# create prod deployment package
- mkdir artifacts/prod
- serverless package --package artifacts/prod --stage prod -v
Note: see how I truncated “staging” to “stg”? If your project/stage names are too long, then the S3 bucket name created by Serverless will get truncated, and you’ll have a mismatch between your IAM policy and the resulting S3 resource.
Now, we need to tell CodeBuild to save the artifacts directory as a build artifact, so it can be used by later stages in our pipeline. Add this to your buildspec.yml
, after the end of the whole phases
section:
artifacts:
files:
- artifacts/**/*
- serverless.yml
- deploy.sh
artifacts/**/*
tells CodeBuild to save anything in our artifacts
directory or its subdirectories. The presence of a serverless.yml
creates a Serverless service directory, thus allowing our deployment. It’s important to know that this serverless.yml
will not be used for the actual deployment; the deployment information is all contained in the package we created. deploy.sh
is the deployment script we’ll create in the next section.
Note: if you created your CodeBuild project without artifacts, you’ll need to re-create your project following the instructions in our . Unfortunately, the CodeBuild console doesn’t currently support adding artifacts to a project after the project has been created.
Deploying to Staging
Staging deployment requires creating a few things:
- A staging-specific IAM role for CodeBuild
- A “DeployStaging” CodePipeline stage
- An action within that stage to run the deploy script
- A deploy script
Creating an IAM role for Staging Deployment
This process is very similar to the IAM role creation that we did in our last tutorial; we’re assuming you’ve read it and give only the high-level overview here. If you need more detailed instructions, you can find them in the previous tutorial. Also, do know that best practice would be to have different AWS accounts for test, staging, and prod. For this tutorial, we’re deploying test, staging, and prod in the same account in the interest of simplicity.
First up, use the Serverless Policy Generator to create a draft policy, this time with “stg” as our Serverless stage name:
Edit the policy to add cloudformation:ValidateTemplate
:
{
"Effect": "Allow",
"Action": [
"cloudformation:List*",
"cloudformation:Get*",
"cloudformation:PreviewStackUpdate",
"cloudformation:ValidateTemplate"
],
"Resource": [
"*"
]
},
One change from the previous tutorial is that we don’t need to add logs:DeleteLogGroup
, since we won’t be automating removal of either the staging or production environments.
Use the IAM console to create this new policy, then add that policy to a new role. Again, we suggest a name like your-project-name-here-staging
for this role.
Creating a DeployStaging Stage in CodePipeline
Next we’ll create a CodePipeline stage, plus a CodeBuild project that will use this role.
First, we may need to dispel a little confusion. In this section, we’re using the word “stage” to refer to the CodePipeline concept of a “stage”—a section of a pipeline that executes a collection of actions. Actions are things like CodeBuild projects, approval gates, etc. This is different from the Serverless framework concept of a “stage,” which means “my app, deployed as its own CloudFormation stack,” and which is roughly analogous to an “environment.” Unless it’s obvious, we’ll clarify when we’re talking about CodePipeline “stages” and Serverless “stages.”
Taking a look at our pipeline, we currently have two CodePipeline stages: Source
, which tracks our code, and Build
, which runs our unit and integration tests.
Before we set up our DeployStaging
stage, we need to tell our Build
stage to output the build artifacts (the deployment packages and such) generated by our current CodeBuild project. Click Edit, then click on the little pencil (edit) icon next to our existing CodeBuild action:
This brings up the editing pane on the right-hand side. Scroll down until you see Output artifacts, then choose a name. This will be a label used within the pipeline to represent the build artifacts created by our current CodeBuild project.
Click Update then Save pipeline changes. When prompted, click Save and continue.
Now, let’s add our DeployStaging
stage. We’ll start by clicking Edit, then click on the grey +Stage button at the bottom of our pipeline:
Start by adding the stage name, DeployStaging
, and then click the +Action button right underneath the name input box. This should bring up a window on the right-hand side of our screen.
Adding an action—a new CodeBuild project—to our stage
First, we see a drop-down menu for Action category. Select Build, and then give our action a name: your-project-name-staging
. Then, choose AWS CodeBuild from the Build provider dropdown. Now, we get to configure the CodeBuild project. First off, let’s create a new project and give it the same name as we gave our action:
Like we did in the first tutorial, we select a Node.js 7.0.0 image for our builds. For Build specification, though, we’ll make a change. Instead of using the buildspec.yml
file to tell our build what commands to execute, we’ll use a Bash shell to run our deploy.sh
file. We’ll also give our output files a path (we won’t use this, but it’s required by the wizard).
Next, select the role we just created as the CodeBuild service role:
Finally, we need to create an environment variable, so our eventual deployment script will know which Serverless stage to deploy. Click Advanced, then create the following environment variable:
The value of our variable, stg
, matches the Serverless stage name we used in our buildspec.yml
when we created our deployment package.
Click Save build project, then scroll down to the Input Artifacts section. Previously, we told our Build
stage in CodePipeline to output our CodeBuild project’s build artifacts under the label serverless-ci-cd-test-output
. Now, we need to use them as inputs to this action (our staging CodeBuild project) in this stage of our pipeline:
Click Add action. Then, click Save pipeline changes, and Save and continue to confirm.
Create a deployment script
We’re going to create a deployment script that can be used for our staging and production deployments. Create a new file, deploy.sh
, in the root of your project repo:
#! /bin/bash
npm install -g serverless
npm install serverless-dynamodb-local serverless-offline serverless-stack-output
serverless deploy --stage $env --package $CODEBUILD_SRC_DIR/artifacts/$env -v
Pretty simple. Install Serverless, install the plugins required by our project (they won’t be used when deploying, but we need them to avoid an error), and use Serverless to deploy the package we created. Since we are reading our stage name and directory name from our environment variable, env
, we can reuse this script later for production deployment.
Commit the changes, push, and you should see your new DeployStaging
stage run this CodeBuild project…and deploy your app to staging! Pretty cool. A quick trip to the CloudFormation console will reveal your new stack:
And your service endpoint should be in your CodeBuild project’s build logs. Onward to production!
Creating a Production Approval Gate
For the purposes of this blog, we want to deploy to staging whenever our tests pass, but we only want to deploy to production when a human clicks a button; this is the difference between the two types of “CD” in “CI/CD.” In “Continuous Delivery,” we manually deploy to production; in “Continuous Deployment,” production deployment is typically automated. To achieve this, we’ll add another stage to our pipeline: an approval gate.
Go back to your pipeline on CodePipeline and click Edit. Then, click the +Stage button at the bottom of your pipeline to add a new stage after DeployStaging. Give it a name, like “Approve Production,” and then click +Action to add a new action. This time, select Approval from the drop-down. Give your action a name, then select Manual approval as the approval type:
You’ll then get a few options for configuring your approval. Maybe you hook it up to an SNS topic to send notifications whenever an approval is waiting, or maybe you enter the URL of your staging environment. These are entirely optional; whenever you’re done, click Add action, then save and confirm your pipeline changes.
Voila! We now have an approval gate that requires manual approval for production deployments.
Deploying to Production
In our last step we’ll create our production deployment stage. There are a couple ways we can approach production deployments from within CodePipeline. Since we’re fans of keeping our production deployment similar to our staging deployment, we’re going to reuse the same process. If you’re interested in another approach, check out CodeDeploy (many tools available, but outside the scope of this tutorial).
And, yes, I literally mean reuse: head back up to the “Deploying to Staging” section and do it again. Create an IAM role for your prod
stage, create a new DeployProduction
stage in CodePipeline, and add a new CodeBuild project as an action in that stage. Be sure to use prod
as your stage name when you create your IAM role and policy; also, use prod
, instead of stg
, as the value of your env
environment variable in your CodeBuild project configuration.
When you’re done, you should have a 5-step pipeline: Source
, Build
, DeployStaging
, ApproveProduction
, and DeployProduction
. Push a new commit to your repo, and watch it march through your pipeline. After DeployStaging
, it’ll get blocked at your approval gate:
Click Review, add a comment, and click Approve. This approval will trigger your production deployment. Once it’s finished, check out your staging and production stacks in CloudFormation:
And admire your beautiful green pipeline. Congrats!
If you have additional questions or would like to schedule a consultation with one of our AWS Experts, please reach out to us at Info@1Strategy.com.
Acknowledgements
This tutorial has built on the work of many people, most of whom are linked above. One who isn’t already mentioned is Sumit Maingi, who wrote two excellent blogs that inspired this tutorial. Thanks.