在開發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 認證
與常見的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"
的
基於redis的ip黑名單插件示例
開頭說到了在多個worker共享一些信息或者接收外部的程序輸入時,可以使用redis來實現。
舉個例子,假設存在一個判斷ip黑名單的服務,它會動態的修改ip黑名單列表,然後需要在kong網關對黑名單進行統一攔截。
這種情況下,要用插件配置來做就不太現實了,而使用redis就可以很方便的實現該功能。
只需要兩步:
- 黑名單服務對redis寫入
前綴:ip
形式的鍵,可以靈活的控制黑名單生效時長 - kong插件在接到請求時判斷是否存在
前綴:ip
形式的鍵,匹配上了直接返回403
目錄結構
插件代碼
- handler.lua
在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
- schema.lua
需要連接的redis配置
return {
no_consumer = false,
fields = {
redisPass = {type = "string", required = true,default = ""},
redisHost = {type = "string", required = true},
redisPort = {type = "string", required = true}
}
}
插件添加方式
- 修改kong配置文件
/etc/kong/kong.conf
# 添加插件
plugins = bundled,ipFilter
# 添加自定義插件代碼路徑
lua_package_path = /home/ubuntu/customPlugin/?.lua;./?.lua;./?/init.lua;
使用
-
對任意的route或service開啓該插件,訪問正常
-
向redis寫入黑名單
set ip:127.0.0.1 xxx
-
再次訪問,返回403