cover-photo

The approach we take to deploying and running our server-side applications that we build on cloud can vary based on the core underlying components we use. For example, managing continuous deployments for AWS Lambda functions will differ significantly when compared to running our services on ECS or EKS.

We could also have our applications running as docker containers on multiple EC2 or On-premise instances.

In this article, we will take a look at how to setup CD pipelines when deploying our services on EC2 instances, so read on.

What we need

cover-photo

We need a few services of AWS to setup the pipelines.

CodeBuild to build/ bundle the application into a docker image and then push it to the container registry (ECR).
CodeDeploy to handle stopping and cleaning up existing containers and then pull the fresh image from the registry and run the container
CodePipeline to tie-up CodeBuild and CodePipeline together, into a single process that runs whenever changes are made to a desired branch in a repository.

Now we’re ready to start setting up our pipeline.

CodeBuild Project

Let us get started by creating a new CodeBuild project.

Please note that as a pre-requisite, we will need to have a container registry and a repository setup in ECR.

The purpose of the build phase of our pipeline will be to pull the source code from CodeCommit, generate a docker image and push that image to a private repository within the Elastic Container Registry.

cover-photo

As we need a docker image to be extracted and pushed to the repo during our ‘build’ process, we need an On-demand EC2 instance which can come from a list of managed images that AWS provides out of the box.

The selection of the ‘image’ will depend upon the type of the EC2 instance that we choose to host our application servers on.

For this example, we are running our applications on the AWS’s graviton family of instances which are ARM based (ARM64 or AARCH64) and hence we choose that the instance that builds our application also be ARM based as it is essential that the CPU architecture of the machine running the docker container be the same as the one building the image.

The buildspec.yml file

We will need to specify a build specification via a YAML file either directly on the CodeBuild project or as an explicit file in the source code.

CodeBuild reads this file as an instruction set to execute and complete the build process.

Looking through the file below, you can note that after logging into the ECR registry using the appropriate commands, the docker image itself is then built and pushed to a specific repository. So it is needless to say that the project has to include a valid and working Dockerfile.

version: 0.2
phases:
install:
runtime-versions:
docker: 23
commands:
- nohup /usr/local/bin/dockerd --host=unix:///var/run/docker.sock --host=tcp://127.0.0.1:2375 --storage-driver=overlay2&
- timeout 15 sh -c "until docker info; do echo .; sleep 1; done"
pre_build:
commands:
- aws --version
- echo $(docker version)
- echo Getting ECR Login
- ECR_PASS=$(aws ecr get-login-password --region us-east-1)
- echo Logging in to ECR
- docker login --username AWS --password $ECR_PASS <account_number>.dkr.ecr.<region>.amazonaws.com
- COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
- IMAGE_TAG=${COMMIT_HASH:=latest}
- REPOSITORY_URI=<erc_repository_uri>
build:
commands:
- echo Build started on `date`
- echo Building the Docker image...
- docker build -t $REPOSITORY_URI:latest .
- docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
- docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$ENV_IMAGE_TAG
post_build:
commands:
- echo Build completed on `date`
- echo pushing to repo
- docker push $REPOSITORY_URI:latest
- docker push $REPOSITORY_URI:$IMAGE_TAG
- docker push $REPOSITORY_URI:$ENV_IMAGE_TAG
- echo Writing image definitions file...
- printf '{"ImageURI":"%s"}' $REPOSITORY_URI:$IMAGE_TAG > imageDetail.json
artifacts:
files:
- imageDetail.json

We can now move on to creating a CodeDeploy application

CodeDeploy

Setting up CodeDeploy consists of two steps.

Creating a CodeDeploy Application
Creating Deployment groups under an application

Setting up the application is simple. It requires us to input a unique name for the account and the Compute Platform - This is where the server application will be deployed - an EC2 instance, so we select ‘EC2/On-premises’.

Deployment group

Setting up the deployment group is simple enough as well. However, we need to be aware of a few key properties.

The deployment type needs to be In-place for EC2 instance deployments.

The environment configuration is where you would tie up the EC2 instances that would be used to deploy the applications and these are identified by the tag groups. So multiple EC2 instances can be targeted using a common tag name.

cover-photo
cover-photo

We could look more into setting up load balancing so that the up time is maintained even during a deployment, but that is beyond the scope of this article.

The appspec.yml file

When the deployments are running, CodeDeploy will look for an ‘appspec.yml’ file in the artifacts provided.

Let us look at the life cycle events that can be hooked on to and the appspec.yml file that lets us tap into these events in during a deployment.

Life Cycle events

ApplicationStop
DownloadBundle
BeforeInstall
Install
AfterInstall
ApplicationStart
ValidateService
version: 0.0
os: linux
hooks:
BeforeInstall:
- location: scripts/clean-up-containers.sh
timeout: 60
ApplicationStart:
- location: scripts/deploy-to-ec2.sh
timeout: 60

The file above taps into the ‘BeforeInstall’ and ‘ApplicationStart’ hooks to clean up the existing running containers and pull and deploy the new containers on the EC2 instance.

CodePipeline

CodePipeline is where all components we have setup so far can come together.

A pipeline will have the Source, Build and Deploy stages.

The Source stage will consist of the repository and the targeted branch - changes on which the pipeline will be triggered.

The Build stage will be configured to point to the CodeBuild project we created earlier which would bundle the application into a docker image and push it to ECR

The Deploy stage will leverage the CodeDeploy application that we just created targeting specific EC2 instances for deployment

cover-photo

One key thing to note is the Input artifacts for the ‘Deploy’ action. By default, CodePipeline sets this value to ‘BuildArtifact’ expecting the appspec.yml file as an output of the CodeBuild action, however, in our case, we have the file located directly in our codebase. So we will change this to ‘SourceArtifact’

cover-photo

And that’s how its done.

Any deployments running on this CodePipeline configuration should result in the scripts included in the ‘buildspec’ and ‘appspec’ files to be executed and any issues can be debugged by accessing the log files in the code-deploy agent folders inside the EC2 instances.