APISIX网关OpenResty插件开发
APISIX 是基于云原生的微服务 API 网关,它是所有业务流量的入口,可以处理传统的南北向流量,也可以处理服务间的东西向流量, APISIX 通过插件机制,提供动态负载平衡、身份验证、限流限速等功能,并且支持你自己开发的插件.
下面介绍自定义APISIX插件开发流程,我们以基于IP黑名单限制插件为例
1. OpenResty脚本开发
插件主要功能基于Redis实现管理黑名单IP池,并利用openresty进行IP限制访问
1. 基于Redis的动态IP黑名单池
2. 插件定时从Redis 获取最新IP黑名单
3. 匹配IP限制访问返回403
*ip-redis-restriction.lua*
local ipairs = ipairs
local core = require("apisix.core")
-- 插件名称
local plugin_name = "ip-redis-restriction"
-- 插件参数schema
local schema = {
type = "object",
properties = {
},
additionalProperties = false
}
-- 插件的配置信息
local _M = {
version = 0.1,
priority = 3000,
name = plugin_name,
schema = schema,
}
-- 需要实现 check_schema(conf) 方法,完成配置参数的合法性校验。
function _M.check_schema(conf)
return core.schema.check(schema, conf)
end
-- redis IP黑名单键值
local key = "ip_blacklist"
-- ipmatcher
local ipmatcher = require("resty.ipmatcher")
local blacklist_matcher = nil
local lastUpdateTime = ngx.now()
-- 关闭redis
local function close_redis(red)
if not red then
return
end
-- 释放连接池设置
local pool_max_idle_time = 10000 --单位毫秒
local pool_size = 100 --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
core.log.error("set keepalive error : ", err)
end
end
local function rebuild_ip_cache()
local iptable = {}
--引入redis模块
local redis = require "resty.redis"
--创建一个对象,注意是用冒号调用的
local red = redis:new()
--设置超时(毫秒)
red:set_timeout(3000)
local ip = "127.0.0.1"
local port = 6379
local passwd = "12345678"
local ok, err = red:connect(ip,port)
if not ok then
core.log.error("red connect error: ", err)
return
end
local count, err = red:get_reused_times()
----count> 0 从连接池中获取连接,无需再次认证密码
if 0 == count then
----新建连接,需要认证密码
ok, err = red:auth(passwd)
if not ok then
core.log.error("failed to auth: ", err)
return
end
elseif err then
core.log.error("failed to get reused times: ", err)
return
end
local ip_blacklist, err = red:smembers(key)
if err then
core.log.error("limit ip smembers ",err)
else
for i,bip in ipairs(ip_blacklist) do
-- cache:set(bip,1)
table.insert(iptable,bip)
end
end
-- 关闭redis释放回连接池
close_redis(red)
local ip,err = ipmatcher.new(iptable)
-- core.log.warn("blacklist_matcher update")
if not ip then
core.log.error("blacklist_matcher update failed",err)
end
blacklist_matcher = ip
end
-- 检测是否需要重新从redis拿IP数据缓存
local function check_cache_need_update()
-- check缓存TTL过期,过期了则从redis取数据
if lastUpdateTime == nil then
lastUpdateTime = ngx.now()
core.log.warn('get_ip_backlist lastUpdateTime is nil ')
end
local diffTime = ngx.now() - lastUpdateTime
-- 每过1分钟了,重新从redis取最新IP黑名单数据
if diffTime >= 60 or blacklist_matcher == nil then
rebuild_ip_cache()
core.log.warn('rebuild_ip_cache begin')
lastUpdateTime = ngx.now()
core.log.warn('rebuild_ip_cache success')
else
-- core.log.warn('no need update cache ',diffTime)
end
end
-- 执行阶段
function _M.access(conf,ctx)
core.log.warn('access conf ',core.json.encode(conf))
local remote_addr = ctx.var.http_x_forwarded_for
if remote_addr == nil then
--取不到就用remote_addr
remote_addr = ctx.var.remote_addr
end
check_cache_need_update()
if blacklist_matcher == nil then
core.log.warn('blacklist_matcher is nill')
return
end
local data,err = blacklist_matcher:match(remote_addr)
if data then
core.log.warn('access cache hit blacklist ',core.json.encode(data),remote_addr)
-- 不能在 rewrite 和 access 阶段调用 ngx.exit 或者 core.respond.exit。如果确实需要退出,只需要 return 状态码和正文,插件引擎将使用返回的状态码和正文进行退出
return 403, {error_msg = "Forbidden"}
-- return core.response.exit(403)
end
end
return _M
2. 配置插件
1. 插件代码开发后我们在./apisix/plugins/ 目录下增加 ip-redis-restriction.lua
2. 在文件./conf/config-defaut.yaml 配置新增插件名,如下
plugins: # plugin list (sorted in alphabetical order)
- api-breaker
- authz-keycloak
- basic-auth
- batch-requests
- consumer-restriction
....
- ip-redis-restriction
*插件热加载生效*
curl http://127.0.0.1:9080/apisix/admin/plugins/reload -H 'X-API-KEY: xxxxxxxx' -X PUT
3.更新dashboard插件信息
执行以下命令
curl 127.0.0.1:9090/v1/schema > schema.json
将schema.json拷贝到 dashboard的工作目录conf下,重启管理API, 让控制台能显示插件,方便我们在dashboard界面上配置管理.
[root@test conf]# ls
conf.yaml schema.json
4. 配置API路由插件
在配置路由设置时插件配置阶段找到ip-redis-restriction插件,启用并保存提交
5. 验证效果
curl验证下插件是否生效. 将58.243.49.20加入IP池,从结果可以判断插件已经生效返回了403
[root//127.0.0.1:9080/test-api/v1/test -H 'x-forwarded-for:58.243.49.20' plugins]# curl -v http:
.....
< HTTP/1.1 403 Forbidden
< Date: Sat, 17 Jul 2021 06:17:14 GMT
< Content-Type: text/plain; charset=utf-8
< Transfer-Encoding: chunked
< Connection: keep-alive
< Server: APISIX/2.6
<
{"error_msg":"Forbidden"}
6. 文档
http://apisix.apache.org/zh/docs/apisix/plugin-develop
https://github.com/apache/apisix-dashboard/blob/master/docs/en/latest/FAQ.md