前言
根据网上的说法,以及暂时使用过程中的感受,使用自由风格或者maven风格来创建jenkins item,虽然也能实现自动化部署,但是面对相对复杂的构建需求时可能就不太好实现。
一般正式的项目,除了基本的拉取代码、编译代码、运行junit、打包、启动或者重启外,可能还会涉及到sonar代码检查、集成测试、关联例如jira或者conflunce等系统。
因此,我目前所知道的很多正式项目在使用jenkins时可能都会使用pipeline流水线,如果要使用pipeline,需要先安装pipeline
插件。
pipeline看起来好像也不是太难,但是真正自己操作的时候可能会发现很多地方会有小问题。例如可能涉及到ssh问题,可能涉及到容器操作问题,也可能涉及到shell脚本问题。
以下是我实际操作过程中的一些记录,主要分为两个部分:一部分是我只使用了一台linux虚拟机,jenkins、git、maven、springboot服务都在上边跑;另一部分是我把spirngboot服务单独放到了另一个虚拟机上。
同一台机的时候,不涉及ssh,但是却会涉及到jenkins杀死java进程的问题,不同机器就涉及到ssh远程调用shell脚本的问题。
构建步骤
jenkins pipeline进行构建的时候,根据需求,可以有各种不同的步骤,我这里模拟的只有这样几个基础的:
拉取git代码;
maven执行junit并打包;
推送jar包到服务目录;
启动或者重启springboot服务。
那么这里,前两步其实是一样的,后两步略有区别。推送jar包,在jenkins本机实际就是用的cp操作,而其他机器则是使用的scp操作。
启动和重启spingboot服务,本机直接启动脚本,远程就需要额外的配置。
部署到jenkins本机
jenkins的pipeline配置,分为脚本式和声明式两种,语法上不同,但是主要内容是差不多的。
这里需要注意的是部署到jenkins本机,最后启动服务的操作。
我一开始写的是sh "/opt/sh/test.sh restart base-springboot.jar"
,然后如果是把这些内容直接贴到jenkins 的管理界面配置pipeline那里,build的时候都是成功的,服务也都是正常启动。
但是当我把这些内容写到jenkinsfile文件中,然后和springboot项目代码放在一起,上传到github上,然后jenkins build的时候也都是成功的,可是却发现每次构建结束后springboot服务都没有启动起来,也查不到相关的java进程
。
这个地方其实花了好长时间,后来查了各种资料才大概知道了jenkins会杀掉这里启动的java进程,至于为什么,到现在都还没能弄清楚。
所以需要把上边的内容改成sh "JENKINS_NODE_COOKIE=dontKillMe /opt/sh/test.sh restart base-springboot.jar"
。
当然了,在使用其他远程机器部署的时候,就不会有这个问题。
jenkins本机部署服务-脚本式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| node { stage('Print Message') { echo "【start build】 workspace: ${WORKSPACE}" }
stage('git pull code') { echo "【git pull】" git branch: 'jenkin-test', credentialsId: '3bdb20f7-1c4f-4a34-98c0-ef1b9202cbe2', url: 'git@github.com:tuzongxun/base-springboot.git' }
//mvn打包 stage('mvn build project') { echo "【mvn build】" sh 'mvn clean package' }
//上传jar stage('Push jar') { echo "【push jar】" sh 'cp /root/.jenkins/workspace/pip-test/target/base-springboot-0.0.1-SNAPSHOT.jar /opt/code/base-springboot.jar' sh '''rm -rf `ls | egrep -v '(Jenkinsfile|start.sh)'` ''' }
stage('start/restart service') { echo "【start/restart service】" sh "JENKINS_NODE_COOKIE=dontKillMe /opt/sh/test.sh restart base-springboot.jar" }
}
|
jenkins本机部署服务-声明式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| pipeline { agent any
stages { //打印信息 stage('Print Message') { steps { echo "【start build】 workspace: ${WORKSPACE}" } }
stage('git pull code') { steps { echo "【git pull】" git branch: 'jenkin-test', credentialsId: '3bdb20f7-1c4f-4a34-98c0-ef1b9202cbe2', url: 'git@github.com:tuzongxun/base-springboot.git' } }
//mvn打包 stage('mvn build project') { steps { echo "【mvn build】" script { try { //刚开始使用mvn clean package 提示找不到mvn,所以就这样指定地址,指定配置文件 sh 'mvn clean package' } catch (err) { echo 'mvn打包失败' } } } }
//上传jar stage('Push jar') { steps { echo "【push jar】" dir('/root/.jenkins/workspace/pip-test') { //指定工作目录 script { try { sh 'cp ./target/base-springboot-0.0.1-SNAPSHOT.jar /opt/code/base-springboot.jar' } catch (err) { echo "上传jar构建失败" } } } //推送镜像后,删除工作空叫初Jenkinsfile & start.sh 以外所有文件 sh '''rm -rf `ls | egrep -v '(Jenkinsfile|start.sh)'` ''' } }
stage('Deploy to the Target server') { steps { sh "JENKINS_NODE_COOKIE=dontKillMe /opt/sh/test.sh restart base-springboot.jar" } }
} }
|
shell脚本
在上边的pipeline中调用了shell脚本来重启springboot服务,或者说java进程,对应的shell脚本如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| #!/bin/bash APP_NAME=$2 source /etc/profile BUILD_ID=dontKillMe usage() { echo "Usage: sh test.sh [start|stop|restart|status]" exit 1 }
is_exist(){ pid=0 javaps=`jps -l |grep base-springboot.jar` if [ -n "$javaps" ]; then echo ${javaps} pid=`echo ${javaps} | awk '{print $1}'` echo "bb":${pid} return 0 else echo "cc":${pid} return 1 fi }
start(){ is_exist if [ $? -eq "0" ]; then echo "${APP_NAME} is already running. pid=${pid} ." else nohup java -jar /opt/code/${APP_NAME} >> /opt/code/test.log 2>&1 & echo "${APP_NAME} start success" fi }
stop(){ is_exist if [ $? -eq "0" ]; then echo "aa":${pid} kill -9 ${pid} else echo "${APP_NAME} is not running" fi }
status(){ is_exist if [ $? -eq "0" ]; then echo "${APP_NAME} is running. Pid is ${pid}" else echo "${APP_NAME} is NOT running." fi }
restart(){ stop sleep 5 start }
case "$1" in "start") start ;; "stop") stop ;; "status") status ;; "restart") restart ;; *) usage ;; esac
exit 0
|
这里其实也有一个问题,由于对linux和shell脚本不太熟,所以很多东西都是网上找的,一开始根据网上找到的资料,查看相关java进程,使用的是这段代码:
1 2 3 4 5 6 7 8 9
| is_exist(){ pid=`ps -ef|grep $APP_NAME|grep -v grep|grep -v $SCRIPT|awk '{print $2}'` echo $pid if [ -z "${pid}" ]; then return 1 else return 0 fi }
|
但是后来发现总在报错,于是又改成了这样:
1 2 3 4 5 6 7 8 9
| is_exist(){ pid=`ps -ef|grep $APP_NAME|grep -v grep|awk '{print $2}'` echo $pid if [ -z "${pid}" ]; then return 1 else return 0 fi }
|
但是这样之后又发现每次拿到的pid都不止1个,于是在后边stop的时候一样有问题。所以最终改成了上边脚本示例的这样:
1 2 3 4 5 6 7 8 9 10 11 12 13
| is_exist(){ pid=0 javaps=`jps -l |grep base-springboot.jar` if [ -n "$javaps" ]; then echo ${javaps} pid=`echo ${javaps} | awk '{print $1}'` echo "bb":${pid} return 0 else echo "cc":${pid} return 1 fi }
|
部署到非jenkins所在机器的pipeline配置
虽然jenkins本机部署可用了,但是一般真实项目,基本都不会是业务服务和jenkins在同一台机上,所以我又克隆了一台虚拟机作为业务服务器。
不过,这里暂时只跑通了脚本式的pipeline配置,声明式的没有成功。以下是脚本式配置的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| node { def remote = [:] remote.name = 'test' remote.host = '192.168.19.200' remote.user = 'root' remote.allowAnyHosts = true
stage('Print Message') { echo "【start build】 workspace: ${WORKSPACE}" }
stage('git pull code') { echo "【git pull】" git branch: 'jenkin-test', credentialsId: '3bdb20f7-1c4f-4a34-98c0-ef1b9202cbe2', url: 'git@github.com:tuzongxun/base-springboot.git' }
//mvn打包 stage('mvn build project') { echo "【mvn build】" sh 'mvn clean package' }
//上传jar stage('Push jar') { echo "【push jar】" sh 'scp /root/.jenkins/workspace/pip-test/target/base-springboot-0.0.1-SNAPSHOT.jar root@192.168.19.200:/opt/code/base-springboot.jar' sh '''rm -rf `ls | egrep -v '(Jenkinsfile|start.sh)'` ''' }
stage('start/restart service') { withCredentials([sshUserPrivateKey(credentialsId: '3bdb20f7-1c4f-4a34-98c0-ef1b9202cbe2', keyFileVariable: 'identity', passphraseVariable: '', usernameVariable: 'root')]) { echo "【start/restart service】" remote.user = 'root' remote.identityFile = identity sshCommand remote: remote, command: "/opt/sh/test.sh restart base-springboot.jar" } }
}
|
这里主要就是上传jar时使用了scp,这里没有出现密码,是因为在另一台机上配置了jenkins所在机的sshkey的公钥。
然后就是远程调用时配置的credentialsId
,我两台机192.168.19.199
和192.168.19.200
,我是需要用jenkins所在的199去调用200机器上的shell脚本。
一开始以为和scp一样,已经在200配置了199的公钥,那么应该可以直接调用了。但是结果发现并不是想的那样,还需要配置jenkins中配置的credentials的Id。
总结
pipeline本身或许不难,但是每一步涉及到的可能都是一个面的知识,例如shell、ssh、jenkins作用域或者工作空间之类的,需要有比较好的综合知识来支持。
pipeline可以支持的需求和功能很多,基于它相关的插件也有很多,待后续再逐步了解。