在kong插件中使用redis

在開發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就可以很方便的實現該功能。

只需要兩步:

  1. 黑名單服務對redis寫入 前綴:ip 形式的鍵,可以靈活的控制黑名單生效時長
  2. 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;

使用

  1. 對任意的route或service開啓該插件,訪問正常
    在這裏插入圖片描述

  2. 向redis寫入黑名單 set ip:127.0.0.1 xxx
    在這裏插入圖片描述

  3. 再次訪問,返回403
    在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章