Nginx+Lua 实现灰度发布

一、安装Lua环境及相关库

1. LuaJIT

1
2
3
4
5
6
wget http://luajit.org/download/LuaJIT-2.0.5.tar.gz
tar -zxvf LuaJIT-2.0.5.tar.gz -C /usr/local/src/
cd /usr/local/src/LuaJIT-2.0.5/
make install PREFIX=/usr/local/LuaJIT
export LUAJIT_LIB=/usr/local/LuaJIT/lib
export LUAJIT_INC=/usr/local/LuaJIT/include/luajit-2.0

2. ngx_devel_kit和lua-nginx-module

1
2
3
4
wget https://github.com/simplresty/ngx_devel_kit/archive/v0.3.1rc1.tar.gz
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.13.tar.gz
tar -zxvf v0.3.1rc1.tar.gz -C /usr/local/src/
tar -zxvf v0.10.13.tar.gz -C /usr/local/src/

3. 编译Nginx

1
2
3
4
5
6
wget http://nginx.org/download/nginx-1.15.2.tar.gz
tar -zxvf nginx-1.15.2.tar.gz -C /usr/local/src/
cd /usr/local/src/nginx-1.15.2/
# 以下编译选项,除后两个,其余其实就是yum安装ngnix下nginx -V的默认编译选项
./configure --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib64/nginx/modules --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-compat --with-file-aio --with-threads --with-http_addition_module --with-http_auth_request_module --with-http_dav_module --with-http_flv_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_mp4_module --with-http_random_index_module --with-http_realip_module --with-http_secure_link_module --with-http_slice_module --with-http_ssl_module --with-http_stub_status_module --with-http_sub_module --with-http_v2_module --with-mail --with-mail_ssl_module --with-stream --with-stream_realip_module --with-stream_ssl_module --with-stream_ssl_preread_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie' --add-module=/usr/local/src/ngx_devel_kit-0.3.1rc1 --add-module=/usr/local/src/lua-nginx-module-0.10.13
make -j 4 && make install

4. 加载lua库,加入到ld.so.conf文件

1
2
echo "/usr/local/LuaJIT/lib" >> /etc/ld.so.conf
ldconfig

二、灰度实现

1. 灰度目标

当客户端ip为192.168.56.1时,让其访问新发布的upstream测试实例;其他ip访问时还是老的upstream实例。相当于前者为新发布功能,后者为稳定的生产功能。

2. 灰度准备

  1. 这里使用 memcache 来存储哪些ip要访问新的,哪些ip访问稳定的老的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    yum install memcached 
    memcached -p11211 -u nobody -d
    # 设置 灰度ip
    telnet 127.0.0.1 11211
    set 192.168.56.1 0 0 1
    1
    get 192.168.56.1
    # 安装lua-resty-memcached
    wget https://github.com/openresty/lua-resty-memcached/archive/v0.14.tar.gz
    tar -zxvf v0.14.tar.gz -C /usr/local/src/
    cp -r /usr/local/src/lua-resty-memcached-0.14/lib/resty /usr/local/LuaJIT/share/luajit-2.0.5/
  2. 准备两个服务新上线服务稳定生产服务,这里使用两个tomcat来演示(如果你熟悉node 也可以使用node快速实现两个后台服务)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #测试页面
    vim test.jsp
    1 <%@ page language="java" import="java.util.*" pageEncoding="utf-8" %>
    2 <!DOCTYPE html>
    3 <html lang="en">
    4 <head>
    5 | <meta charset="UTF-8">
    6 | <title>Test Jsp</title>
    7 </head>
    8 <body>
    9 | <%
    10 | | Random rand = new Random();
    11 | | out.println("<h1>Test server</h1>");
    12 | | out.println("<h1>Random number:</h1>");
    13 | | out.println(rand.nextInt(99) + 100);
    14 | %>
    15 </body>
    16 </html>
    # 两个tomcat: tomcat8080, tomcat9090,前者作为新上线服务,后者作为 稳定的生产服务;将上面的test.jsp页面分别放置到两个tomcat的ROOT下;tomcat9090 删除test.jsp中的11行,以示区别。
    # 分别启动两个tomcat
  3. 配置Nginx nginx.conf

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    |   location / { 
    | | default_type 'text/html';
    | | content_by_lua_file /opt/app/lua/dep.lua;
    | }
    | location @server{
    | | proxy_pass http://127.0.0.1:9090;
    | }
    | location @server_test{
    | | proxy_pass http://127.0.0.1:8080;
    | }
  4. 编写 /opt/app/lua/dep.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
    local clientIP = ngx.req.get_headers()["X-Real-IP"]
    if clientIP == nil then
    | clientIP = ngx.req.get_headers()["x_forwarded_for"]
    end
    if clientIP == nil then
    | clientIP = ngx.var.remote_addr
    end
    local memcached = require "resty.memcached"
    local memc, err = memcached:new()
    if not memc then
    | ngx.say("failed to instance memc:", err)
    | return
    end
    local ok, err = memc:connect("127.0.0.1", 11211)
    if not ok then
    | ngx.say("failed to connect: ", err)
    | return
    end
    local res, flags, err = memc:get(clientIP)
    --ngx.say("value key: ", res, clientIP)
    if err then
    | ngx.say("failed to get clientIP ", err)
    end
    if res == "1" then
    | ngx.exec("@server_test")
    | return
    end
    ngx.exec("@server")
  5. 启动Nginx,访问测试

在本机使用 curl http://127.0.0.1/test.jsp 查看效果,返回结果无 Test server 字样

192.168.56.1 访问 http://192.168.56.101(此ip为Nginx 服务所在ip) 查看效果,返回结果有 Test server 字样

三、小结

以上只是简单的针对某个 ip 做的灰度,完整的灰度,还需要企业后台灵活的管理memcache

另外要实现更灵活的灰度,比如根据 Cookie, header 头信息,也可以个根据这种原理来实现。