OpenResty实现动态路由
0需求场景
动态路由可以解决以下场景
(1)隐藏K8s的外部访问Ip端口,对外提供统一的访问host(普通nginx代理即可)
(2)根据不同请求id动态路由到不同后端服务
(3)跟踪请求id,后续相关浏览器的请求都要进行正确路由
使用OpenResty+Redis+Lua,官方就有一个基本的样例:Dynamic Routing Based On Redis
(这里图不是我画的,意思大概是差不多的。图片来源https://www.jianshu.com/p/ac031facc88d)
实际生产需求举个例子,k8s提供了2个jupyter服务的外部访问ip端口如下
jupyter1 ip1:port1
jupyter2 ip2:port2
现在想通过访问 http://loccalhost/jupyter 代理这两个服务,一个路径选择通过在请求header添加参数requestid来区分两个服务,分别用1000和1001表示如下
jupyter1 ip1:port1 1000
jupyter2 ip2:port2 1001
为了实现上表面的需求3代理后续所有jupyter的请求,可以通过在nginx种cookie来实现requestid的传播。
1实现方案
前端访问http://localhost/jupyter时添加header参数requestid,nginx在location里读到后种到cookie里,再转发到 http://loccalhost/路径,后续请求cookie里都带上requestid。
根路径location里以requestid为key去redis查询ip端口,后端服务在添加端口映射的时候向redis注册<requestid, ip:port>
server {
listen 80;
server_name localhost;
location = /redis {
internal;
set_unescape_uri $key $arg_key;
redis2_query auth 'xxxx';
redis2_query get $key;
redis2_pass redisip:redisport;
}
location = /jupyter{
access_by_lua '
local headers = ngx.req.get_headers()
local sid = headers["requestid"]
if sid == nil then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
local cookie_rid = "requestid="..sid
ngx.header["Set-Cookie"] = {cookie_rid}
ngx.redirect("/")
';
}
location / {
set $target '';
access_by_lua '
local sid = ngx.var.cookie_requestid
if sid == nil then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
local key = sid
local res = ngx.location.capture(
"/redis", { args = { key = key } }
)
if res.status ~= 200 then
ngx.log(ngx.ERR, "redis server returned bad status: ",
res.status)
ngx.exit(res.status)
end
if not res.body then
ngx.log(ngx.ERR, "redis returned empty body")
ngx.exit(500)
end
local parser = require "redis.parser"
local results = parser.parse_replies(res.body,2)
for i, reply in ipairs(results) do
if i == 2 then
server = reply[1]
end
end
if server == "" then
server = "default"
end
server = server .. ngx.var.request_uri
ngx.var.target = server
';
resolver 8.8.8.8;
proxy_pass http://$target;
proxy_redirect off;
proxy_set_header HOST $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Origin "";
proxy_read_timeout 120s;
proxy_next_upstream error;
proxy_buffering off;
}
}
注意:
(1)与官方示例不同,这里redis需要验证密码,redis2_query auth一定要写在get前面,不然会报错。
(2)proxy_set_header Origin "";这一行一定要有,不然所有jupyter post请求会404(?)
(扩展:想想还可以进一步通过sessionId从redis中取信息鉴权等)
2优化
所有请求都要通过redis服务,相当于多走了一次网络,路由路径不常变化,基于性能考虑可以做一个本地内存的缓存。
ngx.shared.DICT :这个 cache 是 nginx 所有 worker 之间共享的,内部使用的 LRU 算法。步骤如下:
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
lua_shared_dict localcache 5m;
server {
listen 80;
server_name localhost;
location = /redis {
internal;
set_unescape_uri $key $arg_key;
redis2_query auth 'xxxx';
redis2_query get $key;
redis2_pass redisip:redisport;
}
location = /instance {
access_by_lua '
local headers = ngx.req.get_headers()
local sid = headers["requestid"]
if sid == nil then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
local cookie_rid = "requestid="..sid
ngx.header["Set-Cookie"] = {cookie_rid}
ngx.redirect("/")
';
}
location / {
set $target '';
access_by_lua '
local sid = ngx.var.cookie_requestid
if sid == nil then
ngx.exit(ngx.HTTP_FORBIDDEN)
end
local key = sid
local cache = ngx.shared.localcache
local server = cache:get(key)
if server == nil then
local res = ngx.location.capture(
"/redis", { args = { key = key } }
)
if res.status ~= 200 then
ngx.log(ngx.ERR, "redis server returned bad status: ",res.status)
ngx.exit(res.status)
end
if not res.body then
ngx.log(ngx.ERR, "redis returned empty body")
ngx.exit(500)
end
local parser = require "redis.parser"
local results = parser.parse_replies(res.body,2)
for i, reply in ipairs(results) do
if i == 2 then
server = reply[1]
end
end
cache:set(key, server, 300)
end
if server == "" then
server = "default"
end
server = server .. ngx.var.request_uri
ngx.var.target = server
';
resolver 8.8.8.8;
proxy_pass http://$target;
proxy_redirect off;
proxy_set_header HOST $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Origin "";
proxy_read_timeout 120s;
proxy_next_upstream error;
proxy_buffering off;
}
#....
}
}
先在http域定义了一个大小为5M的全局缓存,读前先走缓存,没有命中再读redis并将结果写入缓存。