测试发布系统 0-1 实现过程

  • 支持前端项目多分支部署
  • 支持后端项目多分支部署
  • 支持前后端项目自由组合版本
  • 基于IP的免配置版本切换

背景

1.测试人员不太懂技术,测试时nginx配置容易出错。
2.开发人员在同个项目开发不同分支,每个人开发进度不一致,测试时每次只能测一个小功能点。
3.每次测完之后的版本需要保留,出了问题能够定位到是哪个版本开始出现的。
4.开发人员需要一个工具,能够自动化部署测试应用。

流程

首先由开发人员配置前后端项目信息,在完成发布后,配置对应的版本信息(包括nginx路由配置及版本号),版本配置完成后,由测试人员切换到响应版本进行测试。

搭建环境要求

linux+mysql+redis+openresty+tomcat+maven

前端部署

  • 配置前端信息

    图中可以看到一些必要的配置信息:项目名称 git地址 git分支
    通过shell脚本从git上拉取前端代码并生成一个新的目录(项目名称)放置前端的代码(webpack打包压缩等在本地执行)

  • 新建前端项目

    1
    2
    3
    4
    mkdir ${项目名称} &&
    cd ${项目名称} &&
    git clone ${git地址} &&
    cd `ls ${项目名称}` && git checkout ${git分支}
  • 更新前端代码

    1
    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启动脚本

    下面是本人写的一段丑陋的脚本

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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
项目管理脚本
#!/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中配置如下脚本将请求路由到该版本对应的前后端项目

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
在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一起讨论。