openresty 执行阶段问题踩坑

2022-08-10 14:09:20 浏览数 (1)

背景

由于业务需求我们需要根据client_ip对部分接口做一个简单的 ab test,将部分流量导到新的服务上。基于之前做 WAF 的经验,制定了大体方案:根据 client_ip 做 hash, 根据 hash 值来确定是请求新后端还是原来的后端服务。 在说明详细方案前,先说明一下现状。当前的配置如下:

代码语言:javascript复制
location ~ ^/api/rest/vd /Login {
    rewrite ^(.*)$ /loginServer$1 break;

    proxy_http_version 1.1;
    proxy_next_upstream error timeout invalid_header;
    proxy_redirect off;

    proxy_set_header Host $http_host;
    proxy_set_header Clientip $http_clientip;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Scheme $scheme;
    proxy_set_header REMOTE-HOST $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Connection "";

    proxy_pass http://test.fanruo.net;
}

其中,/loginServer是当前服务路径前缀,/loginServerNew是新服务路径前缀。所以,具体需求可以描述为,10% 的用户使用/loginServerNew服务,90% 的用户使用/loginServer服务。

具体方案

  1. 根据字符串 client_ip 转成长整数 ip_num;
  2. 对 ip_num 按 100 取模,取最后两位 hash;
  3. 当 hash < 10 时走/loginServerNew服务, 设置 tag="New", 否则 tag=""
  4. 改造rewrite指令为rewrite ^(.*) /loginServertag

对应配置为

代码语言:javascript复制
location ~ ^/api/rest/vd /Login {
    set $tag "";
    access_by_lua_file conf/lua/abtest.lua;
    rewrite ^(.*)$ /loginServer$tag$1 break;

    proxy_http_version 1.1;
    proxy_next_upstream error timeout invalid_header;
    proxy_redirect off;

    proxy_set_header Host $http_host;
    proxy_set_header Clientip $http_clientip;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Scheme $scheme;
    proxy_set_header REMOTE-HOST $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Connection "";

    proxy_pass http://test.fanruo.net;
}

对应 lua 脚本(conf/lua/btest.lua):

代码语言:javascript复制
local client_ip = ngx.var.http_clientip or ngx.var.remote_addr
local o1,o2,o3,o4 = client_ip:match("(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)" )
local ip_num = 2^24*o1   2^16*o2   2^8*o3   o4
local hash = ip_num % 100

ngx.var.tag = ""
if hash > 10 then
    ngx.var.tag = "New"
end

问题

按照上述实现,最终总是走原来的服务/loginServer。通过分析 tag 的值一直是 “”,因此,需求不能满足。

原因分析

通过分析,由于之前 WAF 是通过直接修改 proxy_pass 参数来完成的,而 proxy_pass 对应 content phase,该阶段在 access phase 之后,因此上面的配置没有问题。而本需求是在 rewrite phase,因此需要在该阶段之前修改对应变量 tag 只能在 set phase 来完成 nginx 变量修改操作。

解决方案

跟进上述分析,修改 nginx.conf 配置和 lua 脚本如下:

lua 脚本修改为:

代码语言:javascript复制
local client_ip = ngx.var.http_clientip or ngx.var.remote_addr
local o1,o2,o3,o4 = client_ip:match("(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)%.(%d%d?%d?)" )
local ip_num = 2^24*o1   2^16*o2   2^8*o3   o4
local hash = ip_num % 100

local tag = ""
if hash > 10 then
    tag = "New"
end

return tag

nginx.conf 配置修改为:

代码语言:javascript复制
location ~ ^/api/rest/vd /Login {
    set_by_lua_file $tag conf/lua/abtest.lua;
    rewrite ^(.*)$ /loginServer$tag$1 break;

    proxy_http_version 1.1;
    proxy_next_upstream error timeout invalid_header;
    proxy_redirect off;

    proxy_set_header Host $http_host;
    proxy_set_header Clientip $http_clientip;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Scheme $scheme;
    proxy_set_header REMOTE-HOST $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Connection "";

    proxy_pass http://test.fanruo.net;
}

0 人点赞