Few months ago, I gave a talk at Nexus User Conference 2018 on how to build a fully automated CI/CD platform on AWS using Terraform, Packer & Ansible. I illustrated how concepts like infrastructure as code, immutable infrastructure, serverless, cluster discovery, etc can be used to build a highly available and cost-effective pipeline. The platform I built is given in the following diagram:
The platform has a Jenkins cluster with a dedicated Jenkins master and workers inside an autoscaling group. Each push event to the code repository will trigger the Jenkins master which will schedule a new build on one of the available slaves. The slave will be responsible of running the unit and pre-integration tests, building the Docker image, storing the image to a private registry and deploying a container based on that image to Docker Swarm cluster.
On this post, I will walk through how to deploy the Jenkins cluster on AWS using top trending automation tools.
The cluster will be deployed into a VPC with 2 public and 2 private subnets across 2 availability zones. The stack will consists of an autoscaling group of Jenkins workers in a private subnets and a private instance for the Jenkins master sitting behind an elastic Load balancer. To add or remove Jenkins workers on-demand, the CPU utilisation of the ASG will be used to trigger a scale out (CPU > 80%) or scale in (CPU < 20%) event. (See figure below)
To get started, we will create 2 AMIs (Amazon Machine Image) for our instances. To do so, we will use Packer, which allows you to bake your own image.
The first AMI will be used to create the Jenkins master instance. The AMI uses the Amazon Linux Image as a base image and for provisioning part it uses a simple shell script:
It will install the latest stable version of Jenkins and configure its settings:
Create a Jenkins admin user.
Create a SSH, GitHub and Docker registry credentials.
Install all needed plugins (Pipeline, Git plugin, Multi-branch Project, etc).
Disable remote CLI, JNLP and unnecessary protocols.
Enable CSRF (Cross Site Request Forgery) protection.
Install Telegraf agent for collecting resource and Docker metrics.
The second AMI will be used to create the Jenkins workers, similarly to the first AMI, it will be using the Amazon Linux Image as a base image and a script to provision the instance:
A Jenkins worker requires the Java JDK environment and Git to be installed. In addition, the Docker community edition (building Docker images) and a data collector (monitoring) will be installed.
Now our Packer template files are defined, issue the following commands to start baking the AMIs:
Packer will launch a temporary EC2 instance from the base image specified in the template file and provision the instance with the given shell script. Finally, it will create an image from the instance. The following is an example of the output:
Sign in to AWS Management Console, navigate to “EC2 Dashboard” and click on “AMI”, 2 new AMIs should be created as below:
Now our AMIs are ready to use, let’s deploy our Jenkins cluster to AWS. To achieve that, we will use an infrastructure as code tool called Terraform, it allows you to describe your entire infrastructure in templates files.
I have divided each component of my infrastructure to a template file. The following template file is responsible of creating an EC2 instance from the Jenkins master’s AMI built earlier:
Another template file used as a reference to each AMI built with Packer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
data "aws_ami" "jenkins-master" { most_recent = true owners = ["self"]
filter { name = "name" values = ["jenkins-master-2.107.2"] } }
data "aws_ami" "jenkins-slave" { most_recent = true owners = ["self"]
filter { name = "name" values = ["jenkins-slave"] } }
The Jenkins workers (aka slaves) will be inside an autoscaling group of a minimum of 3 instances. The instances will be created from a launch configuration based on the Jenkins slave’s AMI:
At boot time, the user-data script above will be invoked and the instance private IP address will be retrieved from the instance meta-data and a groovy script will be executed to make the node join the cluster:
1 2 3 4 5 6 7 8 9 10
data "template_file" "user_data_slave" { template = "${file("scripts/join-cluster.tpl")}"
Moreover, to be able to scale out and scale in instances on demand, I have defined 2 CloudWatch metric alarms based on the CPU utilisation of the autoscaling group:
Finally, an Elastic Load Balancer will be created in front of the Jenkins master’s instance and a new DNS record pointing to the ELB domain will be added to Route 53:
1 2 3 4 5 6 7 8 9 10 11
resource "aws_route53_record" "jenkins_master" { zone_id = "${var.hosted_zone_id}" name = "jenkins.slowcoder.com" type = "A"
alias { name = "${aws_elb.jenkins_elb.dns_name}" zone_id = "${aws_elb.jenkins_elb.zone_id}" evaluate_target_health = true } }
Once the stack is defined, provision the infrastructure with terraform apply command:
1 2 3 4 5 6 7 8
# Install the AWS provider plugin terraform int
# Dry-run check terraform plan
# Provision the infrastructure terraform apply --var-file=variables.tfvars
The command takes an additional parameter, a variables file with the AWS credentials and VPC settings (You can create a new VPC with Terraform from here):
Terraform will display an execution plan (list of resources that will be created in advance), type yes to confirm and the stack will be created in few seconds:
Jump back to EC2 dashboards, a list of EC2 instances will created:
In the terminal session, under the Outputs section, the Jenkins URL will be displayed:
Point your favorite browser to the URL displayed, the Jenkins login screen will be displayed. Sign in using the credentials provided while baking the Jenkins master’s AMI:
If you click on “Credentials” from the navigation pane, a set of credentials should be created out of the box:
The same goes for “Plugins”, a list of needed packages will be installed also:
Once the Autoscaling group finished creating the EC2 instances, the instances will join the cluster automatically as you can see in the following screenshot:
You should now be ready to create your own CI/CD pipeline !
You can take this further and build a dynamic dashboard in your favorite visualisation tool like Grafana to monitor your cluster resource usage based on the metrics collected by the agent installed on each EC2 instance:
Drop your comments, feedback, or suggestions below — or connect with me directly on Twitter @mlabouardy.