Cloud Native 13 min read

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.

DevOps Cloud Academy
DevOps Cloud Academy
DevOps Cloud Academy
Implementing CI/CD and GitOps Pipelines for Microservices with Jenkins, GitLab, and Kubernetes

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
"""
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

ci/cdGitLabGitOpsJenkinshelm
DevOps Cloud Academy
Written by

DevOps Cloud Academy

Exploring industry DevOps practices and technical expertise.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.