背景
- 测试人员不太懂技术,测试时nginx配置容易出错。
- 开发人员在同个项目开发不同分支,每个人开发进度不一致,测试时每次只能测一个小功能点。
- 每次测完之后的版本需要保留,出了问题能够定位到是哪个版本开始出现的。
- 开发人员需要一个工具,能够自动化部署测试应用。
功能
- 支持前端项目多分支部署
- 支持后端项目多分支部署
- 支持前后端项目自由组合版本
- 基于IP的免配置版本切换
实现流程
首先由开发人员配置前后端项目信息,在完成发布后,配置对应的版本信息(包括nginx路由配置及版本号),版本配置完成后,由测试人员切换到响应版本进行测试。
搭建环境要求
linux+mysql+redis+openresty+tomcat+maven
前端部署
配置前端信息
图中可以看到一些必要的配置信息:项目名称 git地址 git分支
通过shell脚本从git上拉取前端代码并生成一个新的目录(项目名称)放置前端的代码(webpack打包压缩等在本地执行)
新建前端项目
mkdir ${项目名称} &&
cd ${项目名称} &&
git clone ${git地址} &&
cd `ls ${项目名称}` && git checkout ${git分支}
- 更新前端代码
cd /data/front/${项目名称}/`ls /data/front/${项目名称}` && git pull
后端部署
定制tomcat模板
- 下载tomcat解压后修改tomcat的server.xml配置
在几个端口配置的位置预留用于替换占位符
tomcat启动参数也可以在catalina.sh中按自己需求去定制下 - 然后像配置前端项目一样,我们先填写一些必要的信息 tomcat端口信息以及一些其他的自定义信息。
- 然后就开始部署一个后端项目
建立项目目录 -> 拷贝tomcat模板到该目录下 -> git上拉代码到代码目录 -> 使用maven编译生成war包 -> 将war解压到当前项目下tomcat的webapp目录 -> 运行tomcat启动脚本
下面是本人写的一段丑陋的脚本
项目管理脚本
#!/bin/bash
#项目名称
PROJECT_NAME="@projectName@"
PROJECT_GIT_URL="@gitPath@"
GITBRANCH="@branch@"
PROPERTY="@profile@"
TOMCAT_PORT="@tomcatPort@"
DUBBO_PORT="@dubboPort@"
MODULE_NAME="@moduleName@"
FINAL_NAME="@finalName@"
#项目根目录
HOME_ROOT="/data/project/"$PROJECT_NAME
#项目源码
SOURCE_DIR=$HOME_ROOT"/source/"
#项目源码构建目录
PROJECT_DIR=$SOURCE_DIR`ls $SOURCE_DIR`
#tomcat webroot
WEB_ROOT=$HOME_ROOT"/code/"
#tomcat 目录
TOMCAT_HOME=$HOME_ROOT"/tomcat/"
MAVEN_HOME="/data/apache-maven-3.3.9"
update_code(){
if [ -d $PROJECT_DIR ]
then
cd $PROJECT_DIR
git checkout $GITBRANCH
git pull
else
cd $SOURCE_DIR
git clone $PROJECT_GIT_URL
cd $PROJECT_DIR
git checkout $GITBRANCH
fi
}
install(){
cd $PROJECT_DIR;
$MAVEN_HOME/bin/mvn clean;
$MAVEN_HOME/bin/mvn -T 1C -Dmaven.test.skip=true -Dmaven.compile.fork=true -P $PROPERTY install;
}
deploy(){
TEMP=$WEB_ROOT"*"
rm -fR $TEMP
TEMP=$PROJECT_DIR"/"$MODULE_NAME"/target/"$FINAL_NAME"/*"
cp -fR $TEMP $WEB_ROOT
}
restart(){
TOMCAT_PID=`jps -v|grep $DUBBO_PORT|awk '{print $1}' `
kill -9 $TOMCAT_PID
sleep 5
bash $TOMCAT_HOME"bin/catalina.sh" start
}
stop(){
TOMCAT_PID=`jps -v|grep $DUBBO_PORT|awk '{print $1}' `
kill -9 $TOMCAT_PID
sleep 5
}
help(){
echo $"Usage: $0 {update_code|install|backup|deploy|restart}"
}
case "$1" in
stop)
stop
;;
update_code)
update_code
;;
install)
install
;;
deploy)
deploy
;;
restart)
restart
;;
-h)
help
;;
--help)
help
;;
*)
update_code
install
deploy
restart
;;
esac
exit 0
项目初始化脚本
#!/bin/bash
echo "project name $1"
echo "tomcat port $2"
echo "dubbo port $3"
echo "git path $4"
echo "git branch $5"
echo "git profile $6"
echo "moduleName $7"
echo "finalName $8"
#检查项目是否创建
if [ ! -d "/data/project/$1" ]; then
mkdir /data/project/$1;
mkdir /data/project/$1/code ;
mkdir /data/project/$1/source ;
cp -R /data/project/tomcat /data/project/$1/;
cp /data/project/publish.sh /data/project/$1/publish.sh;
sed -ig "s/@projectName@/$1/" /data/project/$1/publish.sh;
sed -ig "s/@tomcatPort@/$2/" /data/project/$1/publish.sh;
sed -ig "s/@dubboPort@/$3/" /data/project/$1/publish.sh;
sed -ig "s?@gitPath@?$4?" /data/project/$1/publish.sh;
sed -ig "s/@branch@/$5/" /data/project/$1/publish.sh;
sed -ig "s/@profile@/$6/" /data/project/$1/publish.sh;
sed -ig "s/@moduleName@/$7/" /data/project/$1/publish.sh;
sed -ig "s/@finalName@/$8/" /data/project/$1/publish.sh;
cd /data/project/$1/source && git clone $4 && cd /data/project/$1/source/`ls /data/project/$1/source` && git checkout origin/$5
sed -ig "s/@dubboPort@/$3/" /data/project/$1/tomcat/bin/catalina.sh
sed -ig "s/@tomcatPort@/$2/" /data/project/$1/tomcat/conf/server.xml
sed -ig "s/@ajpPort@/$(($2-1))/" /data/project/$1/tomcat/conf/server.xml
sed -ig "s/@shutdownPort@/$(($2-2))/" /data/project/$1/tomcat/conf/server.xml
fi
基于上述脚本可以完成后端项目的创建更新及重新编译启动
前后端组合版本
基于nginx配置
在后台配置好nginx路由规则后 生成nginx配置文件 替换原有的配置后reload nginx即可
免配置版本切换
将host绑定到nginx所在的机器后执行版本切换操作
点击切换版本后将操作人的IP及切换的版本号写入redis中,openresty中配置如下脚本将请求路由到该版本对应的前后端项目
在openresty配置文件的http模块中配置
init_by_lua_file /root/init.lua;
init.lua
#获取请求的IP对应的版本信息
function getVersion()
local redis = require "resty.redis"
local red = redis:new()
red:set_timeout(1000) -- 1 sec
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("failed to connect: ", err)
return
end
local headers=ngx.req.get_headers()
local ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
local version = red:get(ip)
red:close()
return version
end
server模块处配置 将请求转发到各个版本对应的前后端
location / {
content_by_lua '
local version = getVersion()
ngx.exec("@" .. version)
';
}
location @4.8.3{
proxy_pass http://127.0.0.1:port;#通过这个port再转发到对应的前后端
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 10m;
client_body_buffer_size 128k;
proxy_connect_timeout 90;
proxy_send_timeout 120;
proxy_read_timeout 120;
proxy_buffer_size 4k;
proxy_buffers 4 32k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
add_header Pragma "no-cache";
add_header Cache-Control "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
}
这里主要就是一个 openresty+lua 的玩法
最后通过web界面将这些流程串起来就是一个兼发布及AB测试于一体的系统了
最后
本文主要提供一个实现思路,抛个转。基于此思路继续延展可以做的事还很多,比如通过一些运维技术如ansible可以实现远程部署,基于openresty接入自己的业务系统又可以完成灰度任务。如果讲的有什么不会的地方欢迎大家指正。有什么问题也可以加我QQ一起讨论。