Jenkins自动化集成/部署

一、概述

使用Jenkins挺长时间了,做一下详细的总结和记录。 下图部署了3套Jenkins, 构建打包,自动化集成测试,生产环境。

  1. 构建打包: 从gitlab代码库或者代码,使用maven构建。经历了整个maven构建生命周期如test,package,deploy。在test阶段绑定cobertura,package阶段绑定docekr build,在deploy阶段把jar推送到私服或者绑定push到docker registy。最后生成测试报告等发邮件给相关开发人员。
  2. 集成测试:集成环境最难推行和实践,主要是因为自动测试涉及代码、数据库脚本、测试用例,多方调用等等变动。有docker之后好一点,每个服务有自己的docker-compose编排,根据compose构建启动后,获取项目下的postman.json测试脚本,在newman容器中进行测试。
  3. 生产环境:对于使用spring cloud + k8s ,生产部署就简单很多。Jenkins配置job,获取镜像的tag,远程shell执行kubectl指令即可完成升级发布。

先看下几张效果图: 第一张:maven项目打包Jenkins

第二张:支持多节点构建

第三张:构建的pipeline

第四张:参数化构建(支持git master和其他分支构建,可以是快照或者release)

第五张:构建结果(压缩包,单测覆盖率)

第六张:部署(自动列出docker镜像版本号)

二、Jenkins安装

Jenkins安装比较简单,可以使用容器启动,jar包启动,或者把war包放在tomcat下启动。经过一些实践,我倾向使用最后一种。简单,方便,好配置。

tomcat安装:

  1. 新建要给目录昨晚Jenkins工作目录:export JENKINS_HOME=/data/jenkins
  2. 把Jenkins.war拷贝到webapps,其他tomcat,然后安装提示完成安装

配置maven,jdk等: 系统管理–>Global Tool Configuration

也可以选择“自动安装”。

三、插件

我的插件列表:

curl -s "http://dev:dev@localhost:8080/jenkins/pluginManager/api/xml?depth=1&xpath=/*/*/shortName|/*/*/version&wrapper=plugins" | perl -pe 's/.*?<shortName>([\w-]+).*?<version>([^<]+)()(<\/\w+>)+/\1 \2\n/g'|sed 's/ /:/'
bouncycastle-api:2.16.1
blueocean-web:1.0.1
credentials:2.1.13
branch-api:2.0.9
ssh-credentials:1.13
blueocean-rest:1.0.1
structs:1.6
git-client:2.4.5
github-api:1.85
scm-api:2.1.1
workflow-step-api:2.9
workflow-multibranch:2.14
workflow-scm-step:2.4
pipeline-model-definition:1.1.4
script-security:1.27
blueocean:1.0.1
junit:1.20
matrix-project:1.10
display-url-api:2.0
pubsub-light:1.7
mailer:1.20
git:3.3.0
token-macro:2.1
blueocean-commons:1.0.1
plain-credentials:1.4
sse-gateway:1.15
github:1.27.0
github-branch-source:2.0.5
blueocean-events:1.0.1
workflow-api:2.13
blueocean-jwt:1.0.1
jquery-detached:1.2.1
workflow-support:2.14
blueocean-config:1.0.1
ace-editor:1.1
workflow-cps:2.30
favorite:2.0.4
pipeline-stage-step:2.2
pipeline-input-step:2.7
workflow-job:2.10
pipeline-graph-analysis:1.3
blueocean-i18n:1.0.1
durable-task:1.13
workflow-basic-steps:2.4
workflow-durable-task-step:2.11
git-server:1.7
jackson2-api:2.7.3
cloudbees-folder:6.0.4
workflow-cps-global-lib:2.8
credentials-binding:1.11
metrics:3.1.2.9
pipeline-model-api:1.1.4
pipeline-stage-tags-metadata:1.1.4
authentication-tokens:1.3
icon-shim:2.0.3
variant:1.1
docker-commons:1.6
docker-workflow:1.10
pipeline-model-declarative-agent:1.1.1
pipeline-model-extensions:1.1.4
blueocean-rest-impl:1.0.1
blueocean-pipeline-api-impl:1.0.1
blueocean-personalization:1.0.1
blueocean-dashboard:1.0.1
blueocean-git-pipeline:1.0.1
blueocean-autofavorite:0.7
blueocean-github-pipeline:1.0.1
blueocean-pipeline-editor:0.2.0
blueocean-display-url:2.0
active-directory:2.4
cobertura:1.10
publish-over-ssh:1.17
pam-auth:1.3
windows-slaves:1.3.1
ldap:1.15
matrix-auth:1.5
external-monitor-job:1.7
javadoc:1.4
antisamy-markup-formatter:1.5
ant:1.4
ssh:2.4
extensible-choice-parameter:1.4.0
scriptler:2.9
uno-choice:1.5.3
pipeline-milestone-step:1.3.1
pipeline-rest-api:2.6
handlebars:1.1.1
momentjs:1.1.1
pipeline-stage-view:2.6
pipeline-build-step:2.5
workflow-aggregator:2.5
ssh-agent:1.15
maven-plugin:2.15.1
config-file-provider:2.15.7
pipeline-maven:2.1.0
pipeline-utility-steps:1.3.0
permissive-script-security:0.1
ssh-slaves:1.17
slave-status:1.6
ssh2easy:1.4
run-condition:1.0
conditional-buildstep:1.3.5
parameterized-trigger:2.33
jquery:1.11.2-0
build-pipeline-plugin:1.5.6
simple-theme-plugin:0.3
role-strategy:2.4.0

四、权限配置

  • 系统管理–>Configure Global Security

  • 系统管理–>用户管理:新增用户

  • 系统管理–>Manage and Assign Roles 新增角色 分配角色

五、多节点配置

配置很简单,主要是吧ssh证书配好就行。 多节点构建很有必要。首先,构建分成吃CPU和内存,其次,如果其中一个slave挂了,可以分配到其他节点。

六、pipeline

在截图中图blueocean的工作流,就是通过pipeline实现;

pipeline脚本最好版本控制,而不是直接在Jenkins里写

下面看下重要的package.groovy

#!/usr/bin/env groovy
node {
    def mvnHome
    def mvnVersion
    def release_version
    def next_version
    def pom_obj
    stage('prepare') {
        withCredentials([usernameColonPassword(credentialsId: 'git', variable: 'git')]) {
            git '${GIT_REPO}'
            sh "git clean -f && git reset --hard origin/${GIT_BRANCH}"
            sh "git fetch origin --prune --tags"
        }
        mvnHome = tool 'maven'

        if ("${PKG_TYPE}" == "release") {
            pom_obj = readMavenPom file: 'pom.xml'
            mvnVersion = pom_obj.getVersion()
            if (mvnVersion == null) {
                println()
                sh 'echo "[ERROR] 项目没有版本号" && exit 1'
            }
            if (!mvnVersion.matches('[0-9]{1,3}(\\.[0-9]{1,3}){1,3}-SNAPSHOT')) {
                sh 'echo "项目版本号不符合规范(1.0.0-SNAPSHOT)" && exit 1'
            }
            release_version = mvnVersion.split('-')[0]
            def version_array = release_version.split('\\.')
            version_array[-1] = (version_array[-1] as int) + 1
            next_version = version_array.join('.') + '-SNAPSHOT';
            sh "git tag | grep ${release_version} && echo \"tag已经存在\" && exit 1 || exit 0"


            pom_obj.setVersion(release_version)
            writeMavenPom model: pom_obj
        }
    }

    stage('test') {
        sh "${mvnHome}/bin/mvn clean test -Pdocker"
    }
    stage('docker build') {
        if ("${PKG_TYPE}" == "release") {
            sh "${mvnHome}/bin/mvn package -DdockerImageTags=latest -DdockerImageTags=1.0.0 -Dmaven.test.skip=true -Pdocker"
        } else {
            sh "${mvnHome}/bin/mvn package -Dmaven.test.skip=true -Pdocker"
        }

    }
    stage('docker push') {
        if ("${PKG_TYPE}" == "release") {
            sh "${mvnHome}/bin/mvn docker:push -DdockerImageTags=latest -DdockerImageTags=1.0.0 -Dmaven.test.skip=true -Pdocker"
        } else {
            sh "${mvnHome}/bin/mvn docker:push -Dmaven.test.skip=true -Pdocker"
        }
    }
    stage('git tag') {
        if ("${PKG_TYPE}" == "release") {
            withCredentials([usernameColonPassword(credentialsId: 'git', variable: 'git')]) {
                sh "git add pom.xml && git commit -m \"jks: release ${release_version}\""
                sh "git tag ${release_version} -m \"release ${release_version}\""
                sh "git push --tags"
            }
            pom_obj.setVersion(next_version)
            writeMavenPom model: pom_obj
            withCredentials([usernameColonPassword(credentialsId: 'git', variable: 'git')]) {
                sh "git add pom.xml && git commit -m \"jks: update version ${next_version}\""
                sh "git push origin master:${GIT_BRANCH}"
            }
        } else {
            println('snapshot')
        }
    }
    stage('depoy to dev') {
        sh "echo 'depoy to dev'"
        sshagent (credentials: ['docker_dev']) {
            sh "ssh -l linux_user -p 21987 192.168.99.100 /home/centos/dev/update_app.sh ${JOB_NAME}"
        }
    }
    stage('test report') {
        step([$class: 'CoberturaPublisher', coberturaReportFile: 'target/site/cobertura/coverage.xml', onlyStable: false])
        junit '**/target/surefire-reports/TEST-*.xml'
        archive 'target/*.jar'
    }
}

脚本中需要很多插件的支持,否则会报错。就是插件都安装了,有些脚本还需要in-process Script Approval 同意运行

七、发布

真正发布项目时,其实只需要一个项目名,一个版本号,Jenkins要做的就是根据项目名和版本号,执行远程shell。但版本号收到输入就太low了,可以根据git tag名或者docker镜像的tag。 Jenkins有一个参数化构建插件(uno-choice),可以写groovy,动态获取tag。

import groovy.json.JsonSlurper
import hudson.model.*
/**
 * Created by erdaoya on 17/3/17.
 */

//get jenkins job_name
def build = Thread.currentThread().toString()
def regexp= ".+?/job/([^/]+)/.*"
def match = build  =~ regexp
def job_name = match[0][1]

// call registry v2 api
def url =  'http://xxx.io:5000/v2/xxxx/'+ job_name + '/tags/list'
def tags = url.toURL().text
def jsonSlurper = new JsonSlurper()
def object = jsonSlurper.parseText(tags)

// return tags
return object.tags

打包时获取git分支名也可以使用这个插件:

def git_url=  GIT_REPO
def shell_line = 'git ls-remote --heads ' + git_url
def process = shell_line.execute()
def list = ['master']
process.in.eachLine {
    line -> list.add((line.split()[1]).split('/')[2])
}
return list

八、总结

把Jenkins用好:插件+groovy+shell。当然,需要合理的架构,合理的集成和发布模式,否则,Jenkins需要配置一堆乱七八糟的东西,稍微不小心就出错,也不具可复制性。如果job特别多,需要批量构建或者发布,也可以使用groovy或者python调用Jenkins API。

CONTENTS