基於Jira的運維發布平台的設計與實現


作者:喬克
公眾號:運維開發故事

上線發布是運維的日常工作,常見的發布方式有:

  • 手動發布
  • Jenkins發布平台
  • Gitlab CI
  • ......

除此之外還有需要開源軟件,他們都有非常不錯的發布管理功能。

面臨的問題

作為運維人員,上線發布是必不可少的一環,一個正常的發布流程是怎么樣的?

  • 需求方提發布任務,走發布流程
  • 供應方執行發布上線

環節看似簡單,但是中間其實是有斷層的。一般企業在走上線流程都是通過一些公共渠道,比如郵件、釘釘、飛書的流程,這些都很難和運維執行上線發布平台進行關聯上,而且也不夠直觀。所以我們就需要解決以下幾個問題:

  • 流程和運維平台建立連接
  • 從發起到結束形成閉環

為啥選擇JIRA?

JIRA優秀的項目管理,問題跟蹤的工具,另外它的流程管理和看板模式也能夠非常直觀看到目前流程處在什么位置。另外它可以通過webhook和其他平台建立友好的連接,方便擴展。再者對於開發、測試、項目管理人員等來說Jira是他們日常的工具,使用熟練度非常高,降低了額外的學習成本。鑒於此,我們選擇JIRA作為運維發布平台,爭取做到一個平台做所有事。

方案設計

設計思路

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

  • Jira上更新狀態觸發Jenkins執行合並分支流水線
  • Gitlab上代碼合並成功后觸發Jenkins執行發布流水線
  • 將發布結果通過釘釘等軟件通知相應的人

整體思路相對簡單,難點主要集中在Jenkins獲取Jira、Gitlab的數據,所幸Jenkins的插件功能非常豐富,這里就使用Generic Webhook Trigger插件,可以很靈活地獲取到觸發軟件地信息。

發布流程方案

然后整理出如下地發布流程。
image.png

涉及軟件

軟件 功能
Jira 發布流程管理
Jenkins 執行各種流水線
Gitlab 代碼倉庫
Kubernetes 應用管理
Helm/kustomize 包管理
釘釘 消息通知
trivy 鏡像掃描
鏡像倉庫 阿里雲鏡像倉庫

PS:這里沒有具體的軟件部署

Jira與Jenkins進行集成合並分支

Jenkins配置

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

  • 配置Jenkins ShareLibrary功能
  • 編寫Jira觸發相應的Jenkinsfile

(1)Jenkins上配置ShareLibarary
系統配置-->系統配置-->Global Pipeline Libraries
image.png
(2)創建流水線,配置Webhook以及添加Jenkinsfile

  • 配置觸發器

先配置一個變量和正則
image.png
再配置一個Token即可
image.png

  • 配置流水線,添加對於的Jenkinsfile

image.png

(3)Jenkinsfile的主要邏輯如下

PS:下面僅列出大致的框架,並沒有詳細的代碼

  • 獲取Jira的配置信息進行解析
  • 根據不同信息執行不同的操作
  • 合並分支主要是通過調Gitlab的API接口完成
#!groovy

@Library('lotbrick') _

def gitlab = new org.devops.gitlab()
def tool = new org.devops.tools()
def dingmes = new org.devops.sendDingTalk()

pipeline {
    agent { node { label "master"}}

    environment {
        DINGTALKHOOK = "https://oapi.dingtalk.com/robot/send?access_token=xxxx"   
    }

    stages{

        stage("FileterData"){
            steps{
                script{
                    response = readJSON text: """${webHookData}"""

                    // println(response)

                    env.eventType = response["webhookEvent"]

                    if (eventType == "jira:issue_updated"){
                        // 獲取狀態值
                        env.jiraStatus = response['issue']['fields']['status']['name']
                        env.gitlabInfos = response['issue']['fields']['customfield_10219']
                        infos = "${gitlabInfos}".split("\r\n")
                        for (info in infos){
                            prName = "$info".split("/")[0]
                            // brName = "$info".split("/")[1]
                            brName = info - "${prName}/"
                            println(prName)
                            println(brName)
                            if (jiraStatus == "已發布(UAT)"){
                                println('進行合並PRE分支操作')
                            }else if (jiraStatus == "已發布(PROD)"){
                                println('進行合並PROD分支操作')
                            }else if (jiraStatus == "已完成"){
								println('進行分支打Tag並刪除原分支')
                            }else{
                                println("查無此項")
                            }
                        }
                    }
                }
            }
        }
    }
    // 構建后的操作
	post {
		failure {
			script{ 
				println("failure:只有構建失敗才會執行")
				dingmes.SendDingTalk("分支合並失敗 ❌")
			}
		}
		aborted {
            script{
				println("aborted:只有取消構建才會執行")
				dingmes.SendDingTalk("分支合並取消 ❌","暫停或中斷")
            }
		}
	}
}

以上Jenkins上配置基本完成。

Jira上配置

Jira上的主要配置如下:

  • 建立工作流
  • 工作流關聯項目
  • 配置項目觸發Webhook

建立工作流

image.png

將工作流關聯項目組

image.png

配置webhook

設置-->系統-->網絡鈎子
image.png

上面配置完成后,即完成Jira上配置,然后就可以在對應項目的看板上查看所以待發布的項目,如下:
image.png
然后進行拖拽或者點擊發布按鈕,即可改變狀態,觸發流水線進行相應的操作了。

Gitlab與Jenkins集成發布系統

開發分支簡要

這里主要使用的是功能分支開發模式,主要分為以下幾個分支:

  • DEV分支:開發環境分支
  • TEST分支:測試環境分支
  • UAT分支:聯調環境分支
  • PRE分支:預發布環境分支
  • MASTER分支:生產環境分支

代碼合並路線是:DEV->TEST->UAT->PRE->MASTER
然后根據不同的分支判斷執行不同環境的部署。

Jenkins配置流水線

(1)配置Webhook插件參數
image.png

獲取Gitlab分支
image.png
定義gitlab push條件,不是任何改動都需要觸發流水線
image.png

image.png
image.png
定義過濾正則表達式
image.png
這樣就只有commit的時候才會觸發流水線。

(2)配置Jenkinsfile

def labels = "slave-${UUID.randomUUID().toString()}"

// 引用共享庫
@Library('jenkins_shareLibrary')

// 應用共享庫中的方法
def tools = new org.devops.tools()

def branchName = ""

// 獲取分支
if ("${gitlabWebhook}" == "gitlabPush"){
    branchName = branch - "refs/heads/"
	currentBuild.description = "構建者${userName} 分支${branchName}"
}

pipeline {
    agent {
        kubernetes {
            label labels
            yaml """
apiVersion: v1
kind: Pod
metadata:
  labels:
    some-label: some-label-value
spec:
  volumes:
  - name: docker-sock
    hostPath:
      path: /var/run/docker.sock
      type: ''
  - name: maven-cache
    persistentVolumeClaim:
      claimName: maven-cache-pvc
  containers:
  - name: jnlp
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/inbound-agent:4.3-4
  - name: maven
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/maven:3.5.0-alpine
    command:
    - cat
    tty: true
    volumeMounts:
    - name: maven-cache
      mountPath: /root/.m2
  - name: docker
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/docker:19.03.11
    command:
    - cat
    tty: true
    volumeMounts:
    - name: docker-sock
      mountPath: /var/run/docker.sock
  - name: sonar-scanner
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/sonar-scanner:latest
    command:
    - cat
    tty: true
  - name: kustomize
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/kustomize:v3.8.1
    command:
    - cat
    tty: true
  - name: kubedog
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/kubedog:v0.5.0
    command: ['cat']
    tty: true
  - name: trivy
    image: registry.cn-hangzhou.aliyuncs.com/rookieops/trivy:v2
    command: ['cat']
    tty: true
    volumeMounts:
      - name: docker-sock
        mountPath: /var/run/docker.sock
"""
        }
    }

    environment {
        auth = 'joker'
    }

    options {
        timestamps()    // 日志會有時間
        skipDefaultCheckout()    // 刪除隱式checkout scm語句
        disableConcurrentBuilds()    //禁止並行
        timeout(time:1, unit:'HOURS') //設置流水線超時時間
    }

    stages {
        // 拉取代碼
        stage('GetCode') {
            steps {
                checkout([$class: 'GitSCM', branches: [[name: "${gitBranch}"]],
                    doGenerateSubmoduleConfigurations: false,
                    extensions: [],
                    submoduleCfg: [],
                    userRemoteConfigs: [[credentialsId: '83d2e934-75c9-48fe-9703-b48e2feff4d8', url: "${gitUrl}"]]])
            }
        }

        // 單元測試和編譯打包
        stage('Build&Test') {
            steps {
                container('maven') {
                    script {
                        tools.PrintMes('編譯打包', 'blue')
                    }
                }
            }
        }
        // 代碼掃描
        stage('CodeScanner') {
            steps {
                container('sonar-scanner') {
                    script {
						tools.PrintMes('代碼掃描', 'blue')
                    }
                }
            }
        }
        // 構建鏡像
        stage('BuildImage') {
            steps {
                container('docker') {
					script {
						tools.PrintMes('構建鏡像', 'blue')
					}
				}
            }
        }
		
		// 鏡像掃描
		stage('Vulnerability Scanner') {
            steps {
                container('trivy') {
                    script{
						tools.PrintMes('鏡像掃描', 'blue')
					}            
                }
            }
        }
		
		// 推送鏡像
		stage('Push Image') {
            steps {
                container('docker') {
                    script{
						tools.PrintMes('推送鏡像', 'blue')
					}            
                }
            }
        }
		
        // 部署
        stage('Deploy DEV') {
			when {
				branchName 'dev'
			}
            steps {
                container('kustomize'){
					script{
						tools.PrintMes('部署DEV環境','blue')
					}
				}
            }
        }
		stage('Deploy TEST') {
			when {
				branchName 'test'
			}
            steps {
                container('kustomize'){
					script{
						tools.PrintMes('部署TEST環境','blue')
					}
				}
            }
        }
		stage('Deploy UAT') {
			when {
				branchName 'uat'
			}
            steps {
                container('kustomize'){
					script{
						tools.PrintMes('部署UAT環境','blue')
					}
				}
            }
        }
		stage('Deploy PRE') {
			when {
				branchName 'pre'
			}
            steps {
                container('kustomize'){
					script{
						tools.PrintMes('部署PRE環境','blue')
					}
				}
            }
        }
		stage('Deploy PROD') {
			when {
				branchName 'master'
			}
            steps {
                container('kustomize'){
					script{
						tools.PrintMes('部署PROD環境','blue')
					}
				}
            }
        }
		
		// 跟蹤應用啟動情況
		stage('Check App Start') {
			steps{
				container('kubedog'){
					script{
						tools.PrintMes('跟蹤應用啟動', 'blue')
					}
				}
			}
		}
		
        // 接口測試
        stage('InterfaceTest') {
            steps {
                sh 'echo "接口測試"'
            }
        }

    }
    // 構建后的操作
    post {
        success {
            script {
                println('success:只有構建成功才會執行')
                currentBuild.description += '\n構建成功!'
                dingmes.SendDingTalk("構建成功 ✅")
            }
        }
        failure {
            script {
                println('failure:只有構建失敗才會執行')
                currentBuild.description += '\n構建失敗!'
                dingmes.SendDingTalk("構建失敗 ❌")
            }
        }
        aborted {
            script {
                println('aborted:只有取消構建才會執行')
                currentBuild.description += '\n構建取消!'
                dingmes.SendDingTalk("構建失敗 ❌","暫停或中斷")
            }
        }
    }
}

(3)在Gitlab上配置鈎子
settings->webhook
image.png

到這里,Gitlab和Jenkins集成就差不多完成了,后面就是具體的調試以及配置了。

寫到最后

道路千萬條,適合自己才最好。

上面是根據工作的實際情況做的運維發布,整體思路還有實現方式並不復雜,主要是充分利用各個軟件地webhook能力,以及充分利用Jenkins靈活的插件功能,使得從創建發布計划和執行發布進行打通。

個人覺得還是有必要記錄一下,也希望能幫助到有這方面需要的人。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM