在开发kong自定义插件时,可能会面对需要多个worker共享一些信息或者接收外部的程序输入的情况。而多进程共享信息常用的方式就是redis。
kong里面已经包含了lua-resty-redis 这个redis连接库,在插件代码中直接 require "resty.redis"
就可以使用了
由于kong的插件开启后,对于指定的Route/Service,每个请求都将执行一遍。如果每次都去重新连接redis,效率会很低。
lua-resty-redis提供了pool_size
选项来使用redis连接池。
当在插件代码中使用redis后,需要执行close
关闭连接,或者使用set_keepalive
将当前连接放入连接池中,供下一次使用。
set_keepalive
方法签名: set_keepalive(max_idle_timeout, pool_size)
max_idle_timeout
表示超时断开时间pool_size
表示连接池大小奇怪的是在connect
的选项中也有一个连接池,看到 issue 中的解释是优先使用connect
中的连接池大小,没有设置则用 set_keepalive
中的
与常见的redis客户端在连接时将密码和ip端口等参数一起传进构造函数的的方式不同,lua-resty-redis
是连接后再执行 auth
命令的
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 sec
local ok, err = red:connect("127.0.0.1", 6379)
if not ok then
ngx.say("failed to connect: ", err)
return
end
local res, err = red:auth("foobared")
if not res then
ngx.say("failed to authenticate: ", err)
return
end
执行阶段限制:resty.redis 不能在 init_by_lua*, set_by_lua*, log_by_lua*, and header_filter_by_lua*
这些阶段执行。对应到kong,就是init_worker() header_filter() log()
等函数里不可用使用该redis客户端
resty.redis 的实例不能作为模块级别的变量使用,需要将它作为一个函数级的局部变量。所以下面的示例中,我是在access函数内去执行 require "resty.redis"
的
示例代码
开头说到了在多个worker共享一些信息或者接收外部的程序输入时,可以使用redis来实现。
举个例子,假设存在一个判断ip黑名单的服务,它会动态的修改ip黑名单列表,然后需要在kong网关对黑名单进行统一拦截。
这种情况下,要用插件配置来做就不太现实了,而使用redis就可以很方便的实现该功能。
只需要两步:
前缀:ip
形式的键,可以灵活的控制黑名单生效时长前缀:ip
形式的键,匹配上了直接返回403在access阶段,获取客户端ip,从redis查询是否存在键 ip:${ip}
,查到了就直接在插件返回403响应,不再发送到上游的服务中。
local IpFilterHandler = {}
function IpFilterHandler:access(plugin_conf)
local redisHost = plugin_conf.redisHost
local redisPort = plugin_conf.redisPort
local redisPass = plugin_conf.redisPass
local ipPrefix = "ip:"
local clientIp = kong.client.get_ip();
local redis = require "resty.redis"
local red = redis:new()
red:set_timeouts(1000, 1000, 1000) -- 1 sec
local ok, err = red:connect(redisHost, redisPort)
if not ok then
kong.log.warn("failed to connect redis: ", err)
else
if(redisPass ~= "")
then
local auth, err = red:auth(redisPass)
if not auth then
kong.log.warn("failed to authenticate: ", err)
end
end
local ipRes, err = red:get(ipPrefix..clientIp)
if ipRes ~= ngx.null then
kong.log.err("IP "..clientIp.. " access denied")
kong.response.exit(403, "IP "..clientIp .." forbidden access")
return
end
-- 使用连接池
local ok, err = red:set_keepalive(10000, 100) -- (超时时间 ms, 连接池大小)
end
end
return IpFilterHandler
需要连接的redis配置
return {
no_consumer = false,
fields = {
redisPass = {type = "string", required = true,default = ""},
redisHost = {type = "string", required = true},
redisPort = {type = "string", required = true}
}
}
/etc/kong/kong.conf
# 添加插件
plugins = bundled,ipFilter
# 添加自定义插件代码路径
lua_package_path = /home/ubuntu/customPlugin/?.lua;./?.lua;./?/init.lua;