An ARM Build Farm with Jenkins
There was a time when setting up a Continuous Integration server took a lot of work. I personally have spent several days wrestling with getting CruiseControl installed, configured, and working just right. Today it is much more straightforward and, for the most part, a simple
apt-get install jenkins is all it takes to get a functional Jenkins CI server up and running.
In this tutorial we’re going to look at using Jenkins to set up a build farm of ARM systems. My personal interest in doing so is to support the Swift on ARM group of folks working to get Swift 3.0 support for ARMv7 devices such as the Raspberry Pi 2, BeagleBone Black, etc. While support for cross-compiling Swift is maturing there is still a desire to natively compile on an ARM system.
The Jenkins Build Server
To make things simple we’re going to focus on installing Jenkins on an Ubuntu 14.04 server. Using the instructions on the Jenkins Wiki for an Ubuntu 14.04 system:
wget -q -O - https://jenkins-ci.org/debian/jenkins-ci.org.key | sudo apt-key add -
sudo sh -c 'echo deb http://pkg.jenkins-ci.org/debian binary/ > /etc/apt/sources.list.d/jenkins.list'
sudo apt-get update
sudo apt-get install jenkins
The Jenkins daemon will start automatically upon installation. Once it does, open a browser and go to http://YOUR_HOST:8080/, where YOUR_HOST is the server you just installed Jenkins on.
The initial password can be found on the Jenkins server with
sudo cat /var/lib/jenkins/secrets/initialAdminPassword, and will be something like “54d5f68be4554b5c8316689728721b37”. Paste it in and click Continue.
You’ll be prompted to choose whether or not to install suggested plugins or specifically select plugins. Go with Install suggested plugins for now. The next screen will be all of the plugins being loaded in. Once complete you’ll move on to creating the first admin user:
Once entering the admin user information, you’ll see Jenkins is ready!
Click that Start using Jenkins button and (wait for it), start using Jenkins.
We’re interested in using Jenkins to perform native builds on ARM systems. To that end we’ll add slave nodes to our build server.
First, let’s make sure we have an ARM system to compile on. I’ve become a big fan of using Scaleway for spinning up ARM systems. If you have your own ARM device such as BeagleBoard or Raspberry Pi, those will work as well, just be aware that compiling on single-core ARM devices with limited RAM can be, well, painful. With Scaleway you can spin up quad-core ARM systems with 2G of RAM for about $3.50 a month.
I’m going to assume you have an ARM system (either from Scaleway or a physical device that your Jenkins host can communicate with). Let’s look at what it takes to get things configured. There’s a bit of a dance to do to ensure that the master and slave can communicate with each other. In short we need to:
- Install Java on the ARM system so the slave agent can run
- Create a build user on the ARM system
- Create a public/private key pair on the build server
- Add the build server’s public key to the ARM build user’s
1. Install Java and create a build user
Your ARM device is going to need Java to run the Jenkins slave agent, so on the ARM system run
sudo apt-get install openjdk-7-jre-headless.
Let’s also set up the build user while we’re here with
sudo adduser build. Follow the prompts to create the user appropriately.
2. Create a public/private key pair on the build server
On the build server,
su to the
jenkins user and
cd to its home directory. Run
ssh-keygen to create the key pair.
3. Add the public key to the
Now, add the
~/.ssh/id_rsa.pub contents of the Jenkins server
jenkins user to the
/home/build/.ssh/authorized_keys file of the ARM system build user.
Adding the Build Slave Node
Now, let’s add the ARM device as a build slave. Under Manage Jenkins go to the Manage Nodes link.
Click on New Node and we’ll give it a name like
arm1. Select the Permanent Agent radio button and then click OK.
You’ll move on to a screen that requires some more information (typical!) We’re interested in:
- Remote root directory
- Launch method
For Remote root directory we’ll use the home directory of our new build user, so
/home/build. For the label, type in
arm. It will become apparent as to why in a bit.
For Launch method choose Launch slave agents on Unix machines via SSH. In the host field enter your ARM system’s hostname or IP address that is accessible from your Jenkins build server.
Now, click on the Add button (the one with the key) to go to the Add Credentials configuration page. For the Kind choose SSH Username with private key. For the Username we’ll use
build. For the Private Key select From the Jenkins master ~/.ssh/. Recall earlier when we had you create a public/private key pair for the
jenkins user on the master? This is why. When trying to contact the slave agent the Jenkins system will load the private key from the
~/.ssh/ directory of the
jenkins user. The slave agent will be ready with the public key in the
Click Add to add the credentials, and then Save to save the new node configuration.
You should be taken back to the Node Management page where you’ll see your new node (probably in an offline state):
You can either click Refresh status or go to the node’s logs by clicking on the nodename
arm1 and then selecting Log. If everything was configured properly you’ll see:
[04/27/16 17:01:42] [SSH] Starting slave process: cd "/home/build" && java -jar slave.jar <===[JENKINS REMOTING CAPACITY]===>channel started Slave.jar version: 2.56 This is a Unix agent Evacuated stdout Agent successfully connected and online
Now let’s create a new job and use our ARM agent to build opencv.
We know we’re going to be using
git to check out the OpenCV source tree from Github, and there are a number of other dependencies required for building, so we need to install all of them on our ARM system:
sudo apt-get install build-essential cmake git libgtk2.0-dev pkg-config libavcodec-dev libavformat-dev libswscale-dev python-dev python-numpy libtbb2 libtbb-dev libjpeg-dev libpng-dev libtiff-dev libjasper-dev libdc1394-22-dev
OpenCV is traditionally built with the following commands:
mkdir release && cd release
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ..
These will be our basic build steps, with the exception we’ll use
make -j4 to take advantage of our 4 core ARM build agent.
Creating the Build Project
To create a new build project, click on Create New Item in the main Jenkins menu, and choose Freestyle project. We’ll name our project OpenCV 3.0. Click OK at the bottom of the options.
You’ll be on the General tab to get started. For now we’re interested in the general project options, Source Code Management section, and Build section.
Since we want to build the OpenCV project on the ARM system we are going to restrict the project to only build on those nodes matching our arm label.
For OpenCV 3.0 we’ll choose Git as the SCM type, and then enter the URL to the OpenCV source,
https://github.com/itseez/opencv/. Jenkins allows you to specify the branch to build on, and we’ll leave the default
For the actual build we will enter a single Execute shell build step. It should be noted that this is an example only. There are a number of ways to configure and script out Jenkins project; each project will have different build steps to fit the needs of the underlying task at hand.
Save your project, and then click Build Now! In the Build History pane you’ll see a flashing blue dot (hopefully). Click on the dot to go to the build page where you can select to look at the console output of the build. If everything was set up correctly you should now see OpenCV building away on your ARM build agent!
In our example OpenCV took 42 minutes to build on a quad-core ARM system from Scaleway. Not bad.
This was clearly not a comprehensive Jenkins tutorial. Folks familiar with the craft know that setting up and configuring continuous integration servers and build projects to produce traceable artificacts is a disclipline unto itself. However, it should be clear that creating a compile farm consisting of slave build agents does not have to be complicated.