AWS CI/CD CodePipeline with Blue/Green Deployment21 Nov 2019
We will walk through how to create a CI/CD pipeline in AWS. Basically, CI/CD stands for Continuous Integration/Continuous Delivery which means whenever we push a new code change our pipeline will build the project, run the tests and deploy the new changes. In this tutorial we will use 4 AWS components;
- CodeCommit for code repository
- CodeBuild for continuous integration service to build and test our sources.
- CodeDeploy for continuous deployment to deploy new changes into our compute services which we will use Amazon EC2.
- CodePipeline for combining CodeBuild and CodeDeploy components to build a CI/CD pipeline.
Here is the architecture we will build
- Step 1 - AWS CodeCommit will generate a Source Artifact in S3 bucket
- Step 2 & 3 - AWS CodeBuild will run buildspec.yml. In this step, it will generate Build Artifact and also publish a new Docker image into ECR
- Step 4 & 5 - AWS CodeDeploy will get the new Docker image from ECR and start a new Blue/Green deployment.
CodeCommit is an AWS service to host git repositories. First, we will create a git repository on CodeCommit. I will call repository name as
aws codecommit create-repository --repository-name nodejs-hello-world --repository-description "NodeJS example repository for CI/CD pipeline tutorial"
You might need to configure your aws credentials for CodeCommit if you haven't worked on CodeCommit before. So, I will skip this part and assume you already configured. Otherwise, you can follow the steps from AWS Guide.
After creating the repository we will now clone it to our local;
git clone ssh://git-codecommit.us-east-1.amazonaws.com/v1/repos/nodejs-hello-world
Now it is time to initialize our nodejs project and push the initial commit.
This is how package.json and server.js looks like;
Elastic Container Registry
We will create a Docker registry in ECR which will keep the list of Docker images.
aws ecr create-repository --repository-name nodejs-hello-world
After creating our ECR repository we will push our first image.
AWS CodeBuild is the continuous integration service to build, run tests and produce our software artifacts. Our CodeBuild project will build the new commit we pushed and publish a new Docker image into our ECR repository. We will first create a CodeBuild project with a json file as an input. Then we will move to build our project.
aws codebuild create-project --cli-input-json file://create-codebuild.json
sourceis the part where we give our https git clone url. Here we are pointing to our CodeCommit git repository.
artifactspart is for pointing out where to keep our built artifacts like in a S3 bucket.
environmentpart is the place where define our project's build environment. One thing to note here is
privilegedModehas to be
trueif we will build Docker images.
serviceRoleis the role CodeBuild will use to access any other AWS resources. In our case, CodeBuild should be granted access for ECR, CodeCommit and CloudWatch.
Just in case here is the
CodeBuildPullECRImagesRole with its two attached policies.
You can check AWS documentation for a more detailed codebuild configuration.
After creating our build project in CodeBuild we will create a file called buildspec.yml to describe phases and commands to run for each phase during the build. This is the place we will login into ECR repository, build and tag our Docker image from Dockerfile, and push the new image into our ECR repository. One important point to note here is including the necessary files into Output Artifact which will be used by CodeDeploy application later. We will include 3 files;
- imageDetails.json - This is the file Amazon ECS deploy action expects to generate a new task definition file. It has only one field
ImageURIand it will replace this
IMAGE1_NAMEparameter in task-definition.json we will create. You can find a more detailed reference for image definitions. imageDetails.json will be created in
- appspec.yml - This is the yml file to be used by CodeDeploy to run tasks on ECS cluster.
Spin Up ECS Cluster
We saw how to spin up an ECS Cluster to deploy Docker containers on EC2 instances by creating Elastic Load Balancer with Target Group and ECS Cluster with ECS Service on my previous post. We will follow the same steps except there are a couple of small changes we need to make on our task definition. First, we won't define any
portMappings section to enable dynamic port mapping. Dynamic port mapping for Amazon ECS makes it easier to run multiple tasks on the same ECS service. In this case, multiple tasks refers to Original task and Replacement task generated for Blue/Green deployment. So, whenever there is a new ECR image created, a new task will be created to run along with previous task on same ECS service where it has one EC2 instance. Otherwise, we will have port conflict issue during the deployment.
Now, this was the json we used to register our Task Definition.
Now after creating and registering it to our new ECS cluster we will replace
image value with
<IMAGE1_NAME> which will become
We can then push new task-definition.json change into our code repository. Before creating ECS Service we should have an Application Load Balancer with two Target Groups to listen on ports 80 and 8080. I skipped this part because my previous blog explains how to create an ELB with target groups.
When creating ECS service we will have
deployment-controller set to
CODE_DEPLOY and we will use one Target Group because multiple Target Groups are not supported for CODE_DEPLOY type services.
aws ecs create-service --cluster example-ecs-cluster --service-name example-ecs-service --task-definition nodejs-hello-world-task-def --desired-count 1 --deployment-controller type=CODE_DEPLOY --launch-type EC2 --load-balancers targetGroupArn=arn:aws:elasticloadbalancing:us-east-1:548754742764:targetgroup/example-target-group/0f9efaeceb63ac61,containerName=nodejs-hello-world,containerPort=8080
CodeDeploy is a deployment service to configure and automate deployments on given targets like EC2 instances, AWS Lambda functions or ECS services. In this use case we will of course use ECS type deployments. This is the place we will point the two Target Group we created. One for production port and other one for test port.
aws deploy create-application --application-name nodejs-hello-world-codedeploy --compute-platform ECS
Now we will create a deployment group for our deployment application;
aws deploy create-deployment-group --cli-input-json file://create-deployment-group.json
As the final step we will create and configure CodePipeline by combining the components we talked about earlier. Our pipeline will have 3 stages;
- Source - This the stage we point our GIT repository which is
nodejs-hello-world. So, whenever there is a new commit pushed then this pipeline will get triggered.
- Build - This is the part
buildspec.ymlwill run to build and push Docker images into ECR. Additionally, it will create an artifact which contains 3 files; imageDetail.json, task-definition.json and appspec.yml.
- Deploy - This stage is the core side of our Blue/Green deployment.
aws codepipeline create-pipeline --cli-input-json file://create-codepipeline.json
After creating the codepipeline we can release a new change to verify that everything works as expected.
Now we only have one api which is the
hello-world endpoint. We will add another api
v2/hello-world and verify that during the deployment port 8080 will point to the new revision where port 80 points the current revision. After pushing the new change our Deployment with test traffic enabled will look like;
Here we can see two target groups attached to the ALB as well. One points to port 80 and other one points to 8080
curl curl example-elb-162160344.us-east-1.elb.amazonaws.com:8080/v2/hello-world
On the other hand, if we send the same request to the production port (80 in this case) then server will return 404 error becauase production target group still points to the old revision.
curl -i example-elb-162160344.us-east-1.elb.amazonaws.com:80/v2/hello-world
After waiting 15 minutes target group with production will also point to the new revision and we will have 30 minutes to test production. If something goes wrong we will be able to rollback. Here is the deployment looks like after routing production traffic into new revision;
If we check ALB we will see that now there is only one Target Group pointing both test and prod ports.
Now lets another curl request to verify production serves from correct revision.
Everything works as expected. After 30 minutes the old Task will be terminated on ECS cluster and new Task will be the permanent one. In this tutorial, we saw how to build a CI/CD pipeline with Blue/Green deployment type on ECS cluster. Feel free to ask any questions if you have. Additionally, you can find the resources from my github