我们一般通过jenkins做持续构建,在代码提交之后自动构建maven任务,输出jar包或者war包。但是我们还需要把jar/war包发布到线上机器。这个操作能不能自动化呢?
要实现自动化部署,我们需要解决下面三个问题:
- WHAT to deploy: jar/war/zip, etc. 如果要支持js/css/html,甚至class文件,还需要支持文件发布。
- WHERE to deploy:
- 目标机器
- 目标目录
- HOW to deploy
- backup: 一般来说只需要保留上一个可用版本就可以了。当然也可以保留多个,回滚的时候选择最新的进行回滚。
- remove service from LB: 对于用长连接监控SP可用性的RPC框架,这个过程是自动化的;对于Nginx,则需要将其从upstream中摘除(有些LB支持动态upstream)
- shutdown old running service: 这个简单,简单的kill -9。
- startup the newly deploy service: 这个最好部署服务有自己的startup脚本,直接调用就可以了。
- check if service is OK?
- if OK: resume service to LB: 同
- if NOT OK: rollback to backup
使用Jenkins的节点管理实现自动化部署
我们用Jenkins做持续构建,是不是可以用jenkins做持续部署呢?Jenkins有节点管理可以实现简单的分布式部署功能。
1、配置Node节点
关键在认证登陆这块,Linux机器一般采用SSH,Windows机器则采用Java Web Start。
这里建议直接打通SSH免登陆。具体参见笔者以前的文章:shell如何实现ssh免密码登陆。
Jenkins => Manage Jenkins => Manage Nodes => New Node:
Name: web01
Remote root directory: /home/work/jenkins
Labels: web01 production
Launch method: Launch slave agents on Unix machines via SSh
Host: xxx
Credentials: SSH Username with private key(如果是免登陆处理了,直接勾选 `From the Jenkins master ~/.ssh` 就可以了)
2、配置任务选择label
跟配置普通的任务基本一样,除了一点,就是要选择Restrict where this project can be run
,在Label Expression中输入相应的label。
另外,因为Jenkins本质上只是一个持续构建工具,要实现自动化部署,还需要配置一下Post-build Actions
,执行本地部署shell脚本。
因为通过前面的节点管理和label限制,这个job其实就是跑在要发布的服务器上,所以我们不需要远程执行脚本,只需要本地执行脚本就就可以了,这个Jenkins默认就支持了。
任务配置面板中 Build => Add build step => Execute shell:
输入以下脚本:
#!/bin/sh
APP_DIR="/home/work/apps/mobopay/user"
JENKINS_WORKSPACE="/home/work/jenkins/workspace"
APP_JAR="${JENKINS_WORKSPACE}/user-impl/target/user-impl.jar"
APP_BIN="${JENKINS_WORKSPACE}/user-impl/bin/start_production.sh"
## 1. backup and copy
[ -d $APP_DIR ] || mkdir -p $APP_DIR
cd $APP_DIR
[ -d target ] || mkdir target
[ -d bin ] || mkdir bin
if [ -f target/user-impl.jar ]; then
echo "start backup..."
mv target/user-impl.jar target/user-impl.jar.bak
fi
echo "cp jar and bin to target..."
cp $APP_JAR $APP_DIR/target
[ -f APP_BIN ] || cp $APP_BIN $APP_DIR/bin
## 2. shutdown old running service
APP_PARAMS="/home/work/apps/mobopay/user/target/user-impl.jar"
APP_PID=`ps aux | grep java| grep "$APP_PARAMS" | grep -v grep | awk '{ print $2}'`
for i in $APP_PID; do
echo "Kill PID [ $APP_PID ] contains $APP_PARAMS"
kill -9 $APP_PID
done
## 3. startup the newly deploy service
echo "start uping..."
nohup bin/start_production.sh &
echo "start up!"
NOTES && TIPS
需要注意的是shell的执行错误并不会影响job的执行结果。所以脚本中要多echo一些有用的信息,同时要进入Cosole Output中查看。
3、运行任务
配置后简单的启动构建任务:
Started by user anonymous
Building remotely on web03 (production) in workspace /home/work/jenkins/workspace/user-impl
Checking out a fresh workspace because there's no workspace at /home/work/jenkins/workspace/user-impl
Cleaning local Directory .
Checking out https://svn.baidu.com/app/gensoft/mobopay/trunk/backend/mobopay/remote/user/user-impl at revision '2016-03-30T09:46:29.503 +0800'
...
[INFO] Installing /home/work/jenkins/workspace/user-impl/target/user-impl.jar to /home/work/.m2/repository/com/baidu/mobojoy/mobopay/remote/user/user-impl/1.0.0-SNAPSHOT/user-impl-1.0.0-SNAPSHOT.jar
[INFO] Installing /home/work/jenkins/workspace/user-impl/pom.xml to /home/work/.m2/repository/com/baidu/mobojoy/mobopay/remote/user/user-impl/1.0.0-SNAPSHOT/user-impl-1.0.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 02:30 min
[INFO] Finished at: 2016-03-30T09:49:11+08:00
[INFO] Final Memory: 43M/1119M
[INFO] ------------------------------------------------------------------------
[user-impl] $ /bin/sh /tmp/hudson8177948577949318327.sh
cp jar and bin to target...
start uping...
start up!
/home/work/apps/mobopay/user
Finished: SUCCESS `Building remotely on web03 (production) in workspace /home/work/jenkins/workspace/user-impl
看起来是成功了。但是从日志很容易看出问题:Jenkins并不是在所有label=production
的节点上执行构建任务,而是随机挑选了一台!仔细一想当然是合理的,毕竟人家的初衷就是slave作为构建集群,并不是发布集群。
看来在Jenkins上搞自动化部署,比较靠谱的做法是”一次构建,多次发布”。这其实并不难,只需要把我们jar包scp过去,然后远程执行脚本就可以了。当然,最好有插件可以支持像label一样的选择机器发布,而不是写死在脚本中。
谷歌了一下,发现这篇文章: 使用Jenkins实现多平台并行集成。跟我遇到的问题是一模一样的,里面提到Jenkins的Multi-configuration project: Suitable for projects that need a large number of different configurations, such as testing on multiple environments, platform-specific builds, etc. 用这个就可以实现多slave同时构建了。
与free-style project相比,Multi-Configuration project的配置页面中没有”Restrict where this project can be run”配置选项,但却多出了一个”Configuration Matrix”配置区域。在该区域中,我们可以选择Slaves,在Node/Label中,我们可以看到当前Jenkins中配置的所有Label和Nodes。注意选择label跟前面”Restrict where this project can be run”效果是一样,Jenkins只会从该label中的若干个节点中选择一个来执行构建。所以这里要选择Nodes,然后勾选你需要构建的节点。
保持点击Build Now,在看到任务在所有选中的节点上同时执行了。同时master的输出如下日志:
Started by user anonymous
Building remotely on web02 (production) in workspace /home/work/jenkins/workspace/user-impl-production
Updating https://svn.baidu.com/app/gensoft/mobopay/trunk/backend/mobopay/remote/user/user-impl at revision '2016-03-30T15:01:42.878 +0800'
At revision 1985
no change for https://svn.baidu.com/app/gensoft/mobopay/trunk/backend/mobopay/remote/user/user-impl since the previous build
Triggering user-impl-production » web03
Triggering user-impl-production » web02
Triggering user-impl-production » web01
user-impl-production » web03 completed with result SUCCESS
user-impl-production » web02 completed with result SUCCESS
user-impl-production » web01 completed with result SUCCESS
Finished: SUCCESS
点击到具体的Slave上看,也能看到相应的日志:
Started by upstream project "user-impl-production" build number 4
originally caused by:
Started by user anonymous
Building remotely on web03 (production) in workspace /home/work/jenkins/workspace/user-impl-production/label/web03
Updating https://svn.baidu.com/app/gensoft/mobopay/trunk/backend/mobopay/remote/user/user-impl at revision '2016-03-30T15:01:42.878 +0800'
At revision 1985
no change for https://svn.baidu.com/app/gensoft/mobopay/trunk/backend/mobopay/remote/user/user-impl since the previous build
[web03] $ /home/work/jenkins/tools/hudson.tasks.Maven_MavenInstallation/system/bin/mvn -Dlabel=web03 clean install -Dmaven.test.skip
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building user-impl 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ user-impl ---
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ user-impl ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 7 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ user-impl ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/work/jenkins/workspace/user-impl-production/label/web03/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ user-impl ---
[INFO] Not copying test resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ user-impl ---
[INFO] Not compiling test sources
[INFO]
[INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ user-impl ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ user-impl ---
[INFO] Building jar: /home/work/jenkins/workspace/user-impl-production/label/web03/target/user-impl.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.3.2.RELEASE:repackage (default) @ user-impl ---
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ user-impl ---
[INFO] Installing /home/work/jenkins/workspace/user-impl-production/label/web03/target/user-impl.jar to /home/work/.m2/repository/com/baidu/mobojoy/mobopay/remote/user/user-impl/1.0.0-SNAPSHOT/user-impl-1.0.0-SNAPSHOT.jar
[INFO] Installing /home/work/jenkins/workspace/user-impl-production/label/web03/pom.xml to /home/work/.m2/repository/com/baidu/mobojoy/mobopay/remote/user/user-impl/1.0.0-SNAPSHOT/user-impl-1.0.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.814 s
[INFO] Finished at: 2016-03-30T15:01:50+08:00
[INFO] Final Memory: 39M/1473M
[INFO] ------------------------------------------------------------------------
[web03] $ /bin/sh /tmp/hudson7762667286947684737.sh
cp jar and bin to target...
cp: cannot stat `/home/work/jenkins/workspace/user-impl/target/user-impl.jar': No such file or directory
cp: cannot stat `/home/work/jenkins/workspace/user-impl/bin/start_production.sh': No such file or directory
start uping...
start up!
nohup: failed to run command `bin/start_production.sh': No such file or directory
Finished: SUCCESS
效果还不错!但是有四个问题:
1、第一个问题,就是jenkins的Slave节点是用来构建的,所以它是并行执行的,但是对于发布来说,并行执行意味着同时发布,如果没有做控制的话,会出现所有的服务都同时在发布重启,瞬间所有的服务都不可用了。好在jenkins已经考虑到这问题了,它的Matrix插件有一个选项:
**Run each configuration sequentially**: With this option checked, Jenkins builds each configuration in a sequence. This can be useful if your configuration needs to access the shared resource like database, as well as to avoid crowding out other jobs. (from Matrix Project Plugin)
勾选之后果然就变成串行执行了。
2、Slave的workspace构建jar包路径跟master不一样:
[INFO] Installing /home/work/jenkins/workspace/user-impl-production/label/web03/target/user-impl.jar to /home/work/.m2/repository/com/baidu/mobojoy/mobopay/remote/user/user-impl/1.0.0-SNAPSHOT/user-impl-1.0.0-SNAPSHOT.jar
看到路径中居然还有label信息。。这意味着脚本要不修改,要不就就不要使用workspace路径,直接使用maven本地仓库路径(但是需要注意的是仓库中的构建是带SNAPSHOT的,而且我们的bin目录并不会跟着install过去。。)。
不过我想Jenkins作为这么通用的开放框架,应该有环境变量,谷歌了一下,果然在每次构建的时候Jenkins会设置$WORKSPACE环境变量:Environtment variables for jenkins when script running。整个世界一下子清静很多~
3、脚本构建出错,在Master最终状态或者日志都是显示成功:
cp: cannot stat `/home/work/jenkins/workspace/user-impl/target/user-impl.jar': No such file or directory
cp: cannot stat `/home/work/jenkins/workspace/user-impl/bin/start_production.sh': No such file or directory
这个简单,修改我们的deploy脚本,检查文件是否存在,如果不存在就以非0状态码退出就可以了:
if [ -f $APP_JAR ]; then
cp $APP_JAR $APP_DIR/target
echo "cp $APP_JAR to $APP_DIR/target success"
else
echo "$APP_JAR not exist!"
exit 1
fi
再Build一下:
...
start backup...
cp jar and bin to target...
/home/work/jenkins/workspace/user-impl/target/user-impl.jar not exist!
Build step 'Execute shell' marked build as failure
Finished: FAILURE
OK,不管是从slave的日志或者从管理界面都可以看到失败的slave构建。
4、第四个问题,也是他妈最蛋疼的问题:就是Jenkins会在构建结束之后把它创建的所有进程都干掉!!!所以对于通过Jenkins Execute Shell调起的需要长时间运行的外部任务,即使使用nohup或者&放在后台运行,也一样会被kill掉!!结果就是我们的应用一直没有起来!谷歌了很久(关键词很重要:jenkins shell script background),终于发现解决方案:Spawning processes from build:
Note that this will set the BUILD_ID environment variable for the process being spawned to something other than the current BUILD_ID. Or you can start jenkins with
-Dhudson.util.ProcessTree.disable=true
- see ProcessTreeKiller for details.
也就是说我们要不在启动Jenkins的时候指定不要kill创建的子进程:
java -Dhudson.util.ProcessTree.disable=true -jar jenkins.war
要不在脚本中通过把BUILD_ID环境变量设置成一个与父进程不一样的数值来让Jenkins无法kill掉它:
BUILD_ID=dontKillMe /usr/apache/bin/httpd
综上,最终的deploy脚本如下:
#!/bin/sh
BUILD_ID=dontKillMe
APP_DIR="/home/work/apps/mobopay/user"
JENKINS_WORKSPACE=$WORKSPACE
APP_JAR_NAME="user-impl.jar"
APP_JAR="${JENKINS_WORKSPACE}/target/${APP_JAR_NAME}"
APP_BIN="${JENKINS_WORKSPACE}/bin/start_production.sh"
## 1. backup and copy
[ -d $APP_DIR ] || mkdir -p $APP_DIR
cd $APP_DIR
[ -d target ] || mkdir target
[ -d bin ] || mkdir bin
if [ -f target/${APP_JAR_NAME} ]; then
echo "start backup..."
timestamp=`date +%Y%m%d%H%M%S`
mv target/${APP_JAR_NAME} target/${timestamp}-${APP_JAR_NAME}
fi
echo "cp jar and bin to target..."
if [ -f $APP_JAR ]; then
cp $APP_JAR $APP_DIR/target
echo "cp $APP_JAR to $APP_DIR/target success"
else
echo "$APP_JAR not exist!"
exit 1
fi
if [ -f $APP_BIN ]; then
cp $APP_BIN $APP_DIR/bin
echo "cp $APP_BIN $APP_DIR/bin success"
else
echo "$APP_BIN not exist!"
exit 1
fi
## 2. shutdown old running service
APP_PARAMS="target/${APP_JAR_NAME}"
APP_PID=`ps aux | grep java| grep "$APP_PARAMS" | grep -v grep | awk '{ print $2}'`
for i in $APP_PID; do
echo "Kill PID [ $APP_PID ] contains $APP_PARAMS"
kill -9 $APP_PID
done
## 3. startup the newly deploy service
echo "Starting up with nohup bin/start_production.sh & ..."
nohup bin/start_production.sh >/dev/null 2>&1 &
# wait for app start up
for i in 2 4 6 10; do
sleep $i
echo "wait and check $APP_JAR_NAME starting up..."
APP_PID=`ps aux | grep java| grep "$APP_PARAMS" | grep -v grep | awk '{ print $2}'`
if [ $APP_PID > 0 ]; then
break;
else
echo "$APP_JAR_NAME is still starting up..."
fi
done
if [ $APP_PID > 0 ]; then
echo "$APP_JAR_NAME start up success!"
exit 0
else
echo "$APP_JAR_NAME start up fail!"
exit 1
fi
其中start_production.sh脚本如下:
#!/bin/bash
# determine base directory; preserve where you're running from
#echo "Path to $(basename $0) is $(readlink -f $0)"
realpath=$(readlink -f "$0")
filepath=$(dirname "$realpath")
basedir=${filepath%/*}
LOG_DIR=${basedir}/logs
mkdir -p ${LOG_DIR}
GC_LOG_DIR=${basedir}/logs/gc
mkdir -p ${GC_LOG_DIR}
GC_FILE_PATH="${GC_LOG_DIR}/gc-$(date +%s).log"
echo $basedir
JAVA_OPTS="-Dspring.profiles.active=production -Dlogback.configurationFile=logback-production.xml -DLOG_DIR=${LOG_DIR}"
JAVA_OPTS="$JAVA_OPTS -server -Xmn256M -Xms1024M -Xmx1024M -Djava.net.preferIPv4Stack=true -Djava.awt.headless=true -Dorg.apache.catalina.connector.RECYCLE_FACADES=false -Djava.util.Arrays.useLegacyMergeSort=true -XX:MetaspaceSize=64M -XX:MaxMetaspaceSize=256M -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction=1 -XX:+CMSClassUnloadingEnabled -XX:+UseFastAccessorMethods -XX:+UseCMSInitiatingOccupancyOnly -XX:SurvivorRatio=65536 -XX:MaxTenuringThreshold=0 -XX:CMSInitiatingOccupancyFraction=80 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -verbose:gc -Xloggc:${GC_FILE_PATH} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -XX:+HeapDumpOnOutOfMemoryError"
#JAVA_OPTS="${JAVA_OPTS} -Xdebug -Xrunjdwp:server=y,transport=dt_socket,address=8000,suspend=n"
echo $JAVA_OPTS
java $JAVA_OPTS -jar ${basedir}/target/user-impl.jar
兴高采烈的运行,发现还是不行。。妹,但是发现如果把start_production.sh的脚本内容跟deploy脚本合并成一个就成功了。。也就是说BUILD_ID=dontKillMe java $JAVA_OPTS -jar ${basedir}/target/user-impl.jar &
就生效,但是BUILD_ID=dontKillMe nohup bin/start_production.sh
就没有用。。把BUILD_ID=dontKillMe
放在start_production.sh中也不行。。哭死。。
经过一个下午的试验和谷歌,终于发现了一个解决方案:Nohup doesn’t work when executing script from Jenkins。
实验证明,一定要把start_production.sh的stdout输出重定向到/dev/null,否则就会启动不了。也就是说我们的脚本要改成:
nohup bin/start_production.sh >/dev/null &
这样就可以了:
Started by upstream project "user-impl-production" build number 23
originally caused by:
Started by user anonymous
Building remotely on web02 (production) in workspace /home/work/jenkins/workspace/user-impl-production/label/web02
Updating https://svn.baidu.com/app/gensoft/mobopay/trunk/backend/mobopay/remote/user/user-impl at revision '2016-03-31T20:55:45.084 +0800'
At revision 2044
no change for https://svn.baidu.com/app/gensoft/mobopay/trunk/backend/mobopay/remote/user/user-impl since the previous build
...
[web02] $ /bin/sh /tmp/hudson7126890291497807782.sh
start backup...
cp jar and bin to target...
cp /home/work/jenkins/workspace/user-impl-production/label/web02/target/user-impl.jar to /home/work/apps/mobopay/user/target success
cp /home/work/jenkins/workspace/user-impl-production/label/web02/bin/start_production.sh /home/work/apps/mobopay/user/bin success
Starting up with nohup bin/start_production.sh & ...
Start up success!
Java HotSpot(TM) 64-Bit Server VM warning: UseCMSCompactAtFullCollection is deprecated and will likely be removed in a future release.
Java HotSpot(TM) 64-Bit Server VM warning: CMSFullGCsBeforeCompaction is deprecated and will likely be removed in a future release.
Finished: SUCCESS
可以看到stderr输出是没有关系的,不过我们也可以把它屏蔽掉:
nohup bin/start_production.sh >/dev/null 2>&1 &
至此,终于完全搞定,肉留满面啊,感觉还是自己写一个自动发布平台简单一些。。
TIPS
如果遇到如下错误:
Started by user anonymous
Building remotely on web01 in workspace /home/work/jenkins/workspace/user-impl
Checking out a fresh workspace because there's no workspace at /home/work/jenkins/workspace/user-impl
Cleaning local Directory .
Checking out https://svn.baidu.com/app/gensoft/mobopay/trunk/backend/mobopay/remote/user/user-impl at revision '2016-03-29T16:07:22.391 +0800'
A src
A src/test
...
A bin
AU bin/start_test.sh
AU bin/start_development.sh
AU bin/start_production.sh
A pom.xml
U .
At revision 1974
[user-impl] $ mvn clean install -Dmaven.test.skip
FATAL: command execution failed
java.io.IOException: Cannot run program "mvn" (in directory "/home/work/jenkins/workspace/user-impl"): error=2, No such file or directory
at java.lang.ProcessBuilder.start(ProcessBuilder.java:1048)
at hudson.Proc$LocalProc.<init>(Proc.java:244)
at hudson.Proc$LocalProc.<init>(Proc.java:216)
at hudson.Launcher$LocalLauncher.launch(Launcher.java:815)
at hudson.Launcher$ProcStarter.start(Launcher.java:381)
at hudson.Launcher$RemoteLaunchCallable.call(Launcher.java:1148)
at hudson.Launcher$RemoteLaunchCallable.call(Launcher.java:1113)
at hudson.remoting.UserRequest.perform(UserRequest.java:120)
at hudson.remoting.UserRequest.perform(UserRequest.java:48)
at hudson.remoting.Request$2.run(Request.java:326)
at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:68)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
at ......remote call to web01(Native Method)
at hudson.remoting.Channel.attachCallSiteStackTrace(Channel.java:1416)
at hudson.remoting.UserResponse.retrieve(UserRequest.java:220)
at hudson.remoting.Channel.call(Channel.java:781)
at hudson.Launcher$RemoteLauncher.launch(Launcher.java:928)
at hudson.Launcher$ProcStarter.start(Launcher.java:381)
at hudson.Launcher$ProcStarter.join(Launcher.java:388)
at hudson.tasks.Maven.perform(Maven.java:332)
at hudson.tasks.BuildStepMonitor$1.perform(BuildStepMonitor.java:20)
at hudson.model.AbstractBuild$AbstractBuildExecution.perform(AbstractBuild.java:782)
at hudson.model.Build$BuildExecution.build(Build.java:205)
at hudson.model.Build$BuildExecution.doRun(Build.java:162)
at hudson.model.AbstractBuild$AbstractBuildExecution.run(AbstractBuild.java:534)
at hudson.model.Run.execute(Run.java:1738)
at hudson.model.FreeStyleBuild.run(FreeStyleBuild.java:43)
at hudson.model.ResourceController.execute(ResourceController.java:98)
at hudson.model.Executor.run(Executor.java:410)
Caused by: java.io.IOException: error=2, No such file or directory
at java.lang.UNIXProcess.forkAndExec(Native Method)
at java.lang.UNIXProcess.<init>(UNIXProcess.java:248)
at java.lang.ProcessImpl.start(ProcessImpl.java:134)
at java.lang.ProcessBuilder.start(ProcessBuilder.java:1029)
at hudson.Proc$LocalProc.<init>(Proc.java:244)
at hudson.Proc$LocalProc.<init>(Proc.java:216)
at hudson.Launcher$LocalLauncher.launch(Launcher.java:815)
at hudson.Launcher$ProcStarter.start(Launcher.java:381)
at hudson.Launcher$RemoteLaunchCallable.call(Launcher.java:1148)
at hudson.Launcher$RemoteLaunchCallable.call(Launcher.java:1113)
at hudson.remoting.UserRequest.perform(UserRequest.java:120)
at hudson.remoting.UserRequest.perform(UserRequest.java:48)
at hudson.remoting.Request$2.run(Request.java:326)
at hudson.remoting.InterceptingExecutorService$1.call(InterceptingExecutorService.java:68)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
Build step 'Invoke top-level Maven targets' marked build as failure
Finished: FAILURE
谷歌了一下,根据这个说法 Cannot run program “mvn” error=2, No such file or directory,让Jenkins自动安装Maven就可以了。
总结
利用jenkins的master-slave节点管理实现分布式构建,同时jenkins的labels可以支持(逐个机器)灰度和全量构建,整个过程只需要简单的几步配置就可以了。
但是jenkins毕竟是一个持续构建工具,不是部署工具,要实现自动部署还需要编写shell脚本。另外,每个job都是要走 svn/git checkout => maven构建 的步骤,一个是没必要的构建,另一个是如果不小心提交了新的代码也会被构建部署上线(当然,我们可以通过RELEASE分支避免,但是这样子的话,每次发布都需要修改jenkins任务)。
个人感觉还是将构建和部署分开比较合理。
其他方式讨论
1. 使用Node and Label parameter plugin插件
Node and Label parameter plugin: This plugin adds two new parameter types to job configuration - node and label, this allows to dynamically select the node where a job/project should be executed.
跟前面一样,需要先配置好多个Node节点。但是跟前面不一样的是,不需要创建Multi-Configuration project,安装好Node and Label parameter插件后,勾选This build is parameterized
选项之后,Add Paramter下拉框中会增加Label和Node两个parameter选项。
勾选:Allow multi node selection for concurrent builds
注意同时勾选Execute concurrent builds if necessary
,否则会提示错误:”Execute concurrent builds” and “Allow multi node selection for concurrent builds” only make sense together!
然后会发现Build Now变成了Build with Parameters,点击之后会先进入到parameters选择页面,也就是节点选择。因为我们允许多个节点同时构建,所以下拉框支持多选。选择你需要的构建的节点,点击Build就可以了。
Started by user anonymous
Building remotely on web01 (production) in workspace /home/work/jenkins/workspace/user-impl
Updating https://svn.baidu.com/app/gensoft/mobopay/trunk/backend/mobopay/remote/user/user-impl at revision '2016-03-30T18:02:38.977 +0800'
At revision 1996
no change for https://svn.baidu.com/app/gensoft/mobopay/trunk/backend/mobopay/remote/user/user-impl since the previous build
Next nodes: [web02, web03]
Schedule build on node web02
Schedule build on node web03
[user-impl] $ /home/work/jenkins/tools/hudson.tasks.Maven_MavenInstallation/system/bin/mvn -D=web01 clean install -Dmaven.test.skip
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building user-impl 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ user-impl ---
[INFO] Deleting /home/work/jenkins/workspace/user-impl/target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ user-impl ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 1 resource
[INFO] Copying 7 resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ user-impl ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 2 source files to /home/work/jenkins/workspace/user-impl/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ user-impl ---
[INFO] Not copying test resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ user-impl ---
[INFO] Not compiling test sources
[INFO]
[INFO] --- maven-surefire-plugin:2.18.1:test (default-test) @ user-impl ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-jar-plugin:2.5:jar (default-jar) @ user-impl ---
[INFO] Building jar: /home/work/jenkins/workspace/user-impl/target/user-impl.jar
[INFO]
[INFO] --- spring-boot-maven-plugin:1.3.2.RELEASE:repackage (default) @ user-impl ---
[INFO]
[INFO] --- maven-install-plugin:2.5.2:install (default-install) @ user-impl ---
[INFO] Installing /home/work/jenkins/workspace/user-impl/target/user-impl.jar to /home/work/.m2/repository/com/baidu/mobojoy/mobopay/remote/user/user-impl/1.0.0-SNAPSHOT/user-impl-1.0.0-SNAPSHOT.jar
[INFO] Installing /home/work/jenkins/workspace/user-impl/pom.xml to /home/work/.m2/repository/com/baidu/mobojoy/mobopay/remote/user/user-impl/1.0.0-SNAPSHOT/user-impl-1.0.0-SNAPSHOT.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 2.859 s
[INFO] Finished at: 2016-03-30T18:02:45+08:00
[INFO] Final Memory: 39M/1477M
[INFO] ------------------------------------------------------------------------
[user-impl] $ /bin/sh /tmp/hudson7869689904044983757.sh
start backup...
cp jar and bin to target...
cp /home/work/jenkins/workspace/user-impl/target/user-impl.jar to /home/work/apps/mobopay/user/target success
cp /home/work/jenkins/workspace/user-impl/bin/start_test.sh /home/work/apps/mobopay/user/bin success
Start uping...
Start up success!
/home/work/apps/mobopay/user
Finished: SUCCESS
但是看不到其他节点的日志输出,不知道是否构建成功。效果上不如第一种方法。
参考文章
- Jenkins on-demand slave selection through labels
- Continuous Integration Using Docker, Maven and Jenkins
- Building Gradle Projects with Jenkins CI
- Using Jenkins to run remote deployment scripts over SSH
- Jenkins多服务器自动部署,发布到多台服务器
- JUMPING INTO CONTINUOUS DELIVERY WITH JENKINS AND SSH