Kong自定义插件

Kong 是一个流行的开源 API 网关,它为微服务架构提供了一个前端层。Kong 基于 NGINX,使用 Lua 语言进行扩展,最初在 2015 年由 Mashape(现称为 Kong Inc.)发布。它设计用于处理高并发的 API 请求、路由、安全性、监控和扩展性问题。Kong 可以在任何基础设施上运行,包括云环境和本地服务器,支持 RESTful 和 GraphQL API。

kong的生态

Kong 的生态系统包括以下几个关键部分:

  • Kong Gateway:主要的开源项目,为企业提供核心的 API 网关功能。
  • Kong Enterprise:商业版本,提供额外的管理功能、安全性增强和分析工具。
  • Kong Studio:API 设计和测试平台,支持 API 的生命周期管理。
  • Kong Brain 和 Kong Immunity(企业版功能):为 Kong 提供自动化的 API 流量分析、异常检测和智能文档生成。
  • Kong Konnect:云服务平台,提供 Kong 的所有功能作为服务,让用户无需自行管理底层基础设施。
  • Kong Ingress Controller for Kubernetes:利用 Kong 作为 Kubernetes 集群的入口控制器,实现 API 网关的功能。

编写Kong插件的准备工作

  • 有一定的Lua基础(语法不是很复杂,所以并不需要你有多高的Lua编程水平就可以完成一个kong插件的开发工作)
  • kong插件的层级结构以及生命周期
  • 环境以及CI/CD
  1. 层级结构

    Kong 插件通常包括以下几个主要组件:

  • handler.lua:这是插件逻辑的核心文件,其中定义了插件在不同的请求处理阶段应该执行的操作。
  • schema.lua:定义了插件配置的数据模式,用于验证和存储插件配置。
  • migrations/(可选):如果插件需要执行数据库操作,如添加新的表或字段,这里将包含数据库迁移脚本。
  1. 执行阶段

    Kong 插件可以在请求/响应生命周期的不同阶段进行介入,每个阶段对应不同的处理函数。这些阶段包括:

  • certificate:在 SSL/TLS 握手阶段执行,用于动态地处理证书。
  • rewrite(仅限于 0.10.x 以上版本):在接收到请求后、路由解析前执行,用于改写请求。
  • access:在请求被处理前执行,用于控制访问或修改请求。
  • header_filter:在发送响应头之前执行,用于修改响应头。
  • body_filter:在发送响应体之前执行,用于修改响应体。
  • log:在最后响应发送完毕后执行,用于日志记录。

案例实操

下面我们来实现一个将请求响应的信息写入到mongodb的插件

handler.lua 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
local cjson = require "cjson.safe"
local mongo = require "mongorover"
local ngx_timer_at = ngx.timer.at

local LogToMongoHandler = {
VERSION = "1.0.0",
PRIORITY = 10,
}

local function send_log(premature, conf, message)
if premature then
return
end
local mongo_uri = 'mongodb://' .. conf.mongo_user .. ':' .. conf.mongo_password .. '@' .. conf.mongo_host .. ':' .. conf.mongo_port
-- 创建 MongoDB 连接
local client = mongo.MongoClient.new(mongo_uri)


-- 获取数据库和集合
local db = client:getDatabase(conf.mongo_db)
local col = db:getCollection(conf.mongo_collection)

-- 插入日志
local res, err = col:insert_one(message)
if not res then
kong.log.err("failed to insert log into MongoDB: ", err)
end
end

function LogToMongoHandler:access(conf)
-- 获取请求体并存储在上下文中
local body, err = kong.request.get_raw_body()
if not body then
kong.log.err("Failed to get request body: ", err)
else
kong.ctx.plugin.request_body = body
end
end

function LogToMongoHandler:body_filter(conf)
local chunk, eof = ngx.arg[1], ngx.arg[2]

-- 拼接响应体
kong.ctx.plugin.response_body = (kong.ctx.plugin.response_body or "") .. chunk

if eof then
-- 完整的响应体存储在上下文中
kong.ctx.plugin.response_body_complete = true
end
end

function LogToMongoHandler:log(conf)
-- 获取存储在上下文中的请求体
local request_body = kong.ctx.plugin.request_body
-- 获取存储在上下文中的响应体
local response_body = kong.ctx.plugin.response_body_complete and kong.ctx.plugin.response_body or nil

local message = {
request = {
method = kong.request.get_method(),
uri = kong.request.get_path_with_query(),
headers = kong.request.get_headers(),
body = request_body,
},
response = {
status = kong.response.get_status(),
headers = kong.response.get_headers(),
body = response_body,
},
started_at = ngx.req.start_time() * 1000
}
local json_data, err = cjson.encode(message)
if not json_data then
kong.log.err("failed to encode message: ", err)
else
kong.log.info(json_data)
end

if kong.response.get_status() ~= 200 then
kong.log.debug("ignore non 200 response:",kong.response.get_status())
return
end

local ok, err = ngx_timer_at(0, send_log, conf, message)
if not ok then
kong.log.err("failed to create timer: ", err)
end
end

return LogToMongoHandler

schema.lua 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
local typedefs = require "kong.db.schema.typedefs"

return {
name = "log-to-mongo",
fields = {
{ config = {
type = "record",
fields = {
{ mongo_host = { type = "string", required = true, default = "127.0.0.1" } },
{ mongo_port = { type = "number", required = true, default = 27017 } },
{ mongo_db = { type = "string", required = true, default = "log" } },
{ mongo_collection = { type = "string", required = true, default = "logs" } },
{ mongo_user = { type = "string", required = false } },
{ mongo_password = { type = "string", required = false } },
},
}},
},
}


CI/CD

官方也是提供了基础的deb包以及样例,我们在此基础上,将插件复制到指定的位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46

FROM ubuntu:24.04

COPY build/kong_3.5.0_amd64.deb /tmp/kong.deb

COPY build/sources.list /etc/apt/

RUN set -ex; \
apt-get update \
&& apt-get install --yes curl vim gettext-base \
&& apt-get install --yes /tmp/kong.deb \
&& apt-get install --yes luarocks \
&& apt-get install --yes libbson-dev libmongoc-dev libpq-dev \
&& luarocks install mongorover \
&& luarocks install luasql-postgres PGSQL_INCDIR=/usr/include/postgresql PGSQL_LIBDIR=/usr/include/postgresql \
&& luarocks install redis-lua \
&& rm -rf /var/lib/apt/lists/* \
&& rm -rf /tmp/kong.deb \
&& chown kong:0 /usr/local/bin/kong \
&& chown -R kong:0 /usr/local/kong \
&& ln -s /usr/local/openresty/luajit/bin/luajit /usr/local/bin/luajit \
&& ln -s /usr/local/openresty/luajit/bin/luajit /usr/local/bin/lua \
&& ln -s /usr/local/openresty/nginx/sbin/nginx /usr/local/bin/nginx \
&& kong version

COPY build/docker-entrypoint.sh /docker-entrypoint.sh

COPY build/kong.conf /etc/kong/kong.conf.template

COPY plugins /usr/local/share/lua/5.1/kong/plugins

RUN chown -R kong:kong /usr/local/share/lua/5.1/kong/plugins

RUN chmod +x /docker-entrypoint.sh

USER root

ENTRYPOINT ["/docker-entrypoint.sh"]

EXPOSE 8000 8443 8001 8444 8002 8445 8003 8446 8004 8447

STOPSIGNAL SIGQUIT

HEALTHCHECK --interval=10s --timeout=10s --retries=10 CMD kong health

CMD ["kong", "docker-start"]

Kong自定义插件
https://www.xinyublog.com/tools/kong/
作者
蚂蚁
发布于
2024年5月1日
许可协议