赞
踩
全栈工程师开发手册 (作者:栾鹏)
架构系列文章
kong的插件安装参考:https://blog.csdn.net/luanpeng825485697/article/details/85287291
kong官方插件的使用参考:https://blog.csdn.net/luanpeng825485697/article/details/85326831
Kong 的插件使用了一个叫 Classic 的 class 机制。所有的插件都是从 base_plugin.lua 基类上继承而来。base_plugin.lua 定义了插件在各个阶段被执行的方法名:
每个Nginx worker 进程启动时执行。
function BasePlugin:init_worker()
ngx_log(DEBUG, "executing plugin \"", self._name, "\": init_worker")
end
在SSL握手的SSL证书服务阶段执行。
function BasePlugin:certificate()
ngx_log(DEBUG, "executing plugin \"", self._name, "\": certificate")
end
在作为重写阶段处理程序从客户端接收时针对每个请求执行。
function BasePlugin:rewrite()
ngx_log(DEBUG, "executing plugin \"", self._name, "\": rewrite")
end
针对客户端的每个请求执行,并在代理上游服务之前执行。
function BasePlugin:access()
ngx_log(DEBUG, "executing plugin \"", self._name, "\": access")
end
从上游服务接收到所有响应头字节时执行。
function BasePlugin:header_filter()
ngx_log(DEBUG, "executing plugin \"", self._name, "\": header_filter")
end
从上游服务接收到的响应体的每个块执行。由于响应被流回到客户端,所以它可以超过缓冲区大小,并且通过块被流传输块。因此如果响应大,可以多次调用该方法。
function BasePlugin:body_filter()
ngx_log(DEBUG, "executing plugin \"", self._name, "\": body_filter")
end
最后一个响应字节发送到客户端时被执行。
function BasePlugin:log()
ngx_log(DEBUG, "executing plugin \"", self._name, "\": log")
end
根据方法名也可以看出,这 7 个方法对应于 OpenResty 的不同执行阶段。也就是说插件只能对外暴露出这 7 个方法名中的一个或多个才能被 Kong 的插件机制执行,接下来 Kong 会在 OpenResty 不同的执行阶段,执行插件对应的方法。
文件结构
Kong 插件的文件结构分基本插件模块和完整插件模块两种,基本插件模块结构如下:
simple-plugin
├── handler.lua
└── schema.lua
其中,handler.lua 是插件核心,它是一个接口实现,其中每个函数将在请求生命周期中的期望时刻运行。schema.lua 用于定义插件配置
完整插件模块结构如下:
complete-plugin
├── api.lua
├── daos.lua
├── handler.lua
├── migrations
│ ├── cassandra.lua
│ └── postgres.lua
└── schema.lua
其各个模块的功能如下:
Module name | Required | Description |
---|---|---|
api.lua | No | 插件需要向 Admin API 暴露接口时使用 |
daos.lua | No | 数据层相关,当插件需要访问数据库时配置 |
handler.lua | Yes | 插件的主要逻辑,这个将会被 Kong 在不同阶段执行其对应的 handler |
migrations/*.lua | No | 插件依赖的数据表结构,启用了 daos.lua 时需要定义 |
schema.lua | Yes | 插件的配置参数定义,主要用于 Kong 参数验证 |
其中,api.lua 定义管理API操作接口;daos.lua 定义插件需要并且存储在数据库的实体的DAOs列表;migrations/*.lua 定义了给定数据存储的相应迁移,通常只有当插件必须在数据库中存储自定义实体并通过daos.lua定义的DAO进行交互时,迁移才是必要的。
其中 handler.lua 和 schema.lua 是必需的,上面提到的插件需要暴露出来的方法就定义在 handler.lua 中。
具体关于文件结构的描述参见Plugin Development - File Structure
这里以request-termination熔断为例,这是一个最简单的示例.在kong/kong/plugins/request-termination文件夹里面.
该检查就是为选定的服务或路由返回指定的响应消息.响应消息包含状态码status_code, 消息类型content_type,文本消息message,消息体body
实现逻辑在handler.lua中实现
-- 执行函数. 按照配置返回固定的响应 -- 引入模块(引入基类) local BasePlugin = require "kong.plugins.base_plugin" local singletons = require "kong.singletons" local constants = require "kong.constants" local meta = require "kong.meta" -- 局部变量 local kong = kong local server_header = meta._SERVER_TOKENS -- 默认的response local DEFAULT_RESPONSE = { [401] = "Unauthorized", [404] = "Not found", [405] = "Method not allowed", [500] = "An unexpected error occurred", [502] = "Bad Gateway", [503] = "Service unavailable", } -- 扩展模块(派生子类),其实这里是为了继承来自 Classic 的 __call 元方法,方便 Kong 在 init 阶段预加载插件的时候执行构造函数 new() local RequestTerminationHandler = BasePlugin:extend() -- 设置插件的优先级,Kong 将按照插件的优先级来确定其执行顺序(越大越优先) -- 需要注意的是应用于 Consumer 的插件因为依赖于 Auth,所以 Auth 类插件优先级普遍比较高 RequestTerminationHandler.PRIORITY = 2 RequestTerminationHandler.VERSION = "1.0.0" -- 插件的构造函数,用于初始化插件的 _name 属性,后面会根据这个属性打印插件名 -- 其实这个方法不是必须的,只是用于插件调试 function RequestTerminationHandler:new() RequestTerminationHandler.super.new(self, "request-termination") end -- 表明需要在 access 阶段执行此插件. 也就是在接入上游服务前就直接生成响应数据. function RequestTerminationHandler:access(conf) -- conf就是schema.lua中的config,也就是插件安装时的配置页面 -- 执行父类的 access 方法,其实就是为了调试时输出日志用的 RequestTerminationHandler.super.access(self) -- 接下来的就是插件的主要逻辑 local status = conf.status_code local content = conf.body -- 如果配置了content参数 if content then local headers = { ["Content-Type"] = conf.content_type } if singletons.configuration.enabled_headers[constants.HEADERS.SERVER] then headers[constants.HEADERS.SERVER] = server_header end return kong.response.exit(status, content, headers) end -- 如果没有配置content参数,就直接生成message的消息体 return kong.response.exit(status, { message = conf.message or DEFAULT_RESPONSE[status] }) end return RequestTerminationHandler
Kong 插件通过schema.lua文件定义配置。类似于 JSON Schema,主要用于描述插件参数的数据格式。
schema.lua 返回一个Table类型,包含no_consumer、fields、self_check三个属性:
属性名 | Lua 类型 | 默认值 | 描述 |
---|---|---|---|
no_consumer | Boolean | false | 如果为true将不能应用此插件至指定消费者,只能被应用到 Services 或者 Routes, 例如:认证插件 |
fileds | Table | {} | 插件的 schema,使用一个键值对定义可用属性和他们的规则 |
self_check | Function | nil | 如果在接受插件配置之前需要进行自定义验证,需要实现此函数 |
schema.lua 文件样本如下:
--主要用于描述插件参数的数据格式. -- bashboard页面上创建时需要填写的内容和添加插件时进行的校验 -- 引入模块,赋值table给typedefs变量 local typedefs = require "kong.db.schema.typedefs" -- 自定义局部函数 local is_present = function(v) return type(v) == "string" and #v > 0 -- # 表示取长度 end return { -- 插件名称 name = "request-termination", fields = { { run_on = typedefs.run_on_first }, { config = { type = "record", -- 描述插件参数的数据格式,用于 Kong 验证参数 fields = { { status_code = { type = "integer", default = 503, between = { 100, 599 }, }, }, { message = { type = "string" }, }, { content_type = { type = "string" }, }, { body = { type = "string" }, }, }, -- 自定义更为细粒度的参数校验 custom_validator = function(config) if is_present(config.message) and (is_present(config.content_type) or is_present(config.body)) then return nil, "message cannot be used with content_type or body" end if is_present(config.content_type) and not is_present(config.body) then return nil, "content_type requires a body" end return true end, }, }, }, }
这里以prometheus插件为例, 因为kong自带的prometheus插件支持的度量比较少,我们需要增加一些度量内容,
kong自带的插件在/usr/local/share/lua/5.1/kong/plugins/prometheus目录下. 我们将该文件夹copy出来,
api.lua和serv.lua文件中定义了暴露的接口, 接口中将匹配/metrics是返回收集的度量数据.
schema.lua定义的启动插件时的参数检查, 其实启动这个参数不需要任何参数, 所以这个文件的内容很少.
prometheus.lua文件中定义了基础类Metric和派生类Counter,Gauge,Histogram以及类的方法和属性
handler.lua文件定义了各阶段执行的函数, 分别调用的是exporter.lua文件中定义的各个函数.
exporter.lua文件中主要报错处理函数, 主要为init定义度量的函数, log 设置度量的值的函数 collect 搜索度量返回的函数.
我们关心的是log函数,这个函数是message为参数, 如果需要我们可以添加config也作为参数, 那就需要在调用这个函数的时候将conf参数传入.
message包含了我们需要的所有内容, 下面是他的消息体格式
message{ latencies{ request 103 kong 99 proxy 3 } service{ host license-service.cloudai-2 created_at 1547185659 connect_timeout 60000 id 2641f0cb-c604-48e2-9d5a-0dbe13fd5274 protocol http name license read_timeout 60000 port 8080 updated_at 1547185659 retries 5 write_timeout 60000 } request{ querystring{ } size 351 uri /license/sign url http://192.168.11.127:8443/license/sign headers{ host 192.168.11.127:32443 content-type application/json postman-token 00f49660-5920-432d-adcc-e98721e27e8b accept */* x-b3-parentspanid 0f64da2e24dd4ac6 cache-control no-cache content-length 57 accept-encoding gzip, deflate user-agent PostmanRuntime/7.4.0 x-b3-traceid 2aa7eebf34ea4f44ea201f10ff36da94 x-lantern-version 5.2.0 x-b3-spanid 096d7f0b78a0926a x-b3-sampled 1 } method POST } tries{ { balancer_latency 0 port 8080 balancer_start 1547452777442 ip 10.233.56.89 } } client_ip 10.233.65.0 api{ } upstream_uri /sign response{ headers{ content-type application/json; charset=utf-8 date Mon, 14 Jan 2019 07:59:37 GMT connection close x-ratelimit-limit-second 1 via kong/0.14.1 x-kong-proxy-latency 100 content-length 225 x-kong-upstream-latency 3 x-ratelimit-remaining-second 0 server Python/3.6 aiohttp/3.5.1 cookie --cookie aicloud-cookie=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ2ZXNpb25ib29rIiwiaWF0IjoxNTQ3NDUyNzc3LCJuYmYiOjE1NDc0NTI3NzcsImV4cCI6MTU0NzQ1NjM3N30.fpaSSLuh7TSN9igMzMyIMLXPeAXo3aYBNhq67i3UV2E access-control-allow-methods GET,POST access-control-allow-origin * } status 200 size 824 } route{ created_at 1547185719 strip_path true hosts{ } preserve_host false regex_priority 0 updated_at 1547185719 paths{ 1 /license } service{ id 2641f0cb-c604-48e2-9d5a-0dbe13fd5274 } methods{ 1 GET 2 POST } protocols{ 1 http } id 24be26d7-40e1-487d-9f84-8cca937a02b6 } consumer{ custom_id vesionbook created_at 1547185392 username vesionbook id 711171ba-817e-4a9a-8b93-2c089e5a52b0 } }
我们按照官方的样子,模拟就可以添加自己的度量了.
有时我们需要对黑白名单进行限速, 而官方的插件中黑白名单, 为 完全拒绝访问的形式, 而 限速插件中, 又统一对所有的选定访问客户端, 但是有时我们想对某些ip进行限速. 因此我们需要一个有黑白名单的限速插件.
这个以后有时间再弄吧
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。