Jenkins增量部署 指定文件夹指定文件部署

1 出发点

使用Jenkins部署代码的办法,一种是打包,一种是连接目标服务器拉取代码,虽然实际操作中会一些差别, 例如打包后上传与下载的区别,但大体就是这两种。

对于打包方法而言,通常是打全量包,或者是指定文件与文件拉取代码打包。但是有时候在非严格的版本控制下,需要增量部署, 或者动态地指定文件夹,动态地指定文件拉取部署;就像瓦力那样,但是又不想更换瓦力。

2 思路

通过Git Parameters获取参数。

3 步骤

安装扩展 ↓
安装 Pipeple、Git Parameter、Publisher Over SSH,省略过程。

创建pipeline任务 ↓
省略

添加参数 ↓
勾选参数话过程,添加以下参数
BRANCHORTAG
GIT参数 默认:origin/master

SEGMENT
选项参数 选项: 全量 增量

FILES
文本参数 默认:

添加pipe script 并修改配置 ↓
勾选groovy沙箱

node {

    def project = [
        'repository': 'git@git.deepcase.cn:root/asinus.git',                  // 代码仓
        'credentialsId': 'e6d3c193-91ba-429f-bd3a-06a0adf1fa69'               // 凭证
    ]

    def incrementArr = []                                                     // 增量文件
    def isSparse = false                                                      // 是否是指定文件

    def sshServers = [                                                        // Publish Over SSH - SSH Server Config
        'remoteDirectory': '/home/sync',                                      // 上传目录 和配置一样 不要以/结尾
        'prefix': '',                                                         // 上传至远程服务器重命名前缀
        'directory': '',                                                      // 远程服务器上传目录 相对于上面的uploads /开头 不要以/结尾
        'site': '/home/wwwroot/asinus',                                       // 远程服务器解压目录 不要以/结尾
        'lists': [
            [
                'name': 'racknerd',                                           // 远程服务器配置名
                //'remoteDirectory': '',
                //'prefix': '',
                //'directory': '',
                //'site': ''
            ]
        ]
    ]

    def tarInfo = [                                                           // 打包信息
        'excludes': [                                                         // 例外的文件夹或路径
             '.idea',
             '.git',
             'default',
             'default@tmp',
             'asinus.cn.*'
        ],
        'name': "asinus.cn.${BUILD_ID}.${BUILD_NUMBER}.tar.gz",               // 打包文件名
        'path': "/home/jenkins/packages"                                      // 打包备份路径 jenkins用户权限  不要以/结尾
    ]

    def tarFileName = "${tarInfo.path}/${tarInfo.name}"

    try {
        stage("检出 - ${project.repository}") {
            echo "检出 - ${project.repository} 开始 -------------------------------------------------------------------------------"
            // 定义检出参数
            def checkParameters = [                    //
                $class: 'GitSCM',
                branches: [[name: BRANCHORTAG]],                             // 分支
                extensions: [],
                submoduleCfg: [],
                userRemoteConfigs: [
                    [
                        url: project.repository,                             // 代码仓库
                        credentialsId: project.credentialsId                 // 凭证
                    ]
                ]
            ];

            // 处理增量检出
            if (SEGMENT == '增量') {
                def files = FILES.trim().split("\n")
                int filesSize = files.size()

                if (filesSize > 0 && FILES != '') {                          // 如果已经指定文件
                    isSparse = true
                    def sparseCheckoutPathArr = []
                    for (int i = 0; i < filesSize; i++) {
                        incrementArr.add(files[i].trim())
                        sparseCheckoutPathArr.add([
                             $class:'SparseCheckoutPath',
                             path: files[i].trim()                         // 指定仅检出指定文件
                        ])
                    }

                    def sparseCheckout = [
                        $class: 'SparseCheckoutPaths',
                        sparseCheckoutPaths: sparseCheckoutPathArr
                    ]
                    checkParameters.extensions.add(sparseCheckout)
                }
            }

            // 检出文件
            checkout(checkParameters)

            def diffFiles = sh(returnStdout: true, script: "git diff --name-only ${BRANCHORTAG}^").trim();
            echo "在本次检出 被更新的文件有:"
            echo diffFiles;
             echo "检出 - ${project.repository} 结束 -------------------------------------------------------------------------------"
        }

        stage("打包文件 - ${SEGMENT}") {
            echo "${SEGMENT}打包开始 -----------------------------------------------------------------------------------------------"
            def excludes = tarInfo.excludes.join("  --exclude=").trim()
            def completeExcludes = "--exclude=${excludes}"
            echo completeExcludes

            if (SEGMENT == '增量') {                                            // 仅打包增量文件
                // 设定git忽略的文件夹或文件
                def incrementExcludes = tarInfo.excludes.join("\n")
                sh(returnStdout: false, script: 'echo ${incrementExcludes} >> .git/info/exclude')

                if (isSparse) {                                                // 如果是指定文件
                    def tarFiles = incrementArr.join(" ")
                    def spareTarResult = sh(returnStdout: true, script: "tar -zcvf ${tarFileName} ${tarFiles} ${completeExcludes}")
                    echo spareTarResult
                } else {                                                       // 非指定文件则打包git指定分支或tag最一次变更文件
                    def command = "git archive -o ${tarFileName} ${BRANCHORTAG} \$(git diff --name-only ${BRANCHORTAG}^)";
                    echo command
                    def incrementTarResult = sh(returnStdout: true, script: command)
                    echo incrementTarResult
                }
            } else {                                                           // 打包全部文件
                def completeTarResult = sh(returnStdout: true, script: "tar -zcvf ${tarFileName} . ${completeExcludes}")
                echo completeTarResult
            }

            sh(returnStdout: false, script: "cp ${tarFileName} ./");

            echo "${SEGMENT}打包结束 -----------------------------------------------------------------------------------------------"
        }

        stage("部署至远程服务器") {
            echo "部署开始 --------------------------------------------------------------------------------------------------------"
            def remoteDirectory = sshServers.remoteDirectory.trim()
            def prefix = sshServers.prefix.trim()
            def directory = sshServers.directory.trim()
            def site = sshServers.site.trim()
            def publishers = []

            for(item in sshServers.lists) {
                def serverName = item.name

                // 如果服务器配置了remoteDirectory就覆盖通用配置
                if (item.remoteDirectory != null && item.remoteDirectory.trim() != '') {
                    remoteDirectory = item.remoteDirectory.trim()
                }

                // 如果服务器配置了prefix就覆盖通用配置
                if (item.prefix != null) {
                    prefix = item.prefix.trim()
                }

                // 如果服务器配置了directory就覆盖通用配置
                if (item.directory != null) {
                    directory= item.directory.trim()
                }

                // 如果服务器配置了site就覆盖通用配置
                if (item.site != null) {
                    site= item.site.trim()
                }

                def remoteFilePath = "${remoteDirectory}${directory}/${tarInfo.name}"
                echo remoteFilePath

                def publisher= [
                    'configName': item.name,
                    'verbose': true,
                    'transfers': [
                         ['execCommand': "mkdir -p  ${directory} ${site}"],
                         [
                             'sourceFiles': tarInfo.name,
                             'removePrefix': prefix,
                             'remoteDirectory': directory,
                             'execCommand': "tar -zxvf ${remoteFilePath} -C ${site}"
                         ],
                         //['execCommand': "cd ${site} && /usr/bin/php artisan config:clear && /usr/bin/php artisan config:cache && composer dump-autoload -o && "],
                    ]
                ]

                publishers.add(publisher)
            }

            sshPublisher([
                'publishers': publishers
            ]);

            sh(returnStdout: false, script: "rm -rf  ${tarInfo.name}");

            echo "部署结束 --------------------------------------------------------------------------------------------------------"
        }
    } finally {

    }
}

4 使用

1.全量更新
如果选择全量,FILES参数无效

2.增量不指定FILES更新
拉取代码后,只打包当前分支/tag更新(相对于上一版本号^)的代码并发布到远程

3.增量指定FILES
只会拉取指定分支/tag下的指定文件/文件夹并打包发布

5 总结

jenkins不是个啥好的部署工具,打包方式适合较少的应用服务器,优点是发版文件统一, 缺点是慢, 单服务器独立配置蛋疼。 透过SSH服务器在各个服务器上各自部署, 优点是快以及独立配置方便,缺点是版长期可能会不一致。

publish over ssh 本身只提供同步的操作,所以负载较多的应用还是慎用, 可以用ansile, 异步透过ssh, 在应用服务器上执行更新脚本, 在脚本中拉取指定分支所有变动, 再copy指定或变动文件到生产环境。


补充1 自由风格增量 指定

在构建后执行shell

#!/bin/bash
project=asinus-cn
fileName=$project.$BUILD_ID.$BUILD_NUMBER.tar.gz
backupPath=/home/jenkins/packages
backupFileName="$backupPath/$fileName"
excludes="--exclude='.git' --exclude='default' --exclude='default@tmp' --exclude='asinus.cn.*'"
gitClient=/usr/bin/git

if [ $SEGMENT == "增量" ];
then
    sparseFiles=`echo $FILES | tr "\n" " "`
    sparseFiles=`echo $sparseFiles |sed "s/^[ \s]\{1,\}//g;s/[ \s]\{1,\}$//g"`

    echo "$fileName 将打包以下文件 -----------------------------------------------------------------------------------------------"
    if [ -n "$sparseFiles" ];
    then
        tar -zcvf $backupFileName $sparseFiles $excludes
    else
        $gitClient archive -o $backupFileName $BRANCHORTAG $($gitClient diff --name-only ${BRANCHORTAG}^)
        echo `$gitClient diff --name-only ${BRANCHORTAG}^`
    fi
else
    tar -zcvf $backupFileName . $excludes
fi

cp -r $backupFileName .

补充2 要不要指定文件

最好不要, 指定文件部署通常是因为版本控制及发版流程错误, 引发的破坏当前tag 或 分支引起的需要, 可以使用gitfllow流程,发版采用release 而不是master的tag, 这种在建立hotfix紧急修复后就可以合并到master 和 新的release版本, 而不是在master 的tag上折腾(这样会造成版本控制混乱),master的tag仅作为里程碑和修复坐标系存在,而不是用于发版

但是如果发版流程已经固定, 短期内很难改变, 又需要灵活的紧急修复功能(某一tag中一部分功能保留, 一部分修正, 一部分回滚, 但是无法打新tag), 就可以指定文件部署

发表评论

电子邮件地址不会被公开。 必填项已用*标注