CI/CD With Jenkins, Docker, and Kubernetes on AWS

Isaac Ndung'u
Sep 6, 2018 · 11 min read
A simple pipeline outline

The importance of CI/CD in the software development lifecycle cannot be underrated. This article will mention some of the key reasons why CI/CD plays a crucial role in ensuring steady and consistent delivery of quality software products. We will go further and set up a CI/CD pipeline in Jenkins for an application implementing the microservice architecture and uses Docker to package the constituent microservices and deploy them onto a Kubernetes cluster on AWS.

What is CI/CD?

To understand why CI/CD important we must first answer the question, What is CI/CD?

At its simplest CI/CD essentially can be defined by the sum of its parts. First, we have CI or Continuous Integration in its most basic form is having a single repository of all the software source code and consistently making small incremental changes to it. This can be extended to involve testing of the increments to ensure they meet preset standards or will not, in any way, break the software. CD or Continuous Delivery ensures that the changes added upon passing preset checks are then deployed to the application and are available to its users.

With the above in mind, it is clear that the main concept in CI/CD is ensuring small incremental changes made to a central repository of software source code are regularly built, tested and delivered to users more effectively and efficiently.

Importance of CI/CD

The following are the clear advantages of having a CI/CD setup on your product its size and complexity notwithstanding.

  1. Any changes made are immediately tested and in the event of any failure, the necessary corrections are made. This lowers the risk of introducing changes that could break the application
  2. Having such a pipeline in place provides an avenue for fast feedback which can be acted upon swiftly. This shorter feedback cycles help in making crucial changes soon after they are cited or raised either by peers during reviews or by users about certain aspects of the application.
  3. There is also increased transparency in the sense that since all the code is in a single source and any new changes are immediately reported to all the members of the team and other stakeholders involved in the development process.
  4. A good CI/CD implementation ensures that the quality of the product is maintained and consistently improved. This is because it ensures that existing components of the application have been thoroughly tested and that any addition is also tested. It is easier to determine the quality of anything that can be tested.
  5. As it stands at the moment one can easily integrate various tools with the source control management and CI tools they have in place. This range from messaging/notification tools like Slack, project tracker tools like PivotalTracker and a host of other tools. All these tools extend the benefits of a CI/CD pipeline by providing reporting and general progress tracking feature in an automated fashion.

With above in mind, let’s walk through the process of creating a CI/CD pipeline using Jenkins, Docker, and Kubernetes on AWS.

Setting up Jenkins

Jenkins is an open source automation server written in Java. Jenkins helps to automate the non-human part of the software development process, with continuous integration and facilitating technical aspects of continuous delivery ~ Wikipedia

For the purposes of this demo, we shall be using a Jenkins server that is provisioned is created from an image build using Packer and provisioned using Ansible. The contents of this image along with configurations done can be found in the repository below

Follow the README.md on the repo’s root directory for a walkthrough on how to build an image for the Jenkins server on AWS.

Upon creation of the image, you can head on over to the AMIs section on your EC2 dashboard and launch an instance with the image you created. Ensure you set the instances network security rules to accept HTTP traffic and SSH connections to allow you to progress with the next set of instructions.

Upon completion, you can start an SSH connection to your instance alongside accessing the Jenkins user interface on your browser. You might want to consider getting a domain name and hosting it on AWS Route 53, this is because, in as much as you can access your server with just the public IP, some functionality is highly limited. You can get free domain names on Freenom and follow the AWS official tutorials in how to host your domain through Route 53 hosted zones.

On accessing your server for the first time you shall see the following page.

Jenkins initial setup unlock page

This page is the first in a series of setup pages that you will be required to fill only once. According to the instructions, to unlock your Jenkins server you will need to provide one-time unlock password found in the file whose path is provided in red. To get this password, run the following command.

sudo cat /var/lib/jenkins/secrets/initialAdminPassword

On pressing enter, the password is dumped to your console and you can copy it, enter it in the password field provided and click continue. In the following prompts proceed with the default selection and add the requested details. When you’re done and all is well you should see the following welcome page.

successful setup welcome page

Click start using Jenkins to get to the Jenkins dashboard. In order for Jenkins to automatically pick up and build made any changes to the repository you want to build and deploy, you are required to add a webhook on the integration services section of the repo. To get this webhook, go to the management page Manage Plugins and select Configure System. Under the GitHub section select, Advanced. Check the Specify another URL for GitHub configuration checkbox and copy the URL provided. Add this URL to your repo as the URL for Jenkins (GitHub Plugin).

Accessing Jenkins System Configuration
The GitHub Plugin section
Webhook URL provided after the checkbox is checked

Once this is done head on over to the Manage Plugins section. Select the Available tab and search for Blue Ocean by typing into the search box provided on the top right section of the plugins list.

Access the manage plugins page

Select the Blue Oceans plugin by clicking the checkbox to its left and then click the Install after restart button. Once the installation is complete and Jenkins has restarted, append /blue to the URL on your browser’s address bar, or click Open Blue Ocean on the quick access tab to your left, to access the blue ocean pipelines dashboard.

You should see the above welcome message. Click the create new pipeline and follow the subsequent prompts. Once you have selected the project you want to build, Jenkins automatically searches the project root for a Jenkinsfile defining your pipeline. In this case, my pipeline is a simple three-stage process as shown below.

Demo project pipeline

Securing sensitive data in the pipeline

Like many other pipelines, this setup had some sensitive data that needed to be kept secure and not exposed to the open internet. This was archived using scoped credentials of type secret text which were then availed to the builder as environment variables.

You will need to create your set of credential as well if you are want to have access to your docker hub account during build operations. To do so, click the Credentials option from the quick access menu to your left, in the credentials page click on the global link under the Stores scoped to Jenkins section. On the top left of the following page click on Add Credential. Select credential type from the dropdown as secret text. Provide the secret text value, ID and description as appropriate and save the added credential.

Jenkins vs other CI tools (Travis CI and CircleCI)

Jenkins, unlike Travis CI and CirlcleCI, is not a manage CI solution but a fully customizable open source java based CI tool that requires a dedicated server. Jenkins describes itself as a self-contained Java-based program, ready to run out-of-the-box, with packages for Windows, Mac OS X and other Unix-like operating systems.

Travis CI is a cloud-based CI solution that is available on a freemium model. Open source projects get to register for the service, define their build plans with a simple YAML file, link the project to it and thereafter continue to build and monitor project builds on the free model. They offer a premium model which allows for the building of private repositories for enterprise clients.

CircleCI is a cloud-based CI system — no dedicated server required, and you do not need to administrate it. Unlike Travis CI, CircleCI does offer a free plan for building enterprise projects (private repositories). Build jobs and workflows are defined using a YAML file as in the case of Travis. It also, like Travis CI, support the use of docker to run tests.

For a better feature spec off of these three CI tools, you can refer to this post on HackerNoon.

Pipeline as Code

The pipeline code for this demo is fairly simple but it will serve the purpose of explaining the concept of setting up your “pipeline as code”.

The pipeline is written in the Groovy syntax and it’s basically running three stages. The first builds the image, the next uploads built images and then finally the last makes the deployment to the Kubernetes cluster.

Having the pipeline defined as code makes it easy to manipulate and extend. New steps can be added and more complex dependencies can be made. Current steps can be split and made to run in parallel. A good example would be if any of the current microservices, or any new ones added, had special different specifications in the way it needed to be built and/or tested, once the code is checkout from SCM, all that needs to be done is modifying the Jenkinsfile appropriately. This single point of change and the fact it too is committed to SCM makes it easy to manage the CI/CD pipeline.

How Jenkins interfaces with GitHub

Jenkins through the GitHub plugin is able to use a registered application access token which is created on the settings page of one’s user account. This token gives Jenkins authorized access to the GitHub API. With this token, every request that needs to be made by the server either to pull source code or commit info, or even send notifications on build status to GitHub, all of them are authorized through this token.

Tools used and why

As a DevOps engineer, the choice of tools to use is always a key consideration when setting up any project, infrastructure or service. For this particular case, however, the tools used were suggested to us with the intent of using this as a learning experience.

If the case was different, I would probably look into the tradeoff of having a Kubernetes cluster in on AWS over using the Google Kubernetes Engine. I would factor in several things key among them ease of setup and overall cost considerations for operating in either cloud platforms.

I would probably stick with Docker for my containerization technology since it interfaces really well with Kubernetes. But for the CI platform, I would maybe consider CircleCI. Its robust set of features that come ready out of the box would probably make it easier to implement an even more elaborate CI/CD pipeline.

Release management

The way new changes are received by users greatly determines the release scheme that will be used. There are several tradeoffs between using BlueGreen and Canary deployments. Despite their similarities, I would tend to go the Canary route for the following reasons.

  1. Canary deployment allows for a subset of user traffic to be directed to the app with the new features while most of the users are still using the old one. This allows a closer more effective feedback loop and mitigates the issues that would arise if the users are not entirely pleased with the changes effected.
  2. The BlueGreen deployment model has all user traffic directed to the new application infrastructure and as such, some failsafe needs to be put in place to avoid missed transactions should there occur the switch back to the Blue deployment. This is not necessarily the case in the Canary deployment model since the application would essentially have the same core infrastructure
  3. With canary deployments, before rolling out changes globally you can redirect region specific traffic to the new application as a pilot stage and then upon acting on the necessary feedback you can do several more regions, aggregate the feedback and if it’s generally well received then a global rollout can be made.

Microservices and how they work

microservice app example structure

The application I am deploying has the above architecture and I shall use it to basically explain how microservices work together.

As shown in the picture, at the heart of microservice architecture is REST APIs. The featured app is a to-do list app with 3 REST APIs all written in different programming languages. The Auth API is written in Go, the Users API is written in Java SpringBoot and the TODOs API is written in NodeJS.

Evidently, one of the key benefits of having a microservice architecture is the ability to separate concerns and implementation models for each. The various APIs need only have each others’ the endpoint URLs and they can, in turn, make and respond to requests to/from one another. As a user interfaces with the frontend, they are totally oblivious to what goes on under the hood. The frontend makes requests to appropriate backend service, which may make subsequent requests to other backend services if need be. The general response is back-channeled through a series of responses and is displayed to the user.

This level of independence frees up the development team to take advantage of various technologies or language stacks that are more optimized to handle certain aspects of the business.

Pros and Cons of the Microservice Architecture.

Pros

  • Technology stacks flexibility ensures that the best implementation is made thereby optimizing the application and improving the overall user experience.
  • Since REST APIs are the backbone of microservice architectures, the extensibility of the application grows exponentially supporting multiple devices and platforms while retaining the same quality of service across the board.
  • Apps built of the microservice architecture tend to be more malleable. Microservices implementations of a certain feature can change independently of all other aspects to the overall application with little to no implication on the quality of service offered granted the new implementation serves the purpose the microservice was set up to achieve.
  • Microservice based apps scale better than their monolithic counterparts.

Cons

  • As the application grows in size and more and more microservices join the network of micro-apps it can be fairly complex to manage them.
  • Microservices might, in the long run, end up costing more in terms of resource requirements.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade