AWS CI/CD for API gateway
In this post, I will share how to set up a continuous integration and continuous delivery (CI/CD) pipeline on AWS for API gateway. The following AWS services will be used:
- Cloudformation allows you to model your entire infrastructure and application resources
- CodePipeline is a continuous delivery service
- CodeCommit is a version control service
- CodeBuild is a fully managed build service in the cloud
- Simple Storage Service (Amazon S3) is storage for the Internet
- API Gateway is an AWS service for creating, publishing, maintaining, monitoring, and securing REST, HTTP, and WebSocket APIs
- AWS Lambda is a serverless compute service

Create CodeCommit repository:
- Login AWS Console
- Choose Developer Tools > CodeCommit > Repositories
- Click
Create repository
- Enter the repository name as
my-api
and description (optional)
I refer it asServiceName
in this project - Click
Create
to create the repository
Cloudformation to provision the CI/CD pipeline
IAM Roles
- Role to execute Lambda
- Role to run CodeBuild
- Role to run CodePipeline
- Role that AWS CloudFormation assumes when it operates on resources in the specified stack
LambdaRole: |
CloudFormation tempate to provision CI/CD
- Define parameters for using in the template or pass to CodeBuild, and Lambda functions
- Define a S3 bucket for build artifacts
- Use as build cache in CodeBuild
- Use as Artifacstore in CodePipeline
BuildArtifactsBucket
is the logical ID of the S3 bucket
- CodeBuild project
- CodePipeline
- SAM TemplatePath: BuiltZip::template-packaged.yaml
- A complete Cloudformation template:
AWSTemplateFormatVersion: 2010-09-09
Description: CI/CD for my-api
Parameters:
ServiceName:
Type: String
Default: my-api
Description: CodeCommit repository name
BuildImageName:
Description: Docker image for application build
Type: String
Default: aws/codebuild/standard:5.0
BranchName:
Description: CodeCommit branch to build and deploy
Type: String
Default: release/dev
Env:
Type: String
Default: uat
SecurityGroup:
Type: String
Default: sg-xxxxxxxxxxxxxxx
Subnet1:
Type: String
Default: subnet-xxxxxxxxxxxxxxxx
Subnet2:
Type: String
Default: subnet-xxxxxxxxxxxxxxxxx
Resources:
BuildArtifactsBucket:
Type: AWS::S3::Bucket
CodeBuildProject:
Type: AWS::CodeBuild::Project
Properties:
Description:
!Join
- ''
- - !Sub 'Build project for the ${ServiceName}'
- ' ('
- !Ref AWS::StackName
- ')'
Artifacts:
Type: CODEPIPELINE
Cache:
Location:
!Join
- '/'
- - !Ref BuildArtifactsBucket
- !Sub '${ServiceName}_cache'
Type: S3
Environment:
Type: LINUX_CONTAINER
ComputeType: BUILD_GENERAL1_SMALL
Image: !Sub ${BuildImageName}
EnvironmentVariables:
- Name: ENV
Value: !Ref Env
- Name: SecurityGroup
Value: !Ref SecurityGroup
- Name: Subnet1
Value: !Ref Subnet1
- Name: Subnet2
Value: !Ref Subnet2
- Name: LambdaRole
Value: LambdaRole
- Name: SourceVpc
Value: !Ref SourceVpc
ServiceRole:
!Join
- ''
- - !Sub 'arn:aws:iam::${AWS::AccountId}:role/'
- CBSrvRole
Source:
Type: CODEPIPELINE
Pipeline:
Type: AWS::CodePipeline::Pipeline
Properties:
ArtifactStore:
Location: !Ref BuildArtifactsBucket
Type: S3
RoleArn:
!Join
- ''
- - !Sub 'arn:aws:iam::${AWS::AccountId}:role/'
- CPExecRole
Stages:
- Name: Source
Actions:
- Name: CodeCommitRepo
ActionTypeId:
Category: Source
Owner: AWS
Provider: CodeCommit
Version: 1
Configuration:
RepositoryName: !Sub ${ServiceName}
BranchName: !Sub ${BranchName}
OutputArtifacts:
- Name: SourceZip
RunOrder: 1
- Name: Build
Actions:
- Name: CodeBuild
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: 1
Configuration:
ProjectName: !Ref CodeBuildProject
InputArtifacts:
- Name: SourceZip
OutputArtifacts:
- Name: BuiltZip
- Name: Deploy
Actions:
- Name: CreateChangeSetPublicAPI
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: CloudFormation
Version: 1
Configuration:
ActionMode: CHANGE_SET_REPLACE
RoleArn:
!Join
- ''
- - !Sub 'arn:aws:iam::${AWS::AccountId}:role/'
- CFExecRole
StackName: !Sub '${ServiceName}-stack-${Env}'
ChangeSetName: !Sub '${ServiceName}-changeset-${Env}'
TemplatePath: BuiltZip::template-packaged.yaml
ParameterOverrides: !Sub '{ "Env": "${Env}", "SecurityGroup": "${SecurityGroup}", "Subnet1": "${Subnet1}", "Subnet2": "${Subnet2}"}'
InputArtifacts:
- Name: BuiltZip
RunOrder: 1
- Name: ExecuteChangeSetPublicAPI
ActionTypeId:
Category: Deploy
Owner: AWS
Provider: CloudFormation
Version: 1
Configuration:
ActionMode: CHANGE_SET_EXECUTE
RoleArn:
!Join
- ''
- - !Sub 'arn:aws:iam::${AWS::AccountId}:role/'
- CFExecRole
StackName: !Sub '${ServiceName}-stack-${Env}'
ChangeSetName: !Sub '${ServiceName}-changeset-${Env}'
OutputArtifacts:
- Name: !Sub '${ServiceName}${Env}ChangeSet'
RunOrder: 2
API Project Sample Source Code
Create S3 bucket to store the .zip for each function (e.g.
api-ap-east-1-build-stack-uat
)Now, you can develop your own API in Node.js:
.
├── api-test
│ ├── app.js
│ ├── node_modules
│ ├── package.json
│ └── package-lock.json
├── buildspec.yaml
└── template.yaml- buildspec.yaml
version: 0.2
phases:
pre_build:
commands:
- n 14.17.3
- cd api-test && npm install
- echo "dependencies install for api-test completed `date`"
- cd ../.
build:
commands:
- echo "Starting build `date` in `pwd`"
- aws cloudformation package --template-file template.yaml \
--output-template-file template-packaged.yaml--s3-prefix api-package \
--s3-bucket api-ap-east-1-build-stack-${ENV} --region ap-east-1
artifacts:
files:
- template-packaged.yaml
discard-paths: yes
- template.yaml
- template.yaml
Transform: AWS::Serverless-2016-10-31
Description: API SAM template
# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
# Don't change anything
Globals:
Function:
Runtime: nodejs14.x
Handler: app.lambdaHandler
Timeout: 3
# Don't change anything
Parameters:
Env:
Type: String
AllowedValues:
- uat
- preprod
- prod
- test
Default: uat
SecurityGroup:
Type: String
Subnet1:
Type: String
Subnet2:
Type: String
LambdaRole:
Type: String
Default: LambdaRole
Resources:
SampleFunction:
Type: AWS::Serverless::Function
Properties:
Timeout: 15
Handler: app.handler
Layers:
- !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:layer:oracle-linux-client-layer:1'
CodeUri: api-test/
Role:
!Join
- ''
- - !Sub 'arn:aws:iam::${AWS::AccountId}:role/'
- !Sub ${LambdaRole}
Events:
SampleFunction:
Type: Api
Properties:
Path: /api-test
Method: get
Environment:
Variables:
Env: !Sub ${Env}
VpcConfig:
SecurityGroupIds:
- !Sub ${SecurityGroup}
SubnetIds:
- !Sub ${Subnet1}
- !Sub ${Subnet2}
Outputs:
SampleFunction:
Description: "A sample function"
Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/api-test"
- buildspec.yaml
Push the code to CodeCommit
1
git push origin release/dev
The Pipeline will be executed to:
- Download the source code
- Build the source code
- Prepare the SAM template file
- Deploy the API gateway
Troubleshooting
- Returns the ID and status of each active change set for a stack.
- Use AWS CLI
$ aws cloudformation list-change-sets --stack-name stack-name --profile xxx --region ap-east-1
{
"Summaries": [
{
"StackId": "arn:aws:cloudformation:ap-east-1:account-id:stack/stack-name/d353b870-0a30-11ec-b48b-0e7bb5a4c8fc",
"Status": "FAILED",
"ChangeSetName": "change-set-name",
"CreationTime": "2021-09-02T01:58:11.556Z",
"StatusReason": "Transform AWS::Serverless-2016-10-31 failed with: Invalid Serverless Application Specification document. Number of errors found: 1. Resource with id [SampleFunction] is invalid. Type of property 'Events' is invalid.",
"StackName": "stack-name",
"ExecutionStatus": "UNAVAILABLE",
"ChangeSetId": "arn:aws:cloudformation:ap-east-1:account-id:changeSet/change-set-name/xxxxxxx"
}
]
} - Use AWS Console
- Use AWS CLI