Automating Java CI/CD Pipelines with Jenkins, Docker, and Git
Infrastructure Layout
- Node A (10.0.0.10): CentOS 7, Docker, Harbor, Git Server
- Node B (10.0.0.11): CentOS 7, Docker, Jenkins, Maven, Git Client
The target deployment environment is Node B.
Git Repository Configuration
On Node A, install Git to act as the central version control server.
yum install git -y
useradd gituser
passwd gituser
su - gituser
mkdir -p repos/java-app.git && cd repos/java-app.git
git --bare init
On Node B, install Git as the client, clone a sample Java project, and push it to the private repository.
yum install git -y
git clone gituser@10.0.0.10:/home/gituser/repos/java-app.git
mv java-app java-app-backup
git clone https://github.com/lizhenliang/tomcat-java-demo
cd tomcat-java-demo
Update the remote origin in .git/config to point to the private Git server:
[remote "origin"]
url = gituser@10.0.0.10:/home/gituser/repos/java-app.git
fetch = +refs/heads/*:refs/remotes/origin/*
Commit and push the source code:
git config --global user.name "devadmin"
git config --global user.email "devadmin@internal.net"
git add .
git commit -m 'initial project upload'
git push origin master
Configure passwordless SSH access from Node B (Jenkins) to Node A (Git):
ssh-keygen
ssh-copy-id gituser@10.0.0.10
Tomcat Base Image and Harbor Integration
On Node A, create a Dockerfile to build a custom Tomcat base image.
FROM centos:7
LABEL maintainer="admin"
ARG TOMCAT_VER=9.0.50
RUN yum install -y java-1.8.0-openjdk wget tar gzip && \
yum clean all && \
rm -rf /var/cache/yum/*
RUN wget https://archive.apache.org/dist/tomcat/tomcat-9/v${TOMCAT_VER}/bin/apache-tomcat-${TOMCAT_VER}.tar.gz && \
tar -xzf apache-tomcat-${TOMCAT_VER}.tar.gz && \
mv apache-tomcat-${TOMCAT_VER} /opt/tomcat && \
rm -rf apache-tomcat-${TOMCAT_VER}.tar.gz /opt/tomcat/webapps/* && \
echo "ok" > /opt/tomcat/webapps/health.html && \
sed -i '1a JAVA_OPTS="-Djava.security.egd=file:/dev/./urandom"' /opt/tomcat/bin/catalina.sh && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
ENV PATH=${PATH}:/opt/tomcat/bin
WORKDIR /opt/tomcat
EXPOSE 8080
CMD ["catalina.sh", "run"]
Build and push the image to the Harbor registry:
docker build -t tomcat-base:latest -f Dockerfile-tomcat .
Configure Docker to trust the private registry on both nodes by updating /etc/docker/daemon.:
{ "registry-mirrors": ["http://f1361db2.m.daocloud.io"], "insecure-registries": ["http://10.0.0.10"] }
Restart Docker and push the image:
systemctl daemon-reload
systemctl restart docker
docker tag tomcat-base:latest 10.0.0.10/library/tomcat-base:latest
docker login 10.0.0.10
docker push 10.0.0.10/library/tomcat-base:latest
Jenkins Setup and Pipeline Configuration
On Node B, install the JDK and deploy Jenkins using Tomcat.
tar zxf jdk-8u45-linux-x64.tar.gz
mv jdk-8u45-linux-x64 /usr/local/jdk1.8
Set environment variables in /etc/profile:
export JAVA_HOME=/usr/local/jdk1.8
export PATH=$PATH:$JAVA_HOME/bin
source /etc/profile
Deploy the Jenkins WAR file:
wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war
tar zxf apache-tomcat-8.5.32.tar.gz
mv apache-tomcat-8.5.32 /usr/local/tomcat-jenkins
rm -rf /usr/local/tomcat-jenkins/webapps/*
unzip jenkins.war -d /usr/local/tomcat-jenkins/webapps/ROOT
/usr/local/tomcat-jenkins/bin/startup.sh
Access the Jenkins dashboard at http://10.0.0.11:8080, copmlete the initial setup, and install the Pipeline and Git plugins.
Create a new Pipeline project and enable "This project is parameterized" with a string paramter named BRANCH_SPEC (defaulting to master).
Configrue the Pipeline script using the Declarative syntax:
pipeline {
agent any
environment {
HARBOR_URL = '10.0.0.10'
IMAGE_NAME = 'library/java-webapp'
HARBOR_CREDS = credentials('harbor-login')
}
stages {
stage('Source Checkout') {
steps {
git branch: "${params.BRANCH_SPEC}",
credentialsId: 'git-ssh-key',
url: 'gituser@10.0.0.10:/home/gituser/repos/java-app.git'
}
}
stage('Maven Build') {
steps {
sh '/usr/local/maven/bin/mvn clean package -DskipTests'
}
}
stage('Docker Build & Push') {
steps {
sh """
docker build -t ${HARBOR_URL}/${IMAGE_NAME}:${params.BRANCH_SPEC} .
docker login ${HARBOR_URL} -u ${HARBOR_CREDS_USR} -p ${HARBOR_CREDS_PSW}
docker push ${HARBOR_URL}/${IMAGE_NAME}:${params.BRANCH_SPEC}
"""
}
}
stage('Deploy Container') {
steps {
sh """
docker rm -f webapp-instance || true
docker rmi ${HARBOR_URL}/${IMAGE_NAME}:${params.BRANCH_SPEC} || true
docker run -d --name webapp-instance -p 8081:8080 ${HARBOR_URL}/${IMAGE_NAME}:${params.BRANCH_SPEC}
"""
}
}
}
}
A Dockerfile should exist in the project root to assemble the final application image:
FROM 10.0.0.10/library/tomcat-base:latest
COPY target/*.war /opt/tomcat/webapps/ROOT.war
Deployment Verification
Trigger the Jenkins build with the BRANCH_SPEC parameter. Once completed, verify the running container on Node B.
[root@node-b ~]# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a1b2c3d4e5f6 10.0.0.10/library/java-webapp:master "catalina.sh run" 2 minutes ago Up 2 minutes 0.0.0.0:8081->8080/tcp webapp-instance
Check the application logs:
[root@node-b ~]# docker logs webapp-instance
INFO [main] org.apache.catalina.startup.VersionLoggerListener.log Server version: Apache Tomcat/9.0.50
...
INFO [main] org.apache.catalina.core.StandardService.startInternal Starting service [Catalina]
Validate the HTTP response from the mapped port:
[root@node-b ~]# curl -I 10.0.0.11:8081
HTTP/1.1 200
Content-Type: text/html;charset=utf-8
Content-Length: 2538
Date: Sat, 16 Mar 2019 13:36:13 GMT