如何配置NGINX作为API网关从Zookeeper注册中心读取动态服务信息
NGINX 对接 Zookeeper 实现动态服务发现配置方案
原生NGINX不支持直接对接Zookeeper读取服务注册信息,你可以通过以下两种常用方案实现需求,两种方案都兼容你现有配置:
方案一:使用 OpenResty 实现无重启动态发现(推荐生产环境使用)
OpenResty是基于NGINX + LuaJIT的增强发行版,完全兼容原生NGINX的所有配置,你之前写的代理、upstream规则都可以直接复用,通过Lua扩展即可实现Zookeeper服务发现:
- 第一步:安装OpenResty替换现有NGINX,安装时自带
lua-resty-zk(Zookeeper客户端)、lua-resty-balancer(负载均衡)扩展库 - 第二步:在
nginx.conf的http块中添加Lua初始化逻辑,监听Zookeeper的服务注册节点:
http { # 你之前的原有配置全部保留 include api_gateway.conf; # 初始化Zookeeper连接 lua_shared_dict zk_services 10m; # 开辟10M内存缓存服务实例列表 init_by_lua_block { local zk = require "resty.zk" local zc, err = zk:new({ hosts = {"localhost:2181"}, -- 对应你的Zookeeper连接地址 timeout = 10000 }) -- 监听Spring Cloud Zookeeper默认注册路径 /services/ 下的服务节点 local function watch_service(service_name) local path = "/services/" .. service_name local children, err = zc:get_children(path, watch_service) if children then local instances = {} for _, node in ipairs(children) do local data, err = zc:get(path .. "/" .. node) if data then local ins = require("cjson").decode(data) table.insert(instances, ins.address .. ":" .. ins.port) end end ngx.shared.zk_services:set(service_name, require("cjson").encode(instances)) end end -- 监听你需要的两个服务 watch_service("user_server") watch_service("email_server") } # 原有server块配置保留,只需要修改location的代理逻辑 server { # 你原有监听端口、域名配置不变 location /auth { set $service_name user_server; access_by_lua_block { local balancer = require "resty.balancer.least_conn" local ins = ngx.shared.zk_services:get(ngx.var.service_name) if ins then local instances = require("cjson").decode(ins) local ip_port, err = balancer:new(instances):find() ngx.var.proxy_pass = "http://" .. ip_port end } # 你原有proxy_set_header配置全部保留 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /email { # 同上述逻辑,service_name改为email_server即可 } } }
这个方案无需重启NGINX,Zookeeper节点变化会实时同步到本地缓存,性能损耗极低。
方案二:定时脚本同步配置(轻量方案,适合小项目使用)
无需替换现有NGINX,通过定时脚本拉取Zookeeper服务信息自动更新upstream配置,优雅重载NGINX即可:
- 第一步:写同步脚本,以下为Python示例逻辑(需要安装kazoo库操作Zookeeper):
from kazoo.client import KazooClient import json import os ZK_HOST = "localhost:2181" CONFIG_PATH = "/etc/nginx/api_gateway.conf" NGINX_TEST_CMD = "nginx -t" NGINX_RELOAD_CMD = "nginx -s reload" zk = KazooClient(hosts=ZK_HOST) zk.start() def generate_upstream(): config_content = "" # 遍历所有服务 services = zk.get_children("/services") for service in services: config_content += f"upstream {service} {{\n least_conn;\n" nodes = zk.get_children(f"/services/{service}") for node in nodes: data, _ = zk.get(f"/services/{service}/{node}") ins = json.loads(data.decode()) config_content += f" server {ins['address']}:{ins['port']};\n" config_content += "}\n\n" return config_content if __name__ == "__main__": new_config = generate_upstream() # 读取旧配置对比,有变化才更新 try: with open(CONFIG_PATH, "r") as f: old_config = f.read() except: old_config = "" if new_config != old_config: with open(CONFIG_PATH, "w") as f: f.write(new_config) # 校验配置合法再重载 if os.system(NGINX_TEST_CMD) == 0: os.system(NGINX_RELOAD_CMD) zk.stop()
- 第二步:配置crontab定时执行脚本,比如每10秒执行一次即可,配置更新后会自动优雅重载NGINX,不会丢失请求。
注意事项
- Spring Cloud Zookeeper默认服务注册路径为
/services/服务名,如果你们自定义了注册路径,对应修改上述方案中的读取路径即可 - 方案二的执行频率不要低于5秒,避免频繁重载NGINX造成不必要的性能损耗
- 两种方案都完全兼容你现有已经写好的代理请求头、负载均衡规则配置,不需要修改原有业务逻辑
内容的提问来源于stack exchange,提问作者Dang Nguyen
相关产品推荐
相关产品推荐

