Comprehensive CI/CD Pipeline Tutorial with Jenkins, GitLab Webhooks, GitOps, and Kubernetes Deployment
This article provides a detailed tutorial on designing CI/CD pipelines using Jenkins, GitLab webhooks, and GitOps practices, covering single- and multi-application workflow designs, Jenkinsfile configurations for build, test, SonarQube scanning, Docker image creation, and Kubernetes deployment with Helm.
The guide walks through the design of CI/CD pipelines for both single‑application and multi‑application environments, illustrating how to prepare a code repository and outline the CI workflow steps such as fetching the correct commit, building, testing, scanning, and image creation.
It explains how to use GitLab WebHook to trigger Jenkins jobs, parse the JSON payload to identify the changed service modules, and then launch the appropriate CI jobs for each module.
pipeline {
agent any
stages {
stage("GetData") {
steps {
script {
echo "${webHookData}"
data = readJSON text: "${webHookData}"
env.branchName = data.ref - "refs/heads/"
env.commitId = data.checkout_sha
env.projectId = data.project_id
commits = data["commits"]
changeServices = []
for (commit in commits) {
for (add in commit.added) {
s = add.split("/") as List
if (s.size() > 1 && changeServices.indexOf(s[0]) == -1) {
changeServices.add(s[0])
}
}
for (m in commit.modified) {
s = m.split("/") as List
if (s.size() > 1 && changeServices.indexOf(s[0]) == -1) {
changeServices.add(s[0])
}
}
for (r in commit.removed) {
s = r.split("/") as List
if (s.size() > 1 && changeServices.indexOf(s[0]) == -1) {
changeServices.add(s[0])
}
}
}
println(changeServices)
}
}
}
stage('DefineService') {
steps {
script {
services = ['service02', 'service01']
for (service in services) {
if (changeServices.indexOf(service) != -1) {
jobName = "microservicecicd-${service}-service-CI"
build job: jobName, wait: false, parameters: [
string(name: 'branchName', value: "${env.branchName}"),
string(name: 'commitId', value: "${env.commitId}"),
string(name: 'projectId', value: "${env.projectId}")
]
}
}
}
}
}
}
}The CI job Jenkinsfile further defines stages for code checkout, Maven build & test, SonarQube analysis, Docker image creation, and pushing the image to a private registry.
pipeline {
agent { node { label "build" } }
stages {
stage('GetCode') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: "${branchName}"]],
extensions: [[
$class: 'SparseCheckoutPaths',
sparseCheckoutPaths: [[path: "${moduleName}"], [path: 'Dockerfile']]
]],
userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "${srcUrl}"]]
])
}
}
}
stage('Build&Test') {
steps {
script {
sh """
cd ${moduleName}
mvn clean package
"""
}
}
post { always { junit "${moduleName}/target/surefire-reports/*.xml" } }
}
stage('SonarScan') {
steps {
script {
sonarDate = sh(returnStdout: true, script: 'date +%Y%m%d%H%M%S').trim()
withCredentials([string(credentialsId: 'sonar-admin-user', variable: 'sonartoken'),
string(credentialsId: 'gitlab-user-token', variable: 'gitlabtoken')]) {
sh """
cd ${moduleName}
sonar-scanner \
-Dsonar.projectKey=${JOB_NAME} \
-Dsonar.projectName=${JOB_NAME} \
-Dsonar.projectVersion=${sonarDate} \
-Dsonar.sources=src \
-Dsonar.host.url=http://sonar.idevops.site \
-Dsonar.login=${sonartoken} \
-Dsonar.gitlab.commit_sha=${commitId} \
-Dsonar.gitlab.ref_name=${branchName} \
-Dsonar.gitlab.project_id=${projectId} \
-Dsonar.gitlab.url=http://gitlab.idevops.site \
-Dsonar.gitlab.user_token=${gitlabtoken}
"""
}
}
}
}
stage('BuildImage') {
steps {
script {
withCredentials([usernamePassword(credentialsId: 'aliyun-registry-admin', passwordVariable: 'password', usernameVariable: 'username')]) {
env.nowDate = sh(returnStdout: true, script: 'date +%Y%m%d%H%M%S').trim()
env.releaseVersion = "${env.branchName}"
env.imageTag = "${releaseVersion}-${env.nowDate}-${commitId}"
env.dockerImage = "registry.cn-beijing.aliyuncs.com/microservicecicd/microservicecicd-${moduleName}-service:${env.imageTag}"
env.jarName = "${moduleName}-${branchName}-${commitId}"
sh """
docker login -u ${username} -p ${password} registry.cn-beijing.aliyuncs.com
cd ${moduleName} && docker build -t ${dockerImage} -f ../Dockerfile --build-arg SERVICE_NAME=${jarName} .
docker push ${dockerImage}
docker rmi ${dockerImage}
"""
}
}
}
}
}
}For GitOps integration, an additional stage "PushFile" updates environment configuration files in a separate Git repository after the image is pushed, ensuring the deployment manifests reference the new image tag.
stage("PushFile") {
steps {
script {
if ("${env.branchName}".contains("RELEASE-")) {
env.branchName = "master"
} else {
env.branchName = "feature"
}
for (i = 0; i < 3; i++) {
response = GetRepoFile(40, "${moduleName}%2fvalues.yaml", "${env.branchName}")
yamlData = readYaml text: "${response}"
yamlData.image.version = "${releaseVersion}-${env.nowDate}"
yamlData.image.commit = "${commitId}"
writeYaml file: 'test.yaml', data: yamlData, charset: 'UTF-8'
newYaml = sh(returnStdout: true, script: 'cat test.yaml').trim()
base64Content = newYaml.bytes.encodeBase64().toString()
try {
UpdateRepoFile(40, "${moduleName}%2fvalues.yaml", base64Content, "${env.branchName}")
break
} catch (e) {
sh "sleep 2"
continue
}
}
}
}
}The CD pipeline mirrors the CI flow: a webhook‑triggered CD‑scheduler job parses the GitLab payload, determines which services changed, and then runs Helm commands to deploy or upgrade the services in a Kubernetes namespace.
pipeline {
agent any
stages {
stage('GetCommitService') {
steps {
script {
webhookdata = readJSON text: "${WebHookData}"
changeServices = []
for (commit in webhookdata["commits"]) {
for (add in commit.added) {
s = add.split("/") as List
if (s.size() > 1 && changeServices.indexOf(s[0]) == -1) changeServices.add(s[0])
}
for (m in commit.modified) {
s = m.split("/") as List
if (s.size() > 1 && changeServices.indexOf(s[0]) == -1) changeServices.add(s[0])
}
for (r in commit.removed) {
s = r.split("/") as List
if (s.size() > 1 && changeServices.indexOf(s[0]) == -1) changeServices.add(s[0])
}
}
}
}
}
stage('DefineService') {
steps {
script {
services = ['service02', 'service01']
for (service in services) {
if (changeServices.indexOf(service) != -1) {
jobName = "microservicecicd-${service}-service-CD"
build job: jobName, wait: false, parameters: [string(name: 'branchName', value: "${branchName}")]
}
}
}
}
}
}
}Finally, the CD job checks out the environment repository, creates the target namespace if needed, and uses Helm to install or upgrade the service, followed by listing releases and history for verification.
pipeline {
agent { node { label "k8s" } }
stages {
stage('GetCode') {
steps {
script {
checkout([
$class: 'GitSCM',
branches: [[name: "${env.branchName}"]],
extensions: [[
$class: 'SparseCheckoutPaths',
sparseCheckoutPaths: [[path: "${serviceName}"]]
]],
userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "http://gitlab.idevops.site/microservicecicd/microservicecicd-env.git"]]
])
}
}
}
stage('HelmDeploy') {
steps {
script {
sh """
kubectl create ns ${nameSpace}-uat || echo false
helm install ${serviceName} --namespace ${nameSpace}-uat ./ ${serviceName} || helm upgrade ${serviceName} --namespace ${nameSpace}-uat ./ ${serviceName}
helm list --namespace ${nameSpace}-uat
helm history ${serviceName} --namespace ${nameSpace}-uat
"""
}
}
}
}
}DevOps Cloud Academy
Exploring industry DevOps practices and technical expertise.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.