Multi-version Testing with Cookies and Nginx


前言:

在一般而言,测试的时候,后台起一个服务,对应指定的某一个分支。但是在多分支并行开发不同模块的情况下,多个产品要同时测试和验收自己的模块开发。这样就要反复发布重启环境,并且无法做到多环境并行验收测试。本文描述如何使用 nginx 加上种 cookie 的方式来实现多版本测试。

准备工作

  • 假设测试域名为: saboloh.test
  • 克隆镜像仓库
git clone --mirror git@github.com:jianle/spring-boot-example.git

编写发布脚本

定义脚本名: build.sh

#!/bin/bash

cd $(dirname $0)

REVISION="$1"
PORT="$2"

if [ -z "$REVISION" ]; then
  REVISION="master"
fi

REVISION_STR=$(echo ${REVISION} | sed "s/\//\-/g")

if [ -z "$PORT" ]; then
  PORT=$(grep -oP "(?<=${REVISION_STR}=)(.*)" version_port.txt)
fi

if [ -z "$PORT" ]; then
  LAST_PORT=$(tail -n1 version_port.txt | cut -d'=' -f2)
  PORT=$(($LAST_PORT + 4))
  echo "${REVISION_STR}=${PORT}" >> version_port.txt
  update_nginx.sh ${REVISION_STR} ${PORT}
fi

set -e

BASE_DIR=/root/testing
export JAVA_HOME=/opt/soft/jdk1.8
export PATH=${JAVA_HOME}/bin:$PATH

echo $PATH

cd $BASE_DIR/spring-boot-example.git
git remote update
git log -3

VERSION=`git rev-parse $REVISION`
VERSION=${VERSION:0:7}
DATE=$(date +%Y%m%d)
PREFIX="${DATE}.${VERSION}"

echo "Building $PREFIX.jar ${PORT}"

SOURCEDIR=$BASE_DIR/build/source/$PREFIX
RESOURCEDIR=$SOURCEDIR/src/main/resources/

echo "Extracting source to $SOURCEDIR"
rm -rf $SOURCEDIR
git archive --prefix=$PREFIX/ --format=tar $VERSION | tar xf - -C $BASE_DIR/build/source

echo "Building npm in $RESOURCEDIR"
cd $RESOURCEDIR

ln -s ${BASE_DIR}/node_modules $RESOURCEDIR
npm install --save-dev
PUBLIC_PATH=http://saboloh.test/ npm run build

echo "maven building jar in $SOURCEDIR"
cd $SOURCEDIR
mvn -Dmaven.test.skip=true clean package

echo "
----------- build result ----------
--- Version: $PREFIX $PORT
--- URL: http://saboloh.test/
-----------------------------------

"

$BASE_DIR/start-web.sh $PREFIX $PORT

exit

其中使用到的文件有:

  • version_port.txt(版本对应端口号)
master=8188
testing=8192
  • update_nginx.sh(更新 nginx 配置)
#!/bin/bash

VERSION_STR="$1"
PORT="$2"

MAP_FILE=/usr/local/nginx/conf.d/testing_version.map
STREAM_FILE=/usr/local/nginx/conf.d/testing_version.stream

CHECK_BH=$(grep ${VERSION_STR} ${MAP_FILE})
if [ -z "${CHECK_BH}" ]; then
  MNR=$(wc -l ${MAP_FILE} | cut -d' ' -f1)
  let MNR--
  sed -i '/}/i \ \ \ \ "'''${VERSION_STR}'''"\ \ \ \ "'''${VERSION_STR}'''";' ${MAP_FILE}
fi

CHECK_STM=$(grep ${VERSION_STR} ${STREAM_FILE})
if [ -z "${CHECK_STM}" ]; then
  echo "
upstream ${VERSION_STR} {
    server 127.0.0.1:${PORT};
}
" >> ${STREAM_FILE}
fi

nginx -t

if [ $? == 0 ]; then
  nginx -s reload
fi
  • start-web.sh(启动服务)
#!/bin/bash

version="$1"
port=$2
if [ -z $port ]
then
  port=8188
fi

JMX="-Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false  -Dcom.sun.management.jmxremote.port=3000"
GC="-XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:-CMSConcurrentMTEnabled -XX:CMSInitiatingOccupancyFraction=70 -XX:+CMSParallelRemarkEnabled"
MEM="-Xms2G -Xmx2G"

cd $(dirname $0)

BASE_PATH=/root/testing/build/source/$version

jar=${BASE_PATH}/target/testing-0.0.1-SNAPSHOT.jar
CONF=/root/testing/conf/application.properties

export JAVA_HOME=/opt/soft/jdk1.8
export PATH=${JAVA_HOME}/bin:$PATH

# kill old process
OLD_PID=$(netstat -anltp|grep ${port}|awk '{print $7}'|cut -d'/' -f1|head -n1)
if [ ! -z "$OLD_PID" ]
then
  echo "------- Will kill old process ${OLD_PID} -------"
  echo ''
  kill -9 $OLD_PID
fi

nohup /opt/soft/jdk1.8/bin/java $MEM $GC -jar $jar --server.port=$port --spring.config.location=$CONF >> logs/${version}.${port}.log 2>&1 &

以上都是用于发布&启动的自动化脚本,其实质就是打包生成相关的 nginx 配置文件,然后重启 web 服务并重新加载 nginx 相关修改配置。

配置 Nginx 相关跳转

在 nginx 中,主要根据分支+版本情况,做不同跳转

include  /usr/local/nginx/conf.d/testing_version.map;
include  /usr/local/nginx/conf.d/testing_version.stream;

server {
    listen 80;
    server_name saboloh.test; #绑定域名
    proxy_read_timeout 1000s;

    gzip on;
    gzip_vary on;
    gzip_min_length 10240;
    gzip_proxied expired no-cache no-store private auth;
    gzip_types text/plain text/css text/xml text/javascript application/x-javascript application/xml application/json;
    gzip_disable "MSIE [1-6]\.";

    location / {
        proxy_set_header Host $host;
        #if ($version_port != '8080') {
        #    break;
        #}
        proxy_pass http://$vstream; #使用 upstream
        proxy_set_header X-real-ip $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
}

nginx 配置过程中,一开始想直接在 map 文件中,定义版本对应的端口,然后根据版本,跳转端口,但是一直跳转失败(上述代码注释片段),具体我也不知道说明原因,后面改成使用 upstream 的方式都是正常的。如果有大神知道,还请留言指点一下。

总结

在上述方法中,为了方便,很多地方都使用了脚本来自动配置,如果想简单一些,直接启动号环境之后,修改 nginx 相关配置,即可。还有在使用 upstream 的过程中,为了安全生产,起始还可以在每一个 upstream 中,加一个默认的 server。设置为 backup,相关使用可以查看 nginx 相关文档。