1. APISIX自定义插件开发概述APISIX作为云原生API网关其插件机制是其核心能力之一。通过自定义插件开发我们可以灵活扩展网关功能满足特定业务场景需求。不同于传统Nginx模块开发需要重新编译的繁琐流程APISIX插件采用Lua语言编写支持热加载极大提升了开发效率。在实际业务中我们经常遇到需要定制化认证逻辑、特殊流量控制、请求/响应改写等场景。比如最近我们团队就遇到一个需求需要根据请求头中的特定字段进行动态路由这正是通过开发自定义插件完美解决的。下面我将结合这个实战案例详细介绍APISIX自定义插件的完整开发流程。2. 开发环境准备与插件结构2.1 基础环境配置在开始开发前需要确保APISIX运行环境已正确配置。关键配置项位于conf/config.yamlapisix: 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进程有读取权限建议设置为7552.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 _M3. 插件核心开发详解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) end3.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) end3.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) end4. 高级功能实现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公共API通过public-api插件暴露function _M.api() return { { methods {POST}, uri /my-plugin/operation, handler function(ctx) -- 处理逻辑 end } } end控制API仅限本地访问function _M.control_api() return { { methods {GET}, uris {/v1/plugin/my-plugin/status}, handler function(ctx) return 200, {status ok} end } } end5. 插件测试与部署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 常见问题排查插件未生效检查plugins列表是否包含插件名确认插件文件路径正确查看错误日志logs/error.log优先级冲突使用GET /v1/schema查看所有插件优先级确保自定义插件priority不与内置插件冲突内存泄漏使用ngx.shared.DICT管理共享数据确保外部连接如Redis正确释放6.2 性能优化建议缓存热点数据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减少阻塞操作使用cosocket进行非阻塞IO耗时操作放到定时器或单独阶段合理设置优先级认证类插件设置高优先级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... end7.2 跨插件通信通过ctx共享数据-- 插件A设置数据 ctx.shared_data {key value} -- 插件B读取数据 local data ctx.shared_data7.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级别日志生产环境再调整日志级别。