HelgeCPH / cph-code-infra

Example project for the course "Development of Large Systems" at Copenhagen Business Academy
MIT License
3 stars 6 forks source link

A CI/CD Example Setup

This document is hosted at: https://github.com/HelgeCPH/cph-code-infra This PDF is just a dump of the original README.md.

It is related to the course Development of Large Systems at Copenhagen Business Academy (https://www.cphbusiness.dk/english/study-programmes/top-up-bachelors-degree/software-development/2nd-semester/). Some parts of it build on infrastructure code and experiences from Praqma's CoDe training (https://github.com/praqma-training/code-infra).

This is a guide on how to setup an example continuous integration (CI) chain using the following technologies and tools:

Scenario

We have a Java project consisting of three projects, which depend on each other.

The project's source code can be found here:

In essence, these Java projects form a simple web-application, which serves a list of names via a JSP (http://:8080/choir-frontend/ChoirManager).

The contracts project consists of a set of interfaces and data transfer objects (DTO) and is used to let different groups of students implement their frontends and backend systems independently against a common specification and mockups.

The purpose of this guide is not to detail this Java project. Instead, we just use it to illustrate some of the steps, which are required to setup a complete CI chain automatically deploying the project on a production server.

Importantly, the projects depend on each other. The backend mockup depends on the contracts and the frontend depends on the two other projects. The package dependencies -as specified in the Maven metadata (pom.xml) of the respective projects- are illustrated in the following.

Packages

The contracts project and the backend mockup are build as a JAR files, whereas the frontend project is build as a WAR file.

Remember to fork the backend mockup and the frontend example Java projects (https://github.com/eguahlak/choir-backend-mock, https://github.com/Jegp/choir-frontend) so that you can modify the IP addresses in the pom.xmls to point to your machine running the Artifactory instance. Of course, this is only necessary when you want follow this tutorial and you want to replicate this example.

The CI Setup

We have a set of distributed developers working on their local computers and collaborating on the same source code via a Git repository hosted on GitHub. Since, for this example we do not have access to a proper build server, i.e., a separate machine, we decide to setup an Ubuntu virtual machine (VirtualBox), which will host our Jenkins build server. In case you have access to a proper build server the explanations in the following apply as well except that you can skip reading the part on Vagrant and apply the provision script (vm/provision.sh) directly on your machine.

On a remote server -hosted at Digital Ocean, you can host it anywhere else according to your liking- we have, amongst others, a Docker container running an Artifactory instance, which serves our local Maven dependencies. Finally, to the same remote machine we will automatically deploy a Docker container, which hosts our web-application.

The setup is illustrated in the following.

CI Setup

Consequently, we have to setup a Jenkins build server in a Vagrant machine, we have to configure it, we have to setup a set of build jobs on it, we have to setup a remote machine with a dockerized Artifactory. The following sections provide a step-wise description on how to do so. To run through this example you should start by cloning this repository to your local machine (git clone https://github.com/HelgeCPH/cph-code-infra.git). Note, that /path/to in all of the following examples is the path to the directory in which you cloned this project. Furthermore, this guide assumes that you are in a Linux/Unix environment using BASH.

Setup Your Remote Production Machine

For this example we rent the cheapest possible cloud machine at Digital Ocean -which they call "droplet". You can choose your own servers or any other providers. The descriptions and the provided setup script should be valid for any Debian-based Linux.

Now you have a remote machine up and running with an Artifactory instance and an Apache webserver. To validate that these first steps were successful, point your browser to http:/// where you should see documentation on how to connect to the Artifactory instance.

Setup Your Local Build Machine

Configuring Jenkins

Now that Jenkins is running you have to configure it. On first time use it will present you the following page.

CI Setup

Here you have to insert the key that you get either from the output of the provision script or via:

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

Subsequently, choose to Install suggested plugins. we will install the other required plugins later manually.

Afterwards, create a first admin user on Jenkins. For this example we will call it builder too.

CI Setup

Now, install some more plugins that we need to build our projects.

Navigate to Manage Jenkins -> Manage Plugins -> Available and select the following plugins

After selection, hit Download now and install after restart and on the next page check Restart Jenkins when installation is complete and no jobs are running and wait until Jenkins is restarted.

Now, we configure how Jenkins finds your Maven installation. For this project we will download Maven dynamically: Navigate to Manage Jenkins -> Global Tool Configuration and set the Maven section similar to the following and save.

CI Setup

Navigate to Manage Jenkins -> Configure System, set the Artifactory section to point to your Artifactory instance and test the connection to it. In case you did not modify the configuration of your Artifactory instance, then the standard login is admin and the standard password is password.

CI Setup

Adding Credentials to Jenkins

To allow for the later use of DockerHub as registry for the container with the final web application you need to be registered at https://hub.docker.com.

After you have created a user at DockerHub, navigate to Credentials -> (global) -> Add Credentials (which corresponds to navigating to the following URL: http://localhost:8080/credentials/store/system/domain/_/ ).

There add a Secret text, where the secret is your password to your DockerHub account.

CI Setup

Allow Jenkins User to Execute Deployment Script Remotely

To allow for a login from a non-interactive shell to the remote machine we have to create a pair of SSH keys for the jenkins user, i.e., the Linux user executing the shell scripts in Freestyle jobs.

You enable non-interactive login to the remote machine by logging to the VM, switching to the jenkins user, creating a pass-phrase-less key pair and moving them to the remote machine.

vagrant ssh
sudo su jenkins
ssh-keygen -t rsa -b 2048
cat /var/lib/jenkins/.ssh/id_rsa.pub

Copy the output of the cat command, i.e., the jenkins users' public key into you clipboard. From another terminal session connect to your remote server at DigitalOcean and register yet another public key.

ssh builder@46.101.154.221
echo "<your_public_jenkins_key>" >> ~/.ssh/authorized_keys
exit

To verify that the remote login based on the keys is working, ssh to the machine from the jenkins user in your Vagrant VM:

ssh builder@<your_ip>

Afterwards, to exit from the remote machine, from the jenkins user, and from the VM type:

exit
exit
exit

Creating Your Build Jobs

In total we will create four build jobs. Three Maven build jobs and one Freestyle build job. The latter can incorporate anything and we will use it to execute shell commands to build and deploy our docker containers.

The build jobs are the following:

The dependencies of the build jobs is given by their sequence above.

A Maven Project Build Job for choir-contract

This job will, with the help of Maven, build the choir-contract code, create a JAR file from it and it will push the JAR file to Artifactory so that it can be resolved as dependency from other projects.

Navigate to New Item, select Maven project, and give it the name choir-contract. Subsequently, under Source Code Management choose Git and point it to corresponding repository on GitHub, see the following screenshot.

CI Setup

This build job -as configured so far- will build a JAR file on your build machine. You can try that by hitting Build Now, which schedules the build job. In the list of the build jobs on the left side you can select the one you just triggered and hit Console Output to verify that the build job just created a JAR file for you.

Now, we have to make this JAR file accessible to others by registering it to Artifactory. You do that by adding a Add post-build action -> Deploy artifacts to Artifactory, in which you point to your Artifactory server and the corresponding release and snapshot repositories, see the following screenshot.

CI Setup

Note, we did not setup any Build Triggers so far as for this example we schedule the build job manually. You can select Build Triggers to your project needs, such as Build when a change is pushed to GitHub or Build periodically.

A Maven Project Build Job for choir-backend-mock

This job will, with the help of Maven, build the choir-backend-mock code, create a JAR file from it and it will push the JAR file to Artifactory so that it can be resolved as dependency from other projects.

Navigate to New Item, select Maven project, and give it the name choir-backend-mock. Subsequently, under Source Code Management choose Git and point it to corresponding repository on GitHub, see the following screenshot.

CI Setup

Now, we have to make this JAR file accessible to others by registering it to Artifactory. You do that by adding a Add post-build action -> Deploy artifacts to Artifactory, in which you point to your Artifactory server and the corresponding release and snapshot repositories, see the following screenshot.

CI Setup

Note, we did not setup any Build Triggers so far as for this example we schedule the build job manually. You can select Build Triggers to your project needs, such as Build when a change is pushed to GitHub or Build periodically, or Build after other projects are built, e.g., after choir-contract is built.

Furthermore, when building your own code similar to this you have to remember to modify your pom.xml accordingly. There you have to add another repository to resolve dependencies like this:

<repositories>
  <repository>
    <id>choir-repo</id>
    <name>Our Repo</name>
    <url>http://<your_ip>:8082/artifactory/libs-release-local</url>
  </repository>
</repositories>

A Maven Project Build Job for choir-frontend

This job will, with the help of Maven, build the choir-frontend code, create a WAR file from it. This WAR file will be consumed by the subsequent freestyle build job.

Navigate to New Item, select Maven project, and give it the name choir-frontend. Subsequently, under Source Code Management choose Git and point it to corresponding repository on GitHub, see the following screenshot.

CI Setup

Note, when building your own code similar to this you have to remember to modify your pom.xml accordingly. There you have to add another repository to resolve dependencies like this:

<repositories>
  <repository>
    <id>choir-repo</id>
    <name>Our Repo</name>
    <url>http://<your_ip>:8082/artifactory/libs-release-local</url>
  </repository>
  <repository>
    <id>choir-repo-snapshot</id>
    <name>Our Snapshot Repo</name>
    <url>http://<your_ip>:8082/artifactory/libs-snapshot-local</url>
  </repository>
</repositories>

A Freestyle Project Build Job for choir-build-docker

This job deploys the WAR file -which was generated by the previous build step- to a Docker container, which in turn is registered at the DockerHub. From there the container is deployed automatically to your remote production machine.

That is, the build job consists of two build steps. One for building the Docker container and distributing it to the DockerHub and a second one for deploying that container to the remote machine. Both build steps execute a sequence of shell commands.

Navigate to New Item, select Freestyle project, and give it the name choir-build-docker. Subsequently, under Source Code Management choose Git and point it to the repository on GitHub containing the Dockerfile, see the following screenshot.

CI Setup

Now, under Build -> Add build step choose Execute shell. Paste the following shell script into the Command field.

cp -r /var/lib/jenkins/workspace/choir-frontend-gradle/build/output/choir-frontend ${WORKSPACE}
docker build -t helgecph/choirserver:${BUILD_NUMBER} .

set +x
docker login -u helgecph -p "${DOCKERHUB_PWD}"
set -x

docker push helgecph/choirserver:${BUILD_NUMBER}
docker logout

The set +x suppresses echoing the operation to the console output. Your password should not appear there. With respect to Jenkins your password to the DockerHub is stored in the variable ${DOCKERHUB_PWD}. To fill this variable accordingly you have to add a secret text to the configuration of the credentials plugin, see above. Furthermore, you have to configure the use of this as a Secret text under Build Environment as illustrated in the following screenshot.

CI Setup

Now, the newly build Docker image, which is registered to the DockerHub has to be deployed to the remote machine. For this, add another build step. Under Build -> Add build step choose Execute shell and paste the following shell script into the Command field.

ssh builder@<your_ip> "./deploy.sh ${BUILD_NUMBER} <your_dockerhub_id>"

Of course, remember in both scripts to replace <your_dockerhub_id> with your login name at DockerHub.

This will call the remote deployment script, which was put there during machine setup.

#!/bin/bash

BUILD_NUMBER=$1
DOCKER_ID=$2
# stop all running containers with our web application
docker stop `docker ps -a | grep ${DOCKER_ID}/choirserver | awk '{print substr ($0, 0, 12)}'`
# remove all of those containers
docker rm `docker ps -a | grep ${DOCKER_ID}/choirserver | awk '{print substr ($0, 0, 12)}'`
if (( ${BUILD_NUMBER} >= 2 )); then
    # remove old image
    docker rmi ${DOCKER_ID}/choirserver:${BUILD_NUMBER}
fi
# get the newest version of the containerized web application and run it
docker pull ${DOCKER_ID}/choirserver:${BUILD_NUMBER}
docker run -d -ti -p 8080:8080 ${DOCKER_ID}/choirserver:${BUILD_NUMBER}

We are Done!

That is it! After creating and running the above four build jobs you should have the web application up and running on your remote machine. Try to point your browser to http://:8080/choir-frontend/ChoirManager and you should be served the following webpage.

CI Setup

Improvements to the Example CI Chain

The idea with the latter two would be to allow for a completely automatic and reproducible CI chain setup.