项目部署使用Pipeline的Jenkinsfile脚本代码方式会比基于界面配置好处:
使用脚本的方式唯一缺点就是脚本构建相对界面配置会稍有一点难度,需要pipeline语法,pipeline支持groovy代码,可以基于groovy实现功能更强大的操作。
这个构建脚本模板,定义了编译、部署、关闭、回滚操作,适用于大多数java项目,支持多服务器部署,可用于生产环境。
对于大多数项目,我们都是在安装Jenkins的这台服务器,完成编译和打包工具和制品管理,而在部署的服务器上,一般只安装java运行环境。对于大多公司可以使用Jenkins自带的archiveArtifactsa插件来对制品(部署文件、安装包等)管理,归档制品备份,回滚操作中可以直接使用归档的旧版本包,
可以根据项目需要,配置多个版本的JAVA_HOME,比如本文是同时了OpenJDK8和OpenJDK11,构建时,可以通过在脚本中工具代码块中配置
tools {
jdk 'JAVA_HOME8' # 指定编译用的jdk版本
}
同样可以通过在脚本中工具代码块中配置:
tools {
maven 'MAVEN_HOME'
}
可以在Linux shell中通过scp命令上传文件,ssh命令连接远程服务器操作脚本,参考代码如下,但是使用Publish Over SSH插件会更方便
Pipeline通过Linux shell上传文件和远程服务命令,注意需要先配置远程服务器免密登录。
stage('部署') {
steps {
script{
sh "scp target/JApiTest-1.0.0_56.zip root@192.168.28.129:$upload_dir/JApiTest-1.0.0_56.zip"
sh "ssh -tt root@192.168.28.129 << remotessh
unzip -oq $upload_dir/${app_name}.zip -d ${deploy_dir}/${app_name}
cd ${deploy_dir}/${app_name}
chmod 0755 ./server.sh
./server.sh restart
exit
remotessh"
}
}
}
不推荐上边直接在Linux Shell命令上传和远程执行命令,建议使用Publish Over SSH或SSH Pipeline Steps的方式更好。
Publish Over SSH配置远程服务器连接信息,这里配置了两台部署服务器的连接信息。可以使用帐号密码的方式远程连接,也可以免密的方式远程连接。
创建的任务中,只需要在界面上配置Jenkinsfile文件所在的git仓库和在仓库中的具体目录就行了,其它信息不需要配置。Jenkinsfile代码版本化,更适合项目管理的需要,有能力公司也可以对Jenkinsfile代码进行评审。
完成2.1-2.5配置,就可以在具体的流水线代码上使用这些配置了,为了获取pom.xml文件中的版本,也使用groovy代码写了个version(file)获取版本号的方法供脚本调用。完整的流水线代码如下:
/**
* 获取maven项目版本号
*/
def version(file){
def version = ''
if(fileExists(file)){
def data = readFile(file: file)
def lines = data.readLines()
def isProject = false;
def isParent = false;
for (line in lines) {
if(line.indexOf(") > -1){
isProject = true
}
if(isProject == true && line.indexOf("" ) > -1){
def start = line.indexOf("" );
def end = line.indexOf("");
version = line.substring(start + 9,end);
break;
}
}
}
return version
}
def build(){
checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [],userRemoteConfigs:[[
credentialsId: 'gitee', url: 'https://gitee.com/penngo/XXJApiTest.git']]])
app_version =version(source_dir + '/pom.xml')
env.app_name_version ="${app_name}-${app_version}"
sh("""
java -version
mvn clean package
""".replace(" ", ""))
sh([
"cd ${source_dir}/target",
"cp -f ../server.sh server.sh",
"zip -q -r ${app_name_version}_${BUILD_ID}.zip ${app_name_version}.war server.sh"
].join("\n"))
}
def deploy(servers, build_id_temp){
def build_id = build_id_temp == '' ? readFile(file: "build_id.txt") : build_id_temp;
sh("""
cp -f ${WORKSPACE}/../../jobs/${app_name}/builds/${build_id}/archive/target/*_${build_id}.zip ${app_name}.zip
""".replace("\t",""));
for(ip in servers.tokenize(",")){
echo "=====ssh_ip:" + ip
sshPublisher(
publishers: [
sshPublisherDesc(
configName: "${ip}",
transfers: [
sshTransfer(cleanRemote: false,
excludes: '',
execCommand: """
unzip -oq $upload_dir/${app_name}.zip -d ${deploy_dir}/${app_name}
cd ${deploy_dir}/${app_name}
chmod 0755 ./server.sh
./server.sh restart
""".replace(' ',''),
execTimeout: 120000,
flatten: false,
makeEmptyDirs: false,
noDefaultExcludes: false,
patternSeparator: '[, ]+',
remoteDirectory: "${upload_dir}",
remoteDirectorySDF: false,
removePrefix: "",
sourceFiles: "${app_name}.zip")
],
usePromotionTimestamp: false,
useWorkspaceInPromotion: false,
verbose: true)
])
}
}
def rollback(servers, build_number){
deploy(servers, build_number)
}
pipeline {
agent any
tools {
maven 'MAVEN_HOME'
jdk 'JAVA_HOME8'
}
options {
buildDiscarder(logRotator(numToKeepStr: "10"))
}
environment{
source_dir="${WORKSPACE}/JApiTest"
jenkinsfile_dir="${WORKSPACE}"
jenkinsfile_job_dir="${WORKSPACE}/../../JApiTest"
app_version=version(source_dir + '/pom.xml')
app_name="JApiTest"
app_name_version="${app_name}-${app_version}"
deploy_dir="/data/app"
upload_dir="/data/upload"
}
parameters {
choice(name: 'app', choices:['JApiTest'],description: '应用(请选择需要操作的应用:JApiTest)')
choice(name: 'select', choices:[
'请选择',
'编译',
'部署',
'回滚',
],description: '操作(请选择操作:编译、部署、回滚)')
extendedChoice(description: '服务器', value: '192.168.28.129,192.168.28.132',
descriptionPropertyValue: '192.168.28.129(测试),192.168.28.132(正式)',
multiSelectDelimiter: ',', name: 'servers', quoteValue: false, saveJSONParameterToFile: false, type: 'PT_CHECKBOX', visibleItemCount: 5)
string(
description: """回滚版本号""",
name: 'roll_number',
trim: true
)
}
stages {
stage('编译') {
when {
expression { params.select.trim() == "编译" }
}
steps {
dir("${source_dir}"){
script {
build()
}
}
writeFile(file: "build_id.txt", text: "${BUILD_ID}")
}
}
stage('部署') {
when {
expression { "${params.select}".trim() == "部署" }
}
steps {
script {
deploy(params.servers, '')
}
}
}
stage('回滚') {
when {
expression { "${params.select}".trim() == '回滚' }
}
steps {
script {
rollback(params.servers, params.roll_number)
}
}
}
}
post {
success{
script {
if(params.select.trim() == "编译"){
dir("${source_dir}"){
archiveArtifacts artifacts: "target/${app_name_version}_${BUILD_ID}.zip", fingerprint: false
}
}
}
}
}
}
配合运行的Linux脚本server.sh
#!/bin/bash -il
export JAVA_HOME=/usr/lib/jvm/bellsoft-java8-full.x86_64
export PATH=$JAVA_HOME/bin:$PATH
java -version
APP_NAME="JApiTest"
JAR_NAME="JApiTest-1.0.0.jar"
JAVA_OPTS="-Xmx2G -XX:+UseG1GC"
start() {
pid=`ps -ef | grep "$JAR_NAME" | grep -v grep | awk '{print $2}'`
if [ -z $pid ]; then
export JENKINS_NODE_COOKIE=dontKillMe
nohup java $JAVA_OPTS -jar $JAR_NAME 2>&1 &
sleep 3
pid=`ps -ef | grep "$JAR_NAME" | grep -v grep | awk '{print $2}'`
if [ -z $pid ]; then
echo ".....service ${JAR_NAME} is run error....."
else
echo ""
echo ".....Service ${JAR_NAME} is starting!pid=${pid}....."
echo "........................Start successfully!........................."
fi
else
echo ""
echo "Service ${JAR_NAME} is already running,it's pid = ${pid}"
echo ""
fi
}
stop() {
pid=`ps -ef | grep "$JAR_NAME" | grep -v grep | awk '{print $2}'`
if [ -z $pid ]; then
echo "Service ${JAR_NAME} is not running!"
else
kill -9 $pid
echo "Service stop successfully!pid:${pid} been killed"
fi
}
restart() {
echo ""
echo ".............................Restarting.............................."
stop
start
echo "....................Restart successfully!..........................."
}
if [ ! -n "$1" ] ;then
echo "Usage: $0 {start|stop|restart}"
elif [ $1 = "start" ];then
start
elif [ $1 = "stop" ];then
stop
elif [ $1 = "restart" ];then
restart
fi
使用Pipeline脚本方式部署,必须先执行一次后,才能出现2.7.1参数可选界面,如果未执行过,默认是下边在界面:
构建前需要填入有归档文件的构建任务号。
构建任务中有下载图标的都表示这个构建任务有归档文件。