如何在APISIX serverless-pre-function插件中外置Lua代码?
背景
我正在使用APISIX的serverless-pre-function插件在请求生命周期中执行自定义Lua代码,目前Lua函数直接嵌入JSON配置中,希望将其外置到独立Lua文件以提升可维护性与可读性。
当前可正常运行的配置
{ "plugins": { "serverless-pre-function": { "phase": "access", "functions": [ "return function(conf, ctx) local jwt = require('resty.jwt'); local userinfo = ngx.req.get_headers()['x-userinfo']; if userinfo then local jwt_obj = jwt:verify(nil, userinfo); if jwt_obj.payload then local preferred_username = jwt_obj.payload.preferred_username; if preferred_username then ngx.req.set_header('X-User-Uid', preferred_username); else ngx.log(ngx.ERR, 'Claim preferred_username not found in JWT'); end; else ngx.log(ngx.ERR, 'Failed to decode JWT: ', jwt_obj.reason); end; else ngx.log(ngx.ERR, 'No x-userinfo header found'); end; end" ] } } }
目标
将Lua函数迁移至独立文件(如/usr/local/apisix/lua_scripts/extract_userinfo.lua),并在serverless-pre-function插件配置中引用该文件。
尝试的配置(报错)
{ "plugins": { "serverless-pre-function": { "phase": "access", "functions": [ { "file": "/usr/local/apisix/lua_scripts/extract_userinfo.lua" } ] } } }
错误信息
{"error_msg":"failed to check the configuration of plugin serverless-pre-function err: property \"functions\" validation failed: failed to validate item 1: wrong type: expected string, got table"}
我的APISIX Docker配置(config.yaml)
apisix: node_listen: 9080 enable_admin: true log_level: debug lua_module_cache: true lua_path: "/usr/local/apisix/lua_scripts/?.lua;;" extra_lua_path: /usr/local/apisix/lua_scripts/?.lua proxy: ssl: verify: true trusted_ca_certificates: /usr/local/apisix/keycloak-cert.pem plugins: # plugin list - authz-keycloak - basic-auth - clickhouse-logger - client-control - consumer-restriction - cors - csrf - datadog - echo - error-log-logger - ext-plugin-post-req - ext-plugin-post-resp - ext-plugin-pre-req - fault-injection - file-logger - forward-auth - google-cloud-logging - gzip - hmac-auth - http-logger - ip-restriction - jwt-auth - kafka-logger - kafka-proxy - key-auth - ldap-auth - limit-conn - limit-count - limit-req - loggly - mocking - opa - openid-connect - opentelemetry - openwhisk - prometheus - proxy-cache - proxy-control - proxy-mirror - proxy-rewrite - public-api - real-ip - redirect - referer-restriction - request-id - request-validation - response-rewrite - rocketmq-logger - server-info - serverless-post-function - serverless-pre-function - skywalking - skywalking-logger - sls-logger - splunk-hec-logging - syslog - tcp-logger - traffic-split - ua-restriction - udp-logger - uri-blocker - wolf-rbac - zipkin - elasticsearch-logger - cas-auth plugin_attr: log-rotate: enable_compression: false max_kept: 3 max_size: 10240 deployment: admin: admin_key: - name: "admin" key: "admin123" role: admin allow_admin: - 0.0.0.0/0 etcd: host: - "http://etcd:2379"
疑问
- 如何在serverless-pre-function插件中正确外置Lua代码?
- 在配置中引用外部Lua文件的正确方式是什么?
- Lua文件有哪些特定要求(如返回值、结构)?
- Docker-compose或APISIX Docker配置是否需额外设置?
补充:
- 运行在Docker容器中
- Lua文件已放置在
/usr/local/apisix/lua_scripts/目录 - Lua函数嵌入JSON配置时可正常运行
解答
1. 正确外置Lua代码的方法
你遇到的错误是因为当前使用的APISIX版本不支持functions字段中传入{file: ...}格式的对象。旧版本的serverless-pre-function仅支持字符串形式的Lua代码,因此需要通过require语句引入外部Lua文件,将代码逻辑以字符串形式返回。
如果你的APISIX版本是2.10及以上,则原生支持{file: ...}的对象格式,报错大概率是版本不兼容导致,建议检查APISIX版本。
2. 引用外部Lua文件的正确方式
针对旧版本APISIX(不支持对象格式)
修改插件配置为以下形式,通过require加载外部文件:
{ "plugins": { "serverless-pre-function": { "phase": "access", "functions": [ "return require('extract_userinfo')" ] } } }
由于你已经在config.yaml中配置了lua_path: "/usr/local/apisix/lua_scripts/?.lua;;",直接写require('extract_userinfo')即可自动匹配到/usr/local/apisix/lua_scripts/extract_userinfo.lua文件。
针对2.10及以上版本APISIX
你的尝试配置是可行的,确保配置格式正确即可:
{ "plugins": { "serverless-pre-function": { "phase": "access", "functions": [ { "file": "/usr/local/apisix/lua_scripts/extract_userinfo.lua" } ] } } }
3. Lua文件的特定要求
外部Lua文件必须返回一个接收conf(插件配置)和ctx(请求上下文)两个参数的函数,结构与嵌入时的函数完全一致。示例extract_userinfo.lua内容如下:
local jwt = require('resty.jwt') return function(conf, ctx) local userinfo = ngx.req.get_headers()['x-userinfo'] if userinfo then local jwt_obj = jwt:verify(nil, userinfo) if jwt_obj.payload then local preferred_username = jwt_obj.payload.preferred_username if preferred_username then ngx.req.set_header('X-User-Uid', preferred_username) else ngx.log(ngx.ERR, 'Claim preferred_username not found in JWT') end else ngx.log(ngx.ERR, 'Failed to decode JWT: ', jwt_obj.reason) end else ngx.log(ngx.ERR, 'No x-userinfo header found') end end
核心要求:
- 必须返回一个函数,参数固定为
conf和ctx - 依赖库可在文件顶部提前引入,无需在函数内部重复声明
- 代码逻辑与嵌入时保持一致即可
4. Docker相关配置
(1)目录挂载
确保本地的lua_scripts目录挂载到APISIX容器的/usr/local/apisix/lua_scripts/路径下,否则容器内无法访问外部Lua文件。在docker-compose.yml中添加挂载配置:
services: apisix: image: apache/apisix:latest volumes: - ./config.yaml:/usr/local/apisix/conf/config.yaml - ./lua_scripts:/usr/local/apisix/lua_scripts # 挂载本地lua_scripts目录 ports: - "9080:9080" - "9180:9180" depends_on: - etcd
(2)Lua路径配置
你当前的config.yaml中lua_path和extra_lua_path的配置是正确的,确保路径指向/usr/local/apisix/lua_scripts/?.lua即可。
(3)缓存问题
如果修改外部Lua文件后不生效,可临时关闭lua_module_cache(设置为false),或者直接重启APISIX容器,避免Lua模块被缓存。
内容的提问来源于stack exchange,提问作者user3090303

