DEV Community

Cover image for AWS CDK Continuous Integration and Delivery Using Travis CI
Thomas Poignant for AWS Community Builders

Posted on • Originally published at betterprogramming.pub

5

AWS CDK Continuous Integration and Delivery Using Travis CI

Ship infrastructure like you ship your code

AWS Cloud Development Kit, better known as CDK, is changing the way we do infrastructure as code by using a proper programming language to build your infra.

Since we are building infra with dev tools, we should have a CI/CD for our CDK stack code that looks like any other project. If you look online, AWS explains how to do CI/CD with code build/code pipeline, but these probably aren’t the tools you are using in your day-to-day.

In this article, I will present to you the deployment pipeline I’m using to deploy my CDK stack based on GitHub and TravisCI.

Full CI/CD


Continuous Integration

Continuous integration looks like any code project CI. We validate our code, run the tests, and try to see if the build failed.

This is run in every commit to ensure that we have the shortest feedback loop possible.
CI process

So in Travis configuration, it will look like something like below (here, we have a CDK Python stack):

language: python
python: "3.8"
stages:
- test
before_install:
- npm i -g aws-cdk@1.76.0 # Install CDK
- pip install -r requirements.txt --use-feature=2020-resolver # Download CDK python depedencies
jobs:
include:
- stage: test
name: "Test"
script:
- pytest --junitxml=junit-report.xml
- stage: test
name: "Lint"
before_script:
- pip install yamllint flake8 flake8-checkstyle
script:
- flake8 $(shell git ls-files '*.py')
- yamllint -f parsable $(shell git ls-files '*.yml' '*.yaml')
- stage: test
name: "Coverage"
before_script:
- pip install coverage
script:
- coverage run --omit "*/virtualenv/*,*/tests/*" -m pytest
- stage: test
script:
- cdk synth -c stage=dev
- stage: test
script:
- cdk synth -c stage=pre
- stage: test
script:
- cdk synth -c stage=pro

This CI is classic. We lint the code, test it, and export the coverage. We run cdk synth for each stage on each commit to be sure that the config does not break one stage in particular.

As you can see, we also use multiple Travis CI stages to run everything in parallel in order to have the shortest feedback loop possible.


Open Pull Requests to Validate Your Deployment

The best strategy I have found to deploy the code to AWS is to have one branch per stage (dev, pre, pro), so every time a new commit reaches one of these branches, we are deploying the actual stack. Yes, it looks like GitOps!

But we don’t want to blindly deploy our CDK stack on an AWS account. So I have an automatic way of opening pull requests when something is merged to the main branch:

Process of opening pull request

Each time something is merged to the main branch, we have a specific job that runs cdk diff -c stage=<STAGE> and opens a pull request to the destination branch.

For that, we use the script below. As you can see, it runs cdk diff and uses the results to open a pull request using the GitHub CLI:

function open_pull_requests() {
if [ "$TRAVIS_PULL_REQUEST" == "false" ]; then
STAGES=( dev pre pro )
for STAGE in "${STAGES[@]}"; do
# Run CDK diff
cdk diff -c stage=${STAGE} | tee cdk_diff_${STAGE}.txt
# Prepare PR message
read -r -d '' MESSAGE <<-EOM
> :warning: **Please review this changes before merging.**
**This PR will apply these changes(\`cdk diff\`):**
\`\`\`console
$(cat cdk_diff_${STAGE}.txt)
\`\`\`
EOM
set +e
CMD_MESSAGE_OUTPUT=$(gh pr create --title "Update $STAGE environment." --body "$MESSAGE" --base "$STAGE" --head "$TRAVIS_BRANCH" 2>&1)
CMD_RES=$?
set -e
if [ $CMD_RES -ne 0 ]; then
if [[ "$CMD_MESSAGE_OUTPUT" == *"already exists"* ]]; then
echo "A PR is already opened. Skip opening a new one."
else
travis_terminate 1
fi
fi
done
fi
}

Now we have to call this script inside Travis, so our .travis.yaml file will look like this:

language: python
python: "3.8"
env:
global:
# GITHUB_TOKEN to open PR
- secure: "XXX"
# AWS ACCESS KEY
- secure: "XXX"
# AWS SECRET KEY
- secure: "XXX"
stages:
- test
- pull_request
before_install:
- npm i -g aws-cdk@1.76.0 # Install CDK
- pip install -r requirements.txt --use-feature=2020-resolver # Download CDK python depedencies
jobs:
include:
- stage: test
name: "Test"
script:
- pytest --junitxml=junit-report.xml
- stage: test
name: "Lint"
before_script:
- pip install yamllint flake8 flake8-checkstyle
script:
- flake8 $(shell git ls-files '*.py')
- yamllint -f parsable $(shell git ls-files '*.yml' '*.yaml')
- stage: test
name: "Coverage"
before_script:
- pip install coverage
script:
- coverage run --omit "*/virtualenv/*,*/tests/*" -m pytest
- stage: test
script:
- cdk synth -c stage=dev
- stage: test
script:
- cdk synth -c stage=pre
- stage: test
script:
- cdk synth -c stage=pro
- stage: pull_request
name: "Open Pull request"
if: branch = main AND type != cron AND type != pull_request
before_script:
- wget https://github.com/cli/cli/releases/download/v1.0.0/gh_1.0.0_linux_amd64.deb && sudo dpkg -i gh_*.deb # Install GitHub cli
- gh auth login --with-token <<<"${GITHUB_TOKEN}" # Authentication via the github cli.
script:
- source ./cdk_open_pull_requests.sh
- open_pull_requests

As you can see, we are now using some env variables to specify our AWS Credentials in addition to our GitHub Token. Please make sure to encrypt them before committing them in your repo.

cdk diff in GitHub pull request


Continuous Deployment

Now that you have your pull requests open for all your stages, it’s time to deploy the changes to your stack!

On your PR comment, you are able to see the diff, so you will no longer be blind when it’s time to deploy a specific stage.

CD process

The next step for the CD is to deploy the stack. Each time we have a change in one of the release branches, we want to run cdk deploy -c stage=<STAGE>.

The complete .travis.yaml file will look like this. As you can see, each stage uses a condition for the deployment to be sure to apply it only to a specific AWS stage:

language: python
python: "3.8"
env:
global:
# GITHUB_TOKEN to open PR
- secure: "XXX"
# AWS ACCESS KEY
- secure: "XXX"
# AWS SECRET KEY
- secure: "XXX"
stages:
- test
- pull_request
- release
before_install:
- npm i -g aws-cdk@1.76.0 # Install CDK
- pip install -r requirements.txt --use-feature=2020-resolver # Download CDK python depedencies
jobs:
include:
- stage: test
name: "Test"
script:
- pytest --junitxml=junit-report.xml
- stage: test
name: "Lint"
before_script:
- pip install yamllint flake8 flake8-checkstyle
script:
- flake8 $(shell git ls-files '*.py')
- yamllint -f parsable $(shell git ls-files '*.yml' '*.yaml')
- stage: test
name: "Coverage"
before_script:
- pip install coverage
script:
- coverage run --omit "*/virtualenv/*,*/tests/*" -m pytest
- stage: test
script:
- cdk synth -c stage=dev
- stage: test
script:
- cdk synth -c stage=pre
- stage: test
script:
- cdk synth -c stage=pro
- stage: pull_request
name: "Open Pull request"
if: branch = master AND type != cron AND type != pull_request
before_script:
- wget https://github.com/cli/cli/releases/download/v1.0.0/gh_1.0.0_linux_amd64.deb && sudo dpkg -i gh_*.deb # Install GitHub cli
- gh auth login --with-token <<<"${GITHUB_TOKEN}" # Authentication via the github cli.
script:
- source ./cdk_open_pull_requests.sh
- open_pull_requests
- stage: deploy
deploy:
skip_cleanup: true
on:
branch: dev
provider: script
script: cdk deploy -c stage=dev
- stage: deploy
deploy:
skip_cleanup: true
on:
branch: pre
provider: script
script: cdk deploy -c stage=pre
- stage: deploy
deploy:
skip_cleanup: true
on:
branch: pro
provider: script
script: cdk deploy -c stage=pro
view raw .travis.yaml hosted with ❤ by GitHub

Conclusion

With this GitOps approach, it is easy to understand what you’re deploying in your stack and all your changes are in Git.

It also helps engineers with less knowledge in CDK to feel confident in the deployment of the infrastructure because no manual steps are required to deploy to a “real” AWS stage. The main advantage is that you are using the same tooling that you are using for the rest of your projects.

Sentry image

See why 4M developers consider Sentry, “not bad.”

Fixing code doesn’t have to be the worst part of your day. Learn how Sentry can help.

Learn more

Top comments (0)

Best Practices for Running  Container WordPress on AWS (ECS, EFS, RDS, ELB) using CDK cover image

Best Practices for Running Container WordPress on AWS (ECS, EFS, RDS, ELB) using CDK

This post discusses the process of migrating a growing WordPress eShop business to AWS using AWS CDK for an easily scalable, high availability architecture. The detailed structure encompasses several pillars: Compute, Storage, Database, Cache, CDN, DNS, Security, and Backup.

Read full post

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay