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 = ipairslocal core = require("apisix.core")-- 插件名称local plugin_name = "ip-redis-restriction"-- 插件参数schemalocal 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"-- ipmatcherlocal ipmatcher = require("resty.ipmatcher")local blacklist_matcher = nillocal lastUpdateTime = ngx.now()-- 关闭redislocal function close_redis(red)if not red thenreturnend-- 释放连接池设置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 thencore.log.error("set keepalive error : ", err)endendlocal 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 = 6379local passwd = "12345678"local ok, err = red:connect(ip,port)if not ok thencore.log.error("red connect error: ", err)returnendlocal count, err = red:get_reused_times()----count> 0 从连接池中获取连接,无需再次认证密码if 0 == count then----新建连接,需要认证密码ok, err = red:auth(passwd)if not ok thencore.log.error("failed to auth: ", err)returnendelseif err thencore.log.error("failed to get reused times: ", err)returnendlocal ip_blacklist, err = red:smembers(key)if err thencore.log.error("limit ip smembers ",err)elsefor i,bip in ipairs(ip_blacklist) do-- cache:set(bip,1)table.insert(iptable,bip)endend-- 关闭redis释放回连接池close_redis(red)local ip,err = ipmatcher.new(iptable)-- core.log.warn("blacklist_matcher update")if not ip thencore.log.error("blacklist_matcher update failed",err)endblacklist_matcher = ipend-- 检测是否需要重新从redis拿IP数据缓存local function check_cache_need_update()-- check缓存TTL过期,过期了则从redis取数据if lastUpdateTime == nil thenlastUpdateTime = ngx.now()core.log.warn('get_ip_backlist lastUpdateTime is nil ')endlocal diffTime = ngx.now() - lastUpdateTime-- 每过1分钟了,重新从redis取最新IP黑名单数据if diffTime >= 60 or blacklist_matcher == nil thenrebuild_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)endend-- 执行阶段function _M.access(conf,ctx)core.log.warn('access conf ',core.json.encode(conf))local remote_addr = ctx.var.http_x_forwarded_forif remote_addr == nil then--取不到就用remote_addrremote_addr = ctx.var.remote_addrendcheck_cache_need_update()if blacklist_matcher == nil thencore.log.warn('blacklist_matcher is nill')returnendlocal data,err = blacklist_matcher:match(remote_addr)if data thencore.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)endendreturn _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]# lsconf.yaml schema.json
4. 配置API路由插件
在配置路由设置时插件配置阶段找到ip-redis-restriction插件,启用并保存提交
5. 验证效果
curl验证下插件是否生效. 将58.243.49.20加入IP池,从结果可以判断插件已经生效返回了403
[root plugins]# curl -v http://127.0.0.1:9080/test-api/v1/test -H 'x-forwarded-for:58.243.49.20'.....< 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
