基于Jira的运维发布平台的设计与实现,


上线发布是运维的日常工作,常见的发布方式有:

  • 手动发布
  • Jenkins发布平台
  • Gitlab CI
  • ......

除此之外还有需要开源软件,他们都有非常不错的发布管理功能。

面临的问题

作为运维人员,上线发布是必不可少的一环,一个正常的发布流程是怎么样的?

  • 需求方提发布任务,走发布流程
  • 供应方执行发布上线

环节看似简单,但是中间其实是有断层的。一般企业在走上线流程都是通过一些公共渠道,比如邮件、钉钉、飞书的流程,这些都很难和运维执行上线发布平台进行关联上,而且也不够直观。所以我们就需要解决以下几个问题:

流程和运维平台建立连接

从发起到结束形成闭环

为了选择JIRA?

JIRA优秀的项目管理,问题跟踪的工具,另外它的流程管理和看板模式也能够非常直观看到目前流程处在什么位置。另外它可以通过webhook和其他平台建立友好的连接,方便扩展。再者对于开发、测试、项目管理人员等来说Jira是他们日常的工具,使用熟练度非常高,降低了额外的学习成功。鉴于此,我们选择JIRA作为运维发布平台,争取做到一个平台做所有事。

方案设计

设计思路

充分利用Jira、Gitlab的webhook功能,以及Jenkins的灵活性。

  • Jira上更新状态触发Jenkins执行合并分支流水线
  • Gitlab上代码合并成功后触发Jenkins执行发布流水线
  • 将发布结果通过钉钉等软件通知相应的人

整体思路相对简单,难点主要集中在Jenkins获取Jira、Gitlab的数据,所幸Jenkins的插件功能非常丰富,这里就使用Generic Webhook Trigger插件,可以很灵活地获取到触发软件的信息。

发布流程方案

然后整理出如下的发布流程。

涉及软件

软件 功能
Jira 发布流程管理
Jenkins 执行各种流水线
Gitlab 代码仓库
Kubernetes 应用管理
Helm/kustomize 包管理
钉钉 消息通知
trivy 镜像扫描
镜像仓库 阿里云镜像仓库

PS:这里没有具体的软件部署

Jira与Jenkins进行集成合并分支

Jenkins配置

Jenkins的配置主要有两部分,如下:

  • 配置Jenkins ShareLibrary功能
  • 编写Jira触发相应的Jenkinsfile

(1)Jenkins上配置ShareLibarary 系统配置-->系统配置-->Global Pipeline Libraries

(2)创建流水线,配置Webhook以及添加Jenkinsfile

  • 配置触发器

先配置一个变量和正则

再配置一个Token即可

  • 配置流水线,添加对应的Jenkinsfile

image.png

(3)Jenkinsfile的主要逻辑如下

PS:下面仅列出大致的框架,并没有详细的代码

  • 获取Jira的配置信息进行解析
  • 根据不同信息执行不同的操作
  • 合并分支主要是通过调Gitlab的API接口完成
  1. #!groovy 
  2.  
  3. @Library('lotbrick') _ 
  4.  
  5. def gitlab = new org.devops.gitlab() 
  6. def tool = new org.devops.tools() 
  7. def dingmes = new org.devops.sendDingTalk() 
  8.  
  9. pipeline { 
  10.     agent { node { label "master"}} 
  11.  
  12.     environment { 
  13.         DINGTALKHOOK = "https://oapi.dingtalk.com/robot/send?access_token=xxxx"    
  14.     } 
  15.  
  16.     stages{ 
  17.  
  18.         stage("FileterData"){ 
  19.             steps{ 
  20.                 script{ 
  21.                     response = readJSON text: """${webHookData}""" 
  22.  
  23.                     // println(response) 
  24.  
  25.                     env.eventType = response["webhookEvent"] 
  26.  
  27.                     if (eventType == "jira:issue_updated"){ 
  28.                         // 获取状态值 
  29.                         env.jiraStatus = response['issue']['fields']['status']['name'] 
  30.                         env.gitlabInfos = response['issue']['fields']['customfield_10219'] 
  31.                         infos = "${gitlabInfos}".split("\r\n") 
  32.                         for (info in infos){ 
  33.                             prName = "$info".split("/")[0] 
  34.                             // brName = "$info".split("/")[1] 
  35.                             brName = info - "${prName}/" 
  36.                             println(prName) 
  37.                             println(brName) 
  38.                             if (jiraStatus == "已发布(UAT)"){ 
  39.                                 println('进行合并PRE分支操作') 
  40.                             }else if (jiraStatus == "已发布(PROD)"){ 
  41.                                 println('进行合并PROD分支操作') 
  42.                             }else if (jiraStatus == "已完成"){ 
  43.         println('进行分支打Tag并删除原分支') 
  44.                             }else{ 
  45.                                 println("查无此项") 
  46.                             } 
  47.                         } 
  48.                     } 
  49.                 } 
  50.             } 
  51.         } 
  52.     } 
  53.     // 构建后的操作 
  54.  post { 
  55.   failure { 
  56.    script{  
  57.     println("failure:只有构建失败才会执行") 
  58.     dingmes.SendDingTalk("分支合并失败 ❌") 
  59.    } 
  60.   } 
  61.   aborted { 
  62.             script{ 
  63.     println("aborted:只有取消构建才会执行") 
  64.     dingmes.SendDingTalk("分支合并取消 ❌","暂停或中断") 
  65.             } 
  66.   } 
  67.  } 

以上Jenkins上配置基本完成。

Jira上配置

Jira上的主要配置如下:

  • 建立工作流
  • 工作流关联项目
  • 配置项目触发Webhook

建立工作流

将工作流关联项目组

配置webhook

设置-->系统-->网络钩子

上面配置完成后,即完成Jira上配置,然后就可以在对应项目的看板上查看所以待发布的项目,如下:

然后进行拖拽或者点击发布按钮,即可改变状态,触发流水线进行相应的操作了。

Gitlab与Jenkins集成发布系统

开发分支简要

这里主要使用的是功能分支开发模式,主要分为以下几个分支:

  • DEV分支:开发环境分支
  • TEST分支:测试环境分支
  • UAT分支:联调环境分支
  • PRE分支:预发布环境分支
  • MASTER分支:生产环境分支

代码合并路线是:DEV->TEST->UAT->PRE->MASTER 然后根据不同的分支判断执行不同环境的部署。

Jenkins配置流水线

(1)配置Webhook插件参数

获取Gitlab分支

定义gitlab push条件,不是任何改动都需要触发流水线

定义过滤正则表达式

这样就只有commit的时候才会触发流水线。

(2)配置Jenkinsfile

  1. def labels = "slave-${UUID.randomUUID().toString()}" 
  2.  
  3. // 引用共享库 
  4. @Library('jenkins_shareLibrary') 
  5.  
  6. // 应用共享库中的方法 
  7. def tools = new org.devops.tools() 
  8.  
  9. def branchName = "" 
  10.  
  11. // 获取分支 
  12. if ("${gitlabWebhook}" == "gitlabPush"){ 
  13.     branchName = branch - "refs/heads/" 
  14.  currentBuild.description = "构建者${userName} 分支${branchName}" 
  15.  
  16. pipeline { 
  17.     agent { 
  18.         kubernetes { 
  19.             label labels 
  20.             yaml """ 
  21. apiVersion: v1 
  22. kind: Pod 
  23. metadata: 
  24.   labels: 
  25.     some-label: some-label-value 
  26. spec: 
  27.   volumes: 
  28.   - name: docker-sock 
  29.     hostPath: 
  30.       path: /var/run/docker.sock 
  31.       type: '' 
  32.   - name: maven-cache 
  33.     persistentVolumeClaim: 
  34.       claimName: maven-cache-pvc 
  35.   containers: 
  36.   - name: jnlp 
  37.     image: registry.cn-hangzhou.aliyuncs.com/rookieops/inbound-agent:4.3-4 
  38.   - name: maven 
  39.     image: registry.cn-hangzhou.aliyuncs.com/rookieops/maven:3.5.0-alpine 
  40.     command: 
  41.     - cat 
  42.     tty: true 
  43.     volumeMounts: 
  44.     - name: maven-cache 
  45.       mountPath: /root/.m2 
  46.   - name: docker 
  47.     image: registry.cn-hangzhou.aliyuncs.com/rookieops/docker:19.03.11 
  48.     command: 
  49.     - cat 
  50.     tty: true 
  51.     volumeMounts: 
  52.     - name: docker-sock 
  53.       mountPath: /var/run/docker.sock 
  54.   - name: sonar-scanner 
  55.     image: registry.cn-hangzhou.aliyuncs.com/rookieops/sonar-scanner:latest 
  56.     command: 
  57.     - cat 
  58.     tty: true 
  59.   - name: kustomize 
  60.     image: registry.cn-hangzhou.aliyuncs.com/rookieops/kustomize:v3.8.1 
  61.     command: 
  62.     - cat 
  63.     tty: true 
  64.   - name: kubedog 
  65.     image: registry.cn-hangzhou.aliyuncs.com/rookieops/kubedog:v0.5.0 
  66.     command: ['cat'] 
  67.     tty: true 
  68.   - name: trivy 
  69.     image: registry.cn-hangzhou.aliyuncs.com/rookieops/trivy:v2 
  70.     command: ['cat'] 
  71.     tty: true 
  72.     volumeMounts: 
  73.       - name: docker-sock 
  74.         mountPath: /var/run/docker.sock 
  75. """ 
  76.         } 
  77.     } 
  78.  
  79.     environment { 
  80.         auth = 'joker' 
  81.     } 
  82.  
  83.     options { 
  84.         timestamps()    // 日志会有时间 
  85.         skipDefaultCheckout()    // 删除隐式checkout scm语句 
  86.         disableConcurrentBuilds()    //禁止并行 
  87.         timeout(time:1, unit:'HOURS') //设置流水线超时时间 
  88.     } 
  89.  
  90.     stages { 
  91.         // 拉取代码 
  92.         stage('GetCode') { 
  93.             steps { 
  94.                 checkout([$class: 'GitSCM', branches: [[name: "${gitBranch}"]], 
  95.                     doGenerateSubmoduleConfigurations: false, 
  96.                     extensions: [], 
  97.                     submoduleCfg: [], 
  98.                     userRemoteConfigs: [[credentialsId: '83d2e934-75c9-48fe-9703-b48e2feff4d8', url: "${gitUrl}"]]]) 
  99.             } 
  100.         } 
  101.  
  102.         // 单元测试和编译打包 
  103.         stage('Build&Test') { 
  104.             steps { 
  105.                 container('maven') { 
  106.                     script { 
  107.                         tools.PrintMes('编译打包', 'blue') 
  108.                     } 
  109.                 } 
  110.             } 
  111.         } 
  112.         // 代码扫描 
  113.         stage('CodeScanner') { 
  114.             steps { 
  115.                 container('sonar-scanner') { 
  116.                     script { 
  117.       tools.PrintMes('代码扫描', 'blue') 
  118.                     } 
  119.                 } 
  120.             } 
  121.         } 
  122.         // 构建镜像 
  123.         stage('BuildImage') { 
  124.             steps { 
  125.                 container('docker') { 
  126.      script { 
  127.       tools.PrintMes('构建镜像', 'blue') 
  128.      } 
  129.     } 
  130.             } 
  131.         } 
  132.    
  133.   // 镜像扫描 
  134.   stage('Vulnerability Scanner') { 
  135.             steps { 
  136.                 container('trivy') { 
  137.                     script{ 
  138.       tools.PrintMes('镜像扫描', 'blue') 
  139.      }             
  140.                 } 
  141.             } 
  142.         } 
  143.    
  144.   // 推送镜像 
  145.   stage('Push Image') { 
  146.             steps { 
  147.                 container('docker') { 
  148.                     script{ 
  149.       tools.PrintMes('推送镜像', 'blue') 
  150.      }             
  151.                 } 
  152.             } 
  153.         } 
  154.    
  155.         // 部署 
  156.         stage('Deploy DEV') { 
  157.    when { 
  158.     branchName 'dev' 
  159.    } 
  160.             steps { 
  161.                 container('kustomize'){ 
  162.      script{ 
  163.       tools.PrintMes('部署DEV环境','blue') 
  164.      } 
  165.     } 
  166.             } 
  167.         } 
  168.   stage('Deploy TEST') { 
  169.    when { 
  170.     branchName 'test' 
  171.    } 
  172.             steps { 
  173.                 container('kustomize'){ 
  174.      script{ 
  175.       tools.PrintMes('部署TEST环境','blue') 
  176.      } 
  177.     } 
  178.             } 
  179.         } 
  180.   stage('Deploy UAT') { 
  181.    when { 
  182.     branchName 'uat' 
  183.    } 
  184.             steps { 
  185.                 container('kustomize'){ 
  186.      script{ 
  187.       tools.PrintMes('部署UAT环境','blue') 
  188.      } 
  189.     } 
  190.             } 
  191.         } 
  192.   stage('Deploy PRE') { 
  193.    when { 
  194.     branchName 'pre' 
  195.    } 
  196.             steps { 
  197.                 container('kustomize'){ 
  198.      script{ 
  199.       tools.PrintMes('部署PRE环境','blue') 
  200.      } 
  201.     } 
  202.             } 
  203.         } 
  204.   stage('Deploy PROD') { 
  205.    when { 
  206.     branchName 'master' 
  207.    } 
  208.             steps { 
  209.                 container('kustomize'){ 
  210.      script{ 
  211.       tools.PrintMes('部署PROD环境','blue') 
  212.      } 
  213.     } 
  214.             } 
  215.         } 
  216.    
  217.   // 跟踪应用启动情况 
  218.   stage('Check App Start') { 
  219.    steps{ 
  220.     container('kubedog'){ 
  221.      script{ 
  222.       tools.PrintMes('跟踪应用启动', 'blue') 
  223.      } 
  224.     } 
  225.    } 
  226.   } 
  227.    
  228.         // 接口测试 
  229.         stage('InterfaceTest') { 
  230.             steps { 
  231.                 sh 'echo "接口测试"' 
  232.             } 
  233.         } 
  234.  
  235.     } 
  236.     // 构建后的操作 
  237.     post { 
  238.         success { 
  239.             script { 
  240.                 println('success:只有构建成功才会执行') 
  241.                 currentBuild.description += '\n构建成功!' 
  242.                 dingmes.SendDingTalk("构建成功 ✅") 
  243.             } 
  244.         } 
  245.         failure { 
  246.             script { 
  247.                 println('failure:只有构建失败才会执行') 
  248.                 currentBuild.description += '\n构建失败!' 
  249.                 dingmes.SendDingTalk("构建失败 ❌") 
  250.             } 
  251.         } 
  252.         aborted { 
  253.             script { 
  254.                 println('aborted:只有取消构建才会执行') 
  255.                 currentBuild.description += '\n构建取消!' 
  256.                 dingmes.SendDingTalk("构建失败 ❌","暂停或中断") 
  257.             } 
  258.         } 
  259.     } 

(3)在Gitlab上配置钩子 settings->webhook

到这里,Gitlab和Jenkins集成就差不多完成了,后面就是具体的调试以及配置了。

写到最后

道路千万条,适合自己才最好。

上面是根据工作的实际情况做的运维发布,整体思路还有实现方式并不复杂,主要是充分利用各个软件的webhook能力,以及充分利用Jenkins灵活的插件功能,使得从创建发布计划和执行发布进行打通。

个人觉得还是有必要记录一下,也希望能帮助到有这方面需要的人。

相关内容