Implementing CI/CD and GitOps Pipelines for Microservices with Jenkins, GitLab, and Kubernetes
This article demonstrates how to build a CI pipeline, extend it with GitOps for environment updates, and create a CD pipeline using Jenkins, GitLab webhooks, Docker, Helm, and Kubernetes to automate the build, test, and deployment of multiple microservices.
CI Continuous Integration
Prepare a code repository (e.g., GitHub zip ) that contains multiple microservice modules in separate sub‑directories.
The CI pipeline extracts the service name from the incoming commit, checks out the corresponding module, performs compilation, unit testing, code scanning, and builds a Docker image.
CI‑Scheduler Job
Configure a generic webhook trigger in Jenkins with a unique token, e.g.,
http://jenkins.idevops.site/generic-webhook-trigger/invoke?token=microservicecicd-scheduler-CI.
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"]
// Determine changed services
changeServices = []
for (commit in commits) {
// added files
for (add in commit.added) {
s = add.split("/") as List
if (s.size() > 1 && changeServices.indexOf(s[0]) == -1) {
changeServices.add(s[0])
}
}
// modified files
for (m in commit.modified) {
s = m.split("/") as List
if (s.size() > 1 && changeServices.indexOf(s[0]) == -1) {
changeServices.add(s[0])
}
}
// removed files
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}")
]
}
}
}
}
}
}
}GitLab Webhook Configuration
Enable a webhook in GitLab that posts JSON data to the above Jenkins URL whenever code is pushed.
CI Job per Microservice
Each microservice has its own CI job that receives three string parameters: branch name, commit ID, and project ID.
String branchName = "${env.branchName}"
String moduleName = "${JOB_NAME}".split("/")[1].split("-")[1]
String srcUrl = "http://gitlab.idevops.site/microservicecicd/microservicecicd-demo-service.git"
String commitId = "${env.commitId}"
String projectId = "${env.projectId}"
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 {
echo "Build..........."
sh """
cd ${moduleName}
mvn clean package
"""
}
}
post { always { junit "${moduleName}/target/surefire-reports/*.xml" } }
}
stage('SonarScan') {
steps {
script {
def 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', usernameVariable: 'username', passwordVariable: 'password')]) {
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} .
sleep 1
docker push ${dockerImage}
sleep 1
docker rmi ${dockerImage}
"""
}
}
}
}
}
}GitOps CI Extension
After the image is pushed, the pipeline updates the environment repository (a GitOps repo) with the new image tag and commit SHA.
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')
base64Content = newYaml.bytes.encodeBase64().toString()
try {
UpdateRepoFile(40, "${moduleName}%2fvalues.yaml", base64Content, "${env.branchName}")
break
} catch (e) {
sh "sleep 2"
continue
}
}
}
}
}
def HttpReq(reqType, reqUrl, reqBody) {
def gitServer = "http://gitlab.idevops.site/api/v4"
withCredentials([string(credentialsId: 'gitlab-token', variable: 'gitlabToken')]) {
result = httpRequest(
httpMode: reqType,
contentType: "APPLICATION_JSON",
requestBody: reqBody,
url: "${gitServer}/${reqUrl}",
customHeaders: [[name: 'PRIVATE-TOKEN', value: "${gitlabToken}", maskValue: true]],
consoleLogResponseBody: true,
ignoreSslErrors: true
)
}
return result
}
def GetRepoFile(projectId, filePath, branchName) {
apiUrl = "projects/${projectId}/repository/files/${filePath}/raw?ref=${branchName}"
response = HttpReq('GET', apiUrl, '')
return response.content
}
def UpdateRepoFile(projectId, filePath, fileContent, branchName) {
apiUrl = "projects/${projectId}/repository/files/${filePath}"
reqBody = "{\"branch\":\"${branchName}\",\"encoding\":\"base64\",\"content\":\"${fileContent}\",\"commit_message\":\"update a new file\"}"
response = HttpReq('PUT', apiUrl, reqBody)
println(response)
}CD Scheduler Job
This job also receives a GitLab webhook, but for changes in the environment repository. It triggers Helm deployments for the affected services.
pipeline {
agent any
stages {
stage('GetCommitService') {
steps {
script {
echo 'Hello World'
echo "${WebHookData}"
webhookdata = readJSON text: "${WebHookData}"
eventType = webhookdata["object_kind"]
commits = webhookdata["commits"]
branchName = webhookdata["ref"] - "refs/heads/"
projectID = webhookdata["project_id"]
commitID = webhookdata["checkout_sha"]
changeServices = []
for (commit in commits) {
// parse added/modified/removed similar to CI pipeline
}
currentBuild.description = " Trigger by ${eventType} ${changeServices} "
}
}
}
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}")]
}
}
}
}
}
}
}Helm Deployment Stage
Creates a namespace and installs or upgrades the Helm chart for the service.
sh """
kubectl create ns ${nameSpace}-uat || echo false
helm install ${serviceName} --namespace ${nameSpace}-uat ./
|| helm upgrade ${serviceName} --namespace ${nameSpace}-uat ./
helm list --namespace ${nameSpace}-uat
helm history ${serviceName} --namespace ${nameSpace}-uat
"""Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
