专栏首页一日一工具Jenkins扩展共享库进阶

Jenkins扩展共享库进阶

前言

前面我们介绍了Jenkins多分支流水线、Jenkins流水线即代码之扩展共享库,其实都是“流水线即代码”的体现。我们将Jenkinsfile纳入项目版本库中统一管理,实现了“谁构建、谁运行”的理念。

但是在实际项目中,CI/CD其实是由运维来管理的,这样就会导致运维、开发都要通过版本库去修改Jenkinsfile、项目代码。

试想下运维在调试流水线频繁提交版本,导致远程分支不断更新,如果有钩子触发自动发版,势必会引起开发的烦感。

为了避免这个情况的放生,我们引入了Jenkins扩展共享库,即将流水线操作拆分为两块

  1. Jenkinsfile定义流水线步骤、环境变量、参数等与项目相关的一切变量;
  2. 扩展共享库定义流水线调用的方法、函数、类库等与构建相关的具体操作;

由于一旦流水线步骤及变量确定一般就不会改动了,而扩展共享库的方法等具体操作实现我们可以以代码的方式放入远程版本中,修改提交后Jenkinsfile构建自动加载共享库,获取最新的构建修改

另,通过扩展共享库我们可以提高构建操作的复用,有效减少构建代码量;Jenkinsfile、扩展库还可以作为备份托管在版本库中,可谓是两全其美啊

下面我们对多分支流水线、扩展共享库结合实现Vue项目的发版、回滚来具体讲解下扩展共享库的使用。

注:多分支流水线可以有效将多个分支放到一个项目下统一管理,避免因分支导致的项目分散。

Vue场景

Jenkins+远程web服务器

功能实现:

  • 参数化构建:deploy-发版,rollback-回滚。
  • 发版:判断git版本是否更新,若更新则在Jenkins上打包,并将dist包分发到远程web服务器上;若未更新,则停止构建。
  • 回滚:回滚archiveArtifacts的版本包,分发到远程web服务器上。

注意:我们使用archiveArtifacts来归档版本包,回滚时可从归档路径中获取。

扩展共享库

一、添加扩展共享库

Manage Jenkins--Configure System--Global Pipeline Libraries中添加

二、代码库结构

pipeline-shared-library/
├── resources  #空
├── src  #空
└── vars
    └── vueUpdate.groovy

vueUpdate.groovy 是所有vue项目的构建具体操作。

注意:由于所有的vue项目构建由共享库中的统一的方法实现,因此不同分支对应的环境要高度一致,这样才能最大限度的实现代码复用。

三、具体实现

我们将代码分为三部分,即deploy、rollback、update。

1.deploy-发版

(1)判断版本是否更新

我们通过将本次git的版本id存入文件,以便下次构建时将其与GIT_COMMIT进行比较,实现版本是否更新。

注意:由于第一次构建时,流水线报错“

No such property: GIT_PREVIOUS_SUCCESSFUL_COMMIT for class: groovy.lang.Binding

”。此时是无法通过GIT_PREVIOUS_SUCCESSFUL_COMMIT变量来获取上一次版本的,因此只能将其写入文件存放。

(2)打包

通过npm 打包vue项目生成dist。

注意:我们将dist压缩并改名为dist_temp.zip 作为我们分发到项目的版本包。其目的是作为中间临时文件,用于和项目的实际dist目录进行替换,更新后销毁即可。

另最终归档的版本包也为dist_temp.zip。

2.回滚-rollback

回滚的版本存在于archiveArtifacts归档后的构建目录中,在此目录中

${JENKINS_HOME}/jobs/`echo ${JOB_NAME}|awk -F'/' '{print \$1}'`/branches/${BRANCH_NAME}/builds/${version}/archive/

多分支流水线的目录以分支名区分子目录。

3.分发更新

Jenkins通过sshpublisher将版本包dist_temp.zip 分发到远程web服务器上,通过rsync对项目目录dist进行更新,最后销毁dist_temp.zip。

注意:归档dist_temp.zip 及 邮件通知由Jenkinsfile定义,不放在共享库中。

具体代码如下:

所有的变量由跟随项目的Jenkinsfile提供。

def deploy() {
    sh """
        #新建commitid文件,初次写入0
        if [ ! -f commitid ];then 
            echo 0 > commitid 
        fi
        #从commitid获取上次版本id
        previous_id=`cat commitid`
        if [ \$previous_id != $GIT_COMMIT ];then
            #写入本次git commit,用于下次判断
            echo $GIT_COMMIT > commitid
            echo "start npm install&build"
            /usr/local/nodejs/bin/npm install
            /usr/local/nodejs/bin/npm run build
            [ -f ${ZIP_NAME}.zip ] && rm -rfv ${ZIP_NAME}.zip
            [ -d ${ZIP_NAME} ] && rm -rfv ${ZIP_NAME}
            echo -e "\\033[34m将dist改名为${ZIP_NAME}:\\033[0m"
            mv -v dist ${ZIP_NAME} && echo -e "\\033[32mdist改名为${ZIP_NAME}成功。\\033[0m" || {
                echo -e "\\033[32mdist改名为${ZIP_NAME}失败。\\033[0m"
                exit 1
            }
            zip -r ${ZIP_NAME}.zip ${ZIP_NAME} && echo -e "\\033[32m${ZIP_NAME}压缩成功。\\033[0m" || {
                echo -e "\\033[32m${ZIP_NAME}压缩失败。\\033[0m"
                exit 1
            }
        else
            echo -e "\\033[31mRepositories not update, stop deploy\\033[0m"
            exit 1
        fi
    """
}

def rollback() {
    sh """
    [ ${version} -eq 0 ] && {
        echo -e "\\033[31m版本号非0。\\033[0m"
        exit 1
    } || echo -e "\\033[34mAction:${deploy_env}\\033[0m"
    echo -e "\\033[34mRollback version:${version}\\033[0m"
    rm -rfv ${ZIP_NAME}.zip
    cp -Rv ${JENKINS_HOME}/jobs/`echo ${JOB_NAME}|awk -F'/' '{print \$1}'`/branches/${BRANCH_NAME}/builds/${version}/archive/${ZIP_NAME}.zip .
    [ \$? -eq 0 ] && echo -e "\\033[32m指定版本号${version}的${ZIP_NAME}.zip复制到当前部署目录成功。\\033[0m" || {
        echo -e "\\033[31m指定版本号${version}的${ZIP_NAME}.zip复制到当前部署目录失败。\\033[0m"
        exit 1
    }
    """
}

def update(host) {
    for(i in host){
        sshPublisher(
            publishers: [sshPublisherDesc(configName: "$i", transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: """
            IN_Face=`/sbin/route -n |awk '{if(\$4~/UG/){print \$8}}'|head -n 1`
            Local_IP=`/sbin/ip addr show "\${IN_Face}" | grep -w 'inet' | awk '{print \$2}'`

            cd /App/htdocs/${APP_NAME}                
            echo -e "\\033[34m\${Local_IP}节点解压新版压缩包${ZIP_NAME}.zip:\\033[0m"
            unzip -o ${ZIP_NAME}.zip && echo -e "\\033[32m\${Local_IP}节点${ZIP_NAME}.zip解压成功。\\033[0m" || {
            echo -e "\\033[31m\${Local_IP}节点${ZIP_NAME}.zip解压失败。\\033[0m"
            exit 1
            }
            public_dir=`echo ${ZIP_NAME} | awk -F'_' '{print \$1}'`
            echo -e "\\033[34m\${Local_IP}节点将${ZIP_NAME}目录下数据同步到生产目录\${public_dir}内:\\033[0m"
            [ -d ${ZIP_NAME} ] && rsync -vrl --delete ${ZIP_NAME}/ \${public_dir:-/tmp/aaa}/
            [ \$? -eq 0 ] && echo -e "\\033[32m\${Local_IP}节点数据同步成功。\\033[0m" || {
            echo -e "\\033[31m\${Local_IP}节点数据同步失败。\\033[0m"
            exit 1
            }

            echo -e "\\033[34m\${Local_IP}节点将${ZIP_NAME}.zip删除:\\033[0m"
            rm -rfv ${ZIP_NAME}.zip ${ZIP_NAME}

            echo -e "\\033[32m${JOB_NAME}发布成功。\\033[0m"
            """, execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: "htdocs/${APP_NAME}", remoteDirectorySDF: false, removePrefix: '', sourceFiles: "${ZIP_NAME}.zip")], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)]
        )
    }
}

Jenkinsfile

1.参数化构建

通过parameters 定义构建的参数:deploy、rollback

回滚通过BUILD_NUMBER传输历史版本的归档。

2.环境变量

我们定义两个环境变量:

APP_NAME 项目所在目录,如/App/${APP_NAME}/dist

ZIP_NAME 版本包

3.流水线步骤

发版:调用共享库deploy方法;

回滚:调用共享库rollback方法;

测试部署:调用共享库update方法,传入测试环境服务器列表;

生产部署:调用共享库update方法,传入生产环境服务器列表;

归档:不管构建状态,总是归档版本包dist_temp.zip;

邮件通知:构建不稳定、成功、失败发送邮件通知;

注意:流水线中我们使用when来匹配参数化构建,这样可以比避免在sh中使用case或if 判断,减少代码量。

具体代码如下:

//引入jenkins扩展共享库
@Library('shared-library') _
pipeline {
    agent any
    options {
        ansiColor('xterm')
        timestamps()
    }
    parameters {
        choice choices: ['deploy', 'rollback'], description: '''deploy:发布
            rollback:回滚''', name: 'deploy_env'
        string defaultValue: '0', description: '''回滚版本号,发布时忽略该参数
            版本号为jenkins job BUILD_NUMBER''', name: 'version', trim: false
    }
    environment {
        APP_NAME="www.test.cn"
        ZIP_NAME="dist_temp"
    }
    stages {
        stage('发版') {
            //匹配发版参数
            when {
                expression { params.deploy_env ==~ 'deploy' }
            }
      steps {
                script {
                    vueUpdate.deploy()
                }
            }
        }
        stage('回滚') {
            //匹配回滚参数
            when {
                expression { params.deploy_env ==~ 'rollback' }
            }
            steps {
                script {
                    vueUpdate.rollback()
                }
            }
        }
        stage('测试部署') {
            //匹配develop分支
            when {
                branch 'develop'
            }
            steps {
                script {
                    def host = ["test-10.13", "test-10.14"]
                    vueUpdate.update(host)
            }
        }
        stage('生产部署') {
            //匹配master分支
            when {
                branch 'master'
            }
            steps {
                script {
                    def host = ["prod-10.11", "prod-10.12"]
                    vueUpdate.update(host)
                }
            }
        }
  }
    post {
        always {
            archiveArtifacts "${ZIP_NAME}.zip"
        }
        unstable {
            emailext (
                body: """项目名称:${JOB_NAME}\n构建编号:${BUILD_NUMBER}\n构建日志:${BUILD_URL}console""",
                subject: '【Jenkins构建通知】:$JOB_NAME - Build # $BUILD_NUMBER - Unstable!',
                to: 'xx@test.cn',
                from: 'test@test.cn'
            )
        }
        success {
            emailext (
                body: """项目名称:${JOB_NAME}\n构建编号:${BUILD_NUMBER}\n构建日志:${BUILD_URL}console""",
                subject: '【Jenkins构建通知】:$JOB_NAME - Build # $BUILD_NUMBER - Successful!',
                to: 'xx@test.cn',
                from: 'test@test.cn'
            )
        }
        failure {
            emailext (
                body: """项目名称:${JOB_NAME}\n构建编号:${BUILD_NUMBER}\n构建日志:${BUILD_URL}console""",
                subject: '【Jenkins构建通知】:$JOB_NAME - Build # $BUILD_NUMBER - Failure!',
                to: 'xx@test.cn',
                from: 'test@test.cn'
            )
        }
    }
}

最终结果如下:

总结

Jenkins扩展共享库+多分支流水线一方面可以简化CI/CD过程中的项目管理,一方面可以驱动我们各个环境的标准化,为实现自动化做好铺垫。

反过来环境标准化是我们灵活应用Jenkins扩展共享库的前提,没有足够的标准化,那么我们就需要增加代码量去适配各个环境。

总之,在运维的过程中,你会发现标准化和规范化越来越重要。

本文分享自微信公众号 - 追马Linux(zhuima_k8s)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用Jenkins扩展共享库进行钉钉消息推送

    起因:执行完流水线后进行一定程度的消息推送,所以选择钉钉进行jenkins构建结构的消息推送

    子润先生
  • Jenkins流水线即代码之扩展共享库

    Jenkin的多分支流水线,允许Jenkinsfile与需要 Jenkins 构建的应用程序代码放在一起,然后 Jenkins 从源代码管理系统中检出 Jenk...

    追马
  • 实践: 使用共享库扩展Jenkinsfile

    共享库这并不是一个全新的概念,其实具有编程能力的同学应该清楚一些。例如在编程语言Python中,我们可以将Python代码写到一个文件中,当代码数量增加,我们可...

    DevOps云学堂
  • Jenkins——the thing which auto everything

    今天突然想起来原先的关注者可能还不知道Jenkins是什么,上来就是Jenkins常见问题,有些突兀。是我交代不清楚,所以这篇就来介绍一下。注意...

    DevOps持续交付
  • 第1章 开篇-为什么要做CI/CD?

    本章阐述持续集成系统的发展历程、持续集成系统的原理,以及持续集成系统的实现过程,目的是让大家全面了解持续集成系统,更加深入的学习持续集成系统的原理,为后续章节的...

    DevOps云学堂
  • pipeline 共享库

    当大量使用pipeline后,内置功能并不能照顾到所有需求,这时候需要扩展pipeline。

    陈不成i
  • jenkins pipeline持续集成

     Jenkins 2.x的精髓是Pipeline as Code,那为什么要用Pipeline呢?jenkins1.0也能实现自动化构建,但Pipeline能够...

    py3study
  • TeamCity VS Jenkins:选择正确的CI / CD工具

    每个软件开发周期都涉及三个主要阶段:构建,测试和部署。这三个阶段中的任何一个滞后都会导致产品发布的延迟。为了避免此类延迟,组织依靠CI / CD工具来自动化这些...

    用户7466307
  • MPL - 模块化的流水线库

    尽管通过自动化部署加快了开发速度,但由于在 DevOps 方面缺少协作,我们一个客户正因此而放慢产品的上市时间。虽然他们也投入了资源来做 DevOps ,但每条...

    LinuxSuRen
  • T-Mobile 和 Jenkins 案例研究

    大多数人都知道 T-Mobile 是无线服务提供商。毕竟,我们拥有国际化的业务,并且是美国第三大移动运营商。但是我们还是一家技术公司,提供的新产品包括 TVis...

    LinuxSuRen
  • 如何对 Jenkins 共享库进行单元测试

    Jenkins 共享库是除了 Jenkins 插件外,另一种扩展 Jenkins 流水线的技术。通过它,可以轻松地自定义步骤,还可以对现有的流水线逻辑进行一定程...

    LinuxSuRen
  • Jenkins 共享库使用示例

    如果你经常使用 Jenkins Pipeline 一定会遇到多个不同流水线中有大量重复代码的情况,很多时候为了方便我们都是直接复制粘贴到不同的管道中去的,但是长...

    我是阳明
  • 使用 YAML 文件配置 Jenkins 流水线

    几年前,我们的 CTO 写了一篇关于使用 Jenkins 和 Docker 为 Ruby On Rails 应用提供持续集成服务的文章。这些年,我们一直使用这个...

    LinuxSuRen
  • 介绍 Jenkins 模板引擎

    在企业范围内实施 DevSecOps 实践具有挑战性。由于组织内的不同应用程序正在使用多种编程语言、自动化测试框架和安全遵从性安全合规工具,因此每个团队构建和维...

    LinuxSuRen
  • Jenkins常见问题集锦(八)

    Hudson由Sun公司在2004年启动,第一个版本于2005年在java.net发布。

    DevOps持续交付
  • Kotlin 进阶用法:扩展

    越来越多的Android开发者开始使用kotlin了,最近项目中也需要用到,于是就对kotlin中一些有趣的用法进行了记录。

    PhoenixZheng
  • CI/CD 工具选型:Jenkins 还是 Bamboo?

    持续集成和持续交付是在软件开发生命周期中获得交付一致性的方法。作为一个流程,它帮助你自动化开发管道,同时确保所有事情都可跟踪。其中有趣的部分是在开发阶段中引入自...

    深度学习与Python
  • JenkinsPipeline插件的十大最佳实践

    Jenkins是卓越的自动化工具之一。Jenkins可通过使用插件进行设计扩展。插件使Jenkins拥有极大的灵活性,可以在各种平台上自动执行各种流程。Jenk...

    DevOps云学堂
  • Alpha 版本的插件管理库和 CLI 工具

    我的 Google Summer of Code project 项目试图解决这个问题,方法是创建一个库,该库将在 Jenkins 的不同实现中统一插件管理逻辑...

    LinuxSuRen

扫码关注云+社区

领取腾讯云代金券