微服务CI/CD实践-GitOps完整设计与实现

云计算
当我在Gitlab提交了代码,会通过GitLab webhook 触发Jenkins Scheduler 作业, 会将此次提交代码所产生的hook data数据信息以POST的方式传给Jenkins Job。

[[422238]]

单应用与环境

多应用与环境

CI持续集成

首先,准备一个代码库:

https://github.com/DevOpsCICDCourse/microservicescicd/blob/main/microservice-demo-service-master.zip

我们来梳理一下CI流水线的步骤:

  • 由于此次实现的代码仓库类型为单一存储库,即一个存储库存放多个服务模块代码,每个子目录为一个服务模块。
  • 首先,我们的持续集成流水线需要能够正确获取,当前的commit是哪个服务的代码。
  • 确定好服务,然后下载该服务的代码,进行编译打包、单元测试、代码扫描和构建镜像等步骤。

如何获取commit的服务信息?这里我们使用GitLab WebHook功能和Jenkins 的job 构建触发器对接来实现。

工作流程是:当我在Gitlab提交了代码,会通过GitLab webhook 触发Jenkins Scheduler 作业, 会将此次提交代码所产生的hook data数据信息以POST的方式传给Jenkins Job。此时Jenkins job可以编写使用Generic Hook插件获取此次POST请求传输过来的请求体Body信息。是一段JSON数据, 该job运行后编写Pipeline 解析JSON中的数据拿到所变更的服务模块信息。最后触发对应服务的CI作业进行构建。

CI-Scheduler 作业

此作业只需要开启webhook, 配置触发token(唯一性)。生成hookurl:http://jenkins.idevops.site/generic-webhook-trigger/invoke?token=microservicecicd-scheduler-CI

Jenkinsfile

  1. pipeline { 
  2.  agent any  
  3.  
  4.  stages{ 
  5.  
  6.   stage("GetData"){ 
  7.    steps{ 
  8.     script { 
  9.      echo "${webHookData}" 
  10.  
  11.      data = readJSON  text: "${webHookData}" 
  12.  
  13.      println(data) 
  14.  
  15.      env.branchName = data.ref - "refs/heads/" 
  16.      env.commitId = data.checkout_sha 
  17.      env.projectId = data.project_id 
  18.      commits = data["commits"
  19.  
  20.      println("${env.branchName}"
  21.      println("${env.commitID}"
  22.      println("${env.projectId}"
  23.  
  24.      //env.moduleName = "service01" 
  25.      changeServices = [] 
  26.                     for(commit in commits) { 
  27.                         println(commit.id) 
  28.  
  29.                         //added 
  30.                         for (add in commit.added) { 
  31.                             s = add.split("/"as List 
  32.                             if (s.size() > 1){ 
  33.                                 if (changeServices.indexOf(s[0]) == -1){ 
  34.                                     changeServices.add(s[0]) 
  35.                                 } 
  36.                             } 
  37.                         } 
  38.  
  39.                         //modified 
  40.                         for (m in commit.modified) { 
  41.                             s = m.split("/"as List 
  42.                             // println s 
  43.                             // println s.size() 
  44.                             // println s[0] 
  45.                             if (s.size() > 1){ 
  46.                                 // println changeServices.indexOf(s[0]) 
  47.                                 if (changeServices.indexOf(s[0]) == -1){ 
  48.                                     changeServices.add(s[0]) 
  49.                                 } 
  50.                             } 
  51.                         } 
  52.  
  53.                         //removed 
  54.                         for (r in commit.removed) { 
  55.                             s = r.split("/"as List 
  56.                             println s 
  57.                             if (s.size() > 1){ 
  58.                                 if (changeServices.indexOf(s[0]) == -1){ 
  59.                                     changeServices.add(s[0]) 
  60.                                 } 
  61.                             } 
  62.                         } 
  63.                     } 
  64.  
  65.                     println(changeServices) 
  66.                     //currentBuild.description = " Trigger by  ${eventType} ${changeServices}  
  67.     } 
  68.    } 
  69.   } 
  70.  
  71.   stage('DefineService') { 
  72.             steps { 
  73.                 script{ 
  74.                     println(changeServices) 
  75.                     //服务构建顺序控制 
  76.                     services = ['service02''service01'
  77.                     for (service in services){ 
  78.                         if (changeServices.indexOf(service) != -1){ 
  79.                             jobName = 'microservicecicd-'+service+'-service-CI' 
  80.                             build job: jobName, wait: false,  parameters: [string(name'branchName', value: "${env.branchName}" ), 
  81.                                                                            string(name'commitId',   value: "${env.commitId}" ),  
  82.                                                                            string(name'projectId',  value: "${env.projectId}" )] 
  83.                         } 
  84.                     } 
  85.                 } 
  86.             } 
  87.         } 
  88.  } 

GitLab 配置WebHook

开启webhook,配置hookurl:http://jenkins.idevops.site/generic-webhook-trigger/invoke?token=microservicecicd-scheduler-CI

CI流水线-CI作业

每个微服务创建一个CI作业,具有三个字符串参数:分支名称、commitID、项目ID。

Jenkinsfile

  1. String branchName = "${env.branchName}" 
  2. String moduleName = "${JOB_NAME}".split("/")[1].split("-")[1] 
  3. String srcUrl = "http://gitlab.idevops.site/microservicecicd/microservicecicd-demo-service.git" 
  4. String commitId = "${env.commitId}" 
  5. String projectId = "${env.projectId}" 
  6.  
  7. pipeline { 
  8.     agent { node { label "build" } } 
  9.  
  10.     stages { 
  11.         stage('GetCode') { 
  12.             steps { 
  13.                 script { 
  14.                     checkout([$class: 'GitSCM',  
  15.                             branches: [[name"${branchName}"]],  
  16.                             doGenerateSubmoduleConfigurations: false
  17.                             extensions: [[$class: 'SparseCheckoutPaths',  
  18.                                         sparseCheckoutPaths: [[path: "${moduleName}"],[path: 'Dockerfile']]]],  
  19.                             submoduleCfg: [],  
  20.                             userRemoteConfigs: [[credentialsId: 'gitlab-admin-user'
  21.                                                 url: "${srcUrl}"]]]) 
  22.                 } 
  23.                  
  24.             } 
  25.         } 
  26.  
  27.         stage("Build&Test"){ 
  28.             steps{ 
  29.                 script{ 
  30.                     echo "Build..........." 
  31.  
  32.                     sh ""
  33.                     cd ${moduleName}  
  34.                     mvn clean package 
  35.  
  36.                     ""
  37.                 } 
  38.             } 
  39.             post { 
  40.                 always { 
  41.                     junit "${moduleName}/target/surefire-reports/*.xml" 
  42.                 } 
  43.             } 
  44.         } 
  45.  
  46.         stage("SonarScan"){ 
  47.             steps{ 
  48.                 script{ 
  49.  
  50.                     def sonarDate = sh returnStdout: true, script: 'date  +%Y%m%d%H%M%S' 
  51.                     sonarDate = sonarDate - "\n" 
  52.  
  53.                     withCredentials([string(credentialsId: 'sonar-admin-user', variable: 'sonartoken'), 
  54.                                     string(credentialsId: 'gitlab-user-token', variable: 'gitlabtoken')]) { 
  55.                         // some block 
  56.                         sh ""
  57.                         cd ${moduleName}  
  58.                         sonar-scanner \ 
  59.                         -Dsonar.projectKey=${JOB_NAME} \ 
  60.                         -Dsonar.projectName=${JOB_NAME} \ 
  61.                         -Dsonar.projectVersion=${sonarDate} \ 
  62.                         -Dsonar.ws.timeout=30 \ 
  63.                         -Dsonar.projectDescription="xxxxxxx" \ 
  64.                         -Dsonar.links.homepage=http://www.baidu.com \ 
  65.                         -Dsonar.sources=src \ 
  66.                         -Dsonar.sourceEncoding=UTF-8 \ 
  67.                         -Dsonar.java.binaries=target/classes \ 
  68.                         -Dsonar.java.test.binaries=target/test-classes \ 
  69.                         -Dsonar.java.surefire.report=target/surefire-reports \ 
  70.                         -Dsonar.host.url="http://sonar.idevops.site" \ 
  71.                         -Dsonar.login=${sonartoken} \ 
  72.                         -Dsonar.gitlab.commit_sha=${commitId} \ 
  73.                         -Dsonar.gitlab.ref_name=${branchName} \ 
  74.                         -Dsonar.gitlab.project_id=${projectId} \ 
  75.                         -Dsonar.dynamicAnalysis=reuseReports \ 
  76.                         -Dsonar.gitlab.failure_notification_mode=commit-status \ 
  77.                         -Dsonar.gitlab.url=http://gitlab.idevops.site \ 
  78.                         -Dsonar.gitlab.user_token=${gitlabtoken} \ 
  79.                         -Dsonar.gitlab.api_version=v4 
  80.  
  81.                         ""
  82.  
  83.                     } 
  84.   
  85.                 } 
  86.             } 
  87.         } 
  88.  
  89.         stage("BuildImage"){ 
  90.             steps{ 
  91.                 script{ 
  92.  
  93.                      withCredentials([usernamePassword(credentialsId: 'aliyun-registry-admin', passwordVariable: 'password', usernameVariable: 'username')]) { 
  94.                  
  95.                          env.nowDate = sh  returnStdout: true, script: 'date  +%Y%m%d%H%M%S' 
  96.                          env.nowDate = env.nowDate - "\n" 
  97.  
  98.                          env.releaseVersion = "${env.branchName}" 
  99.                          env.imageTag = "${releaseVersion}-${nowDate}-${commitId}" 
  100.                          env.dockerImage = "registry.cn-beijing.aliyuncs.com/microservicecicd/microservicecicd-${moduleName}-service:${env.imageTag}" 
  101.                          env.jarName = "${moduleName}-${branchName}-${commitId}" 
  102.                          sh ""
  103.                              docker login -u ${username} -p ${password}  registry.cn-beijing.aliyuncs.com 
  104.                              cd ${moduleName} && docker build -t ${dockerImage} -f ../Dockerfile --build-arg SERVICE_NAME=${jarName} . 
  105.                              sleep 1 
  106.                              docker push ${dockerImage} 
  107.                              sleep 1 
  108.                              docker rmi ${dockerImage} 
  109.                           ""
  110.                     } 
  111.  
  112.  
  113.                 } 
  114.             } 
  115.         } 
  116.  
  117.          
  118.     } 

GitOps-CI扩展部分

在原始CI作业的步骤基础上,增加了一个更新环境的步骤。GitOps实践会将当前的基础环境部署文件存放到一个Git仓库中。我们的CI作业在完成镜像上传后,同时更新环境部署文件中的镜像标签信息。(所以我们需要先获取该环境文件并更新上传)

  1. stage("PushFile"){ 
  2.           // when { 
  3.           //   expression { "${env.branchName}".contains("RELEASE-") } 
  4.           // } 
  5.           steps{ 
  6.             script{ 
  7.               if ("${env.branchName}".contains("RELEASE-")){ 
  8.                 println("branchName = branchName"
  9.                 env.branchName = "master" 
  10.  
  11.               } else { 
  12.                 env.branchName = "feature" 
  13.               } 
  14.  
  15.                 for (i = 0; i < 3; i++) { 
  16.                     //下载版本库文件  
  17.                     response = GetRepoFile(40,"${moduleName}%2fvalues.yaml""${env.branchName}"
  18.                     //println(response) 
  19.                      
  20.                     //替换文件中内容 
  21.                     yamlData = readYaml text: """${response}""" 
  22.  
  23.                     println(yamlData.image.version) 
  24.                     println(yamlData.image.commit
  25.                     yamlData.image.version = "${releaseVersion}-${env.nowDate}" 
  26.                     yamlData.image.commit  = "${commitId}" 
  27.  
  28.                     println(yamlData.toString()) 
  29.  
  30.                     sh "rm -fr test.yaml" 
  31.                     writeYaml charset: 'UTF-8', data: yamlData, file: 'test.yaml' 
  32.                     newYaml = sh returnStdout: true, script: 'cat test.yaml' 
  33.                      
  34.                     println(newYaml) 
  35.                     //更新gitlab文件内容 
  36.                     base64Content = newYaml.bytes.encodeBase64().toString() 
  37.  
  38.                     // 会有并行问题,同时更新报错 
  39.                     try { 
  40.                       UpdateRepoFile(40,"${moduleName}%2fvalues.yaml",base64Content, "${env.branchName}"
  41.                       break; 
  42.                     } catch(e){ 
  43.                       sh "sleep 2" 
  44.                       continue
  45.                     } 
  46.                 } 
  47.             } 
  48.           } 
  49.         } 
  50.          
  51.  //封装HTTP请求 
  52. def HttpReq(reqType,reqUrl,reqBody){ 
  53.     def gitServer = "http://gitlab.idevops.site/api/v4" 
  54.     withCredentials([string(credentialsId: 'gitlab-token', variable: 'gitlabToken')]) { 
  55.       result = httpRequest customHeaders: [[maskValue: truename'PRIVATE-TOKEN', value: "${gitlabToken}"]],  
  56.                 httpMode: reqType,  
  57.                 contentType: "APPLICATION_JSON"
  58.                 consoleLogResponseBody: true
  59.                 ignoreSslErrors: true,  
  60.                 requestBody: reqBody, 
  61.                 url: "${gitServer}/${reqUrl}" 
  62.                 //quiet: true 
  63.     } 
  64.     return result 
  65.  
  66.  
  67. //获取文件内容 
  68. def GetRepoFile(projectId,filePath,branchName){ 
  69.     apiUrl = "projects/${projectId}/repository/files/${filePath}/raw?ref=${branchName}" 
  70.     response = HttpReq('GET',apiUrl,''
  71.     return response.content 
  72.  
  73. //更新文件内容 
  74. def UpdateRepoFile(projectId,filePath,fileContent, branchName){ 
  75.     apiUrl = "projects/${projectId}/repository/files/${filePath}" 
  76.     reqBody = """{"branch": "${branchName}","encoding":"base64", "content": "${fileContent}", "commit_message": "update a new file"}""" 
  77.     response = HttpReq('PUT',apiUrl,reqBody) 
  78.     println(response) 
  79.  

images

GitOps-CD部分

CD-Scheduler作业

此作业其实也是接收GitLab的webhook请求, 与CI-scheduler作业类似。不同的是这个CD-scheduler作业是用来接收环境仓库的代码变更。开启webhook, 配置触发token。生成hookurl:http://jenkins.idevops.site/generic-webhook-trigger/invoke?token=microservicecicd-scheduler-CD

Jenkinsfile

  1. pipeline { 
  2.     agent any 
  3.  
  4.     stages { 
  5.         stage('GetCommitService') { 
  6.             steps { 
  7.                 script{ 
  8.                     echo 'Hello World' 
  9.                     echo "${WebHookData}" 
  10.                      
  11.                     // Git Info 
  12.                     webhookdata = readJSON text: """${WebHookData}""" 
  13.                     eventType = webhookdata["object_kind"
  14.                     commits = webhookdata["commits"
  15.                     branchName = webhookdata["ref"] - "refs/heads/" 
  16.                     projectID = webhookdata["project_id"
  17.                     commitID = webhookdata["checkout_sha"
  18.  
  19.  
  20.                     changeServices = [] 
  21.                     for(commit in commits) { 
  22.                         println(commit.id) 
  23.  
  24.                         //added 
  25.                         for (add in commit.added) { 
  26.                             s = add.split("/"as List 
  27.                             if (s.size() > 1){ 
  28.                                 if (changeServices.indexOf(s[0]) == -1){ 
  29.                                     changeServices.add(s[0]) 
  30.                                 } 
  31.                             } 
  32.                         } 
  33.  
  34.                         //modified 
  35.                         for (m in commit.modified) { 
  36.                             s = m.split("/"as List 
  37.                             // println s 
  38.                             // println s.size() 
  39.                             // println s[0] 
  40.                             if (s.size() > 1){ 
  41.                                 // println changeServices.indexOf(s[0]) 
  42.                                 if (changeServices.indexOf(s[0]) == -1){ 
  43.                                     changeServices.add(s[0]) 
  44.                                 } 
  45.                             } 
  46.                         } 
  47.  
  48.                         //removed 
  49.                         for (r in commit.removed) { 
  50.                             s = r.split("/"as List 
  51.                             println s 
  52.                             if (s.size() > 1){ 
  53.                                 if (changeServices.indexOf(s[0]) == -1){ 
  54.                                     changeServices.add(s[0]) 
  55.                                 } 
  56.                             } 
  57.                         } 
  58.                     } 
  59.  
  60.                     println(changeServices) 
  61.                     currentBuild.description = " Trigger by  ${eventType} ${changeServices} " 
  62.                 } 
  63.             } 
  64.         } 
  65.  
  66.         stage('DefineService') { 
  67.             steps { 
  68.                 script{ 
  69.                     println(changeServices) 
  70.                     //服务构建顺序控制 
  71.                     services = ['service02''service01'
  72.                     for (service in services){ 
  73.                         if (changeServices.indexOf(service) != -1){ 
  74.                             jobName = 'microservicecicd-'+service+'-service-CD' 
  75.                             build job: jobName, wait: false,  parameters: [string(name'branchName', value: "${branchName}" )] 
  76.                         } 
  77.                     } 
  78.                 } 
  79.             } 
  80.         } 
  81.     } 

环境库配置webhook

开启webhook,配置hookurl:http://jenkins.idevops.site/generic-webhook-trigger/invoke?token=microservicecicd-scheduler-CD

CD流水线-CD作业

Jenkinsfile

  1. String serviceName ="${JOB_NAME}".split("-")[1] 
  2. String nameSpace = "${JOB_NAME}".split("-")[0].split("/")[-1] 
  3.  
  4.  
  5. //pipeline 
  6. pipeline{ 
  7.     agent { node { label "k8s"}} 
  8.      
  9.     stages{ 
  10.  
  11.        stage("GetCode"){ 
  12.             steps{ 
  13.                 script{ 
  14.                     println("${branchName}"
  15.                     println("${env.branchName}".contains("RELEASE-")) 
  16.                     println "获取代码" 
  17.                     checkout([$class: 'GitSCM', branches: [[name"${env.branchName}"]],  
  18.                                       doGenerateSubmoduleConfigurations: false,  
  19.                                       extensions: [[$class: 'SparseCheckoutPaths',  
  20.                                                     sparseCheckoutPaths: [[path: "${serviceName}"]]]],  
  21.                                       submoduleCfg: [],  
  22.                                       userRemoteConfigs: [[credentialsId: 'gitlab-admin-user', url: "http://gitlab.idevops.site/microservicecicd/microservicecicd-env.git"]]]) 
  23.                 } 
  24.             } 
  25.         } 
  26.  
  27.         stage("HelmDeploy"){ 
  28.             steps{ 
  29.                 script{ 
  30.                   sh ""
  31.                       kubectl create ns "${nameSpace}-uat"  || echo false 
  32.  
  33.                       helm install "${serviceName}" --namespace "${nameSpace}-uat" ./"${serviceName}" ||  helm upgrade "${serviceName}" --namespace "${nameSpace}-uat" ./"${serviceName}" 
  34.  
  35.                       helm list --namespace "${nameSpace}-uat" 
  36.                       helm history "${serviceName}" --namespace "${nameSpace}-uat" 
  37.  
  38.                   ""
  39.                 } 
  40.             } 
  41.         } 
  42.     } 

本文转载自微信公众号「DevOps云学堂」

 

责任编辑:姜华 来源: DevOps云学堂
相关推荐

2021-02-10 08:24:47

微服务CICD

2021-07-09 06:40:59

TektonArgo CD GitOps

2021-05-18 08:00:00

Kubernetes容器进程

2023-04-26 11:29:58

Jenkins版本Java 11

2021-08-09 11:35:40

设计实践应用

2022-04-25 10:44:08

微服务架构设计

2020-10-21 14:10:28

工具测试开发

2023-06-30 09:46:00

服务物理机自动化

2022-09-05 15:12:34

数据库GitHub开发

2022-08-16 22:39:01

Argo CDKubernetes

2021-01-11 09:17:49

GitLabCIMonorepoDocker

2021-01-18 09:35:17

Travis-CGithub ActiLinux

2022-02-22 09:00:00

软件开发CI/CD 管道工具

2021-07-28 13:23:32

CICD管道安全漏洞

2019-07-25 10:31:55

AWSDevOps架构

2021-07-04 07:24:48

GitOps 工具 Argo CD

2019-12-26 15:49:14

微服务架构业务

2016-10-31 16:18:56

架构 设计

2022-08-31 22:25:53

微服务架构DevOPs

2020-12-28 12:22:12

微服务架构微服务API
点赞
收藏

51CTO技术栈公众号