kong笔记 目录导航
本文通过一个自定义插件的编写,来了解Kong的插件机制
插件功能:
- 原本返回百度首页,在添加插件之后,返回插件自定义的内容;
前提
- 使用kong代理百度的接口,配置route路径;
- 了解kong 插件的目录结构及部署(上一篇说到的)
实战
上一篇我们学到,一个插件必须包含两个文件:
- handler.lua
- schema.lua
handler.lua主要负责业务逻辑功能编写,schema.lua主要负责插件参数定制,类似将handler.lua中写死的变量抽出来写到schema.lua中,变为动态注入。
既然是自定义插件,又想应用到kong中,这期间肯定会有一些规范做适配,kong也不例外,在kong中,我们可以通过继承一个base插件:
kong.plugins.base_plugin
这个base插件提供了一些方法待实现,这些方法是基于 openresty的http模块来定义的
函数名 | LUA-NGINX-MODULE Context | 描述 |
---|---|---|
:init_worker() | init_worker_by_lua | 在每个 Nginx 工作进程启动时执行 |
:certificate() | ssl_certificate_by_lua | 在SSL握手阶段的SSL证书服务阶段执行 |
:rewrite() | rewrite_by_lua | 从客户端接收作为重写阶段处理程序的每个请求执行。在这个阶段,无论是API还是消费者都没有被识别,因此这个处理器只在插件被配置为全局插件时执行 |
:access() | access_by_lua | 为客户的每一个请求而执行,并在它被代理到上游服务之前执行(路由) |
:header_filter() | header_filter_by_lua | 从上游服务接收到所有响应头字节时执行 |
:body_filter() | body_filter_by_lua | 从上游服务接收的响应体的每个块时执行。由于响应流回客户端,它可以超过缓冲区大小,因此,如果响应较大,该方法可以被多次调用 |
:log() | log_by_lua | 当最后一个响应字节已经发送到客户端时执行 |
在各个阶段分别可以处理响应的业务。
另外,kong自身也提供了一些方法,有上下文的,有request、response等等的工具类,便于我们使用
PDK名称 | 功能描述 |
---|---|
kong.client | 提供客户端的ip, 端口等信息 |
kong.ctx | 提供了插件之间共享并传递参数的桥梁 |
kong.ip | 提供了kong.ip.is_trusted(address)IP白名单检测方法 |
kong.log | 日志方法 |
kong.node | 返回此插件的UUID信息 |
kong.request | 仅 提供request信息的读取功能,access() 中可读 |
kong.response | 提供response信息的读写功能, access() 中不可用 |
kong.router | 返回此请求关联的router信息 |
kong.service | 返回此请求关联的service,可以动态修改 后端服务信息 |
kong.service.request | 仅用于access() 方法中,可以读写 请求信息 |
kong.service.response | 仅可用于header_filter(), body_filter() 方法中,只提供header 信息的读取功能 |
kong.table | kong提供的一套数据结构功能 |
更多方法参考:PDK - v2.5.x | Kong Docs (konghq.com)
插件代码
插件名称为:demo
-
handler.lua
local BasePlugin = require "kong.plugins.base_plugin" local DemoHandler = BasePlugin:extend() DemoHandler.VERSION = "0.1.0" DemoHandler.PRIORITY = 2000 -- 在 'init_by_lua_block' 中运行 function DemoHandler:new() DemoHandler.super.new(self, "demo") end -- 在 'init_worker_by_lua_block' 中运行 function DemoHandler:init_worker() DemoHandler.super.init_worker(self) end -- 在 'ssl_certificate_by_lua_block' 中运行 function DemoHandler:certificate(conf) DemoHandler.super.certificate(self) end -- 在 'rewrite_by_lua_block' 中运行 function DemoHandler:rewrite(conf) DemoHandler.super.rewrite(self) end -- 在 'access_by_lua_block' 中运行 function DemoHandler:access(conf) DemoHandler.super.access(self) return kong.response.exit(200,conf.content) end -- 在 'header_filter_by_lua_block' 中运行 function DemoHandler:header_filter(conf) DemoHandler.super.header_filter(self) end -- 在 'body_filter_by_lua_block' 中运行 function DemoHandler:body_filter(conf) DemoHandler.super.body_filter(self) end -- 在 'log_by_lua_block' 中运行 function DemoHandler:log(conf) DemoHandler.super.log(self) end return DemoHandler
可以看到, 主要内容就是实现一个
BasePlugin:extend()
,实现一系统生命周期对应的方法后,返回这个实例即可.注意看一下
access()
方法那块,我添加了一些内容!!!!而且, 所有方法在实现的时候, 都要先调用一下父类方法.
-
schema.lua
return { no_consumer = true, fields = { content = { type = "string", default = "success!!" }, } }
部署
-
先把代码放置到
/opt/share/kong/plugins/demo
位置 -
修改
/etc/kong/kong.conf
文件,加载插件lua_package_path = /opt/share/?.lua;; plugins = bundled,demo
-
重启Kong服务:
kong restart
验证
-
打开konga,可以看到插件的部署情况:
底色是绿色的代表正在使用的插件!
-
添加插件
在这里我需要说明一下,kong插件的作用域范围有4个
-
Consumer作用域
-
route作用域
仅针对route配置起作用(一个route可以配置多个path,也就是说这个route下的所有path插件都生效,不是这个route的path不生效) -
service作用域
针对service配置起作用(一个service有多个route,只要是属于这个service下的route,插件都生效)
-
global作用域
所有请求,插件都生效
目前我们演示route作用域,其他感兴趣的自己可以尝试一下:
-
添加插件
参数详情:
-
访问结果
-
拓展
上述的实战比较简单,相信你也可以顺利完成,接下来我来说几个参坑点吧
-
lua库扫描以及引入第三方lua库
-
在我们编写lua脚本的时候,可能需要引入其他的库(kong自身没有提供的库),这个时候当我们在handler.lua中require的时候,则需要在kong.conf配置
untrusted_lua_sandbox_requires
属性,例如:在handler.lua中
local template = require "resty.template" local split = require "kong.tools.utils".split
那么我们必须在kong.conf中配置
untrusted_lua_sandbox_requires = resty.template, kong.tools.utils
-
还有一种方案是,直接将自己写好的库跟handler.lua,schema.lua放到一个文件夹里面,然后通过下列方式来引入
local xxx = require "kong.plugins.插件名称.自己库的名称"
例如:插件名称叫
demo
,自己写的库名称是redisUtil.lua
,那么引入方式为:local redisUtil = require "kong.plugins.demo.redisUtil"
这种方案有个前提是!
你的redisutil 库必须有返回,如果是函数,想做到通用,就必须return,并且函数不要用local修饰,例如:我想让get,put,incr这三个函数通用,我的redisutil就必须返回这三个函数
return { get = get; put = put; incr = incr; }
如果全部封装到table里面,在最后返回时,也可以这么写:
return _M
-
-
schema文件拓展内容
在schema文件中,支持的类型共有下面这些类型
map, number, array, foreign, function, integer, boolean, record, string, set
其中,在type=record的时候,这种就必须定义field,类似java中的JavaBean一样,定义属性,以及属性值
比如:
return { name = "ip-restriction", fields = { { protocols = typedefs.protocols_http }, { config = { type = "record", fields = { { allow = { type = "array", elements = typedefs.ip_or_cidr, }, }, { deny = { type = "array", elements = typedefs.ip_or_cidr, }, }, }, }, }, }, }
这种就相当与是有一个
config
对象,包含两个属性,这个两个属性都是数组类型,数组内部的元素必须符合ip校验规则,其数据结构对应为:{ "name": "ip-restriction", "config": { "allow": [ "127.0.0.1" ], "deny": [ "127.0.0.1" ] } }
在type=map的时候
比如:
return { name = "response-ratelimiting", fields = { { protocols = typedefs.protocols_http }, { config = { type = "record", fields = { { limits = { type = "map", required = true, len_min = 1, keys = { type = "string" }, values = { type = "record", required = true, fields = { { second = { type = "number", gt = 0 }, }, { minute = { type = "number", gt = 0 }, }, { hour = { type = "number", gt = 0 }, }, { day = { type = "number", gt = 0 }, }, { month = { type = "number", gt = 0 }, }, { year = { type = "number", gt = 0 }, }, }, }, }, }, }, }, }, }, }
这种就相当于是JsonObject结构,其数据结构为:
{ "name": "response-ratelimiting", "config": { "limits": { "second":1, "minute":1, "hour":1, "day":1, "month":1, "year":1 } } }
还有一些比较复杂的嵌套对象,比如:
return { name = "demo-ip-restriction", fields = { { protocols = typedefs.protocols_http }, { config = { type = "record", fields = { { rfilter = { type = "array", elements = { type = "record", fields = { { appkey = { type = "string", required = true }, }, { deny = { type = "array", elements = typedefs.ip_or_cidr, }, }, }, }, } }, }, }, }, }, }
这种对应的数据结构为:
{ "name": "demo-ip-restriction", "config": { "rfilter": [ { "appkey": "1111", "deny": [ "142.0.0.1/24" ] }, { "appkey": "2222", "deny": [ "132.0.0.1/32" ] } ] } }
-
部署中常见的问题
在部署插件中我们可能会遇到这么几个问题“-
plugin is in use but not enabled;
这个是因为你之前配置了一个自定义插件,并在某种场合使用了该插件,后来由于某种原因,你下架了该自定义插件,重启kong后,就会报该错误;
想解决很简单,在数据库
plugins
中删除使用添加该插件的节点 -
plugin is enabled but not installed;
这个是因为你在kong.conf中配置了自定义插件(plugins = bundled,demo),没有配置lua源(lua_package_path = /opt/share/?.lua;;)
-
-
多个作用域插件的执行顺序
在上文我们提到插件可以作用域在consumer,route,service,global
当多个作用域的插件出现在一个场景时,他们的优先级法则为:一个插件相对于它所配置的实体数量越具体,它的优先级就越高。
多次配置插件时的完整优先级顺序为:
- 在以下组合上配置的插件:route、service和consumer。(consumer表示请求必须经过身份验证)。
- 在route和consumer的组合上配置的插件。(consumer表示请求必须经过身份验证)。
- 在service和consumer的组合上配置的插件。(consumer表示请求必须经过身份验证)。
- 在route和service的组合上配置的插件。
- 在consumer上配置的插件。(consumer表示请求必须经过身份验证)。
- 在route上配置的插件。
- 在service上配置的插件。
- 配置为global运行的插件。
示例:如果插件应用两次(具有不同的配置):对于service(插件配置 A)和对于consumer(插件配置 B),则验证此consumer身份的请求将运行插件配置 B 并忽略 A。但是,不验证此consumer身份的请求将回退到运行插件配置 A。请注意,如果禁用了配置 B(其标志设置为 ),则配置 A 将应用于与配置 B 匹配的请求。
rate-limiting``enabled``false
-
多个插件的执行顺序
当一个路径涉及到多个插件时,有时候后一个插件需要依赖前一个插件的数据,所以出现了优先级问题,这个时候你可以通过
CustomHandler.PRIORITY = 10
来设置你的插件优先级
优先级越高,相对于其他插件的阶段(如:access()、:log()等),插件的阶段执行得越快。 已有捆绑插件的当前执行顺序为:
插件 优先级 pre-function +inf
zipkin 100000 ip-restriction 3000 bot-detection 2500 cors 25000 jwt 1005 oauth2 1004 key-auth 1003 ldap-auth 1002 basic-auth 1001 hmac-auth 1000 request-size-limiting 951 acl 950 rate-limiting 901 response-ratelimiting 900 request-transformer 801 response-transformer 800 aws-lambda 750 azure-functions 749 prometheus 13 http-log 12 statsd 11 datadog 10 file-log 9 udp-log 8 tcp-log 7 loggly 6 syslog 4 galileo 3 request-termination 2 correlation-id 1 post-function -1000