Building an End-to-End CI/CD Pipeline for a Spring Boot Java To-Do List App Using Jenkins, SonarQube, Docker, Kubernetes, and Argo CD
In this blog, we'll walk through how to set up a simple end-to-end CI/CD pipeline for a Spring Boot Java To-Do List application. This application will be built, tested, containerized, and deployed automatically using a combination of Jenkins, Docker, Kubernetes, and Argo CD. Along the way, we'll learn about each of these tools and how they fit into the CI/CD pipeline.
1. Continuous Integration (CI)
CI focuses on automating the process of integrating code changes into a shared repository. With each code commit, the pipeline automatically performs actions like build, test, and static analysis.
In our case, the CI pipeline is powered by Jenkins, an automation server that helps manage all these tasks. Let's first look at the steps involved in the CI process:
Jenkinsfile (Pipeline Script)
In Jenkins, the Jenkinsfile defines the steps and stages of your CI pipeline. This file is written in Declarative Pipeline syntax, which makes it easy to structure your pipeline into stages such as Build, Test, and Deploy.
Here’s an example of a simple Jenkinsfile:
pipeline {
agent any
environment {
DOCKER_IMAGE = 'your-docker-image'
}
stages {
stage('Build') {
steps {
script {
sh 'mvn clean package'
}
}
}
stage('Unit Test') {
steps {
script {
sh 'mvn test'
}
}
}
stage('Static Code Analysis') {
steps {
script {
sh 'mvn sonar:sonar'
}
}
}
stage('DAST Analysis') {
steps {
script {
sh 'zap-cli quick-scan --url http://your-deployed-app-url'
}
}
}
stage('Build Docker Image') {
steps {
script {
sh 'docker build -t your-image-name .'
}
}
}
stage('Push Docker Image') {
steps {
script {
withCredentials([usernamePassword(credentialsId: 'docker-hub-credentials', usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
sh '''
echo $DOCKER_PASSWORD | docker login -u $DOCKER_USERNAME --password-stdin
docker push your-image-name
'''
}
}
}
}
stage('Deploy to Kubernetes') {
steps {
script {
sh 'kubectl apply -f application.yaml'
sh 'argocd app sync your-app'
}
}
}
}
}
In this pipeline:
Build: We use Maven to compile the code.
Unit Testing: Runs unit tests to ensure the code is working as expected.
Static Code Analysis (SonarQube): Analyzes code quality and generates a report.
Build Docker Image: Once the code passes tests, we package it in a Docker container.
Jenkins Port Change
By default, Jenkins runs on port 8080. However, since our Spring Boot application also runs on port 8080
, we decided to change Jenkins’ port to 8081. This avoids conflicts when both Jenkins and our app are running locally.
You can change the port by running Jenkins on Docker with the following command:
docker run -d -p 8081:8080 jenkins/jenkins:lts
This makes Jenkins available at localhost:8081, while your Spring Boot app inside Kubernetes continues to use port 8080
.
2. Continuous Deployment (CD)
CD automates the deployment of your application. After the CI pipeline successfully builds and tests the application, it moves on to the deployment stage. In this project, we deploy the application on Kubernetes with Argo CD to automate the deployment process.
Kubernetes Deployment
The Deployment object in Kubernetes is used to define how your app should be deployed, including the number of replicas (pods) to run and the container image to use. The Deployment manages the state of the application and ensures that the desired number of replicas are always running, even if a pod crashes.
Here's an example deployment.yaml for our Spring Boot app:
apiVersion: apps/v1
kind: Deployment
metadata:
name: todo-app
spec:
replicas: 3 # Ensures 3 pods of the app are running
selector:
matchLabels:
app: todo-app
template:
metadata:
labels:
app: todo-app
spec:
containers:
- name: todo-app
image: your-docker-image-name # The Docker image we built
ports:
- containerPort: 8080 # Spring Boot listens on port 8080 inside the container
Significance of the Deployment:
Replicas: Specifies how many pods (instances of your application) should be running.
Selector: Ensures that the deployment manages the correct set of pods using labels.
Container Port: Defines the port on which the application inside the container listens, which Kubernetes will use for routing.
Kubernetes Service
The Service is a Kubernetes object that exposes your app to the outside world (or within the cluster) by creating a stable IP address and DNS name for your application.
Here's an example service.yaml:
apiVersion: v1
kind: Service
metadata:
name: todo-app-service
spec:
selector:
app: todo-app
ports:
- protocol: TCP
port: 80 # Exposed port on the service
targetPort: 8080 # The port the container listens to
type: LoadBalancer # Can be ClusterIP or NodePort based on your requirements
Significance of the Service:
Target Port: Forwards traffic from the service to the port on which the application container is listening.
Type: A LoadBalancer is used to expose the service to external traffic, but you could also use ClusterIP for internal communication or NodePort for external access without a LoadBalancer.
Argo CD - Continuous Deployment
Argo CD is a GitOps continuous delivery tool for Kubernetes. It monitors your Git repository and ensures the application in your cluster matches the code in the repository.
Argo CD automatically deploys any changes to your Kubernetes cluster when the Git repository is updated.
It tracks changes to your deployment.yaml, service.yaml, and other Kubernetes resources in Git, and syncs them with the actual running state in your Kubernetes cluster.
3. Code Quality and Security Analysis
In modern CI/CD pipelines, it's essential to ensure that the code is not only functional but also secure and high-quality. This is where SonarQube, SAST, and DAST come into play.
SonarQube - Static Code Analysis
SonarQube is a static code analysis tool that helps detect code quality issues such as bugs, vulnerabilities, and code smells. It provides detailed reports and suggestions on how to improve the code's quality.
Significance of SonarQube: It helps catch problems early in the development cycle and ensures that the code meets industry standards for maintainability, security, and functionality.
Integration in CI: In our Jenkins pipeline, SonarQube is integrated to analyze the code after the build stage. This ensures that any issues with the code are identified before the app is packaged and deployed.
SAST - Static Application Security Testing
SAST tools analyze the source code or binaries of your application for vulnerabilities without running the application. These tools help identify issues like SQL injection, cross-site scripting (XSS), and other vulnerabilities that could be exploited by attackers.
Significance of SAST: Helps ensure that your application is secure by catching vulnerabilities early in the development process.
In Our Pipeline: We used SonarQube to perform basic static analysis. However, for more comprehensive security checks, specialized SAST tools can be added at this stage.
DAST - Dynamic Application Security Testing
DAST involves testing the running application in an environment to find vulnerabilities while the app is in execution. This helps identify security flaws that arise during runtime, such as broken authentication, input validation issues, and exposure of sensitive data.
Significance of DAST: Ensures the application is secure when deployed and running, simulating real-world attacks to identify exploitable weaknesses.
In Our Pipeline: While we didn’t set up a specific DAST tool here, tools like OWASP ZAP can be added as a DAST stage to scan the running application for vulnerabilities.
OWASP ZAP (Zed Attack Proxy) is an open-source security testing tool used for finding vulnerabilities in web applications during runtime, often categorized as Dynamic Application Security Testing (DAST).
Conclusion
By automating the entire process from code commit to deployment, we’ve built a robust CI/CD pipeline that enables continuous integration and deployment for our Spring Boot Java To-Do List app.
CI automates testing, code quality analysis (using SonarQube), and Docker image building with Jenkins.
CD automates deployment to Kubernetes with Argo CD, ensuring that changes are automatically deployed and monitored.
Security is enhanced with SAST (static analysis) and DAST (dynamic analysis), which safeguard the application against vulnerabilities.
This setup allows for fast, reliable, and consistent releases, significantly reducing the time and effort required for manual deployments.
Incorporating tools like SonarQube, Docker, Kubernetes, Argo CD, and DAST/SAST into your DevOps workflow provides a streamlined process for modern application delivery. Let the automation handle the heavy lifting, and focus on developing features and improving your application!
Thankyou