kong笔记 目录导航
本文通过一个自定义插件的编写,来了解Kong的插件机制
插件功能:
上一篇我们学到,一个插件必须包含两个文件:
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
当多个作用域的插件出现在一个场景时,他们的优先级法则为:一个插件相对于它所配置的实体数量越具体,它的优先级就越高。
多次配置插件时的完整优先级顺序为:
示例:如果插件应用两次(具有不同的配置):对于service(插件配置 A)和对于consumer(插件配置 B),则验证此consumer身份的请求将运行插件配置 B 并忽略 A。但是,不验证此consumer身份的请求将回退到运行插件配置 A。请注意,如果禁用了配置 B(其标志设置为 ),则配置 A 将应用于与配置 B 匹配的请求。rate-limiting``enabled``false
官网解释:Admin API - v2.7.x | Kong Docs (konghq.com)
多个插件的执行顺序
当一个路径涉及到多个插件时,有时候后一个插件需要依赖前一个插件的数据,所以出现了优先级问题,这个时候你可以通过
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 |