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 https://codeload.github.com/phpredis/phpredis/zip/2.2.7
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; } }
} |