APISIX自定义插件开发实战指南
2026/7/18 6:56:55 网站建设 项目流程

1. APISIX自定义插件开发概述

APISIX作为云原生API网关,其插件机制是其核心能力之一。通过自定义插件开发,我们可以灵活扩展网关功能,满足特定业务场景需求。不同于传统Nginx模块开发需要重新编译的繁琐流程,APISIX插件采用Lua语言编写,支持热加载,极大提升了开发效率。

在实际业务中,我们经常遇到需要定制化认证逻辑、特殊流量控制、请求/响应改写等场景。比如最近我们团队就遇到一个需求:需要根据请求头中的特定字段进行动态路由,这正是通过开发自定义插件完美解决的。下面我将结合这个实战案例,详细介绍APISIX自定义插件的完整开发流程。

2. 开发环境准备与插件结构

2.1 基础环境配置

在开始开发前,需要确保APISIX运行环境已正确配置。关键配置项位于conf/config.yaml

apisix: extra_lua_path: "/usr/local/apisix/plugins/?.lua" # 自定义插件路径 lua_module_hook: "custom_hook" # 可选:全局方法钩子

建议的插件目录结构如下:

/usr/local/apisix/ ├── plugins/ │ ├── custom_hook.lua # 全局钩子文件(可选) │ └── apisix/ │ └── plugins/ │ ├── my-plugin.lua # 自定义插件主文件 │ └── ... # 其他依赖文件

注意:目录权限需要确保APISIX进程有读取权限,建议设置为755

2.2 插件基础模板

每个插件都需要遵循基本结构模板:

local core = require("apisix.core") local plugin_name = "my-plugin" local schema = { type = "object", properties = { -- 插件配置参数定义 key = {type = "string", default = "value"} }, required = {"key"} -- 必填参数 } local _M = { version = 1.0, priority = 500, -- 执行优先级(避免与内置插件冲突) name = plugin_name, schema = schema, } function _M.check_schema(conf) return core.schema.check(schema, conf) end function _M.access(conf, ctx) -- 主要业务逻辑 core.log.info("Hit my-plugin with conf: ", core.json.encode(conf)) return 200, "Hello from my-plugin" end return _M

3. 插件核心开发详解

3.1 插件生命周期与执行阶段

APISIX插件支持OpenResty的所有阶段,最常用的包括:

阶段描述典型用途
rewrite请求重写阶段认证、请求改写
access访问控制阶段限流、权限检查
header_filter响应头处理添加/修改响应头
body_filter响应体处理响应内容修改
log日志记录阶段请求日志记录

以认证插件为例,典型阶段实现如下:

function _M.rewrite(conf, ctx) -- 1. 获取认证信息(如JWT Token) local token = core.request.header(ctx, "Authorization") -- 2. 验证逻辑 if not token or not validate_token(token) then return 401, {message = "Unauthorized"} end -- 3. 设置上下文信息 ctx.auth_info = decode_token(token) end

3.2 配置管理与Schema验证

完善的Schema定义能确保配置的正确性:

local schema = { type = "object", properties = { redis_host = {type = "string", default = "127.0.0.1"}, redis_port = {type = "integer", minimum = 1, maximum = 65535}, timeout = {type = "number", minimum = 0, default = 1000}, whitelist = { type = "array", items = {type = "string"}, minItems = 1 } }, required = {"redis_host", "redis_port"}, encrypt_fields = {"redis_password"} -- 加密字段 }

验证函数示例:

function _M.check_schema(conf, schema_type) -- 支持多种schema类型校验 if schema_type == core.schema.TYPE_METADATA then return core.schema.check(metadata_schema, conf) end return core.schema.check(schema, conf) end

3.3 上下文对象关键属性

ctx对象包含丰富的请求上下文信息:

function _M.access(conf, ctx) -- 请求信息 local method = ctx.var.request_method local uri = ctx.var.uri -- 路由匹配信息 local route_id = ctx.matched_route and ctx.matched_route.value.id -- 上游信息 local upstream = ctx.matched_upstream -- 自定义变量 core.ctx.register_var("my_var", function(ctx) return ctx.var.arg_foo or "default" end) end

4. 高级功能实现

4.1 外部依赖集成

当插件需要连接Redis/MySQL等外部服务时:

local redis = require("resty.redis") function _M.init() -- 初始化共享字典 local ok, err = ngx.shared.my_dict:set("init", true) if not ok then core.log.error("failed to init shared dict: ", err) end end function _M.access(conf, ctx) local red = redis:new() red:set_timeout(conf.redis_timeout) local ok, err = red:connect(conf.redis_host, conf.redis_port) if not ok then core.log.error("failed to connect redis: ", err) return 500, {message = "Internal Error"} end -- 业务逻辑... end

需要在Nginx配置中添加共享内存:

nginx_config: http_configuration_snippet: | lua_shared_dict my_dict 10m;

4.2 自定义API暴露

插件可以注册两种类型的API:

  1. 公共API(通过public-api插件暴露):
function _M.api() return { { methods = {"POST"}, uri = "/my-plugin/operation", handler = function(ctx) -- 处理逻辑 end } } end
  1. 控制API(仅限本地访问):
function _M.control_api() return { { methods = {"GET"}, uris = {"/v1/plugin/my-plugin/status"}, handler = function(ctx) return 200, {status = "ok"} end } } end

5. 插件测试与部署

5.1 单元测试示例

测试用例通常放在t/plugin目录下:

=== TEST 1: basic schema validation --- config location /t { content_by_lua_block { local plugin = require("apisix.plugins.my-plugin") local ok, err = plugin.check_schema({ redis_host = "127.0.0.1", redis_port = 6379 }) if not ok then ngx.say(err) end ngx.say("passed") } } --- request GET /t --- response_body passed --- no_error_log [error]

5.2 插件启用配置

conf/config.yaml中启用插件:

plugins: - my-plugin # 自定义插件 - key-auth # 内置插件 - limit-req # 内置插件

路由配置示例:

{ "uri": "/api/*", "plugins": { "my-plugin": { "redis_host": "10.0.0.1", "redis_port": 6379 } }, "upstream": { "type": "roundrobin", "nodes": { "backend:8080": 1 } } }

6. 实战经验与排错指南

6.1 常见问题排查

  1. 插件未生效

    • 检查plugins列表是否包含插件名
    • 确认插件文件路径正确
    • 查看错误日志logs/error.log
  2. 优先级冲突

    • 使用GET /v1/schema查看所有插件优先级
    • 确保自定义插件priority不与内置插件冲突
  3. 内存泄漏

    • 使用ngx.shared.DICT管理共享数据
    • 确保外部连接(如Redis)正确释放

6.2 性能优化建议

  1. 缓存热点数据
local cached_data = ngx.shared.my_cache:get("key") if not cached_data then cached_data = fetch_data() ngx.shared.my_cache:set("key", cached_data, 60) -- 缓存60秒 end
  1. 减少阻塞操作

    • 使用cosocket进行非阻塞IO
    • 耗时操作放到定时器或单独阶段
  2. 合理设置优先级

    • 认证类插件设置高优先级(1000+)
    • 日志类插件设置低优先级(100-)

7. 插件开发进阶技巧

7.1 动态配置加载

实现配置热更新:

local last_update = 0 local cached_config function _M.access(conf, ctx) if ngx.now() - last_update > 60 then -- 60秒更新一次 cached_config = fetch_latest_config() last_update = ngx.now() end -- 使用cached_config... end

7.2 跨插件通信

通过ctx共享数据:

-- 插件A设置数据 ctx.shared_data = {key = "value"} -- 插件B读取数据 local data = ctx.shared_data

7.3 自定义日志格式

在插件中记录结构化日志:

function _M.log(conf, ctx) core.log.warn(core.json.encode({ time = ngx.localtime(), client_ip = ctx.var.remote_addr, request_id = ctx.var.request_id, custom_field = ctx.custom_value })) end

我在实际开发中发现,良好的日志设计能极大提升问题排查效率。建议为每个重要操作添加DEBUG级别日志,生产环境再调整日志级别。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询