Nginx实战灰度发布

灰度发布,就是根据各种条件,让一部分用户使用旧版本,另一部分用户使用新版本,而并不是直接将所有流量切过来,测试人员对新版本做运行状态观察,收集各种运行时数据,也就是所谓的A/B测试,如果没有问题,那么可以将少量的用户流量导入到新版本上,将负面影响控制在最小范围内。

灰度发布类型

依据实现终端不同,我认为灰度发布可以分为两类:

  • 客户端灰度:客户端依据特殊条件,例如用户ID/IP等条件实现分流
  • 服务端灰度:依托Web服务器反向代理或者编码,对不同IP/用户ID等条件实现分流

实践

这里我们主要讨论服务段灰度发布,我们依托Openresty(Nginx+Lua)+Redis来实现灰度发布,原理如下图:

灰度发布

执行过程:

  1. 当用户请求到达前端web(代理)服务器Openresty,内嵌的lua模块解析Nginx配置文件中的lua脚本代码;
  2. Lua获取客户端IP地址,去查询Redis中是否有该键值,如果有返回值执行@clien2,否则执行@client1;
  3. Location @client2把请求转发给预发布服务器,location @client1把请求转发给生产服务器,服务器返回结果,整个过程完成;

Openresty配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
upstream client1 { 
server 127.0.0.1:8080; #模拟生产服务器
}
upstream client2 {
server 127.0.0.1:8090; #模拟预发布服务器
}

server {
listen 80;
server_name localhost;

location ^~ /test {
content_by_lua_file ab.lua
}

location @client1{
proxy_pass http://client1;
}
location @client2{
proxy_pass http://client2;
}

ab.lua代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
local redis = require "resty.redis" 
local cache = redis.new()
cache:set_timeout(60000)

local ok, err = cache.connect(cache, '127.0.0.1', 6379)
if not ok then
ngx.say("failed to connect:", err)
return
end


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
--ngx.say("local_ip is : ", local_ip)

local intercept = cache:get(local_ip)


if intercept == local_ip then
ngx.exec("@client2")
return
end

ngx.exec("@client1")

local ok, err = cache:close()

if not ok then
ngx.say("failed to close:", err)
return
end

到此最基本的灰度发布已经实现,如果要做更细粒度灰度发布可参考新浪开源的一个动态路由系统考ABTestingGateway项目。ABTestingGateway是一个可以动态设置分流策略的灰度发布系统,工作在7层,基于nginx和ngx-lua开发,使用redis作为分流策略数据库,可以实现动态调度功能。

ABTestingGateway:https://github.com/CNSRE/ABTestingGateway

有用就打赏一下作者吧!