起因
有一些运行在内网的服务器需要通过vpn去访问,无法外网直接通过ssh登录,无法使用scp命令,从而导致无法使用jenkins自动部署服务
后来想到内网的服务器也对外开放80端口提供web服务,就可以直接使用80端口部署服务
具体做法
一、直接在项目中创建controller
@RestController
public class DeployController extends BaseController {
/**
* 部署上传war包的默认路径
*/
private String DEFAULT_DEPLOY_UPLOAD_PATH="";
/**
* 部署执行脚本的位置
*/
private String DEFAULT_DEPLOY_SCRIPT_FILE="";
/**
* 密码是:KnLEkez50d84J7zPuBRyi4bg1oCWsDjP
*/
private String PASSWORD="6c94c97418f8c64ca63ec8acb34dff49";
/**
* 上传文件
* @param request
* @return
* @throws IOException
*/
@RequestMapping(value = {"/deploy/upload"}, method = {RequestMethod.POST})
public String deployUpload(MultipartHttpServletRequest request){
log.info("开始上传war包");
try {
//检查密码
checkPassword(request.getParameter("password"));
//检查部署平台
checkPlatform(request.getParameter("platform"));
//获取内容
MultipartFile multipartFile = request.getFile("file");
//保存成文件
if (multipartFile != null) {
//输入流
BufferedInputStream bufferedInputStream = new BufferedInputStream(multipartFile.getInputStream());
//输出流
FileOutputStream fileOutputStream = new FileOutputStream(DEFAULT_DEPLOY_UPLOAD_PATH +"/"+ multipartFile.getOriginalFilename());
BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(fileOutputStream);
FileUtil.createFile(bufferedInputStream,bufferedOutputStream);
log.info("上传war包成功");
return "上传成功!";
} else {
return "file参数不正确,获取不到数据!";
}
}catch (FileNotFoundException e){
return "文件没有权限!"+e.getMessage();
} catch (IOException e) {
log.error("上传war包失败!",e);
return "上传war包失败!"+e.getMessage();
} catch (Exception e) {
log.error("上传war包失败!",e);
return "上传war包失败!"+e.getMessage();
}
}
/**
* 开始执行部署
* @param request
* @return
* @throws IOException
*/
@RequestMapping(value = {"/deploy/execute"}, method = {RequestMethod.GET})
public String deployExecute(HttpServletRequest request){
log.info("开始执行部署脚本");
try {
checkPassword(request.getParameter("password"));
//检查部署平台
checkPlatform(request.getParameter("platform"));
String command = "java -classpath "+DEFAULT_DEPLOY_UPLOAD_PATH+" RuntimeExec sh " + DEFAULT_DEPLOY_SCRIPT_FILE;
Runtime.getRuntime().exec(command);
log.info("执行部署脚本成功.命令:"+command);
return "执行部署脚本成功!";
} catch (Exception e) {
log.error("执行部署脚本失败!");
return "执行部署脚本失败!"+e.getMessage();
}
}
/**
* 检查密码
* @return
*/
private void checkPassword(String password) {
log.warn("密码:" + password);
log.warn("密码md5值:" + MD5.md5(password));
log.warn("正确密码md5值:" + PASSWORD);
if (!PASSWORD.equalsIgnoreCase(MD5.md5(password))) {
log.warn("密码错误");
throw new RuntimeException("密码错误");
}
}
/**
* 检查平台通用方法
* @return
*/
private void checkPlatform(String platform) throws RuntimeException{
//广东
log.info("platform:"+platform);
if("guangdong".equals(platform)){
DEFAULT_DEPLOY_UPLOAD_PATH = "/mnt";
DEFAULT_DEPLOY_SCRIPT_FILE = "/mnt/publish.sh " + platform;
}else{
throw new RuntimeException("platform参数错误!未知平台:"+platform);
}
}
}
对外暴露两个接口,一个是上传war包的接口,一个是开始执行部署的接口。(注意:做好安全验证)
1、可以使用先登录,才可调用两个接口,否则被拦截。(或者是再增加一下角色校验,例如只有管理员账户才能调用接口)
2、或者为了方便,可以增加免拦截,增加一些密码校验等(上图采用这种)
二、创建一个java类供第一步调用
public class RuntimeExec{
public static void main(String[] args) throws Exception{
for (String arg : args) {
System.out.println("参数:"+arg);
}
Runtime.getRuntime().exec(args);
Thread.sleep(3*60*1000);
}
}
执行:javac RuntimeExec 编译一下生成class文件
问:为什么要创建一个java类供第一步调用,直接让项目执行部署任务不行吗?
不行,因为项目执行/mnt/publish.sh脚本,执行的过程中还是由项目驱动。现在脚本中要kill掉项目,那就等于把自己杀死了,部署也就停止了。
现在把驱动publish.sh脚本的任务转交给RuntimeExec,它是一个独立的线程运行,kill掉项目不会对它执行publish.sh脚本有影响。
三、配置部署脚本
#/bin/bash
set -e
tomcatName=""
tomcatPort=""
deploy_dir=""
datetime=$(date "+%y%m%d-%H:%M:%S")
# 处理部署目录、tomcat名称和端口
deploy_dir="jsw"
tomcatName=apache-tomcat-6.0.24-gd-jsw-7085-shengchan
tomcatPort=7085
#备份ROOT文件夹及properties配置文件
echo "备份ROOT文件夹及properties配置文件" >> /mnt/RuntimeExec.log
if [ -f "/mnt/$deploy_dir/$tomcatName/webapps/ROOT/WEB-INF/classes/application.properties" ]; then
cp -rf /mnt/$deploy_dir/$tomcatName/webapps/ROOT/WEB-INF/classes/application.properties /mnt/$deploy_dir
fi
if [ -d "/mnt/$deploy_dir/$tomcatName/webapps/ROOT" ]; then
tar -zcf /mnt/war_bak/ROOT_$1_$datetime.tar.gz -C /mnt/$deploy_dir/$tomcatName/webapps ROOT
fi
#结束tomcat进程
echo "结束tomcat进程" >> /mnt/RuntimeExec.log
pid=$(ps -ef|grep tomcat| grep $tomcatPort | grep -v grep|awk '{print $2}' | xargs);
if [ -n "$pid" ]; then
echo "$pid not null" >> /mnt/RuntimeExec.log
kill -9 $pid
fi
#复制部署文件
echo "删除旧war包,复制新war包" >> /mnt/RuntimeExec.log
rm -rf /mnt/$deploy_dir/$tomcatName/webapps/ROOT
rm -f /mnt/$deploy_dir/$tomcatName/webapps/ROOT.war
cp -rf /mnt/jsw_platform.war /mnt/$deploy_dir/$tomcatName/webapps/ROOT.war
#启动tomcat
echo "启动tomcat" >> /mnt/RuntimeExec.log
cd /mnt/$deploy_dir/$tomcatName/bin
./startup.sh
四、配置jenkins
export path=/mnt/jenkins/workspace/jsw_gd
#进入git目录
cd $path
# maven打包
mvn -q clean
#1、替换配置文件
if [ -f "$path/src/main/resouces/application_product.properties" ]; then
cp -rf $path/src/main/resouces/application_product.properties $path/src/main/resouces/application.properties
cp -rf $path/src/main/resouces/kuaidipay_product.properties $path/src/main/resouces/kuaidipay.properties
cp -rf $path/src/main/resouces/weixinpay_product.properties $path/src/main/resouces/weixinpay.properties
echo "替换生产环境配置文件"
fi
#2、maven打包
mvn -q package -Dmaven.test.skip=true
#3、上传war包
echo "生产环境"
#用curl命令模拟post请求上传war包
curl -L -F file=@/mnt/jenkins/workspace/jsw_gd/target/jsw_gd.war -F password=KnLEkez50d84J7zPuBRyi4bg1oCWsDjP -F platform=xxx http://baidu.com/deploy/upload
#睡眠10秒,为了刚上传的文件留出保存的时间
sleep 10s
#4、执行开始部署接口
curl https://baidu.com/deploy/execute\?password=KnLEkez50d84J7zPuBRyi4bg1oCWsDjP\&platform=xxx
echo $platformname"生产环境部署完成"
五、验证
部署成功!jenkins console日志
延伸
还有一种方法,就是新创建一个java web deploy项目,专门用于部署工作。这样可以免于污染项目代码。
只需修改nginx配置,把某些特定的请求(例如上传war包和执行部署接口),转发到此deploy项目即可部署目标项目,就可以省去上面第二步操作。