Nginx实现灰色发布

什么是灰度发布

灰度发布是指在黑与白之间,能够平滑过渡的一种发布方式。AB test就是一种灰度发布方式,让一部分用户继续用A,一部分用户开始用B,如果用户对B没有什么反对意见,那么逐步扩大范围,把所有用户都迁移到B上面来。灰度发布可以保证整体系统的稳定,在初始灰度的时候就可以发现、调整问题,以保证其影响度。

 

下面是实验拓扑,客户端没有表现出来。

 

我们这里是用户访问我们时候,依然默认到旧服务器,在旧服务显著位置提示“使用新版”当用户点击“使用新版”后页面自动刷新到新版本的页面中,新版页面中,也有返回旧版本的提示,我们也可以挑选部分VIP客户进行新版本的体验。试验中没有使用基于IP的访问控制,避免因IP原因导致用户的体验问题。

 

实验说明

我们使用PHP+redis+lua+nginx来实现灰色发布。先说下程序

Web服务器

Shell>#yum –y install php

安装phpredis客户端

依赖

Shell>#yum –y install gcc make php-devel

获取phpredis

Shell>#wget

Shell>#unzip phpredis-2.2.7.zip

Shell>#cd phpredis-2.2.7

Shell>#phpize

Shell>#./configure

Shell>#make && make install

将redis添加的php服务器中

Shell>#vi /etc/php 添加内容如下

extension="redis.so"

重启http服务

Shell>#service httpd restart

验证php是否能使用redis

页面如下

Shell>#test.php

<?php

$redis=  new Redis();

$redis->connect('192.168.186.136',6379);

$redis->set('KeyTest','1');

echo  $redis->get('KeyTest');

?>

Shell>#php test.php

输入如下表示ok

1

然后我们说下测试用web站点的页面信息

文件名

功能

备注

index.php

首页

login.html

登陆页内容

login.php

登陆页程序

logout.php

登出页

·

switch.html

切换页

switch.php

切换页面程序

内容如下

Page:index.php

<?php

isset($_COOKIE["UID"])?($uid=$_COOKIE['UID']):($uid='-1');

if  ( empty($_COOKIE["UID"])  ||  $uid == '-1' ) {

       include('login.html');

}else{

       include('switch.html');

}

echo  $_SERVER['SERVER_ADDR'];

?>

 

Page:login.html

<!DOCTYPE  html>

<html>

<head>

<meta  http-equiv="Content-Type" content="text/html;  charset=utf-8" />

<meta  http-equiv="Content-Language" content="zh-cn" />

</head>

<body>

       <form action="login.php"  method="get">

       <input type='text'  name="username" />

       <input type='password'  name="password" />

       <input type="submit"  value="Login" />

       <hr>

       <p>说明:</p>

       <p>灰色发布示例</p>

       <p>用户名不能为-1,其它随意,密码不验证,随便填写</p>

</body>

 

Page:login.php

<?php

isset($_GET['username'])?($USER=$_GET['username']):($USER='-1'); 

if  ( empty($USER)){

       $USER='-1';

}

       if($USER == '-1' ){

              echo '错误,您输入的用户名错误!';

       }else{

              setcookie('UID',$USER );

header("Location:  index.php");

       }

?>

 

Page:logout.php

<?php

setcookie('UID');

header("Location:  index.php");

?>

 

Page:switch.html

新版本服务器

旧版本服务器

<!DOCTYPE  html>

<html>

<head>

<meta  charset=utf-8" />

<meta  http-equiv="Content-Language" content="zh-cn" />

</head>

<body>

       <?php echo '<p>当前用户ID:'.$uid.'</p>'  ?>

       <form action="switch.php"  method="get">

       <input type="hidden"  name='type' value='0'/>

       <input type="submit"  value='切换到旧版本'/>

       </form>

       <div style="float:  right">

        <form  action="logout.php" method="get">

        <input  type="submit"  value='切换用户'/>

        </form>

       </div>

       <hr>

       <h1>这是新版程序!</h1>

</body>

<!DOCTYPE  html>

<html>

<head>

<meta  charset=utf-8" />

<meta  http-equiv="Content-Language" content="zh-cn" />

</head>

<body>

       <?php echo '<p>当前用户ID:'.$uid.'</p>'  ?>

       <form action="switch.php"  method="get">

       <input type="hidden"  name='type' value='1'/>

       <input type="submit"  value='切换到新版本'/>

       </form>

       <div style="float:  right">

        <form  action="logout.php" method="get">

        <input  type="submit"  value='切换用户'/>

        </form>

       </div>

       <hr>

       <h1>这是旧版程序!</h1>

</body>

 

Page:switch.php

<?php

isset($_COOKIE["UID"])?($uid=$_COOKIE['UID']):(exit('NO  UID'));

$KEY='user_'.$uid;

isset($_GET['type'])?($TID=$_GET['type']):($TID='-1');

if($TID  == '1')

{

       $redis= new Redis();

         $redis->connect('192.168.186.136',6379);

        $redis->set($KEY,'1');

      

       if ( $redis->get($KEY) == '1' )

       {

              header("Location:  index.php");

       }else{

              echo 'ERR:';

       }

}elseif($TID  == '0'){

       $redis= new Redis();

         $redis->connect('192.168.186.136',6379);

        $redis->delete($KEY);

        if ( ! var_dump($redis->get($KEY))  )

        {

                header("Location:  index.php");

        }else{

                echo 'ERR:';

        }

}else{

       echo 'ERR: TID is error';

}

?>

 

Nginx部分

我们需要使用lua语言来实现判断,所以我们需要安装lua支持。至于安装lua环境,我们在WAF中已经举例说明了。这里不再罗嗦,我们需要的组件如下。

  • lua-resty-cookie

  • lua-resty-redis

 

获取文件

Shell>#wget https://codeload.github.com/cloudflare/lua-resty-cookie/zip/master

Shell>#wget https://codeload.github.com/openresty/lua-resty-redis/zip/master

解压缩文件

Shell>#unzip lua-resty-redis-master.zip

Shell>#unzip lua-resty-cookie-master.zip

移动文件到指定目录

Shell>#mv ./lua-resty-cookie-master/lib/resty /etc/nginx/lua/resty

Shell>#mv ./lua-resty-redis-master/lib/resty /etc/nginx/lua/resty

 

修改权限

Shell>#chown –R nginx:nginx /et/nginx

 

nginx的主配置文件如下

user  nginx;

worker_processes  2;

events  {

       use epoll;

       worker_connections  60000;

       multi_accept on;

}

http  {

       include   mime.types;

       lua_package_path  "/etc/nginx/lua/?.lua;;";

       default_type  application/octet-stream;

       charset UTP-8;

       fastcgi_intercept_errors on;

       server_names_hash_bucket_size 128;

       client_header_buffer_size 4k;

       large_client_header_buffers 4 32k;

       client_max_body_size 300m;

       tcp_nodelay on;

       server_tokens  off;

       client_body_buffer_size  512k;

       sendfile on;

       tcp_nopush on;

       keepalive_timeout  60;

       gzip   on;

       gzip_min_length  1k;

       gzip_buffers     4 16k;

       gzip_comp_level 2;

       gzip_types       text/plain application/x-javascript  text/css application/xml;

       gzip_vary on;

       open_file_cache max=204800  inactive=20s;

       open_file_cache_min_uses 2;

       open_file_cache_valid 30s;

       server {

              listen   443;

              server_name  localhost;

              ssl on;

              ssl_certificate /var/openssl/server.crt;

              ssl_certificate_key  /var/openssl/server_nopass.key;

              location / {

              default_type text/plain;

              rewrite_by_lua '

              local ck = require  "resty.cookie"

                local cookie, err = ck:new()

              local redis = require  "resty.redis"

              local red = redis:new()

              red:set_timeout(1000)

              local ok, err =  red:connect("127.0.0.1", 6379)

              if not ok then

                     ngx.say("failed to  connect: ", err)

              return

              end

               

              if not cookie then

                     ngx.say("Sorry!")

                    return

                end

 

                -- get single cookie

                local field, err =  cookie:get("UID")

                if not field then

                      ngx.exec("@ProxyOld")

                    ngx.say("no  fond")

                    return

                end

              myuser="user_"..field

              -- ngx.say(myuser)

              local res, err = red:get( myuser  )

              if not res then

                     ngx.exec("@ProxyOld")

                     return

              end

              -- ngx.say(res)

              if res == ngx.null then

                     ngx.exec("@ProxyOld")               

                     return

              end

              ngx.exec("@ProxyNew")';     

              }

 

               location  @ProxyOld {

                        proxy_pass  http://clusterOld;

                }

 

                location @ProxyNew {

                        proxy_pass  http://clusterNew;

                }

 

       }

      

       upstream clusterOld{

               server 192.168.186.139:80;

       }

 

       upstream clusterNew{

               server 192.168.186.138:80;

       }

      

       server {

              listen   80;

              server_name  localhost;

              location / {

                     rewrite ^(.*)$  https://$host$1 permanent; 

              }

       }

 

}