AB
The third part of our end-to-end CI/CD series, focusing on implementing a Java application pipeline with Azure DevOps for enhanced DevOps capabilities.
In this project, we will create an end-to-end Continuous Integration and Continuous Deployment (CI/CD) pipeline for a Java application using Azure DevOps. This is the third part of our series, and we’ll leverage Azure DevOps’ powerful CI/CD capabilities to build, test, and deploy our Spring Boot application.
The sample code for this project is available at: Spring PetClinic GitHub Repository.
Before starting this project, make sure you have the following prerequisites:
Install Java on the EC2 instance:
sudo apt install openjdk-17-jdk -y
Install Maven on the EC2 instance:
sudo apt install maven -y
SonarQube connection in Azure DevOps:
SSH connection in Azure DevOps:
Install this after installing Java:
Download the Tomcat9 package:
sudo apt install tomcat9 -y
Start the Tomcat service:
sudo systemctl start tomcat9
Enable the Tomcat service:
sudo systemctl enable tomcat9
Check the status of the Tomcat service:
sudo systemctl status tomcat9
In the agent of Azure DevOps, install Docker using these commands:
sudo apt-get update
sudo apt-get install docker.io -y
sudo systemctl start docker
sudo docker run hello-world
sudo systemctl enable docker
docker --version
sudo usermod -a -G docker $(whoami)
newgrp docker
Run this Docker command to set up SonarQube:
docker run -d --name sonarqube -p 9000:9000 sonarqube:lts-community
Access SonarQube by navigating to http://<server-ip>:9000
in your browser.
The default username and password are both: admin
To create a token for connecting Azure DevOps with SonarQube:
wget https://vstsagentpackage.azureedge.net/agent/2.204.2/vsts-agent-linux-x64-2.204.2.tar.gz
tar xzvf vsts-agent-linux-x64-2.204.2.tar.gz
./config.sh
./run.sh
Note: In the agent, add User defined capabilities. Give it a name (e.g., Maven) and value as true.
First task is to compile the code. Change the command to “compile” in classic editor.
For this, first install the SonarQube scanner extension:
Add the following tasks:
Prepare Analysis Configuration:
sonar.java.binaries=.
Run Code Analysis
Publish Quality Gate Result
First, install the OWASP dependency check extension:
Add an OWASP Dependency Check task:
--disableYarnAudit
Use the SSH task:
scp ./target/my-app-1.0-SNAPSHOT.jar ubuntu@<ec2-ip-address>:/var/lib/tomcat9/webapps
Here’s what the complete pipeline would look like in YAML format:
trigger:
- main
pool:
name: "your-agent-pool"
variables:
buildConfiguration: "Release"
projectName: "petclinic"
steps:
- task: Maven@3
displayName: "Compile Java Code"
inputs:
mavenPomFile: "pom.xml"
goals: "compile"
- task: SonarQubePrepare@4
displayName: "Prepare SonarQube Analysis"
inputs:
SonarQube: "SonarQube Connection"
scannerMode: "Other"
extraProperties: |
sonar.projectKey=$(projectName)
sonar.projectName=$(projectName)
sonar.projectVersion=1.0
sonar.sources=src
sonar.java.binaries=.
- task: SonarQubeAnalyze@4
displayName: "Run SonarQube Analysis"
- task: SonarQubePublish@4
displayName: "Publish SonarQube Results"
inputs:
pollingTimeoutSec: "300"
- task: dependency-check-build-task@5
displayName: "Run OWASP Dependency Check"
inputs:
projectName: "$(projectName)"
scanPath: "./"
format: "XML"
additionalArguments: "--disableYarnAudit"
- task: Maven@3
displayName: "Build Java Artifact"
inputs:
mavenPomFile: "pom.xml"
goals: "clean install"
- task: CopyFilesOverSSH@0
displayName: "Deploy to Tomcat"
inputs:
sshEndpoint: "EC2 SSH Connection"
sourceFolder: "target"
contents: "*.jar"
targetFolder: "/var/lib/tomcat9/webapps"
The pipeline is triggered whenever a change is pushed to the main branch. This ensures continuous integration.
We use our custom agent pool with the agent we set up earlier. This agent runs on our EC2 instance.
We define variables to be used across tasks:
buildConfiguration
: Determines the build configuration (Release)projectName
: Name of our project, used in SonarQube and OWASP tasksAs an alternative to deploying to Tomcat directly, you can modify the pipeline to build and deploy using Docker:
- task: Docker@2
displayName: "Build Docker Image"
inputs:
command: build
Dockerfile: "**/Dockerfile"
repository: "$(dockerRegistryServiceConnection)/$(projectName)"
tags: |
$(Build.BuildId)
latest
- task: Docker@2
displayName: "Push Docker Image"
inputs:
command: push
containerRegistry: "$(dockerRegistryServiceConnection)"
repository: "$(projectName)"
tags: |
$(Build.BuildId)
latest
- task: SSH@0
displayName: "Deploy Docker Container"
inputs:
sshEndpoint: "EC2 SSH Connection"
runOptions: "inline"
inline: |
docker pull $(dockerRegistryServiceConnection)/$(projectName):latest
docker stop $(projectName) || true
docker rm $(projectName) || true
docker run -d -p 8080:8080 --name $(projectName) $(dockerRegistryServiceConnection)/$(projectName):latest
This extended pipeline would:
Azure DevOps provides several ways to monitor your pipeline:
For better control over deployments, you can set up approval gates:
Then modify your pipeline to use this environment:
- deployment: Deploy
displayName: "Deploy to Production"
environment: "Production"
strategy:
runOnce:
deploy:
steps:
- task: CopyFilesOverSSH@0
inputs:
sshEndpoint: "EC2 SSH Connection"
sourceFolder: "target"
contents: "*.jar"
targetFolder: "/var/lib/tomcat9/webapps"
In this tutorial, we’ve set up a complete CI/CD pipeline for a Java application using Azure DevOps. We’ve configured:
Azure DevOps provides a robust platform for implementing DevOps practices, with features like:
In the next part of this series, we’ll explore enhancing our CI/CD pipeline with webhooks and additional security features.