基于nginx-redis 前端灰度
前提
项目采用的是前后端分离,前端使用vue 后端使用spring cloud。单点采用cas。本文暂且讨论前端基于cookie-token的灰度。
目前实现的方式有三种
- nginx+lua:根据访问者ip地址区分,由于公司出口是一个ip地址,会出现访问网站要么都是老版,要么都是新版,采用这种方式并不适合;
- nginx:根据cookie分流,灰度发布基于用户才更合理(本例子采用该种方式)。
整体思路:
1.首先用户经过cas登录后访问vue前端资源会携带access_token。
2.在lua脚本连接redis服务器,验证token是否在灰度列表和清单中。
3.token灰度验证通过,交由nginx服务器进行反向代理服务到灰度服务器;
准备
安装浏览器cookie插件 editthiscookie
nginx lua 环境安装
mkdir /usr/local/openresty/gray
mkdir /usr/local/openresty/gray/conf
mkdir /usr/local/openresty/gray/logs
mkdir /usr/local/openresty/gray/lua
nginx.conf
user root;
worker_processes 1;
error_log /usr/local/openresty/gray/logs/error.log;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /usr/local/openresty/gray/logs/access.log main;
#添加;;标识默认路径下的lualib
lua_package_path "$prefix/lualib/?.lua;;";
lua_package_cpath "$prefix/lualib/?.so;;";
upstream prod1 {
server 47.114.161.16:80;
}
upstream prod2 {
server 47.114.50.9:80;
}
server {
listen 8056;
server_name localhost;
location / { #为每个请求执行gray.lua脚本
content_by_lua_file /usr/local/openresty/gray/lua/gray.lua;
}
location @prod1 {
proxy_pass http://prod1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location @prod2 {
proxy_pass http://prod2;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
gray.lua
local cjson = require("cjson")
--引入nginx-redis模块
local redis=require "resty.redis";
--初始化redis客户端
local red=redis:new();
--设置超时时间1000ms
red:set_timeout(1000);
--redis建立连接
local ok,err=red:connect("39.100.254.100", 6379);
--如果建立连接失败,则请求正常服务
if not ok then
ngx.say("failed to connect redis ",err);
return;
end
-- 请注意这里 auth 的调用过程 这是redis设置密码的
local count
count, err = red:get_reused_times()
if 0 == count then
--密码
ok, err = red:auth("2019")
if not ok then
ngx.say(cjson.encode({code = 500,message = err}))
return
end
elseif err then
ngx.say("failed to get reused times: ", err, "<br/>")
return
end
--选择的桶
red:select(8)
--获取请求ip
local local_ip = ngx.req.get_headers()["X-Real-IP"];
if local_ip == nil then
local_ip = ngx.req.get_headers()["x_forwarded_for"];
end
if local_ip == nil then
local_ip = ngx.var.remote_addr;
end
local_ip=ngx.var.remote_addr;
--redis中获取白名单
local ip_lists=red:get("gray");
--ngx.say("ip_list" .. ip_lists);
--判断是否在白名单然后转到对应服务
if string.find(ip_lists,local_ip) == nil then
--一定要注意,当这里ngx.exec()的时候,前面一定不能用任何的输出
ngx.exec("@prod1");
--ngx.say("prod1");
else
--一定要注意,当这里ngx.exec()的时候,前面一定不能用任何的输出
ngx.exec("@prod2");
--ngx.say("prod2");
end
local ok,err=red:close();
启动
/usr/local/openresty/nginx/sbin/nginx -c /usr/local/openresty/gray/conf/nginx.conf
停止
/usr/local/openresty/nginx/sbin/nginx -c /usr/local/openresty/gray/conf/nginx.conf -s stop
备注
脚本还在根据实际项目调整中,还未更加cookie中的access_token 来进行灰度。
local cjson = require("cjson")
local redis=require "resty.redis";
local red=redis:new();
--ngx.log(ngx.INFO, " msg:", "helloworld")
red:set_timeout(1000);
--redis连接
local ok,err=red:connect("39.100.254.140", 6379);
if not ok then
ngx.say("failed to connect redis ",err);
return;
end
-- 请注意这里 auth 的调用过程 这是redis设置密码的
local count
count, err = red:get_reused_times()
if 0 == count then
ok, err = red:auth("portal2019")
if not ok then
ngx.say(cjson.encode({code = 500,message = err}))
return
end
elseif err then
ngx.say("failed to get reused times: ", err, "<br/>")
return
end
--密码和选择的桶
red:select(8)
--accessToken
function get_cookie(s_cookie, key)
local value = nil
-- string.gfind is renamed to string.gmatch
for item in string.gmatch(s_cookie, "[^;]+") do
local _, _, k, v = string.find(item, "^%s*(%S+)%s*=%s*(%S+)%s*")
if k ~= nil and v~= nil and key ==k then
return v
end
end
return value
end
local isNULL = function(v)
return not v or v == ngx.null
end
local raw_cookie = ngx.req.get_headers()["Cookie"]
local accessToken = get_cookie(raw_cookie, "accessToken");
--ngx.say("accessToken :" .. accessToken)
-- 当accessToken不存在时候默认路由
if accessToken == nil then
--ngx.say("prod1");
ngx.exec("@prod1");
local ok,err=red:close();
return;
end
--redis中获取白名单
local rdsAccessToken=red:get("gray:token:" .. accessToken);
--ngx.say("rdsAccessToken:", cjson.encode(rdsAccessToken));
--判断是否在白名单然后转到对应服务
if isNULL(rdsAccessToken) then
--一定要注意,当这里ngx.exec()的时候,前面一定不能用任何的输出
ngx.exec("@prod1");
--ngx.say("prod1");
else
--一定要注意,当这里ngx.exec()的时候,前面一定不能用任何的输出
ngx.exec("@prod2");
--ngx.say("prod2");
end
local ok,err=red:close();