A simple pipeline using Jenkins, JUnit, Sonar and deploying to k8s

So we setup a decent cluster and some usual CI/CD components before. Now lets get a pipeline running.

In a basic scenario, we could do the following;

  • Checkout our code,
  • See if critical components exist
  • Run unit tests,
  • Run through static code analysis
  • Package the app
  • Dockerize,
  • Deploy

From recent stories, we should already have a running Jenkins, with Sonar attached.

To test the pipeline, create any Spring Boot app on github, if you want you can use mine.

Now lets get it connected to github;

First we create 2 credentials in Jenkins, one for the github access, the other for the SSH to master node which we will be needing later.

Next we should create a pipeline project.

Pipeline should be using script from our Jenkinsfile

Thats about it actually. Not lets have a look at the Jenkinsfile and the Dockerfile.

Here we use a jdk11 alpine as a base to our image, get the jar file as a parameter JAR_FILE, add it to our image as app.jar, then run with java -jar when container runs.

FROM adoptopenjdk/openjdk11:alpine
ARG JAR_FILE=target/*.jar
ADD ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

We should also create a place in master for our app yamls. Also, apparently if you run Jenkins in k8s you can’t use docker so we have to do these things outside. I’m bummed about this but hey, we have a cluster and not much space for anything else so we will make due.

The jenkins file is a little more detailed so, lets get to it.

We have 3 variables.

  • image contains our custom harbor tag, our image name with jenkins build no as version tag.
  • file_name is the name of the jar file. No magic here for now.
  • master is the ip of the master node where we will be doing the building.

If you notice the operations are divided to several stages,

  • checkout project stage connects to github and pulls latest code.
  • check env stage looks for mvn and java components and if they exist
  • test stage runs the unit tests
  • package stage builds our code
  • report stage shows the result of our unit tests in Jenkins with the Jenkins JUnit plugin.
  • artifact stage creates and displays app build artifacts
  • sonarqube analysis stage connects with our sonar (the one we setup in jenkins configs) and pushes the analysis to it.
  • quality gate stage waits for the response and quits with error if any problems arise. There is 10 mins of max wait.
  • the next stages make use of master credentials we put in jenkins. For example the modify configuration stage first replaces our spring_test_app k8s yaml image name with the sed command. It then does an ssh to master and kubectl cp’s the file to the master /opt/local-apps folder.
  • docker build & push stage does an ssh to master again but this time copies the jar and dockerfile to /opt/local-apps/docker/ folder. We then docker build and push to our repo.
  • the deploy stage does ssh for the last time and just does an apply using our spring_test_app k8s yaml.
node {
def image = "192.168.56.109:443/local/spring_test_app:v1.${currentBuild.number}"
def file_name="test1-0.0.1-SNAPSHOT.jar"
def master="192.168.56.106"
try{
stage 'checkout project'
checkout scm

stage 'check env'
sh "mvn -v"
sh "java -version"

stage 'test'
sh "mvn test"

stage 'package'
sh "mvn clean package"

stage 'report'
step([$class: 'JUnitResultArchiver', testResults: '**/target/surefire-reports/TEST-*.xml'])

stage 'Artifact'
step([$class: 'ArtifactArchiver', artifacts: '**/target/*.jar', fingerprint: true])

stage("SonarQube analysis") {
withSonarQubeEnv('sonarLocal') {
sh 'mvn clean package sonar:sonar'
}
}

stage("Quality Gate"){
timeout(time: 10, unit: 'MINUTES') {
def qg = waitForQualityGate()
if (qg.status != 'OK') {
error "Pipeline aborted due to quality gate failure: ${qg.status}"
}
}
}
withCredentials([usernamePassword(credentialsId:'master-creds', passwordVariable: 'Password', usernameVariable: 'Username')]) {
stage("Modify Configuration"){
sh " sed -i 's|REPLACE|${image}|g' docker/spring_test_app.yaml"
sh """ssh ${Username}@${master} \
" kubectl cp ${hostname}:'/var/jenkins_home/workspace/spring test1/docker/spring_test_app.yaml' /opt/local-apps/spring_test_app.yaml"
"""
}

stage("Docker Build & Push")
{
sh """ssh ${Username}@${master} \
" kubectl cp ${hostname}:'/var/jenkins_home/workspace/spring test1/target/${file_name}' /opt/local-apps/docker/${file_name} \
&& kubectl cp ${hostname}:'/var/jenkins_home/workspace/spring test1/docker/Dockerfile' /opt/local-apps/docker/Dockerfile \
&& cd /opt/local-apps/docker/ \
&& docker build --build-arg JAR_FILE=${file_name} -t ${image} . \
&& docker push ${image}"
"""
}

stage("Deploy"){
sh """ssh ${Username}@${master} \
" kubectl apply -f /opt/local-apps/spring_test_app.yaml "
"""
}
}

}catch(e){
throw e;
}
}

So when we run this.

tada

So thats about it. Don’t worry about the not ready part. Something basic is wrong with our jar but it’s beyond the scope of this doc as of now.

Just a software everything fighting battles against mostly myself, and gaining small victories lately.