基于Jira的運維發(fā)布平臺的設(shè)計與實現(xiàn)
本文轉(zhuǎn)載自微信公眾號「運維開發(fā)故事」,作者喬克 。轉(zhuǎn)載本文請聯(lián)系運維開發(fā)故事公眾號。
上線發(fā)布是運維的日常工作,常見的發(fā)布方式有:
- 手動發(fā)布
- Jenkins發(fā)布平臺
- Gitlab CI
- ......
除此之外還有需要開源軟件,他們都有非常不錯的發(fā)布管理功能。
面臨的問題
作為運維人員,上線發(fā)布是必不可少的一環(huán),一個正常的發(fā)布流程是怎么樣的?
- 需求方提發(fā)布任務(wù),走發(fā)布流程
- 供應(yīng)方執(zhí)行發(fā)布上線
環(huán)節(jié)看似簡單,但是中間其實是有斷層的。一般企業(yè)在走上線流程都是通過一些公共渠道,比如郵件、釘釘、飛書的流程,這些都很難和運維執(zhí)行上線發(fā)布平臺進行關(guān)聯(lián)上,而且也不夠直觀。所以我們就需要解決以下幾個問題:
流程和運維平臺建立連接
從發(fā)起到結(jié)束形成閉環(huán)
為了選擇JIRA?
JIRA優(yōu)秀的項目管理,問題跟蹤的工具,另外它的流程管理和看板模式也能夠非常直觀看到目前流程處在什么位置。另外它可以通過webhook和其他平臺建立友好的連接,方便擴展。再者對于開發(fā)、測試、項目管理人員等來說Jira是他們?nèi)粘5墓ぞ?,使用熟練度非常高,降低了額外的學(xué)習(xí)成功。鑒于此,我們選擇JIRA作為運維發(fā)布平臺,爭取做到一個平臺做所有事。
方案設(shè)計
設(shè)計思路
充分利用Jira、Gitlab的webhook功能,以及Jenkins的靈活性。
- Jira上更新狀態(tài)觸發(fā)Jenkins執(zhí)行合并分支流水線
- Gitlab上代碼合并成功后觸發(fā)Jenkins執(zhí)行發(fā)布流水線
- 將發(fā)布結(jié)果通過釘釘?shù)溶浖ㄖ鄳?yīng)的人
整體思路相對簡單,難點主要集中在Jenkins獲取Jira、Gitlab的數(shù)據(jù),所幸Jenkins的插件功能非常豐富,這里就使用Generic Webhook Trigger插件,可以很靈活地獲取到觸發(fā)軟件的信息。
發(fā)布流程方案
然后整理出如下的發(fā)布流程。
涉及軟件
軟件 | 功能 |
---|---|
Jira | 發(fā)布流程管理 |
Jenkins | 執(zhí)行各種流水線 |
Gitlab | 代碼倉庫 |
Kubernetes | 應(yīng)用管理 |
Helm/kustomize | 包管理 |
釘釘 | 消息通知 |
trivy | 鏡像掃描 |
鏡像倉庫 | 阿里云鏡像倉庫 |
PS:這里沒有具體的軟件部署
Jira與Jenkins進行集成合并分支
Jenkins配置
Jenkins的配置主要有兩部分,如下:
- 配置Jenkins ShareLibrary功能
- 編寫Jira觸發(fā)相應(yīng)的Jenkinsfile
(1)Jenkins上配置ShareLibarary 系統(tǒng)配置-->系統(tǒng)配置-->Global Pipeline Libraries
(2)創(chuàng)建流水線,配置Webhook以及添加Jenkinsfile
- 配置觸發(fā)器
先配置一個變量和正則
再配置一個Token即可
- 配置流水線,添加對應(yīng)的Jenkinsfile
image.png
(3)Jenkinsfile的主要邏輯如下
PS:下面僅列出大致的框架,并沒有詳細的代碼
- 獲取Jira的配置信息進行解析
- 根據(jù)不同信息執(zhí)行不同的操作
- 合并分支主要是通過調(diào)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"){
- // 獲取狀態(tài)值
- 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 == "已發(fā)布(UAT)"){
- println('進行合并PRE分支操作')
- }else if (jiraStatus == "已發(fā)布(PROD)"){
- println('進行合并PROD分支操作')
- }else if (jiraStatus == "已完成"){
- println('進行分支打Tag并刪除原分支')
- }else{
- println("查無此項")
- }
- }
- }
- }
- }
- }
- }
- // 構(gòu)建后的操作
- post {
- failure {
- script{
- println("failure:只有構(gòu)建失敗才會執(zhí)行")
- dingmes.SendDingTalk("分支合并失敗 ❌")
- }
- }
- aborted {
- script{
- println("aborted:只有取消構(gòu)建才會執(zhí)行")
- dingmes.SendDingTalk("分支合并取消 ❌","暫?;蛑袛?quot;)
- }
- }
- }
- }
以上Jenkins上配置基本完成。
Jira上配置
Jira上的主要配置如下:
- 建立工作流
- 工作流關(guān)聯(lián)項目
- 配置項目觸發(fā)Webhook
建立工作流
將工作流關(guān)聯(lián)項目組
配置webhook
設(shè)置-->系統(tǒng)-->網(wǎng)絡(luò)鉤子
上面配置完成后,即完成Jira上配置,然后就可以在對應(yīng)項目的看板上查看所以待發(fā)布的項目,如下:
然后進行拖拽或者點擊發(fā)布按鈕,即可改變狀態(tài),觸發(fā)流水線進行相應(yīng)的操作了。
Gitlab與Jenkins集成發(fā)布系統(tǒng)
開發(fā)分支簡要
這里主要使用的是功能分支開發(fā)模式,主要分為以下幾個分支:
- DEV分支:開發(fā)環(huán)境分支
- TEST分支:測試環(huán)境分支
- UAT分支:聯(lián)調(diào)環(huán)境分支
- PRE分支:預(yù)發(fā)布環(huán)境分支
- MASTER分支:生產(chǎn)環(huán)境分支
代碼合并路線是:DEV->TEST->UAT->PRE->MASTER 然后根據(jù)不同的分支判斷執(zhí)行不同環(huán)境的部署。
Jenkins配置流水線
(1)配置Webhook插件參數(shù)
獲取Gitlab分支
定義gitlab push條件,不是任何改動都需要觸發(fā)流水線
定義過濾正則表達式
這樣就只有commit的時候才會觸發(fā)流水線。
(2)配置Jenkinsfile
- def labels = "slave-${UUID.randomUUID().toString()}"
- // 引用共享庫
- @Library('jenkins_shareLibrary')
- // 應(yīng)用共享庫中的方法
- def tools = new org.devops.tools()
- def branchName = ""
- // 獲取分支
- if ("${gitlabWebhook}" == "gitlabPush"){
- branchName = branch - "refs/heads/"
- currentBuild.description = "構(gòu)建者${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') //設(shè)置流水線超時時間
- }
- 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')
- }
- }
- }
- }
- // 構(gòu)建鏡像
- stage('BuildImage') {
- steps {
- container('docker') {
- script {
- tools.PrintMes('構(gòu)建鏡像', '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環(huán)境','blue')
- }
- }
- }
- }
- stage('Deploy TEST') {
- when {
- branchName 'test'
- }
- steps {
- container('kustomize'){
- script{
- tools.PrintMes('部署TEST環(huán)境','blue')
- }
- }
- }
- }
- stage('Deploy UAT') {
- when {
- branchName 'uat'
- }
- steps {
- container('kustomize'){
- script{
- tools.PrintMes('部署UAT環(huán)境','blue')
- }
- }
- }
- }
- stage('Deploy PRE') {
- when {
- branchName 'pre'
- }
- steps {
- container('kustomize'){
- script{
- tools.PrintMes('部署PRE環(huán)境','blue')
- }
- }
- }
- }
- stage('Deploy PROD') {
- when {
- branchName 'master'
- }
- steps {
- container('kustomize'){
- script{
- tools.PrintMes('部署PROD環(huán)境','blue')
- }
- }
- }
- }
- // 跟蹤應(yīng)用啟動情況
- stage('Check App Start') {
- steps{
- container('kubedog'){
- script{
- tools.PrintMes('跟蹤應(yīng)用啟動', 'blue')
- }
- }
- }
- }
- // 接口測試
- stage('InterfaceTest') {
- steps {
- sh 'echo "接口測試"'
- }
- }
- }
- // 構(gòu)建后的操作
- post {
- success {
- script {
- println('success:只有構(gòu)建成功才會執(zhí)行')
- currentBuild.description += '\n構(gòu)建成功!'
- dingmes.SendDingTalk("構(gòu)建成功 ✅")
- }
- }
- failure {
- script {
- println('failure:只有構(gòu)建失敗才會執(zhí)行')
- currentBuild.description += '\n構(gòu)建失敗!'
- dingmes.SendDingTalk("構(gòu)建失敗 ❌")
- }
- }
- aborted {
- script {
- println('aborted:只有取消構(gòu)建才會執(zhí)行')
- currentBuild.description += '\n構(gòu)建取消!'
- dingmes.SendDingTalk("構(gòu)建失敗 ❌","暫停或中斷")
- }
- }
- }
- }
(3)在Gitlab上配置鉤子 settings->webhook
到這里,Gitlab和Jenkins集成就差不多完成了,后面就是具體的調(diào)試以及配置了。
寫到最后
道路千萬條,適合自己才最好。
上面是根據(jù)工作的實際情況做的運維發(fā)布,整體思路還有實現(xiàn)方式并不復(fù)雜,主要是充分利用各個軟件的webhook能力,以及充分利用Jenkins靈活的插件功能,使得從創(chuàng)建發(fā)布計劃和執(zhí)行發(fā)布進行打通。
個人覺得還是有必要記錄一下,也希望能幫助到有這方面需要的人。