使用Jenkins构建CI/CD之多分支流水线指北(实战)

手把手带你一块儿从0到1搭建一个企业级的自动化构建流程 网上完整的关于多分支流水线的配置不多,但愿这篇不短的文章能给你带来帮助html

简介

  • 从0到1打造先后端自动化工做流
  • 企业级的实践笔记
  • 最佳实践(可在此基础逐步完善)

缘起

因为公司的Jenkins配置没有部署成功的通知,在我学了几天的Jenkins后终因而对公司的Jenkins配置下手了,结果我刚装完dingtalk插件自动重启后,发现以前主管配置的构建项目数据都丢失了,害,正好给了我练手的机会,因而就有了如下从0到1的辛酸历程。前端

在Docker中安装并运行Jenkins

这里假设你的服务器已经装好了dockerjava

使用的镜像是jenkinsci/blueocean,这是一个jenkins的稳定及持续维护的镜像源,自己就集成了Blue Ocean等使用插件,很是方便。node

拉取镜像

docker pull jenkinsci/blueocean
复制代码

运行Jenkins

docker run -idt --name kmywjenkins -p 9090:8080 -p 60000:50000 -v jenkins-data:/var/jenkins_home -v /data/web-data/docker.sock:/var/run/docker.sock jenkinsci/blueocean
复制代码

参数解释:linux

-idt 以交互的方式、新建一个模拟终端运行容器git

--name 容器的别名web

-p 指定容器映射宿主机的端口 -> 宿主机端口:容器端口正则表达式

-v jenkins-data:/var/jenkins_home Jenkins容器在工做的时候,若是要执行Docker的命令(例如 docker ps、docker run等),须要有个途径能链接到宿主机的docker服务,此参数就是用来创建容器和宿主机docker服务的链接的spring

-v /data/web-data/docker.sock:/var/run/docker.sock 将该容器的数据保留在宿主机的目录,这样即便容器崩溃了,里面的配置和任务都不会丢失docker

须要注意的是,docker中默认是以jenkins用户运行的Jenkins,如需以root用户能够加参数-u root,本示例未指定root。

访问Jenkins Docker容器

有时候须要进入Jenkins容器执行一些命令,能够经过docker exec命令访问,例如:docker exec -it [containerid] bash

若要手动重启Jenkins,能够执行如下命令:docker restart [containerid]

Jenkins基本配置

经过以上步骤,若是正常走到这里,能够经过如下地址访问http://121.41.16.183:9090/,ip地址为服务器的地址。

解锁jenkins

输入一下命令获取解锁的token,docker exec kmywjenkins cat /var/jenkins_home/secrets/initialAdminPassword

在浏览器中输入对应的token以解锁:

Unlock Jenkins page

建立凭据

链接git仓库,ssh链接服务器均须要相应的凭据,能够在凭据管理中先建立好,而后须要使用的地方直接选择凭据便可。这里以链接git、ssh须要的凭据为例:

  1. 我司用得版本管理工具是gitte,以gitte为例,其它版本管理工具配置也同样

​ 类型选择Username with password

​ 用户名密码为登陆gitte的帐号密码

​ ID是凭据的惟一标识,可自定义,后面在JenkinsFile中经过ID去引用凭据

image-20201015110502890

配置后的结果

image-20201015110924897

  1. ssh链接服务器时须要密钥,咱们先在服务器生成一对公私钥,而后复制私钥,填入便可

    类型选择SSH Username with private key

    Username是链接服务器的用户名,如jenkins

    Private Key项选中Enter directly,点击Add,粘贴刚复制的私钥

image-20201015111417561

配置后的结果

image-20201015111823495

建立一个多分支流水线

以前的Jenkins任务是FreeStyle的方式建立的,这种方式不够灵活,界面也不够清爽,这里选择使用声明式流水线方式(Declarative Pipeline)建立,能够多分支独立构建,便于之后的扩展。

咱们这里使用 BlueOcean 这种方式来完成此处 CI/CD 的工做,BlueOcean 是 Jenkins 团队从用户体验角度出发,专为 Jenkins Pipeline 从新设计的一套 UI 界面,仍然兼容之前的 fressstyle 类型的 job,BlueOcean 具备如下的一些特性:

  • 连续交付(CD)Pipeline 的复杂可视化,容许快速直观的了解 Pipeline 的状态
  • 能够经过 Pipeline 编辑器直观的建立 Pipeline
  • 须要干预或者出现问题时快速定位,BlueOcean 显示了 Pipeline 须要注意的地方,便于异常处理和提升生产力
  • 用于分支和拉取请求的本地集成能够在 GitHub 或者 Bitbucket 中与其余人进行代码协做时最大限度提升开发人员的生产力。

若是安装的是jenkinsci/blueocean镜像,默认是已经集成了BlueOcean,没有的可前往插件管理安装对应的插件。

install BlueOcean

点击打开Blue Ocean,能够看到已经建立好的两个流水线,分别是前端和后台,须要用到不一样的工具,在后面会提到,如何建立流水线

image-20201015113510340

点击建立流水线

image-20201015113846536

我司用的是gitte,因此选择Git,而后填入要链接的仓库地址,须要链接到Git仓库的凭据,咱们以前已经建立好了,直接选中便可,若是未建立,在下面的表单直接编辑便可,最后点击建立流水线

image-20201015114115860

到这里咱们就建立了一个多分支流水线,Jenkins会扫描仓库,带有JenkinsFile的分支会被检测出来,JenkinFile是多分支流水线的配置文件,使用的是Groovy语法,能够直接点击建立流水线,Jenkins会自动为你的项目建立一个JenkinsFile

image-20201015114453409

如今能够可视化地编辑想要执行的阶段及步骤,这里加了一个打包的阶段,里面有个步骤是提示开始打包,点击保存

image-20201015115159132

填入提交信息,点击Save & Run,会讲JenkinsFile上传到git,并根据JenkinsFile执行一个构建任务,目前的构建步骤只有一个,是提示开始打包

image-20201015115411207

我这里不知道为何会卡在这个地方不动,因此我在vscode直接建立并编辑JenkinsFile,这种方式更灵活,我更推荐这种方式,下面我会先简单介绍下JeninsFile的基础语法,仅包含本项目用到的,对于中小企业的构建需求,基本够用了。

JenkinsFile基础语法

只需先了解大体的语法,具体的用法会在后面说明

// 前端项目JenkinsFile配置,后端项目配置稍有不一样,后面会区分说明
pipeline {
  agent any
  environment {
    HOST_TEST = 'root@121.41.16.183'
    HOST_ONLINE = 'jenkins@39.101.219.110'
    SOURCE_DIR = 'dist/*'
    TARGET_DIR = '/data/www/kuaimen-yunying-front'
  }
  parameters {
    choice(
      description: '你须要选择哪一个环境进行部署 ?',
      name: 'env',
      choices: ['测试环境', '线上环境']
    )    
    string(name: 'update', defaultValue: '', description: '本次更新内容?')      
  }
  triggers {
    GenericTrigger(
     genericVariables: [
      [key: 'ref', value: '$.ref']
     ],
     causeString: 'Triggered on $ref',
     token: 'runcenter-front-q1w2e3r4t5',
     tokenCredentialId: '',
     printContributedVariables: true,
     printPostContent: true,
     silentResponse: false,
     regexpFilterText: '$ref',
     regexpFilterExpression: 'refs/heads/' + BRANCH_NAME
    )
  } 
  stages {
    stage('获取git commit message') {
     steps {
       script {
         env.GIT_COMMIT_MSG = sh (script: 'git log -1 --pretty=%B ${GIT_COMMIT}', returnStdout: true).trim()
       }
     }
  }
    
    stage('打包') {
      steps {
        nodejs('nodejs-12.16') {
          echo '开始安装依赖'
          sh 'yarn'
          echo '开始打包'
          sh 'yarn run build'
        }
      }
    }

    stage('部署') {
      when {
        expression {
          params.env == '测试环境'
        }
      }
      steps {
        sshagent(credentials: ['km-test2']) {
          sh "ssh -o StrictHostKeyChecking=no ${HOST_TEST} uname -a"
          sh "scp -r ${SOURCE_DIR} ${HOST_TEST}:${TARGET_DIR}"
          sh 'echo "部署成功~"'
        }
      }
    }

    stage('发布') {
      when {
        expression {
          params.env == '线上环境'
        }
      }
      steps {
        sshagent(credentials: ['km-online']) {
          sh "ssh -o StrictHostKeyChecking=no ${HOST_ONLINE} uname -a"
          sh "scp -r ${SOURCE_DIR} ${HOST_ONLINE}:${TARGET_DIR}"
          sh 'echo "发布成功~"'
        }
      }
    }
  }

  post {
    success {
      dingtalk (
        robot: '77d4c82d-3794-4583-bc7f-556902fee6b0',
        type: 'MARKDOWN',
        atAll: true,
        title: '你有新的消息,请注意查收',
        text:[
          '# 运营管理系统发布通知',
          '---',
          '#### **所属:前端**',
          "#### **构建任务:${env.BUILD_DISPLAY_NAME}**",
          "#### **Git commit:${env.GIT_COMMIT_MSG}**",
          "#### **本次更新内容:${params.update}**",
          "#### **部署环境:${params.env}**",
          '#### **构建结果:成功**'
        ]
      )
    }
  }
}
复制代码

pipeline 必须在最外层

agent 定义了在哪一个环境里执行,默认any

stages 阶段,标识构建流程的标签块,子节点是stage

steps 执行步骤

post 全部阶段执行完成后执行一些逻辑

when 能够控制该阶段是否执行

environment 环境变量,在这里定义的变量,JenkinsFile的任何地方均可以访问

tools 项目使用到的构建工具,声明系统配置中已经定义好的工具,如maven

parameters 定义参数,能够提供用户输入或者选择

post 构建结束后会执行这里,有successfailuresuccess,本示例将在success(构建成功时)发起钉钉通知

CI/CD流程

因为我司的技术团队较小,CI/CD流程就没那么复杂,不会包含代码检查、自动化测试、Code Review等流程,我将简要说明我所搭建的前端与后端CI/CD流程以及为何这么搭建。

前端

提供两种构建方式,一种是代码上传自动构建,一种是参数化构建,可选择部署到测试环境仍是线上环境。

自动构建默认部署到测试环境,因为线上环境很重要,自动化构建会有必定风险,因此须要人工干预选择参数进行构建。

  1. 提交代码到master,自动触发构建 若是是参数化构建,这一步是手动选择要构建的环境,而后开始构建
  2. 安装依赖
  3. 打包
  4. 上传到服务器
  5. 若是成功发起钉钉通知

后端

后端的全部项目都是放在一个git仓库中,因此就没有作自动构建

  1. 参数化构建 可选择要构建的环境、打包的项目、是否须要全量打包
  2. 清除旧数据
  3. 打包
  4. 上传到服务器
  5. 杀掉相应的进程
  6. 启动相应的进程
  7. 若是成功发起钉钉通知

接下来就每一步做详细说明,以及可能遇到的坑

自动触发构建

什么是自动触发构建

当咱们提交新的代码到git仓库,Jenkins就会自动开始构建已经配置好的该项目的任务

原理

在git仓库配置一个Jenkins服务器的webhook地址,当git仓库有变更时会请求这个地址,Jenkins就能收到通知而后开始构建任务

配置

  1. 咱们须要先安装一个插件Multibranch Scan Webhook Trigger,可进入插件管理搜索进行安装

  2. 进入项目的配置界面,勾选Scan by webhook,填入自定义token,须要确保token的惟一性,不会与其它项目的冲突 image-20201015145053900

  3. 过滤分支

这是一个多分支流水线,Jenkins默认会检出全部包含Jenkinsfile的分支,若是配置了webhook,就会自动触发相应分支的构建任务;有时候咱们只想master发生变化后才去构建任务,这时就用到了过滤分支的配置,进入项目配置,在分支源git项找到add按钮并点击

image-20201021161744848

选择根据名称过滤(支持通配符),或者你能够选择根据名称过滤(支持正则表达式),效果同样,只是过滤格式不太同样,我这里在相应的地方填入master,即只检索master分支,这样就达到咱们想要的效果了。

image-20201021161836184

  1. 进入远端仓库(我这里是Gitte),点击Webhooks,接着点击添加 WebHook image-20201015145511825

  2. 填入URL,IP地址为Jenkins部署的服务器,token为咱们刚设置的,/multibranch-webhook-trigger/invoke 是固定地址,点击添加 image-20201015145820987

  3. 会自动发起一个请求,即咱们刚填写的,如相应以下则表示配置成功,相应的构建任务也会自动执行 image-20201015150206085

自动化打包

前端

使用了yarn进行安装依赖及打包,须要先配置nodejs环境

  1. 进入插件管理搜索nodejs进行安装 image-20201015152417028
  2. 进入全局工具配置,新增以下配置,别名能够自定义,建议格式为nodejs-版本号,该项目用的是yarn,因此在Global npm package to install,加入了配置项,构建的时候会自动安装yarn,若是是npm能够忽略该配置 image-20201015152605860
  3. Jenkinsfile配置 前端的比较简单
pipeline {
   stage('打包') {
      steps {
        // 执行环境,nodejs-12.16是咱们刚配置的别名,还有一种方式是在agent中配置执行环境,在tools中配置使用的包,感兴趣的能够自行研究
        nodejs('nodejs-12.16') {
          echo '开始安装依赖'
          sh 'yarn'
          echo '开始打包'
          sh 'yarn run build'
        }
      }
    }
}
复制代码

后端(Java)

pipeline {
  tools {
        maven 'Maven3.6.3'
    }
  parameters {
        // 提供要部署的服务器选项
        choice(
            description: '你须要选择哪一个环境进行部署 ?',
            name: 'env',
            choices: ['测试环境', '线上环境']
        ) 
        // 提供构建的模块选项
        choice(
            description: '你须要选择哪一个模块进行构建 ?',
            name: 'moduleName',
            choices: ['kuaimen-contract', 'kuaimen-core', 'kuaimen-eureka-server', 'kuaimen-manage', 'kuaimen-member', 'kuaimen-order', 'kuaimen-shop', 'tiemuzhen-manage']
        )   
        booleanParam(name: 'isAll', defaultValue: false, description: '是否须要全量(包含clean && build)')     
        string(name: 'update', defaultValue: '', description: '本次更新内容?')    
    }
  
  stages {
    stage('全量清除旧数据...') {
            when {
                expression {
                    params.isAll == true
                }
            }
            steps {
                echo "开始全量清除"      
                sh "mvn package clean -Dmaven.test.skip=true"
            }
        }
        stage('全量打包应用') {
            when {
                expression {
                    params.isAll == true
                }
            }
            steps {
                echo "开始全量打包"   
                sh "mvn package -Dmaven.test.skip=true"
                echo '打包成功'
            }
        }
        stage('清除旧数据...') {
            when {
                expression {
                    params.isAll == false
                }
            }
            steps {
                echo "开始清除${params.moduleName}模块"      
                sh "cd ${params.moduleName} && mvn package clean -Dmaven.test.skip=true"
            }
        }
        stage('打包应用') {
            when {
                expression {
                    params.isAll == false
                }
            }
            steps {
                echo "开始打包${params.moduleName}模块"   
                sh "cd ${params.moduleName} && mvn package -Dmaven.test.skip=true"
                echo '打包成功'
            }
        }
  }
}
复制代码

parameters

parameters中主要是提供参数化构建的选项,在其它地方能够经过"${params.isAll}"这种形式拿到用户的交互信息,配置后效果以下:

image-20201015155415457

when>expression表达式中的参数若是未true,则执行,反之跳过该stage

mvn在系统配置中默认就已经提供了该环境,进入系统全局工具配置,添加以下配置(相似nodejs)

image-20201015160714295

这种方式引用

tools {
  maven 'Maven3.6.3'
}
复制代码

自动化部署

前端

pipeline {
  agent any
  environment {
    HOST_TEST = 'root@121.41.16.183'
    HOST_ONLINE = 'jenkins@39.101.219.110'
    SOURCE_DIR = 'dist/*'
    TARGET_DIR = '/data/www/kuaimen-yunying-front'
  }
   stage('部署') {
      when {
        expression {
          params.env == '测试环境'
        }
      }
      steps {
        sshagent(credentials: ['km-test2']) {
          sh "ssh -o StrictHostKeyChecking=no ${HOST_TEST} uname -a"
          // 将打包好的文件上传到服务器
          sh "scp -r ${SOURCE_DIR} ${HOST_TEST}:${TARGET_DIR}"
          sh 'echo "部署成功~"'
        }
      }
    }

    stage('发布') {
      when {
        expression {
          params.env == '线上环境'
        }
      }
      steps {
        sshagent(credentials: ['km-online']) {
          sh "ssh -o StrictHostKeyChecking=no ${HOST_ONLINE} uname -a"
          sh "scp -r ${SOURCE_DIR} ${HOST_ONLINE}:${TARGET_DIR}"
          sh 'echo "发布成功~"'
        }
      }
    }
  }
}
复制代码

environment定了全局变量,在其它地方可直接引用

sshagent用于链接服务器,须要先安装插件ssh-agentcredentials是链接服务器的凭据ID,咱们在一开始已经教你们建立好了

后端

pipeline {
  agent any
  environment {
        HOST_TEST = 'root@121.41.16.183'
        TARGET_DIR = '/data/www/kuaimen-auto'
        HOST_ONLINE = 'jenkins@39.101.219.110'
  }
  
  tools {
        maven 'Maven3.6.3'
  }
  
  stage('部署应用') {
            when {
                expression {
                    params.env == '测试环境'
                }
            }
            steps {
                echo "开始部署${params.moduleName}模块"   
                sshagent(credentials: ['km-test2']) {
                    sh "ssh -v -o StrictHostKeyChecking=no ${HOST_TEST} uname -a"
                    // 将打包后的文件上传到服务器
                    sh "cd ${params.moduleName}/target && scp *.jar ${HOST_TEST}:${TARGET_DIR}/${params.moduleName}"
                    // 匹配出该Java进程而后杀掉
                    sh "ssh -o StrictHostKeyChecking=no ${HOST_TEST} \"uname;ps -ef | egrep ${params.moduleName}.*.jar | egrep -v grep | awk '{print \\\$2}' | xargs -r sudo kill -9\""
                    // 启动该进程
                    sh "ssh -o StrictHostKeyChecking=no ${HOST_TEST} \"nohup /data/apps/jdk1.8/bin/java -jar ${TARGET_DIR}/${params.moduleName}/${params.moduleName}-0.0.1-SNAPSHOT.jar --spring.profiles.active=test >/dev/null 2>&1 &\""
                    sh 'echo "部署成功~"'
                }
                echo '部署成功'
            }
        }
        stage('发布应用') {
            when {
                expression {
                    params.env == '线上环境'
                }
            }
            steps {
                echo "开始发布${params.moduleName}模块"   
                sshagent(credentials: ['km-online']) {
                    sh "ssh -v -o StrictHostKeyChecking=no ${HOST_ONLINE} uname -a"
                    sh "cd ${params.moduleName}/target && scp *.jar ${HOST_ONLINE}:${TARGET_DIR}/${params.moduleName}"
                    sh "ssh -o StrictHostKeyChecking=no ${HOST_ONLINE} \"uname;ps -ef | egrep ${params.moduleName}.*.jar | egrep -v grep | awk '{print \\\$2}' | xargs -r sudo kill -9\""
                    sh "ssh -o StrictHostKeyChecking=no ${HOST_ONLINE} \"nohup /data/apps/jdk1.8/bin/java -jar ${TARGET_DIR}/${params.moduleName}/${params.moduleName}-0.0.1-SNAPSHOT.jar --spring.profiles.active=dev >/dev/null 2>&1 &\""
                    sh 'echo "发布成功~"'
                }
                echo '发布成功'
            }
        }
}
复制代码

须要注意的是,在匹配进程的那段shell中的awk '{print \\\$2}'$符号须要用三个反斜线进行转义,否则会没法执行成功,这里曾卡了很久,但愿大家别踩坑了

部署完成后发起通知

咱们这里使用钉钉发起通知,主要原理是在钉钉群建立一个webhook机器人,而后把webhook的地址填入DingTalk插件的配置项,最后在JenkinsFile中进行以下配置便可:

pipeline {
  stage('获取git commit message') {
     steps {
       script {
         // 将获取到的git commit赋值给GIT_COMMIT_MSG
         env.GIT_COMMIT_MSG = sh (script: 'git log -1 --pretty=%B ${GIT_COMMIT}', returnStdout: true).trim()
       }
     }
  }
  post {
     success {
            dingtalk (
                robot: '77d4c82d-3794-4583-bc7f-556902fee6b0',
                type: 'MARKDOWN',
                atAll: true,
                title: '你有新的消息,请注意查收',
                text:[
                '# 运营管理系统发布通知',
                '---',
                '#### **所属:后端**',
                "#### **构建任务:${env.BUILD_DISPLAY_NAME}**",
                "#### **本次更新内容:${params.update}**",
                "#### **部署环境:${params.env}**",
                '#### **构建结果:成功**'
                ]
            )
        }
  }
}
复制代码

GIT_COMMIT这个是Jenkins系统全局变量,得到的是git commit ID,而后经过它拿到具体的提交信息,并赋值给env.GIT_COMMIT_MSG,全局变量能够经过这种方式访问env.BUILD_DISPLAY_NAME

robot为机器人ID,在系统配置中添加以下配置项

webhook在建立完机器人的时候可以拿到

image-20201015162449026

如何建立钉钉机器人

点击群设置 -> 智能群助手

image-20201015162819482

选择自定义机器人,配置完成后就能够看到webhook的地址了

image-20201015162922118

开始构建

通过上面的配置,咱们已经完成了前端、后台的自动化构建配置,接下来再说明一下分别是如何触发构建的

前端

  1. 提交代码到master,会自动执行构建任务,并部署到测试环境,部署成功后会在钉钉群发起提醒
  2. 参数化构建 image-20201015163313512 点击Build with Parameters,选择相应的参数进行构建,线上环境必须经过这种方式,保证必定的安全性 image-20201015163435799

后端

后端只配置了参数化构建,缘由前面已经说了,选择要构建的环境、模块进行构建

image-20201015163643880

使用Blue Ocean构建(推荐)

点击打开Blue Ocean

image-20201015163808172

选择要构建的分支

image-20201015163855221

弹出参数选择,这和Build with Parameters差很少,可是界面更好看,更清爽了,选择后点击Run便可开始构建

image-20201015164023218

构建结果,很直观,根据颜色能够判断构建成功了,若是失败了是红色

image-20201015164227693

回滚

在Blue Ocean的活动栏能够看到历史构建,点击以下位置的按钮能够从新构建该历史项,即回滚

image-20201015164435230

写在最后

到这里终于告一段落了,虽然折腾了很多时间,可是将公司的工程化流程完善了仍是有点小小的成就感的,之后能够愉快得写代码了,自动化的事情就交给Jenkins了。

将这个记录下来一个是方便之后随时查阅,还有一个是但愿能让朋友们少踩些坑,完~

附录

Jenkins官方文档

BlueOcean实战多分支pipeline构建(Jenkins)

Complete Jenkins Pipeline Tutorial for Beginners [FREE]

实战笔记:Jenkins打造强大的前端自动化工做流

Jenkins:添加SSH全局凭证

钉钉通知系列Jenkins发布后自动通知

使用 Generic Webhook Trigger 触发 Jenkins 多分支流水线自动化构建

Jenkins pipeline单引号、双引号和转义字符

Jenkins Blue Ocean 的使用

How to Execute Linux Commands on Remote System over SSH

相关文章
相关标签/搜索