关于nginx:灰度发布浅析

7次阅读

共计 4327 个字符,预计需要花费 11 分钟才能阅读完成。

定义

灰度公布就是已一种平滑过渡的形式来公布,通过切换线上新旧版本之间的路由权重,逐渐从旧版本切换到新版本;比方要上线新性能,首先只是更新大量的服务节点,通过路由权重,让少部分用户体验新版本,如果没有什么问题,再更新所有服务节点;这样能够在呈现问题把影响面降到最低,保障了零碎的稳定性。

灰度公布

一个零碎往往有接入层比方 nginx(Openresty),网关层比方 zuul,以及服务层比方各种 rpc 框架;在这几层都有路由性能,也就是说这几层都能够做灰度;接入层能够应用 nginx+lua 来实现灰度,网关层 zuul 能够联合 ribbon 来实现灰度,rpc 框架如 dubbo 自身提供了路由性能能够间接做灰度解决;上面看看具体如何去实现;

接入层灰度

接入层咱们这里应用性能更弱小的 Openresty,而后应用 lua 进行路由转发,相干的路由策略能够配置在分布式缓存 redis 外面,当然也能够长久化到数据库外面;

  • 筹备

筹备一台 Openresty,两台 web 服务器 tomcat(端口别离是 8081,8082),以及 redis;为了不便模仿在 redis 外面配置白名单,如果在白名单外面就走 8082,不在则走 8081;

  • Openresty 配置

须要在 Openresty 中配置反对 lua,以及相干路由的 lua 脚本,nginx.conf 配置如下:

http {
    ...
    lua_package_path "/lualib/?.lua;;";  #lua 模块  
    lua_package_cpath "/lualib/?.so;;";  #c 模块   
    
    upstream tomcat1 {server 127.0.0.1:8081;}
    upstream tomcat2 {server 127.0.0.1:8082;}

    server {
        listen 80;
        server_name localhost;
        location / {content_by_lua_file lua/gray.lua;}
        location @tomcat1 {proxy_pass http://tomcat1;}
        location @tomcat2 {proxy_pass http://tomcat2;}
    }
    ...
}

配置了所有申请都会通过 lua 目录下的 gray.lua 脚本,如下所示:

local redis = require "resty.redis";
local redis_obj = redis:new();
redis_obj:set_timeout(2000);
local ok,err = redis_obj:connect("127.0.0.1", 6379);

if not ok then
  ngx.say("failed to connect redis",err);
  return;
end

-- 获取申请 ip
local_ip = ngx.var.remote_addr;

--redis 中获取白名单
local whitelist = redis_obj:get("whitelist");

-- 判断是否在白名单而后转到对应服务
if string.find(whitelist,local_ip) == nil then
  ngx.exec("@tomcat1");
else
  ngx.exec("@tomcat2");
end
local ok,err = redis_obj:close();

Openresty 内置的功能模块能够间接连贯 redis,而后从 redis 外面取出白名单,看以后的申请 ip 是否在白名单内,而后做简略的路由性能;能够动静批改 redis 外面的白名单,实时更新。

localhost:0>set whitelist 127.0.0.1
"OK"
localhost:0>get whitelist
"127.0.0.1"
  • 启动测试

别离启动 tomcat1,tomcat2 以及 Openresty,拜访 http://localhost 即可,能够动静批改 redis 外面的白名单,而后拜访查看后果验证。

网关层灰度

网关层已 zuul 为例,zuul 的灰度须要批改 ribbon 的负载策略,就是依据 eureka 的 metadata 进行自定义元数据,而后批改 ribbon 的策略规定;

  • 筹备

测试服务别离筹备两台端口别离为:8765,8766,application.yml 配置如下:

server:
  port: 8765
eureka:
  instance:
    metadata-map:
      route: 1

同时筹备申请地址 /hiGray,返回值为 route1;

server:
  port: 8766
eureka:
  instance:
    metadata-map:
      route: 2

同时筹备申请地址 /hiGray,返回值为 route2;用于辨别是否走了灰度服务器;而后在 zuul 端须要引入一个插件:

<dependency>
    <groupId>io.jmnarloch</groupId>
    <artifactId>ribbon-discovery-filter-spring-cloud-starter</artifactId>
    <version>2.1.0</version>
</dependency>

而后须要筹备一个 pre 类型的 filter,具体如下:

@Configuration
public class GrayFilter extends ZuulFilter {

    @Override
    public Object run() {RequestContext ctx = RequestContext.getCurrentContext();
        HttpServletRequest request = ctx.getRequest();
        String ip = request.getRemoteAddr();
        //ipv6 本地地址,也就是 127.0.0.1
        if ("0:0:0:0:0:0:0:1".equals(ip)) {RibbonFilterContextHolder.getCurrentContext()
                    .add("route", "1");
        }  else {RibbonFilterContextHolder.getCurrentContext()
                    .add("route", "2");
        }
        return null;
    }
    ...
}

以上也是应用白名单为例子,这里为了不便就没有把白名单配置在 redis 外面,配置的白名单地址为 ipv6:0:0:0:0:0:0:0:1,如果是白名单地址则路由到 8765 端口服务,否则为 8766 端口服务;

  • 测试

别离启动 eureka-server,两个 eureka-client,以及 zuul 网关,拜访网关地址即可;别离通过 127.0.0.1 和本地 ip 拜访即可测试;

服务层灰度

服务器已 rpc 框架 dubbo 为例,dubbo 自身提供了各种路由规定包含:条件路由,脚本路由等,这里同样应用脚本路由为例,脚本路由规定反对 JDK 脚本引擎的所有脚本,比方:javascript, jruby, groovy 等,这里应用缺省的 JavaScript 为例;

  • 筹备

注册核心 zookeeper,两台 Provider 能够在本地别离指定端口为 20881 和 20882,消费者,以及上面重点介绍的路由脚本:

function gray_rule(invokers, context) {var tag = context.getAttachment("tag");
    
    var result = new java.util.ArrayList(invokers.size());
    if(tag == "gray"){for (i = 0; i < invokers.size(); i ++) {if (invokers.get(i).getUrl().getPort()==20881) {result.add(invokers.get(i));
            }
        }
    } else {for (i = 0; i < invokers.size(); i ++) {if (invokers.get(i).getUrl().getPort()==20882) {result.add(invokers.get(i));
            }
        }
    }
    return result;
} (invokers,context)

dubbo 在运行脚本的时候会传入三个参数别离是:invokers,Invocation 以及 RpcContext.getContext();通过在生产端在 RpcContext 中设置 tag:

RpcContext.getContext().setAttachment("tag", "gray");

这样就能够在脚本中进行判断,tag 为 gray 的生产端才走 20881 端口的服务端,其余走 20882 服务端;
以上的脚本须要注册到 zookeeper 中,手动注册代码如下,当然也能够应用 dubbo 提供的 dubbo-admin 来设置路由脚本:

URL registryUrl = URL.valueOf("zookeeper://127.0.0.1:2181");
ZookeeperRegistryFactory zookeeperRegistryFactory = new ZookeeperRegistryFactory();
zookeeperRegistryFactory.setZookeeperTransporter(new CuratorZookeeperTransporter());
Registry zookeeperRegistry = (ZookeeperRegistry) zookeeperRegistryFactory.createRegistry(registryUrl);
URL routerURL = URL.valueOf("script://0.0.0.0/com.dubboApi.DemoService?category=routers&dynamic=false");
routerURL = routerURL.addParameter("rule",
URL.encode("(..JavaScript 脚本..)"));
zookeeperRegistry.register(routerURL); // 注册 

具体能够参考官网文档:旧路由规定

  • 测试

启动 zookeeper,而后别离启动两台生产者,启动消费者时通过批改 tag 而后察看路由;

总结

本文别离从接入层,网关层,服务层这三层简要的介绍了通过路由规定来实现灰度公布;已每层比拟典型的中间件来介绍具体如何去实现简略的灰度公布;总体来说就是应用中间件的路由性能,动静加载内部自定义的一些路由策略脚本,以此来达到灰度公布的目标。

代码地址

Dubbo
Spring-Cloud

感激关注

能够关注微信公众号「 回滚吧代码 」,第一工夫浏览,文章继续更新;专一 Java 源码、架构、算法和面试。

正文完
 0