Java Web Application - CI/CD End-to-End DevOps Project

Java Web Application - CI/CD End-to-End DevOps Project

From Code to Kubernetes Deployment

p0

Introduction: In today's software development landscape, Continuous Integration and Continuous Deployment (CI/CD) play a vital role in delivering high-quality applications with speed and efficiency. In this Project, we will explore an end-to-end CI/CD pipeline for a Java web application, starting from code pushed to GitHub to the deployment of the application on Kubernetes using Argo CD. We will cover each step, including code build, code scanning, artifact management, Docker image creation and publishing, and finally, the deployment process.

  1. Code Pushed to GitHub: The journey begins with the developer pushing the Java web application code to a Git repository hosted on GitHub. This step marks the initiation of the CI/CD pipeline.

  2. Jenkins Scripted Pipeline: Build and Test Once the code is pushed to GitHub, Jenkins comes into play. We leverage a Jenkins scripted pipeline to automate the build process. The pipeline triggers code changes and utilizes Maven as the build tool. Jenkins fetches the code from GitHub, compiles it, runs tests, and generates the application artifact.

  3. Code Scan with SonarQube: To ensure code quality, we integrate SonarQube into our CI/CD pipeline. The Jenkins pipeline runs static code analysis using SonarQube, which examines the codebase for potential issues, vulnerabilities, and code smells. It provides valuable feedback to the development team, allowing them to address any identified problems.

  4. Artifact Management with Nexus: Next, we move on to artifact management using Nexus Repository Manager. Jenkins securely transfers the built artifact to the Nexus repository. Nexus serves as a central repository for storing and managing application artifacts, making them readily accessible for subsequent stages of the pipeline.

  5. Docker Image Build using Dockerfile: To containerize our Java web application, we employ Docker. A Dockerfile, residing in the project repository, defines the instructions to build the Docker image. Jenkins pulls the code, builds the Docker image, and tags it appropriately. This step ensures consistency and reproducibility across different environments.

  6. Docker Image Pushed to DockerHub: After the Docker image is built, Jenkins pushes it to a Docker registry, such as DockerHub. The registry acts as a centralized location to store and distribute Docker images. By pushing the image to DockerHub, it becomes easily accessible for subsequent deployment steps.

  7. Kubernetes Deployment Code Pushed to GitHub for Argo CD: In a Kubernetes environment, we leverage Argo CD for continuous deployment. The deployment configuration, defined as YAML files, resides in a GitHub repository. Upon code changes, the updated latest Docker Image Tag deployment configuration is pushed to the GitHub repository.

  8. Argo CD Updates Kubernetes Deployment: Argo CD continuously monitors the GitHub repository for changes. When a change is detected, Argo CD synchronizes the Kubernetes cluster with the desired state described in the deployment configuration. It updates the existing deployment or creates new deployment pods as necessary.

GitHub URL: https://github.com/Sivaprakash-pk/Java-Web-Application---CI-CD-End-to-End-Project.git

Infrastructure Setup & Project steps :

Step 1: Prepare AWS Account

Before we dive into setting up the infrastructure, ensure you have the following prerequisites:

  • An AWS account with appropriate permissions to create EC2 instances and IAM roles.

Step 2: Create EC2 Instances

Jenkins Server:

  • Launch an EC2 instance with the Ubuntu AMI.

  • Install Jenkins on the instance following the commands.

sudo apt update
sudo apt install openjdk-17-jre
java –version

curl -fsSL https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key | sudo tee \
  /usr/share/keyrings/jenkins-keyring.asc > /dev/null
echo deb [signed-by=/usr/share/keyrings/jenkins-keyring.asc] \
  https://pkg.jenkins.io/debian-stable binary/ | sudo tee \
  /etc/apt/sources.list.d/jenkins.list > /dev/null
sudo apt-get update
sudo apt-get install jenkins
  • Open port 8080 in the instance's security group to access the Jenkins web interface.

SonarQube Server:

  • Launch another EC2 instance with the Ubuntu AMI.
  • Install SonarQube on the instance, configuring it to run as a service.
#update packages
sudo apt update
#install java
sudo apt install openjdk-11-jre
java -version    

#install sonarqube
apt install unzip
adduser sonarqube
su sonarqube
cd /home/sonarqube
wget https://binaries.sonarsource.com/Distribution/sonarqube/sonarqube-9.4.0.54424.zip
unzip *
chmod -R 755 /home/sonarqube/sonarqube-9.4.0.54424
chown -R sonarqube:sonarqube /home/sonarqube/sonarqube-9.4.0.54424
cd sonarqube-9.4.0.54424/bin/linux-x86-64/
./sonar.sh start
  • Use the below credentials to login

    username: admin

    password: admin

  • Open ports 9000 (SonarQube web interface) in the security group.

Nexus Server:

  • Create a new EC2 instance with the Ubuntu AMI.

  • Install Nexus Repository Manager.

#update packages
sudo apt-get update
#install openjdk 8
sudo apt install openjdk-8-jdk
#change user to sudo
sudo su
#browse to /opt. where we will install nexus
cd /opt
#get nexus binary
wget https://download.sonatype.com/nexus/3/nexus-3.56.0-01-unix.tar.gz
tar -zxvf nexus-3.56.0-01-unix.tar.gz 
mv /opt/nexus-3.56.0-01 /opt/nexus
#add user. (best practice to avoid root user. create new user)
sudo adduser nexus
#add priviledge to new user
visudo
###add --> nexus ALL=(ALL) NOPASSWD: ALL
#change ownership to new user
sudo chown -R nexus:nexus /opt/nexus
sudo chown -R nexus:nexus /opt/sonatype-work
#update /opt/nexus/bin/nexus.rc file, just uncomment run_as_user
vi /opt/nexus/bin/nexus.rc
-->  run_as_user="nexus"
#Add nexus as a service at boot time
sudo ln -s /opt/nexus/bin/nexus /etc/init.d/nexus
#start nexus
/etc/init.d/nexus start
#check the services running on ports
netstat –nlpt

#http://nexuxserver url:8081 (default 8081). Make sure you have 8081 port in your security group (for AWS) / network rule (for Azure) / ingress rule ( for GCP)
#Use below credentials to login
#username : admin
#get initial password from path /opt/sonatype-work/nexus3/admin.password
sudo cat /opt/sonatype-work/nexus3/admin.password
  • Open port 8081 in the security group to access the Nexus web interface.

Step 3: Deploy Kubernetes Cluster with Kops

Step 4: AWS Infrastructure Setup

Step 5: Installing Argo CD on Kubernetes

  • We will start to Install Argo CD applications by creating a namespace
#create a namespace
kubectl create namespace argocd

#install argo to the argocd created namespace
kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml

#change the argocd-server service type to NodePort
kubectl patch svc argocd-server -n argocd -p '{"spec": {"type": "NodePort"}}' 
(or)
#Manually edit the file
kubectl edit svc argocd-server -n argocd

#Get the password of the Argo CD application
kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath="{.data.password}" | base64 -d
#default username : 'admin'
  • Create an Argo cd application

Step 6: Install SonarQube Plugin in Jenkins

  • In the Jenkins web interface, navigate to "Manage Jenkins" > "Manage Plugins."

  • Go to the "Available" tab and search for the "SonarQube Scanner" plugin.

  • Install the SonarQube Scanner plugin and restart Jenkins for the changes to take effect.

Step 7: Configure SonarQube Server in Jenkins

  • In Jenkins, go to "Manage Jenkins" > "Configure System."

  • Scroll down to the "SonarQube servers" section and click on "Add SonarQube”

  • Login to the Sonar web interface and go to My Account -> Security -> Generate Token(create new token)

  • Add Sonarqube credentials on Jenkins.

  • Provide a name for your SonarQube server configuration and enter the SonarQube server URL Click "Save".

Step 8: Install Nexus Artifact Uploader Plugin in Jenkins

  • In the Jenkins web interface, navigate to "Manage Jenkins" > "Manage Plugins."
  • Go to the "Available" tab and search for the "Nexus Artifact Uploader" plugin.
  • Install the Nexus Artifact Uploader plugin and restart Jenkins for the changes to take effect.

Step 9: Create and Configure Repository in Nexus Repository Manager

  • In Nexus Web Interface go to repository and create a new one for our project.

Step 10: Install Required Plugins

  • To enable Pipeline functionality, ensure you have the following plugins installed:

  • Pipeline Plugin: Install the "Pipeline" plugin from the Jenkins Plugin Manager.

  • Git Plugin (Optional): Install the "Git" plugin if you plan to use a Git repository for version control in your pipeline.

Step 11: Create a New Jenkins Pipeline Project

  • In the Jenkins dashboard, click on "New Item" to create a new project.

  • Provide a name for your project, choose "Pipeline" as the project type, and click "OK."

  • Scroll down to the "Pipeline" section and select "Pipeline script from SCM" in the "Definition" dropdown.

  • Create a parameterized project for variables.

  • Choose the appropriate SCM (Git, SVN, etc.) and provide the repository URL and credentials (if required).

  • Specify the path to your Jenkinsfile within the repository (e.g., Jenkinsfile if it's at the root).

  • Click "Save" to create the pipeline project.

Jenkinsfile:

node
{
    // Delete the workspace before build starts
    cleanWs()

    stage('clone repo'){
        checkout scmGit(branches: [[name: '*/master']], extensions: [], userRemoteConfigs: [[url: 'https://github.com/Sivaprakash-pk/Java-Web-Application---CI-CD-End-to-End-Project.git']])
    }

    stage('build code from maven'){
        def mavenHome = tool name: "maven-3.9.3", type: "maven"
        def mavenCMD = "${mavenHome}/bin/mvn"
        sh "${mavenCMD} clean package"
    }
    stage('Sonarqube code analysis'){
        withSonarQubeEnv('sonar-server-9.4'){
            def mavenHome = tool name: "maven-3.9.3", type: "maven"
            def mavenCMD = "${mavenHome}/bin/mvn"
            sh "${mavenCMD} sonar:sonar"
        }
    }
    stage('Upload build artifacts to nexus repo'){
        nexusArtifactUploader artifacts: [[artifactId: 'Example', classifier: '', file: 'target/Example-0.0.1-SNAPSHOT.war', type: 'war']], credentialsId: 'Nexus-Credential', groupId: 'in.java-siva', nexusUrl: '13.233.237.234:8081', nexusVersion: 'nexus3', protocol: 'http', repository: 'demo-java', version: '1.0-SNAPSHOT'
    }
    stage('Build Docker Image'){
        // Define the Docker image name and tag using the parameterized values
        def imageName = params.IMAGE_NAME
        def imageTag = params.IMAGE_TAG
        sh "docker build -t $imageName:$imageTag ."
        sh "docker images"
    }
    stage('Push Docker Image to DockerHub') {
        def imageName = params.IMAGE_NAME
        def imageTag = params.IMAGE_TAG
        withCredentials([usernamePassword(credentialsId: 'Dockerhubcred', passwordVariable: 'dockerHubpasswd', usernameVariable: 'dockerHubuser')]) {
        sh "echo \"\$dockerHubpasswd\" | docker login -u \"\$dockerHubuser\" --password-stdin"
        sh "docker push $imageName:$imageTag"
        sh "docker logout"
        }
    }
    stage('Argo CD Code upload & K8s Deployment') {
        withCredentials([string(credentialsId: 'Github-creds', variable: 'Github')]) {
            sh '''
                git clone https://github.com/Sivaprakash-pk/Kubernetes-code.git
                cd Kubernetes-code
                git config user.email "siva@gmail.com"
                git config user.name "Siva"
                IMAGE_TAG=${IMAGE_TAG}
                sed -i "s/replaceImageTag/${IMAGE_TAG}/g" deployment.yml
                git add deployment.yml
                git commit -m "Update deployment image to version ${IMAGE_TAG}"
                git push https://${Github}@github.com/Sivaprakash-pk/Kubernetes-code HEAD:main
            '''
        }   
    }
}

Step 12: Run Your Jenkins Pipeline

  • Go back to the Jenkins dashboard and open your pipeline project.

  • Click on "Build Now" to trigger the pipeline.

  • Jenkins will now fetch the Jenkinsfile from your repository, and the defined stages will be executed.

  • If all stages complete successfully, you will see a success message in the Jenkins pipeline output.

Conclusion:

Through this thrilling project, we have successfully realized an end-to-end CI/CD pipeline, enabling automated builds, ensuring code quality, managing artifacts, and deploying flawlessly using Docker and Kubernetes. Our faithful companions, Jenkins and Argo CD, have been instrumental in navigating this journey of automation and excellence.

This project has been an invaluable learning experience, equipping us with the skills and knowledge to confidently steer future projects toward success. I'm looking forward to continuing our voyage in the world of Cloud, DevOps, and beyond, setting sail toward new horizons of innovation and technological marvels.

Let's learn together! I appreciate any comments or suggestions you may have to improve my learning and blog content.

Thank you,

Sivaprakash S

Sivaprakash S|Linkedin

#CI/CD #DevOps #Kubernetes #ArgoCD #JavaWebApplication #Docker #CodeQuality #ContinuousDeployment #Automation #GitOps #SoftwareDevelopment #InfrastructureAsCode #Containerization #DeploymentPipeline #CodeScanning #NexusRepository #DockerHub #GitHub #ApplicationDeployment #ContainerOrchestration #DeploymentAutomation #SoftwareDelivery #DevOpsJourney #AgileDevelopment