Dockerised Jenkins for mobile testing

Dockerised Jenkins for mobile testing

Step-by-step guide on how to set up a Dockerised Jenkins Master-Slave-node architecture for mobile testing.
Trung Le
Trung Le
March 12, 2018
iOS Android Mobile

Throughout this post, I will be assuming that you’re already familiar with both Jenkins and Docker. If you are not, I encourage you to check out their respective websites and documentations.

Like many developers, our team at Nimble has been struggling with setting up an efficient workflow for mobile applications testing. Because testing is so deeply rooted in our culture, we persisted and figured out a better way.

Prior to using Jenkins, we used well-known Continuous Integration/ Continuous Delivery (CI/CD) providers such as Travis CI, Bitrise, Circle CI, etc. Unfortunately, using emulators on these platforms always remained a struggle. The main bottleneck is that the standard container instances specs are usually too limited. For instance, none of the aforementioned providers offers x86 emulator to enable hardware acceleration.

To leverage real hardware acceleration, we needed to host our own CI server. We chose Jenkins as the foundational piece of software for our automation workflow.

Stylish BlueOcean work pipeline 💯Stylish BlueOcean work pipeline 💯

Jenkins is arguably the most well-known and used tool for running a CI/CD server. And with the recently-introduced BlueOcean plugin, pipelining the workload is easier than ever and Jenkins has a UI on par — or even better — than the service providers.

With little efforts, we managed to set up our own workflow and everything worked as expected.

Still, we were still very hesitant about hosting our own CI server. Setup time, maintenance overhead, and the difficulty to replicate the setup for new projects were pretty big concerns for us.

And then we thought of Docker 💡

Jenkins on Docker

Docker is a lightweight, open-source platform that helps build, ship, and run distributed applications on different platforms. While we use it extensively for web application development, we did not include it just yet in our toolbox for mobile development.

The very same things that make Docker shines for web development — its flexibility, ease of use, and mobility (moving containers through various environments/platforms) — could benefit to mobile application development.

The Fun Part

In this walkthrough, we’re going to deploy Jenkins with these architectural components:

  • Jenkins Master server with data: Java process, plugins, job definitions, artifact storage, etc.

  • Build Slave agents: machines that use the JNLP (Java Launch Network Protocol) to connect to the Jenkins master server.

Jenkins master / slave (agent) setup

Let’s get started 💻

Step 1: Install Docker

Simply follow the instruction in the Docker official site to get it installed (for this post, we are using the build for MacOS).

Upon successful installation, you should be able to see this tiny icon in the system menu tray:

Docker status on the menu bar

To check if the setup is finished, open the Terminal and type:

$ docker ps

This command will print out all running containers; no container should be listed at the moment. By default, this command only shows the running containers; to see all available containers, use it with the flag --all or -a (in its short version).

Step 2: Pull the Jenkins image into Docker

Now we need to start creating our Jenkins-based Docker container, right?

Luckily for us, the Cloudbees foundation provides a Docker-based image of Jenkins on the Docker hub. Even sweeter, they also provide a separate image with the BlueOcean plugin. Things just got easier to start now!

So let’s pull the Jenkins image with BlueOcean installed to a docker container. Name it as you want. I’ll call it jenkins-master .

First, pull the image:

$ docker pull jenkinsci/blueocean

Then, create a container based on that image. We will keep the default settings such as the port 8080. If you want to use a different port, simply modify the port flag value -p 8085:8080.

$ docker run -p 8080:8080 --name=jenkins-master -i jenkinsci/blueocean

Or with the customized port:

$ docker run -p 8085:8080 --name=jenkins-master -i jenkinsci/blueocean

Then to check if our container is online:

$ docker ps

The above command should print out a list of the running containers. Your container should now be listed.

Listing Docker containers in the command line

Give it a few seconds to boot up, then access your server via localhost:8080. You should now see that our Jenkins server is ready!

Jenkins settung up admin password on the first run

Before being able to access the Jenkins dashboard, we need to retrieve the administrator initial password. The easiest is to check the console log from Docker as the password has been printed out:

Jenkins output of the initial password in the command line

Grab the password and login.

Jenkins customization screen after installation

Don’t grab your required plugins here just yet, trust me, move forward and you will find out why 🤓

Step 3: Persist the data

The setup of Jenkins is pretty straightforward as we can even pick all the plugins without any manual work involved. But when the server is reset, or simply when the Docker container is itself reset, all the configuration is entirely wiped out forcing you to re-do everything again 😱

Don’t believe it? Try to stop and start the container:

$ docker stop jenkins-master
$ docker start jenkins-master

This happens because we have not configured the data volume that will be used for persisting data yet. The data corresponds to everything that will be generated throughout the running process, including credentials, plugins data, repository configuration etc..

To persist data, there are several alternatives. For the purpose of this post, I chose to store the volume on the hosting machine’s file system. So it will be easy to inspect, manipulate the data as things go on. To achieve this, we first we need to remove the old container (because you cannot re-create the same container names jenkins-master):

$ docker rm jenkins-master

Then re-create a new container again. But this time, we will configure where the data will be mounted using the following flags -d -v $path/to/data (more details about the flags can be found here 🤓)

$ docker run -d -v my_workspace/jenkins-data:/var/jenkins_home -p 8080:8080 --name=jenkins-master -i jenkinsci/blueocean

To test if this worked properly — i.e. the data is persisted after restarting the Docker container — simply stop and start the docker container again. You should not be asked to type in the initial password again this time 🤑

Note that the data which is required by each initialized Jenkins server — now stored in the jenkins-data sub-directory — can easily be re-used by a new Jenkins instance and also backed-up!

Step 4: Initialize slaves — the essential elements

In our setup, the slaves, which are connected via the JNLP (Java Launch Network Protocol), will do the heavy lifting 🐌💪 .

Their responsibilities will be:

  1. Building the mobile app package (in both iOS and Android).

  2. Executing the unit tests and UI Testing on emulators.

  3. Deploying the applications for distributions on platforms like Fabric for beta testing and the Play/App stores for product-ready releases.

  4. Reporting the results back to the Jenkins Master server.

Setting up the slave is not hard. But the setup instructions required for mobile application testing will be discussed in a future post. For now, there is still some configuration work required on the Master server.

By default, the connections to the Slaves nodes— via the JNLP — use the port 50000 . But in order for this to work, we need to configure the Docker container to have this port open.

First, remove the old docker container:

$ docker rm jenkins-master

Then, create a new one with the desired extra port:

$ docker run -d -v my_workspace/jenkins-data:/var/jenkins_home -p 8080:8080 -p 50000:50000 --name=jenkins-master -i jenkinsci/blueocean

or with a customized port 49000 :

$ docker run -d -v my_workspace/jenkins-data:/var/jenkins_home -p 8085:8080 -p 49000:49000 --name=jenkins-master -i jenkinsci/blueocean

Unlike the steps to customize the HTTP port forwarding, the Jenkins server is already configured with an environment variable(JENKINS_SLAVE_AGENT_PORT) to define the port for JNLP connections (by default, 50000 ). In order to change this setting to the customized port that we want( 49000 ), we can just pass the value using flag --env or -e (short version).

So our setup now looks like this (don’t forget to remove the old container before re-creating this new container):

$ docker run -d -v my_workspace/jenkins-data:/var/jenkins_home -p 8085:8080 -p 49000:49000 --name=jenkins-master --env JENKINS_SLAVE_AGENT_PORT=49000 -i jenkinsci/blueocean

Note that it’s also possible change this settings via the Jenkins Dashboard: https://your-jenkins-url:port/configureSecurity -> Agents -> Change the default fixed port

Setting up the port for Jenkins Agents

Now you should have master-slave connections established successfully. If you have some prior experience with using a master-slave node architecture with Jenkins, go ahead and start creating as many slaves as you want. if you’re not familiar with this setup, keep on reading and I’ll show you da way in the last part of the post 😆

Step 5: So are we done?

Almost there for the master-slave node setup, but you might have wondered:

So do we still need to start the docker container up by ourselves every time the host machine get restarted?

With this current setup, the answer is YES. But this can easily be resolved as Docker provides us with a simple way to configure this container to be automatically started when the Docker application starts.

Just add the flags --restart or -r flag with your desired option (check the documentation here). As I want the Docker container to always restart, I use: --restart always .

So the complete set up for our Dockerised Jenkins is:

$ docker run --restart always -d -v my_workspace/jenkins-data:/var/jenkins_home -p 8085:8080 -p 49000:49000 --name=jenkins-master --env JENKINS_SLAVE_AGENT_PORT=49000 -i jenkinsci/blueocean

Try to restart the whole hosting machine, you should be able to access your Jenkins server after the reset with the data persisted!

In the next post, we will dive into the setup of a project with the Pipeline model using the BlueOcean plugins. We will also show you how to delegate the work to the Slaves, therefore achieving the automation for our CI/CD development cycle 🚀

If you like it, gimme some 👏 as encouragements. Feedback and questions are also welcome since it’s been my very first post 🙇

If this is the kind of challenges you wanna tackle, Nimble is hiring awesome web and mobile developers to join our team in Bangkok, Thailand, Ho Chi Minh City, Vietnam, and Da Nang, Vietnam✌️