Modern-day applications that reside on AWS have several distinct environments and accounts, such as dev, test, and staging. An application has to go through an elaborate process of deployment and testing in these environments before reaching its final destination. To achieve automated deployment of the application across different environments, you must use CI/CD pipelines.
Different DevOps models have been proposed that depict how a CI/CD pipeline deploys and promotes an application from one environment to another. In a typical model, pipelines are locally situated in each AWS account where deployment needs to happen. This post, however, focuses on a different model, in which CI/CD pipelines reside in a central AWS account called tools, and carry out deployments across other AWS accounts. This model has several advantages:
For more information about CI/CD cross-account pipeline strategies, see Building a Secure Cross-Account Continuous Delivery Pipeline. In this post, we apply this strategy to deploying AWS Lambda-based APIs using the third-party Serverless Framework.
Before proceeding any further, you need to identify and designate two AWS accounts required for the solution to work:
Start by building the necessary resources in the target account, as shown in the following architecture diagram. This consists of an IAM role that trusts the tools account and provides the required deployment-specific permissions. This IAM role is assumed by AWS CodeBuild in the tools account to carry out deployment. For simplicity, we refer to this role as the cross-account role, as specified in the architecture diagram.
In addition, you also create an AWS CloudFormation execution role to be assumed by AWS CloudFormation in the target account. This role has permissions to create your API resources, such as a Lambda function and Amazon API Gateway, in the target account.
You then build a CI/CD pipeline in the tools account using AWS CloudFormation, AWS CodePipeline, AWS CodeBuild, and AWS CodeCommit. After completing all the steps in this post, you will have a fully functioning CI/CD pipeline that deploys your API in the target account. The pipeline starts automatically every time you check in your changes into your CodeCommit repository.
The following architecture diagram shows the AWS resources across the tools and target accounts. Red arrows depict the flow of events that lead to cross-account deployment of the Lambda-based API.
Your API is serverless, which consists of a Lambda function fronted by an API Gateway REST API. You use the Serverless Framework to carry out API deployment. The Serverless Framework is designed to help you define the function and its associated infrastructure components, such as Amazon API Gateway, Amazon Simple Queue Service (Amazon SQS), Amazon Simple Notification Service (Amazon SNS), and Amazon DynamoDB, with minimal configurations.
For instructions in installing the Serverless Framework, see Get Started with Serverless Framework Open Source & AWS.
In this step, you create the following resources in the target account:
Download the following CloudFormation templates corresponding to the defined roles:
To create the CloudFormation stacks for the templates you downloaded, complete the following steps:
AWS CloudFormation creates your stack. After it’s finished, the stack status shows as CREATE_COMPLETE.
You create the CI/CD pipeline in the tools account using the CloudFormation template, which provisions the following required resources:
To set up the code pipeline, complete the following steps:
OutCodeCommitRepoUrl
, which you use in the next section.You need to have following utilities installed on your workstation before proceeding further:
To deploy the API, complete the following steps:
git clone https://git-codecommit.us-east-1.amazonaws.com/v1/repos/my-serverless-api
You can get the CodeCommit URL from output parameter OutCodeCommitRepoUrl
. The following screenshot shows your output; you have created a new folder in your workspace.
Your project structure should now look like the following screenshot.
Per Serverless Framework specifications, every serverless project should have serverless.yml
at the project root location. This file defines specifications for deploying the Lambda function and its associated resources (such as API Gateway and DynamoDB) that are required for API implementation. For more information, see Serverless.yml Reference.
The folder /lambda
consists of two Lambda APIs written in Python:
These Lambda source code locations are referenced in serverless.yml
.
The pipeline you created in the previous step defines the deploy stage, which is driven by the CodeBuild project. It specifies configuration on how to package and deploy the code. The configuration includes two important properties:
buildspec.yml
to be at the root of the source code folder structure. You need to specify a file with any other name or location.buildspec.yml
runs.See the following code:
CodeDeploy:
Type: AWS::CodeBuild::Project
Properties:
Name: !Join [ '-', [ 'Serverless-CodeBuild-Deploy', !GetAtt CodeCommitRepo.Name, !Join [ '-', !Split [ '/', !Ref CodeCommitRepoBranch ] ] ]]
Artifacts:
Type: CODEPIPELINE
Source:
Type: CODEPIPELINE
ServiceRole: !GetAtt CodeBuildRole.Arn
Environment:
Type: LINUX_CONTAINER
Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
ComputeType: BUILD_GENERAL1_SMALL
EnvironmentVariables:
-
Name: CROSS_ACCOUNT_ROLE
Type: PLAINTEXT
Value: !Sub 'arn:aws:iam::${TargetAccountID}:role/${CodePipelineAssumeRoleName}'
-
Name: CF_EXECUTION_ROLE
Type: PLAINTEXT
Value: !Sub 'arn:aws:iam::${TargetAccountID}:role/${CFExecutionRoleName}'
-
Name: TARGET_ACCOUNT_ID
Type: PLAINTEXT
Value: !Ref TargetAccountID
-
Name: STAGE
Type: PLAINTEXT
Value: !Ref DeploymentEnvironment
Tags:
- Key: category
Value: goldmine
- Key: project_name
Value: serverless-cross-account-deployment
The crux of the deployment logic resides in the buildspec.yml
. It has instructions on how to build, package, and deploy the serverless project. Instructions are in the form of bash commands to be executed in a Linux container provisioned as part of the CodeBuild project. You can choose an appropriate Docker image for the container. For sample images, see Docker images provided by CodeBuild. Additionally, you can specify a runtime version such as Python or Node.js in the buildspec.yml
, which gets installed in the container during the install phase.
The buildspec.yml
file consists of two phases: install and build. The install phase defines instructions to install prerequisites, which for this use case is the serverless npm package needed to build and deploy the API using Serverless Framework.
In the build phase, you first set up an AWS cross-account profile (as the default profile) using the cross-account role you passed as the environment variable. This post provides a shell script aws-profile-setup.sh
(located at the root location of the project) that creates this profile for you.
You then create a serverless package using the sls package
command. This takes the configurations you defined in serverless.yml
, packages the entire infrastructure into the serverless-package
directory, and makes it ready for deployment. You can also pass custom command line parameters and use them inside serverless.yml
. One of the parameters you pass here is cfnRoleArn
. This represents the AWS CloudFormation execution role to be assumed by AWS CloudFormation service to deploy the stack resources in the target account.
Lastly, you deploy the package using the sls deploy
command. This takes the prebuilt package located in the /serverless-package
directory and uses the cross-account profile you set up to create a CloudFormation stack in the target account. AWS CloudFormation assumes the IAM role you supplied as cfnRoleArn
to provision all the resources that are part of your stack.
You also extract the API endpoint URL from the output of sls info
command and pass it on as output artifact so it’s available to subsequent stages of your pipeline. As a future enhancement, you may append another stage to this pipeline to test this API endpoint.
cd my-serverless-api
git add –A
git commit -a -m "Initial code checkin"
git push origin master
The following screenshot shows the output.
On the CodePipeline console, you should see that the pipeline kicked off automatically and eventually deploys the API to the target account.
In the target account, you can see a stack created with name in the format <SERVICE>-<STAGE>
, where <SERVICE>
is what you define in serverless.yml
and <STAGE>
is the environment variable you choose when creating CodePipeline stack. If you used the default configuration, the stack name becomes product-catalog-service-DEV
. This stack outputs an API endpoint with the key ServiceEndpoint
. In the next step, you test the API endpoint.
In this step, you use a postman client to test the API you just deployed. You first create a new product and then query that product using its ID. See the following code:
Create Product Endpoint: <api-endpoint>/product/create
Method: POST
Request:
{
"id": "1001",
"title": "Amazon Echo",
"description": "Amazon Echo",
"price": "89.99"
}
The following screenshot shows the successful response you receive from the create product API endpoint when above request is sent.
Use the following code to search the product you created using its ID:
Find Product Endpoint: <api-endpoint>/product/find/1001
Method: GET
The following screenshot shows that the product information is retrieved successfully when you find the product using its ID.
Cross-account IAM roles are very powerful and therefore need to be handled very carefully. For this post, we have strictly limited the cross-account role to specific Amazon S3, AWS CloudFormation, and API Gateway permissions. This makes sure the role has limited access. Actual creation of resources such as Lambda, API Gateway, and DynamoDB happens via the CloudFormation execution role, which is assumed by AWS CloudFormation in the target account.
To avoid incurring charges please execute following steps in their given order to remove all the resources created in target and tools account.
product-catalog-service-DEV
from target account. This removes DynamoDB, Lambda, API Gateway resources and their associated IAM roles created by serverless framework. Note that the stack name is in the format <SERVICE>-<STAGE>
. Name may differ based on the values you set for service and stage as explained in the above section Deploying the API.In this post, we showed how to integrate AWS developer tools such as CodeCommit, CodePipeline, and CodeBuild with the Serverless framework to create a CI/CD pipeline that can deploy a Lambda-based REST API from a centralized tools account to any number of target AWS accounts.
Build your own CI/CD pipeline as shown in this blog using AWS developer tools and Serverless framework.
*Originally Published on AWS Devops Blog