How to build projects in Jenkins when multiple environments are needed

How to build projects in Jenkins when multiple environments are needed

Habr has many articles about Jenkins, but few describe examples of working with Jenkins and Docker agents. All popular project build tools like Drone.io, Bitbucket Pipeline, GitLab, GitHub actions and others can build everything in containers. But what about Jenkins?

Today, there is a solution to this problem: Jenkins 2 works wonderfully with Docker agents. In this article, I want to share my experience and show you how you can do this yourself.

Why did I tackle this problem?

Since we at Citronium use many different technologies, our build machine needs to hold various versions of Node.JS, Gradle, Ruby, JDK, and others. But version conflicts are often unavoidable. Yes, you would be right to say that there are various version managers like nvm, rvm, but it’s not all that smooth with them, and these solutions have their issues:

  • a large volume of runtimes that developers forget to clean;
  • there are conflicts between different versions of the same runtimes;
  • each developer needs a different set of components.

There are other problems as well, but let me tell you about the solution instead.

Jenkins in Docker

Since Docker is now well entrenched in development, almost everything can be launched using Docker. My solution is that Jenkins runs in Docker and can launch other Docker containers. This question was raised back in 2013 in the article “Docker can now run within Docker«.

In short, it is necessary to install Docker itself in the working container and mount the file. /var/run/docker.sock.

Here’s an example Dockerfile that was created for Jenkins.

FROM jenkins/jenkins:lts

USER root

RUN apt-get update && 

apt-get -y install apt-transport-https 
     ca-certificates 
     curl 
     gnupg2 
     git 
     software-properties-common && 
curl -fsSL https://download.docker.com/linux/$(. /etc/os-release; echo "$ID")/gpg > /tmp/dkey; apt-key add /tmp/dkey && 
add-apt-repository 
   "deb [arch=amd64] https://download.docker.com/linux/$(. /etc/os-release; echo "$ID") 
   $(lsb_release -cs) 
   stable" && 
apt-get update && 
apt-get -y install docker-ce && 
usermod -aG docker jenkins

RUN curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose 

RUN apt-get clean autoclean && apt-get autoremove —yes && rm -rf /var/lib/{apt,dpkg,cache,log}/

USER jenkins

This way, we got a Docker container that can execute Docker commands on the host machine.

Build Configuration

Not long ago, Jenkins gained the ability to describe its rules using Pipeline syntax, which allows for fairly simple changes to the build script and storing it in a repository.

So let's place a special Dockerfile directly in the repository, which will contain all the necessary libraries for building. This way, the developer can prepare a reproducible environment and won't need to ask OPS to install a specific version of Node.JS on the host.

FROM node:12.10.0-alpine

RUN npm install yarn -g

Such a build image is suitable for most Node.JS applications. But what if you need an image for a JVM project with an integrated Sonar scanner? You are free to choose the necessary components for the build.

FROM adoptopenjdk/openjdk12:latest

RUN apt update 
    && apt install -y 
        bash unzip wget

RUN mkdir -p /usr/local/sonarscanner 
    && cd /usr/local/sonarscanner 
    && wget https://binaries.sonarsource.com/Distribution/sonar-scanner-cli/sonar-scanner-cli-3.3.0.1492-linux.zip 
    && unzip sonar-scanner-cli-3.3.0.1492-linux.zip 
    && mv sonar-scanner-3.3.0.1492-linux/* ./ 
    && rm sonar-scanner-cli-3.3.0.1492-linux.zip 
    && rm -rf sonar-scanner-3.3.0.1492-linux 
    && ln -s /usr/local/sonarscanner/bin/sonar-scanner /usr/local/bin/sonar-scanner

ENV PATH $PATH:/usr/local/sonarscanner/bin/
ENV SONAR_RUNNER_HOME /usr/local/sonarscanner/bin/

We have described the build environment, but what does Jenkins have to do with this? Jenkins agents can work with such Docker images and perform builds inside.

stage("Build project") {
    agent {
        docker {
            image "project-build:${DOCKER_IMAGE_BRANCH}"
            args "-v ${PWD}:/usr/src/app -w /usr/src/app"
            reuseNode true
            label "build-image"
        }
    }
    steps {
        sh "yarn"
        sh "yarn build"
    }
}

Directive agent uses the property docker, where you can specify:

  • the name of the build container according to your naming policy;
  • the arguments necessary to run the build container, where in our case we mount the current directory as a directory inside the container.

And in the build steps, we specify which commands to execute inside the build Docker agent. It can be anything; this way, I also deploy applications using Ansible.

Below, I want to show a general Jenkinsfile that can build a simple Node.JS application.

def DOCKER_IMAGE_BRANCH = ""
def GIT_COMMIT_HASH = ""

pipeline { 
    options {
        buildDiscarder(
            logRotator(
                artifactDaysToKeepStr: "",
                artifactNumToKeepStr: "",
                daysToKeepStr: "",
                numToKeepStr: "10"
            )
        )
        disableConcurrentBuilds()
    }

    agent any

    stages {

        stage("Prepare build image") {
            steps {
                sh "docker build -f Dockerfile.build . -t project-build:${DOCKER_IMAGE_BRANCH}"
            }
        }

        stage("Build project") {
            agent {
                docker {
                    image "project-build:${DOCKER_IMAGE_BRANCH}"
                    args "-v ${PWD}:\/usr\/src\/app -w \/usr\/src\/app"
                    reuseNode true
                    label "build-image"
                }
            }
            steps {
                sh "yarn"
                sh "yarn build"
            }
        }

    post {
        always {
            step([$class: "WsCleanup"])
            cleanWs()
        }
    }

}

What was the result?

Thanks to this approach, we solved the following issues:

  • the build environment configuration time is reduced to 10-15 minutes per project;
  • a fully reproducible application build environment, as it can be built on a local computer;
  • no issues with conflicts of different versions of build tools;
  • always a clean workspace that does not get cluttered.

The solution itself is simple and obvious and allows us to gain numerous advantages. Yes, the entry threshold has slightly increased compared to simple build commands, but now there is a guarantee that it will always build, and the developer can choose everything they need for their build process.

You can also use the image I built Jenkins + Docker. All the source codes are open and available at rmuhamedgaliev/jenkins_docker.

During the writing of the article, there was a discussion about using agents on remote servers to avoid loading the master node using the plugin docker-plugin. But I will talk about this in the future.

Source: habr.com

Buy reliable website hosting with DDoS protection, VPS VDS servers 🔥 Buy reliable website hosting with DDoS protection, VPS VDS servers | ProHoster