背景

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

功能

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

实现流程

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

搭建环境要求
linux+mysql+redis+openresty+tomcat+maven

前端部署

配置前端信息

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

新建前端项目

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

后端部署

定制tomcat模板

  • 下载tomcat解压后修改tomcat的server.xml配置
    在几个端口配置的位置预留用于替换占位符
    一种多版本测试解决方案及实现过程 - 图1
    tomcat启动参数也可以在catalina.sh中按自己需求去定制下
    一种多版本测试解决方案及实现过程 - 图2
  • 然后像配置前端项目一样,我们先填写一些必要的信息 tomcat端口信息以及一些其他的自定义信息。
  • 然后就开始部署一个后端项目
    建立项目目录 -> 拷贝tomcat模板到该目录下 -> git上拉代码到代码目录 -> 使用maven编译生成war包 -> 将war解压到当前项目下tomcat的webapp目录 -> 运行tomcat启动脚本

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

  1. 项目管理脚本
  2. #!/bin/bash
  3. #项目名称
  4. PROJECT_NAME="@projectName@"
  5. PROJECT_GIT_URL="@gitPath@"
  6. GITBRANCH="@branch@"
  7. PROPERTY="@profile@"
  8. TOMCAT_PORT="@tomcatPort@"
  9. DUBBO_PORT="@dubboPort@"
  10. MODULE_NAME="@moduleName@"
  11. FINAL_NAME="@finalName@"
  12. #项目根目录
  13. HOME_ROOT="/data/project/"$PROJECT_NAME
  14. #项目源码
  15. SOURCE_DIR=$HOME_ROOT"/source/"
  16. #项目源码构建目录
  17. PROJECT_DIR=$SOURCE_DIR`ls $SOURCE_DIR`
  18. #tomcat webroot
  19. WEB_ROOT=$HOME_ROOT"/code/"
  20. #tomcat 目录
  21. TOMCAT_HOME=$HOME_ROOT"/tomcat/"
  22. MAVEN_HOME="/data/apache-maven-3.3.9"
  23. update_code(){
  24. if [ -d $PROJECT_DIR ]
  25. then
  26. cd $PROJECT_DIR
  27. git checkout $GITBRANCH
  28. git pull
  29. else
  30. cd $SOURCE_DIR
  31. git clone $PROJECT_GIT_URL
  32. cd $PROJECT_DIR
  33. git checkout $GITBRANCH
  34. fi
  35. }
  36. install(){
  37. cd $PROJECT_DIR;
  38. $MAVEN_HOME/bin/mvn clean;
  39. $MAVEN_HOME/bin/mvn -T 1C -Dmaven.test.skip=true -Dmaven.compile.fork=true -P $PROPERTY install;
  40. }
  41. deploy(){
  42. TEMP=$WEB_ROOT"*"
  43. rm -fR $TEMP
  44. TEMP=$PROJECT_DIR"/"$MODULE_NAME"/target/"$FINAL_NAME"/*"
  45. cp -fR $TEMP $WEB_ROOT
  46. }
  47. restart(){
  48. TOMCAT_PID=`jps -v|grep $DUBBO_PORT|awk '{print $1}' `
  49. kill -9 $TOMCAT_PID
  50. sleep 5
  51. bash $TOMCAT_HOME"bin/catalina.sh" start
  52. }
  53. stop(){
  54. TOMCAT_PID=`jps -v|grep $DUBBO_PORT|awk '{print $1}' `
  55. kill -9 $TOMCAT_PID
  56. sleep 5
  57. }
  58. help(){
  59. echo $"Usage: $0 {update_code|install|backup|deploy|restart}"
  60. }
  61. case "$1" in
  62. stop)
  63. stop
  64. ;;
  65. update_code)
  66. update_code
  67. ;;
  68. install)
  69. install
  70. ;;
  71. deploy)
  72. deploy
  73. ;;
  74. restart)
  75. restart
  76. ;;
  77. -h)
  78. help
  79. ;;
  80. --help)
  81. help
  82. ;;
  83. *)
  84. update_code
  85. install
  86. deploy
  87. restart
  88. ;;
  89. esac
  90. exit 0
  91. 项目初始化脚本
  92. #!/bin/bash
  93. echo "project name $1"
  94. echo "tomcat port $2"
  95. echo "dubbo port $3"
  96. echo "git path $4"
  97. echo "git branch $5"
  98. echo "git profile $6"
  99. echo "moduleName $7"
  100. echo "finalName $8"
  101. #检查项目是否创建
  102. if [ ! -d "/data/project/$1" ]; then
  103. mkdir /data/project/$1;
  104. mkdir /data/project/$1/code ;
  105. mkdir /data/project/$1/source ;
  106. cp -R /data/project/tomcat /data/project/$1/;
  107. cp /data/project/publish.sh /data/project/$1/publish.sh;
  108. sed -ig "s/@projectName@/$1/" /data/project/$1/publish.sh;
  109. sed -ig "s/@tomcatPort@/$2/" /data/project/$1/publish.sh;
  110. sed -ig "s/@dubboPort@/$3/" /data/project/$1/publish.sh;
  111. sed -ig "s?@gitPath@?$4?" /data/project/$1/publish.sh;
  112. sed -ig "s/@branch@/$5/" /data/project/$1/publish.sh;
  113. sed -ig "s/@profile@/$6/" /data/project/$1/publish.sh;
  114. sed -ig "s/@moduleName@/$7/" /data/project/$1/publish.sh;
  115. sed -ig "s/@finalName@/$8/" /data/project/$1/publish.sh;
  116. cd /data/project/$1/source && git clone $4 && cd /data/project/$1/source/`ls /data/project/$1/source` && git checkout origin/$5
  117. sed -ig "s/@dubboPort@/$3/" /data/project/$1/tomcat/bin/catalina.sh
  118. sed -ig "s/@tomcatPort@/$2/" /data/project/$1/tomcat/conf/server.xml
  119. sed -ig "s/@ajpPort@/$(($2-1))/" /data/project/$1/tomcat/conf/server.xml
  120. sed -ig "s/@shutdownPort@/$(($2-2))/" /data/project/$1/tomcat/conf/server.xml
  121. fi

基于上述脚本可以完成后端项目的创建更新及重新编译启动

前后端组合版本

基于nginx配置

在后台配置好nginx路由规则后 生成nginx配置文件 替换原有的配置后reload nginx即可
一种多版本测试解决方案及实现过程 - 图3

免配置版本切换

将host绑定到nginx所在的机器后执行版本切换操作
一种多版本测试解决方案及实现过程 - 图4

点击切换版本后将操作人的IP及切换的版本号写入redis中,openresty中配置如下脚本将请求路由到该版本对应的前后端项目

  1. openresty配置文件的http模块中配置
  2. init_by_lua_file /root/init.lua;
  3. init.lua
  4. #获取请求的IP对应的版本信息
  5. function getVersion()
  6. local redis = require "resty.redis"
  7. local red = redis:new()
  8. red:set_timeout(1000) -- 1 sec
  9. local ok, err = red:connect("127.0.0.1", 6379)
  10. if not ok then
  11. ngx.say("failed to connect: ", err)
  12. return
  13. end
  14. local headers=ngx.req.get_headers()
  15. local ip=headers["X-REAL-IP"] or headers["X_FORWARDED_FOR"] or ngx.var.remote_addr or "0.0.0.0"
  16. local version = red:get(ip)
  17. red:close()
  18. return version
  19. end
  20. server模块处配置 将请求转发到各个版本对应的前后端
  21. location / {
  22. content_by_lua '
  23. local version = getVersion()
  24. ngx.exec("@" .. version)
  25. ';
  26. }
  27. location @4.8.3{
  28. proxy_pass http://127.0.0.1:port;#通过这个port再转发到对应的前后端
  29. proxy_redirect off;
  30. proxy_set_header Host $host;
  31. proxy_set_header X-Real-IP $remote_addr;
  32. proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  33. client_max_body_size 10m;
  34. client_body_buffer_size 128k;
  35. proxy_connect_timeout 90;
  36. proxy_send_timeout 120;
  37. proxy_read_timeout 120;
  38. proxy_buffer_size 4k;
  39. proxy_buffers 4 32k;
  40. proxy_busy_buffers_size 64k;
  41. proxy_temp_file_write_size 64k;
  42. add_header Pragma "no-cache";
  43. add_header Cache-Control "no-store, no-cache, must-revalidate, post-check=0, pre-check=0";
  44. }

这里主要就是一个 openresty+lua 的玩法

最后通过web界面将这些流程串起来就是一个兼发布及AB测试于一体的系统了

最后

本文主要提供一个实现思路,抛个转。基于此思路继续延展可以做的事还很多,比如通过一些运维技术如ansible可以实现远程部署,基于openresty接入自己的业务系统又可以完成灰度任务。如果讲的有什么不会的地方欢迎大家指正。有什么问题也可以加我QQ一起讨论。