1. 插件系统概述
1.1 什么是Kong插件
Kong插件是扩展Kong功能的模块,可以在请求/响应的生命周期中执行自定义逻辑。插件使用Lua语言开发,基于OpenResty平台运行。
1.2 插件的作用
- 认证和授权: 验证用户身份和权限
- 安全防护: 防止恶意攻击和滥用
- 流量控制: 限制请求频率和大小
- 监控和日志: 收集指标和记录日志
- 数据转换: 修改请求和响应数据
- 协议转换: 支持不同的通信协议
1.3 插件执行顺序
Kong按照优先级顺序执行插件,优先级数字越小,执行越早:
认证插件 (1000) -> 安全插件 (900) -> 流量控制 (800) -> 转换插件 (700)
2. 内置插件介绍
2.1 认证插件
2.1.1 Basic Authentication
# 启用Basic Auth插件
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=basic-auth"
# 创建消费者
curl -X POST http://localhost:8001/consumers \
--data "username=testuser"
# 为消费者添加凭证
curl -X POST http://localhost:8001/consumers/testuser/basic-auth \
--data "username=user" \
--data "password=pass"
2.1.2 JWT Authentication
# 启用JWT插件
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=jwt"
# 为消费者创建JWT凭证
curl -X POST http://localhost:8001/consumers/testuser/jwt \
--data "key=my-key" \
--data "secret=my-secret"
2.1.3 OAuth 2.0
# 启用OAuth2插件
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=oauth2" \
--data "config.scopes=read,write" \
--data "config.mandatory_scope=true"
2.2 安全插件
2.2.1 Rate Limiting
# 启用限流插件
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=rate-limiting" \
--data "config.minute=100" \
--data "config.hour=1000"
2.2.2 IP Restriction
# IP白名单
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=ip-restriction" \
--data "config.allow=192.168.1.0/24,10.0.0.0/8"
# IP黑名单
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=ip-restriction" \
--data "config.deny=192.168.1.100"
2.2.3 CORS
# 启用CORS插件
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=cors" \
--data "config.origins=*" \
--data "config.methods=GET,POST,PUT,DELETE" \
--data "config.headers=Accept,Content-Type,Authorization"
2.3 监控插件
2.3.1 Prometheus
# 启用Prometheus插件
curl -X POST http://localhost:8001/plugins \
--data "name=prometheus"
# 访问指标
curl http://localhost:8001/metrics
2.3.2 StatsD
# 启用StatsD插件
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=statsd" \
--data "config.host=statsd-server" \
--data "config.port=8125"
2.4 转换插件
2.4.1 Request Transformer
# 请求转换插件
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=request-transformer" \
--data "config.add.headers=X-Custom-Header:value" \
--data "config.add.querystring=new_param:value"
2.4.2 Response Transformer
# 响应转换插件
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=response-transformer" \
--data "config.add.headers=X-Response-Header:value" \
--data "config.remove.headers=Server"
3. 自定义插件开发
3.1 插件结构
3.1.1 目录结构
my-plugin/
├── kong/
│ └── plugins/
│ └── my-plugin/
│ ├── handler.lua
│ ├── schema.lua
│ └── migrations/
│ └── 000_base_my_plugin.lua
└── my-plugin-1.0.0-1.rockspec
3.1.2 Rockspec文件
-- my-plugin-1.0.0-1.rockspec
package = "my-plugin"
version = "1.0.0-1"
source = {
url = "git://github.com/username/my-plugin",
tag = "v1.0.0"
}
description = {
summary = "My custom Kong plugin",
detailed = "A detailed description of my plugin",
homepage = "https://github.com/username/my-plugin",
license = "MIT"
}
dependencies = {
"lua >= 5.1"
}
build = {
type = "builtin",
modules = {
["kong.plugins.my-plugin.handler"] = "kong/plugins/my-plugin/handler.lua",
["kong.plugins.my-plugin.schema"] = "kong/plugins/my-plugin/schema.lua",
}
}
3.2 Schema定义
3.2.1 基本Schema
-- kong/plugins/my-plugin/schema.lua
local typedefs = require "kong.db.schema.typedefs"
return {
name = "my-plugin",
fields = {
{ consumer = typedefs.no_consumer },
{ protocols = typedefs.protocols_http },
{ config = {
type = "record",
fields = {
{ message = {
type = "string",
default = "Hello World",
required = true,
}},
{ timeout = {
type = "number",
default = 1000,
between = { 1, 60000 },
}},
{ enabled = {
type = "boolean",
default = true,
}},
},
}},
},
}
3.2.2 复杂Schema示例
local typedefs = require "kong.db.schema.typedefs"
return {
name = "advanced-plugin",
fields = {
{ consumer = typedefs.no_consumer },
{ protocols = typedefs.protocols_http },
{ config = {
type = "record",
fields = {
{ api_key = {
type = "string",
required = true,
encrypted = true, -- 加密存储
}},
{ allowed_ips = {
type = "array",
elements = typedefs.ip,
default = {},
}},
{ rate_limit = {
type = "record",
fields = {
{ requests = { type = "number", default = 100 }},
{ window = { type = "number", default = 60 }},
},
}},
{ headers = {
type = "map",
keys = { type = "string" },
values = { type = "string" },
}},
},
}},
},
}
3.3 Handler实现
3.3.1 基本Handler
-- kong/plugins/my-plugin/handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local MyPluginHandler = BasePlugin:extend()
MyPluginHandler.PRIORITY = 1000
MyPluginHandler.VERSION = "1.0.0"
function MyPluginHandler:new()
MyPluginHandler.super.new(self, "my-plugin")
end
function MyPluginHandler:access(conf)
MyPluginHandler.super.access(self)
-- 在这里实现插件逻辑
kong.response.set_header("X-My-Plugin", conf.message)
end
return MyPluginHandler
3.3.2 完整生命周期Handler
local BasePlugin = require "kong.plugins.base_plugin"
local json = require "cjson"
local http = require "resty.http"
local AdvancedPluginHandler = BasePlugin:extend()
AdvancedPluginHandler.PRIORITY = 1000
AdvancedPluginHandler.VERSION = "1.0.0"
function AdvancedPluginHandler:new()
AdvancedPluginHandler.super.new(self, "advanced-plugin")
end
-- 初始化阶段
function AdvancedPluginHandler:init_worker()
AdvancedPluginHandler.super.init_worker(self)
-- 初始化工作进程
end
-- 证书阶段
function AdvancedPluginHandler:certificate(conf)
AdvancedPluginHandler.super.certificate(self)
-- SSL证书处理
end
-- 重写阶段
function AdvancedPluginHandler:rewrite(conf)
AdvancedPluginHandler.super.rewrite(self)
-- URL重写逻辑
end
-- 访问阶段
function AdvancedPluginHandler:access(conf)
AdvancedPluginHandler.super.access(self)
-- IP检查
local client_ip = kong.client.get_ip()
if not self:is_ip_allowed(client_ip, conf.allowed_ips) then
return kong.response.exit(403, { message = "IP not allowed" })
end
-- 速率限制
if not self:check_rate_limit(conf.rate_limit) then
return kong.response.exit(429, { message = "Rate limit exceeded" })
end
-- 添加自定义头
for key, value in pairs(conf.headers or {}) do
kong.service.request.set_header(key, value)
end
end
-- 响应阶段
function AdvancedPluginHandler:header_filter(conf)
AdvancedPluginHandler.super.header_filter(self)
-- 修改响应头
end
-- 响应体阶段
function AdvancedPluginHandler:body_filter(conf)
AdvancedPluginHandler.super.body_filter(self)
-- 修改响应体
end
-- 日志阶段
function AdvancedPluginHandler:log(conf)
AdvancedPluginHandler.super.log(self)
-- 记录访问日志
local log_data = {
timestamp = ngx.time(),
client_ip = kong.client.get_ip(),
method = kong.request.get_method(),
uri = kong.request.get_path(),
status = kong.response.get_status(),
latency = kong.ctx.shared.response_time
}
kong.log.info(json.encode(log_data))
end
-- 辅助方法
function AdvancedPluginHandler:is_ip_allowed(ip, allowed_ips)
if #allowed_ips == 0 then
return true
end
for _, allowed_ip in ipairs(allowed_ips) do
if ip == allowed_ip then
return true
end
end
return false
end
function AdvancedPluginHandler:check_rate_limit(rate_config)
-- 实现速率限制逻辑
local key = "rate_limit:" .. kong.client.get_ip()
local current_requests = kong.cache.get(key) or 0
if current_requests >= rate_config.requests then
return false
end
kong.cache.set(key, current_requests + 1, rate_config.window)
return true
end
return AdvancedPluginHandler
3.4 数据库迁移
3.4.1 迁移文件
-- kong/plugins/my-plugin/migrations/000_base_my_plugin.lua
return {
postgres = {
up = [[
CREATE TABLE IF NOT EXISTS "my_plugin_data" (
"id" UUID PRIMARY KEY,
"created_at" TIMESTAMP WITH TIME ZONE DEFAULT (CURRENT_TIMESTAMP(0) AT TIME ZONE 'UTC'),
"consumer_id" UUID REFERENCES "consumers" ("id") ON DELETE CASCADE,
"key" TEXT UNIQUE,
"value" TEXT
);
CREATE INDEX IF NOT EXISTS "my_plugin_data_consumer_id_idx" ON "my_plugin_data" ("consumer_id");
]],
teardown = function(connector)
return connector:query([[
DROP TABLE "my_plugin_data";
]])
end,
},
cassandra = {
up = [[
CREATE TABLE IF NOT EXISTS my_plugin_data (
id uuid PRIMARY KEY,
created_at timestamp,
consumer_id uuid,
key text,
value text
);
CREATE INDEX IF NOT EXISTS ON my_plugin_data (consumer_id);
]],
teardown = function(connector)
return connector:query("DROP TABLE my_plugin_data")
end,
},
}
4. 插件安装和管理
4.1 安装自定义插件
4.1.1 使用LuaRocks安装
# 从本地安装
luarocks make
# 从Git仓库安装
luarocks install my-plugin --from=https://github.com/username/my-plugin
# 从rockspec文件安装
luarocks install my-plugin-1.0.0-1.rockspec
4.1.2 手动安装
# 复制插件文件
sudo cp -r kong/plugins/my-plugin /usr/local/share/lua/5.1/kong/plugins/
# 更新Kong配置
echo "plugins = bundled,my-plugin" >> /etc/kong/kong.conf
# 重启Kong
kong restart
4.2 插件配置管理
4.2.1 全局插件
# 启用全局插件
curl -X POST http://localhost:8001/plugins \
--data "name=my-plugin" \
--data "config.message=Global Hello"
# 查看全局插件
curl http://localhost:8001/plugins
4.2.2 服务级插件
# 为特定服务启用插件
curl -X POST http://localhost:8001/services/{service}/plugins \
--data "name=my-plugin" \
--data "config.message=Service Hello"
4.2.3 路由级插件
# 为特定路由启用插件
curl -X POST http://localhost:8001/routes/{route}/plugins \
--data "name=my-plugin" \
--data "config.message=Route Hello"
4.2.4 消费者级插件
# 为特定消费者启用插件
curl -X POST http://localhost:8001/consumers/{consumer}/plugins \
--data "name=my-plugin" \
--data "config.message=Consumer Hello"
4.3 插件调试
4.3.1 日志调试
-- 在插件中添加日志
kong.log.debug("Debug message")
kong.log.info("Info message")
kong.log.warn("Warning message")
kong.log.err("Error message")
4.3.2 使用ngx.log
-- 直接使用nginx日志
ngx.log(ngx.ERR, "Error occurred: ", error_message)
ngx.log(ngx.INFO, "Request processed: ", request_id)
4.3.3 调试工具
# 检查插件配置
curl http://localhost:8001/plugins/{plugin_id}
# 查看Kong错误日志
tail -f /usr/local/kong/logs/error.log
# 启用调试模式
export KONG_LOG_LEVEL=debug
kong restart
5. 插件最佳实践
5.1 性能优化
5.1.1 缓存使用
-- 使用Kong缓存
local cache_key = "my_plugin:" .. consumer_id
local cached_data, err = kong.cache:get(cache_key, nil, expensive_function, arg1, arg2)
if err then
kong.log.err("Cache error: ", err)
end
5.1.2 避免阻塞操作
-- 使用非阻塞HTTP客户端
local http = require "resty.http"
local httpc = http.new()
httpc:set_timeout(1000) -- 设置超时
local res, err = httpc:request_uri("http://api.example.com/validate", {
method = "POST",
body = json.encode(data),
headers = {
["Content-Type"] = "application/json",
}
})
5.2 错误处理
5.2.1 优雅降级
function MyPluginHandler:access(conf)
local ok, err = pcall(function()
-- 主要逻辑
self:validate_request(conf)
end)
if not ok then
kong.log.err("Plugin error: ", err)
-- 降级处理,不阻断请求
if conf.fail_open then
return -- 继续处理请求
else
return kong.response.exit(500, { message = "Internal error" })
end
end
end
5.2.2 输入验证
function MyPluginHandler:validate_input(data)
if not data or type(data) ~= "table" then
error("Invalid input data")
end
if not data.api_key or #data.api_key < 10 then
error("Invalid API key")
end
return true
end
5.3 安全考虑
5.3.1 敏感数据处理
-- 不要在日志中记录敏感信息
local safe_config = {
timeout = conf.timeout,
enabled = conf.enabled,
-- 不包含api_key等敏感信息
}
kong.log.info("Plugin config: ", json.encode(safe_config))
5.3.2 输入清理
function MyPluginHandler:sanitize_input(input)
if type(input) ~= "string" then
return nil
end
-- 移除危险字符
input = input:gsub("[<>\"'&]", "")
-- 限制长度
if #input > 1000 then
input = input:sub(1, 1000)
end
return input
end
6. 插件测试
6.1 单元测试
6.1.1 测试框架
-- spec/my-plugin/01-unit_spec.lua
local PLUGIN_NAME = "my-plugin"
local schema = require("kong.plugins." .. PLUGIN_NAME .. ".schema")
describe(PLUGIN_NAME .. ": (schema)", function()
it("accepts valid configuration", function()
local ok, err = schema:validate({
message = "Hello World",
timeout = 5000,
enabled = true
})
assert.truthy(ok)
assert.is_nil(err)
end)
it("rejects invalid configuration", function()
local ok, err = schema:validate({
timeout = -1 -- 无效值
})
assert.falsy(ok)
assert.not_nil(err)
end)
end)
6.1.2 集成测试
-- spec/my-plugin/02-integration_spec.lua
local helpers = require "spec.helpers"
for _, strategy in helpers.each_strategy() do
describe(PLUGIN_NAME .. ": (access) [#" .. strategy .. "]", function()
local client
lazy_setup(function()
local bp = helpers.get_db_utils(strategy, nil, { PLUGIN_NAME })
local route1 = bp.routes:insert({
hosts = { "test1.com" },
})
bp.plugins:insert {
name = PLUGIN_NAME,
route = { id = route1.id },
config = {
message = "Test Message"
},
}
assert(helpers.start_kong({
database = strategy,
nginx_conf = "spec/fixtures/custom_nginx.template",
plugins = "bundled," .. PLUGIN_NAME,
}))
end)
lazy_teardown(function()
helpers.stop_kong(nil, true)
end)
before_each(function()
client = helpers.proxy_client()
end)
after_each(function()
if client then client:close() end
end)
describe("request", function()
it("adds custom header", function()
local r = client:get("/request", {
headers = {
host = "test1.com"
}
})
assert.response(r).has.status(200)
assert.response(r).has.header("X-My-Plugin")
end)
end)
end)
end
6.2 运行测试
# 安装测试依赖
luarocks install busted
luarocks install luacov
# 运行测试
busted spec/
# 运行特定测试
busted spec/my-plugin/
# 生成覆盖率报告
busted --coverage spec/
luacov
7. 总结
Kong的插件系统是其最强大的特性之一,通过插件可以轻松扩展Kong的功能。本章介绍了插件的基本概念、内置插件的使用、自定义插件的开发流程,以及插件的管理和测试方法。
在下一章节中,我们将详细介绍Kong的API网关配置和路由管理。