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), 就可以指定文件部署

发表回复

您的电子邮箱地址不会被公开。