灰度发布

灰度发布的简单来说就是让小部分用户或指定用户访问指定的环境,减少版本发布中如果出现问题,导致大面积用户收到影响

实现手段

  • IP:nginx中通过$remote_addr也可以做到上面的效果,但是通过这种方式粒度太大了,因为可能很多用户都是通过一个公网出口,例如办公环境
  • cookie:

方案一:nginx + lua + memcahed

环境描述

基础环境

  • OS: CentOS release 6.6
  • Nginx: nginx/1.12.2 (src)

Lua相关

  • lua: lua-5.1.4-4.1.el6.x86_64(yum)
  • LuaJIT: LuaJIT-2.0.2(src)
  • lua-nginx: lua-nginx-module-0.10.9rc7
  • lua-resty-memcached:lua-resty-memcached-0.11
  • memcached: memcached-1.4.4-5.el6.x86_64(yum)

模拟后端服务

  • python: Python 3.5.1
  • flask: flask-0.12.2

预计效果

通过在memcached中配置特定IP访问灰度环境

效果演示

未配置IP访问灰度

[root@bj-vmware-test1 ~]# curl http://www.gray_release.com/
Flask: 5001

配置IP信息到memcached

[root@bj-vmware-test1 ~]# telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

set 192.168.2.20 0 0 1
1
STORED

再次测试访问

[root@bj-vmware-test1 ~]# curl http://www.gray_release.com/
Flask: 5002

下载相关包

相关包都下载到/usr/local/src/

cd /usr/local/src/
wget http://nginx.org/download/nginx-1.12.2.tar.gz
wget http://luajit.org/download/LuaJIT-2.0.2.tar.gz
wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.9rc7.tar.gz
wget https://github.com/agentzh/lua-resty-memcached/archive/v0.11.tar.gz

然后解压

tar xf nginx-1.12.2.tar.gz
tar xf LuaJIT-2.0.2.tar.gz
tar xf v0.3.0.tar.gz
tar xf lua-nginx-module-0.10.9rc7.tar.gz
tar xf v0.11.tar.gz

安装

安装lua和memcached

yum -y install lua memcached

编译nginx添加lua模块

cd /usr/local/src/nginx-1.12.2/
./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 --param=ssp-buffer-size=4 -m64 -mtune=generic -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie' --add-module=/usr/local/src/echo-nginx-module-master/ --add-module=/usr/local/src/ngx_devel_kit-0.3.0 --add-module=/usr/local/src/lua-nginx-module-0.10.9rc7

make && make install

检查编译参数nginx -V

测试nginx+lua化境 编辑配置文件,启动nginx,测试访问,PS: 域名是我写死到hosts里的

[root@bj-vmware-test1 src]# cat /etc/nginx/conf.d/gray_released.conf 
server {
    listen     80;
    server_name www.gray_release.com;

    location /hello {
        default_type 'text/plain';
        content_by_lua 'ngx.say("hello world!")';
    }
}

访问结果,看到hello world就算了环境ok了~

[root@bj-vmware-test1 src]# curl www.gray_release.com/hello
hello world!

后端服务准备

写了个小flask应用以模拟后端

import sys
from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def index():
    return 'Flask: {}\n'.format(port)

if __name__ == '__main__':
    port=int(sys.argv[1])
    app.run(host='0.0.0.0', port=port, debug=True)

启动两个flask实例,分别监听5001、5002

[root@bj-vmware-test1 tmp]# python35 app.py 5001
 * Running on http://0.0.0.0:5001/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 326-390-028

新建窗口

[root@bj-vmware-test1 tmp]# python35 app.py 5002
 * Running on http://0.0.0.0:5002/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 326-390-028

测试访问flask

[root@bj-vmware-test1 ~]# curl 192.168.2.20:5001
Flask: 5001
[root@bj-vmware-test1 ~]# curl 192.168.2.20:5002
Flask: 5002

启动memcached

/etc/init.d/memcached start

实现灰度lua脚本

nginx中gray_release.conf引用lua脚本

    location / {
        default_type "text/html";
        content_by_lua_file /opt/app/lua/dep.lua;
    }

    location @server {
        proxy_pass http://192.168.2.20:5001;
    }

    location @server_test {
        proxy_pass http://192.168.2.20:5002;
    }

/opt/app/lua/dep.lua

[root@bj-vmware-test1 src]# cat /opt/app/lua/dep.lua
-- 从请求头中获取X-Real-IP,这个变量是需要和上层约定好的,默认情况下请求头中没有它
clientIP = ngx.req.get_headers()["X-Real-IP"]
if clientIP == nil then
    -- 如果没有X-Real-IP则获取x_forwarded_for,这里其实有些疑问,据我所知x_forwarded_for的格式是 "client_ip, proxy1_ip, proxy2_ip",如果是这样的话,在通过配置IP为key的灰度发布的思路下,通过xff应该会存在问题,嗯,有空验证下~
    clientIP = ngx.req.get_headers()["x_forwarded_for"]
end
-- 如果xff也没有获取到的话,就干脆从nginx内置变量中获取remote_addr
if clientIP == nil then
    clientIP = ngx.var.remote_addr
end
    -- 引入memcached模块,我理解呢跟python中import module一个意思
    local memcached = require "resty.memcached"
    -- 调用memcached模块的new()方法,实例化出memc对象,memc, err很像python中拆包(解包)
    local memc, err = memcached:new()
    -- 如果没有memc对象,说明实例化过程中出现问题,输出错误信息到前端
    if not memc then
        ngx.say("failed to instantiate memc: ", err)
        -- 这里的return暂时不还不清楚啥意思
        return
    end
    -- 调用memc对象的connect方法连接memcached
    local ok, err = memc:connect("127.0.0.1", 11211)
    -- 这里ok是什么也还不知道
    if not ok then
        ngx.say("failed to connect: ", err)
        return
    end
    -- 调用memc对象的get方法获取key为clientIP的value,res就是valu,flags为设置key时的flags
    local res, flags, err = memc:get(clientIP)
    if  res == "1" then
        -- 如果等于1则走location @server_test灰度环境
        result = ngx.exec("@server_test")
    end
    -- 否则走location @server正式环境
    ngx.exec("@server")

灰度发布

灰度发布的简单来说就是让小部分用户或指定用户访问指定的环境,减少版本发布中如果出现问题,导致大面积用户收到影响

实现手段

  • IP:nginx中通过$remote_addr也可以做到上面的效果,但是通过这种方式粒度太大了,因为可能很多用户都是通过一个公网出口,例如办公环境
  • cookie:

方案一:nginx + lua + memcahed

环境描述

基础环境

  • OS: CentOS release 6.6
  • Nginx: nginx/1.12.2 (src)

Lua相关

  • lua: lua-5.1.4-4.1.el6.x86_64(yum)
  • LuaJIT: LuaJIT-2.0.2(src)
  • lua-nginx: lua-nginx-module-0.10.9rc7
  • lua-resty-memcached:lua-resty-memcached-0.11
  • memcached: memcached-1.4.4-5.el6.x86_64(yum)

模拟后端服务

  • python: Python 3.5.1
  • flask: flask-0.12.2

预计效果

通过在memcached中配置特定IP访问灰度环境

效果演示

未配置IP访问灰度

[root@bj-vmware-test1 ~]# curl http://www.gray_release.com/
Flask: 5001

配置IP信息到memcached

[root@bj-vmware-test1 ~]# telnet 127.0.0.1 11211
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

set 192.168.2.20 0 0 1
1
STORED

再次测试访问

[root@bj-vmware-test1 ~]# curl http://www.gray_release.com/
Flask: 5002

下载相关包

相关包都下载到/usr/local/src/

cd /usr/local/src/
wget http://nginx.org/download/nginx-1.12.2.tar.gz
wget http://luajit.org/download/LuaJIT-2.0.2.tar.gz
wget https://github.com/simpl/ngx_devel_kit/archive/v0.3.0.tar.gz
wget https://github.com/openresty/lua-nginx-module/archive/v0.10.9rc7.tar.gz
wget https://github.com/agentzh/lua-resty-memcached/archive/v0.11.tar.gz

然后解压

tar xf nginx-1.12.2.tar.gz
tar xf LuaJIT-2.0.2.tar.gz
tar xf v0.3.0.tar.gz
tar xf lua-nginx-module-0.10.9rc7.tar.gz
tar xf v0.11.tar.gz

安装

安装lua和memcached

yum -y install lua memcached

编译nginx添加lua模块

cd /usr/local/src/nginx-1.12.2/
./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 --param=ssp-buffer-size=4 -m64 -mtune=generic -fPIC' --with-ld-opt='-Wl,-z,relro -Wl,-z,now -pie' --add-module=/usr/local/src/echo-nginx-module-master/ --add-module=/usr/local/src/ngx_devel_kit-0.3.0 --add-module=/usr/local/src/lua-nginx-module-0.10.9rc7

make && make install

检查编译参数nginx -V

测试nginx+lua化境 编辑配置文件,启动nginx,测试访问,PS: 域名是我写死到hosts里的

[root@bj-vmware-test1 src]# cat /etc/nginx/conf.d/gray_released.conf 
server {
    listen     80;
    server_name www.gray_release.com;

    location /hello {
        default_type 'text/plain';
        content_by_lua 'ngx.say("hello world!")';
    }
}

访问结果,看到hello world就算了环境ok了~

[root@bj-vmware-test1 src]# curl www.gray_release.com/hello
hello world!

后端服务准备

写了个小flask应用以模拟后端

import sys
from flask import Flask, request

app = Flask(__name__)

@app.route('/')
def index():
    return 'Flask: {}\n'.format(port)

if __name__ == '__main__':
    port=int(sys.argv[1])
    app.run(host='0.0.0.0', port=port, debug=True)

启动两个flask实例,分别监听5001、5002

[root@bj-vmware-test1 tmp]# python35 app.py 5001
 * Running on http://0.0.0.0:5001/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 326-390-028

新建窗口

[root@bj-vmware-test1 tmp]# python35 app.py 5002
 * Running on http://0.0.0.0:5002/ (Press CTRL+C to quit)
 * Restarting with stat
 * Debugger is active!
 * Debugger PIN: 326-390-028

测试访问flask

[root@bj-vmware-test1 ~]# curl 192.168.2.20:5001
Flask: 5001
[root@bj-vmware-test1 ~]# curl 192.168.2.20:5002
Flask: 5002

启动memcached

/etc/init.d/memcached start

实现灰度lua脚本

nginx中gray_release.conf引用lua脚本

    location / {
        default_type "text/html";
        content_by_lua_file /opt/app/lua/dep.lua;
    }

    location @server {
        proxy_pass http://192.168.2.20:5001;
    }

    location @server_test {
        proxy_pass http://192.168.2.20:5002;
    }

/opt/app/lua/dep.lua

[root@bj-vmware-test1 src]# cat /opt/app/lua/dep.lua
-- 从请求头中获取X-Real-IP,这个变量是需要和上层约定好的,默认情况下请求头中没有它
clientIP = ngx.req.get_headers()["X-Real-IP"]
if clientIP == nil then
    -- 如果没有X-Real-IP则获取x_forwarded_for,这里其实有些疑问,据我所知x_forwarded_for的格式是 "client_ip, proxy1_ip, proxy2_ip",如果是这样的话,在通过配置IP为key的灰度发布的思路下,通过xff应该会存在问题,嗯,有空验证下~
    clientIP = ngx.req.get_headers()["x_forwarded_for"]
end
-- 如果xff也没有获取到的话,就干脆从nginx内置变量中获取remote_addr
if clientIP == nil then
    clientIP = ngx.var.remote_addr
end
    -- 引入memcached模块,我理解呢跟python中import module一个意思
    local memcached = require "resty.memcached"
    -- 调用memcached模块的new()方法,实例化出memc对象,memc, err很像python中拆包(解包)
    local memc, err = memcached:new()
    -- 如果没有memc对象,说明实例化过程中出现问题,输出错误信息到前端
    if not memc then
        ngx.say("failed to instantiate memc: ", err)
        -- 这里的return暂时不还不清楚啥意思
        return
    end
    -- 调用memc对象的connect方法连接memcached
    local ok, err = memc:connect("127.0.0.1", 11211)
    -- 这里ok是什么也还不知道
    if not ok then
        ngx.say("failed to connect: ", err)
        return
    end
    -- 调用memc对象的get方法获取key为clientIP的value,res就是valu,flags为设置key时的flags
    local res, flags, err = memc:get(clientIP)
    if  res == "1" then
        -- 如果等于1则走location @server_test灰度环境
        result = ngx.exec("@server_test")
    end
    -- 否则走location @server正式环境
    ngx.exec("@server")

导入lua-memcached客户端lua

cp -rf /usr/local/src/lua-resty-memcached-0.11/lib/resty  /usr/local/LuaJIT/share/luajit-2.0.2/

启动nginx测试 PS: 出现问题阅读/var/log/nginx/eror.log

总结

这个方案的优点:

  • 满足灰度的基本需求了

缺点:

  • 通过这种方式粒度太大了,因为可能很多用户都是通过一个公网出口,例如办公环境
  • memcached的数据类型太少了,心里还是比较喜欢一个list存放灰度ip或者其他标识(device_id)

results matching ""

    No results matching ""