back to all blogsSee all blog posts

Continuous integration and delivery (CI/CD) of cloud-native Java applications using Jenkins

image of author
Shamjith Antholi on May 27, 2022
Post available in languages:

Continuous integration and continuous deployment (CI/CD) is a process in which developers frequently merge code changes into a source code repository, which then triggers code builds, runs security scans, runs tests, and then deploys the application. Automation of this process, through adopting a DevOps culture, tools, and processes, enables organizations to release on a more frequent basis without compromising on quality. Various tools are available to automate the CI/CD process that can be readily integrated into a DevOps deployment pipeline. In this blog post, we’ll discuss how you can use Jenkins to automate the CI/CD process for building, testing, and deploying your cloud-native Java applications that run on Open Liberty. As the CI/CD process is triggered by source changes in Git, this is also known as the GitOps approach to DevOps.

Jenkins is a popular open source automation server that has hundreds of plug-ins to help integrate it into your existing DevOps pipeline. The following diagram shows a simple architecture for a Jenkins deployment pipeline that builds and deploys a Jakarta EE and MicroProfile application in a single Kubernetes environment. When a code change is made in the Git repository, GitHub will trigger Jenkins to build the code, runs JUnit tests and then SonarQube static code analysis scans against the built code. Jenkins then generates a Docker container image of the application with Open Liberty as its runtime, saves the image to a Docker container repository, and then scans the image with Trivy. Jenkins then triggers a deployment by using CLI commands and deploys the Docker container image to Kubernetes.

Liberty DevOps architecture diagram

In this blog post, I will assume that you have a basic understanding of Git, Docker, and Kubernetes. I will also assume that the Jakarta EE and MicroProfile application code is stored in GitHub. I will use Docker Hub to store the Docker container images and I will deploy the containerized application to IBM Cloud Kubernetes Service.

Installing and configuring Jenkins to set up CI/CD of a cloud-native Java application

Install Jenkins with the following plugins on the base image of your Jenkins container image, or on the Jenkins controller (Jenkins host):

  • Maven, to build java code

  • Pipeline, for creating Jenkins pipeline jobs

  • Multibranch Scan Webhook Trigger, to create Jenkins pipeline type jobs which pull all branches of code from Git to Jenkins

  • Docker or equivalent like Podman, to build and push container images

  • Kubernetes, to use the Kubernetes template

Jenkins builds your Java application code running on Liberty using Jenkins pipeline scripts. The script can run directly on your Jenkins host (also known as the Jenkins controller). If the application requires a lot of memory, you may need to use a Jenkins agent as the job execution environment. For more information about about setting up Jenkins agents, see Using Jenkins agents.

Writing Jenkins pipeline scripts

It is a good practice to adhere to the concept of Infrastructure as Code (IaC) when creating DevOps pipelines. Creating Jenkins jobs with pipeline scripts is a good example of IaC.

You can write Jenkins pipeline code in one of the following ways:

  • As pipeline code written directly in the Jenkins UI and stored on the Jenkins controller (host). This is a useful way to get started. You will need to take a backup of the Jenkins instance in this case to save the pipeline code because the code is stored as part of the Jenkins instance.

Pipeline code directly on Jenkins
  • As plain text in a Jenkinsfile (a plain text file) in Git and mapping it to Jenkins. This is better for ensuring that your configuration is always under version control. If you have specific build and deployment configurations for separate environments, such as dev, staging, and production, you can create a separate Jenkinsfile for each environment and store it in that environment-specific Git repository branches. You can use either the "Pipeline" or "Multibranch pipeline" type of job in this case.

Pipeline code stored in a Jenkinsfile on Git

Building the cloud-native Java application with Open Liberty on Jenkins

The following sample pipeline code builds your Java application code, packages it into a Docker container image, and pushes the container image to a remote container image repository, such as Docker Hub or an equivalent within your enterprise:

 pipeline {
     agent any
      stages {
       stage('Build') {
                    steps {
              checkout([$class: 'GitSCM', branches: [[name: '*/main']], extensions: [], userRemoteConfigs: [[credentialsId: ‘<git token>, url: 'https://github.com/liberty/app.git']]])
                    sh '''
                         mvn -U package
                         docker login <remote-docker-image-repository-url> -u "${USERNAME}" -p “${PASSWORD}”
                         docker build -t liberty-$<code identifier>:$<docker image version> .
                         #eg: docker build -t liberty-app:v1.0 .
                         docker tag liberty-$<code identifier>:$<docker image version> <remote-docker-image-repository-url>/<docker-repo-name>/liberty-$<code identifier>:$<docker image version>
                         #eg: docker tag liberty-app:v1.0 docker.io/someid/liberty-app:v1.0
                         docker push <remote-docker-image-repository-url>/<docker-repo-name>/liberty-$<code identifier>:$<docker image version>
                         #eg: docker push docker.io/someid/liberty-app:v1.0
                       '''
                  }
               }
            }
          }

Where:

  • git-token is the personal access token you have generated from your Github account.

  • remote-docker-image-repository-url is the location of the Docker image repository.

  • username is your user name for the Docker image repository.

  • password is your password for the Docker image repository.

  • docker-image-version is the version number of your Docker image, a unique identifier.

Build your container image using a Dockerfile or a Containerfile generated by the Liberty Starter or by following the pattern described in Open Liberty Images. The Containerize guide is a helpful resource that goes into more details on how to create a container image for applications running on Liberty.

For static code analysis, you can use SonarQube Community edition. The Jenkins client setup details for SonarQube are given at SonarScanner for Jenkins. The following sample Maven command packages the code with Maven and runs the SonarQube scan:

mvn package verify sonar:sonar -Dsonar.projectKey=sampleapp -Dsonar.host.url=http://localhost:9000 -Dsonar.login=<generated-login-key>

For scanning the container image (for security purpose), you can use Trivy. This scan provides the vulnerability details of open source JAR files that are used to resolve the dependencies while building the application. The following Docker command runs Trivy against your container image:

docker run aquasec/trivy image docker.io/<docker-repo>/liberty-app:v1.0

Where:

  • docker-repo is the name of the Docker container repository that contains your image

Deploying cloud-native Java applications with Open Liberty to Kubernetes with Jenkins

For simplicity, I will use the command line (CLI) code in a Jenkins pipeline job to deploy a Jakarta EE and MicroProfile application with Open Liberty into Kubernetes. You can also use other tools like Helm, Travis CI, and CircleCI.

In your pipeline code, add these CLI commands in a new stage. The following sample pipeline code connects to IBM Cloud from the CLI and then connects to the Kubernetes cluster running inside that, then it runs all the Kubernetes deployment-related configurations.

ibmcloud login --apikey $IBM_CLOUD_API_KEY -g $IBM_CLOUD_RSGRP
ibmcloud ks cluster config --cluster $CLUSTER-ID
kubectl config current-context
kubectl create -f deploy/deployment.yaml #( simple k8s deployment command )
kubectl create -f deploy/service.yaml #( simple k8s service creation command )
kubectl create -f deploy/route.yaml #( simple k8s route creation command )

Make sure that your Kubernetes configuration files are stored in the same Git repository as your Jenkinsfile in a sub-directory called deploy. Also ensure that the Docker image name in the Kubernetes deployment configuration file is updated according to the container image name/tag in the Dockerfile (manually, or programmatically if it needs to change at run time):

Image reference in deployment yaml

When Jenkins has checked out the Java application code for the code build, all the Kubernetes configuration files are also downloaded to the Jenkins workspace so that Jenkins can run the IBM Cloud and Kubernetes commands to connect to the Kubernetes cluster and deploy the application.

See the Kubernetes documentation for other commands.

QA testing cloud-native Java applications with Jenkins

Apart from running JUnit test cases along with the code build phase, Jenkins can trigger functional and integration QA test cases automatically after deploying the cloud-native Java application.

Configure the test cases in the Jenkins job and test it manually. Create a remote job identifier authentication token in the "Trigger builds remotely" section under "Build Triggers". Trigger this test case from the Docker "entrypoint" file by using a remote rest API call that uses this authentication token as the identifier.

For example, run the following command in a terminal:

curl -I -u <auth-token> https://<jenkins-host>/job/<job-name>/build?token=<remote-job-identifier-authentication-token>

You can generate an authentication token (auth-token) with Postman using the Jenkins login credentials.

Kubernetes monitoring tools

You can use the following Kubernetes commands to check the application or cluster logs and the memory and CPU usage:

kubectl logs ..
cat /sys/fs/cgroup/cpu/cpuacct.usage (after connecting to k8s pod)
cat /sys/fs/cgroup/memory/memory.usage_in_bytes (after connecting to k8s pod)

You can integrate different applications with Kubernetes to persist logs and usage statistics, such as Prometheus and Grafana.

Liberty makes it easy to collect and visualize system and application metrics for observability by using Prometheus and Grafana. You can find guidance and more details in the resources listed here.

Conclusion

You can configure your DevOps pipeline in many ways. This blog post is a quick introduction to how you can use Jenkins to set up a simple CI/CD pipeline to build and deploy your cloud-native Java applications on Liberty.