AB
The second part of our end-to-end CI/CD series, focusing on extending your pipeline with Kubernetes for container orchestration and deployment.
In this tutorial, we’ll walk through the process of creating a robust end-to-end Continuous Integration and Continuous Deployment (CI/CD) pipeline for a Java application. This is the second part of our E2E CI/CD series, focusing on Jenkins integration with Docker and Kubernetes for CD. We’ll leverage industry-standard tools to automate building, testing, and deploying applications efficiently.
The sample code for this project is available at Spring PetClinic GitHub Repository.
Before starting this project, make sure you have the following prerequisites:
For this we need to use an EC2 instance of type t2.large so that we have enough resources to run the Jenkins server. And also to install other services like Docker, SonarQube and Trivy.
Steps to install Jenkins on an EC2 instance:
sudo apt update -y
sudo apt install openjdk-17-jdk -y or sudo apt install default-jre
java -version
sudo wget -O /usr/share/keyrings/jenkins-keyring.asc \
https://pkg.jenkins.io/debian-stable/jenkins.io-2023.key
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
Don’t forget to open port 8080 in security group to access Jenkins.
Open the Jenkins URL in the browser and get the initial admin password by following the instructions in the browser.
Install suggested plugins.
Set up the admin user.
Once Jenkins is set up, you can access the Jenkins dashboard.
Two important plugins you need to install:
Next we need to configure it so go to Global Tool Configuration:
Next is to create a pipeline job:
We are installing docker in jenkins server/ec2 instance.
Steps to install Docker on the EC2 instance:
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
For this we need at least 2 EC2 instances of type t2.medium. One will be master node and other will be worker node. Follow this link and create it:
Here’s a summary of the steps to set up a Kubernetes cluster:
Update and Install Required Packages on Master Node
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl
Add Kubernetes Repository
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
Install Kubernetes Components
sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
Disable Swap
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
Configure Containerd
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
Install Containerd
sudo apt install -y containerd
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup \= false/SystemdCgroup \= true/g' /etc/containerd/config.toml
sudo systemctl restart containerd
Initialize Kubernetes Cluster
sudo kubeadm init --pod-network-cidr=10.244.0.0/16
Configure kubectl
mkdir -p $HOME/.kube
sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config
sudo chown $(id -u):$(id -g) $HOME/.kube/config
Deploy Network Plugin (Flannel)
kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml
Update and Install Required Packages on Worker Node
sudo apt update
sudo apt install -y apt-transport-https ca-certificates curl
Add Kubernetes Repository
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.29/deb/Release.key | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.29/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list
Install Kubernetes Components
sudo apt update
sudo apt install -y kubelet kubeadm kubectl
sudo apt-mark hold kubelet kubeadm kubectl
Disable Swap
sudo swapoff -a
sudo sed -i '/ swap / s/^\(.*\)$/#\1/g' /etc/fstab
Configure Containerd
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
cat <<EOF | sudo tee /etc/sysctl.d/99-kubernetes-cri.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sudo sysctl --system
Install Containerd
sudo apt install -y containerd
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml
sudo sed -i 's/SystemdCgroup \= false/SystemdCgroup \= true/g' /etc/containerd/config.toml
sudo systemctl restart containerd
Join Worker Node to the Cluster
Run the join command provided from the master node. It will look something like:
sudo kubeadm join <master-ip>:6443 --token <token> --discovery-token-ca-cert-hash <hash>
Once your Kubernetes cluster is set up, you need to integrate it with Jenkins:
~/.kube/config
on your Kubernetes master nodeCreate a simple Jenkins pipeline job to test the connection:
pipeline {
agent any
stages {
stage('Test Kubernetes Connection') {
steps {
withKubeConfig([credentialsId: 'kubernetes-config']) {
sh 'kubectl get nodes'
}
}
}
}
}
Now, let’s create the necessary Kubernetes YAML files for deploying our Java application:
Create a file named deployment.yaml
:
apiVersion: apps/v1
kind: Deployment
metadata:
name: petclinic
labels:
app: petclinic
spec:
replicas: 2
selector:
matchLabels:
app: petclinic
template:
metadata:
labels:
app: petclinic
spec:
containers:
- name: petclinic
image: ${DOCKER_IMAGE}:${IMAGE_TAG}
ports:
- containerPort: 8080
resources:
limits:
memory: "512Mi"
cpu: "500m"
requests:
memory: "256Mi"
cpu: "250m"
Create a file named service.yaml
:
apiVersion: v1
kind: Service
metadata:
name: petclinic-service
spec:
selector:
app: petclinic
ports:
- port: 80
targetPort: 8080
type: LoadBalancer
If you’re using an Ingress controller, create a file named ingress.yaml
:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: petclinic-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: petclinic.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: petclinic-service
port:
number: 80
Now let’s create a complete Jenkins pipeline that builds, tests, and deploys our application to Kubernetes:
pipeline {
agent any
tools {
maven "maven3"
jdk "jdk11"
}
environment {
DOCKER_IMAGE = "your-dockerhub-username/petclinic"
IMAGE_TAG = "${BUILD_NUMBER}"
SONAR_URL = tool name: 'sonarqube-scanner'
}
stages {
stage('Git Checkout') {
steps {
git branch: 'main', url: 'https://github.com/spring-projects/spring-petclinic.git'
}
}
stage('Build & Test') {
steps {
sh 'mvn clean package'
}
}
stage('SonarQube Analysis') {
steps {
withSonarQubeEnv('sonarqube') {
sh '''
${SONAR_URL}/bin/sonar-scanner \
-Dsonar.projectKey=petclinic \
-Dsonar.projectName=petclinic \
-Dsonar.sources=src/main \
-Dsonar.tests=src/test \
-Dsonar.java.binaries=target/classes
'''
}
}
}
stage('Build & Push Docker Image') {
steps {
script {
withDockerRegistry([credentialsId: 'docker-hub', url: '']) {
sh "docker build -t ${DOCKER_IMAGE}:${IMAGE_TAG} ."
sh "docker push ${DOCKER_IMAGE}:${IMAGE_TAG}"
}
}
}
}
stage('Security Scan') {
steps {
sh "trivy image ${DOCKER_IMAGE}:${IMAGE_TAG}"
}
}
stage('Deploy to Kubernetes') {
steps {
script {
withKubeConfig([credentialsId: 'kubernetes-config']) {
sh "envsubst < deployment.yaml | kubectl apply -f -"
sh "kubectl apply -f service.yaml"
// Optional: apply ingress configuration
// sh "kubectl apply -f ingress.yaml"
}
}
}
}
stage('Verify Deployment') {
steps {
withKubeConfig([credentialsId: 'kubernetes-config']) {
sh "kubectl rollout status deployment/petclinic"
sh "kubectl get services petclinic-service"
}
}
}
}
post {
success {
echo "Pipeline executed successfully!"
}
failure {
echo "Pipeline execution failed!"
}
}
}
Your application will need a Dockerfile. Here’s a sample for a Spring Boot application:
FROM openjdk:11-jre-slim
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
The pipeline deploys three Kubernetes resources:
The pipeline uses environment variables in the Kubernetes deployment file:
${DOCKER_IMAGE}
: The Docker image name (from Jenkins environment)${IMAGE_TAG}
: The image tag (build number)The envsubst
command replaces these variables with their actual values before deployment.
kubectl get deployments
kubectl get pods
kubectl get services
kubectl logs -l app=petclinic
kubectl describe deployment petclinic
kubectl describe pod <pod-name>
You can scale your application horizontally by updating the replicas in the deployment.yaml or using kubectl:
kubectl scale deployment petclinic --replicas=5
In this tutorial, we’ve created a complete CI/CD pipeline for Java applications using Jenkins and Kubernetes. This pipeline:
In the next part of this series, we’ll explore integrating this pipeline with Azure DevOps for a more comprehensive CI/CD solution.