使用 Lua 创建自定义插件
APISIX 的主要功能之一是其通过 插件 的可扩展性。除了各种现有插件外,APISIX 还允许你构建自定义插件以添加额外功能并使用自定义流 程管理 API 流量。通常,你使用 Lua 编程语言来实现新插件。APISIX 分 阶段 处理请求,相关的插件逻辑在请求路由期间的每个阶段执行。
本指南将引导你完成为 APISIX 开发一个新的自定义 Lua 插件的示例过程。
前置条件
开发文件代理插件
在本节中,你将使用 Lua 为 APISIX 创建一个名为 file-proxy 的新自定义插件。此插件将用于通过 API 公开静态文件(YAML、JSON、JavaScript、CSS 或图像文件)并从指定的 URL 获取文件。
例如,API 用户可以通过指定的 URL http://127.0.0.1:9080/openapi.yaml 访问 openapi.yaml 文件。
创建 Lua 文件
为插件源代码创建一个名为 file-proxy.lua 的新 Lua 文件。
导入模块
为 file-proxy 插件导入必要的模块:
local core = require("apisix.core")
local io = require("io")
local ngx = ngx
定义插件名称
为插件声明一个唯一的名称:
local plugin_name = "file-proxy"
定义插件架构
为插件参数创建一个架构。架构定义了可用参数及其数据类型、属性、默认值、有效值等。
file-proxy 插件需要有一个 文件路径 参数,以便 APISIX 知道在响应中返回其内容之前从哪里读取文件。
local plugin_schema = {
type = "object",
properties = {
# highlight-start
path = {
// Annotate 1
type = "string"
},
# highlight-end
},
# highlight-next-line
// Annotate 2
required = {"path"}
}
❶ 要服务的文件的路径。
❷ 路径设置为必需参数。
定义 Lua 模块表
为插件定义属性 version、priority、name 和 schema。name 和 schema 是之前定义的插件名称和架构。version 和 priority 由 APISIX 用于管理插件。
local _M = {
# highlight-start
// Annotate 1
version = 1.0,
// Annotate 2
priority = 1000,
// Annotate 3
name = plugin_name,
// Annotate 4
schema = plugin_schema
# highlight-end
}
❶ version:该字段通常是指当前正在使用的版本。如果你发布并更新你的插件逻辑,它将是 1.1(你可以设置你希望的任何版本)。
❷ priority:该字段用于在执行插件的每个阶段之前对插件进行排序。具有较高优先级的插件首先执行。确保自定义插件的优先级适合现有插件的优先级。现有插件的优先级记录在 config.yaml.example 文件中。
❸ name:插件的名称。
❹ schema:插件的架构。
定义架构检查函数
定义一个架构检查器以根据定义的架构验证用户输入的参数:
function _M.check_schema(conf)
local ok, err = core.schema.check(plugin_schema, conf)
if not ok then
return false, err
end
return true
end
定义自定义逻辑
APISIX 允许你在各种 阶段 中注入自定义逻辑。请参阅 Lua NGINX 模块指令 以了解有关不同阶段的更多信息。
在 access 函数中实现 file-proxy 插件的自定义逻辑,该函数将在 access 阶段执行。逻辑应该打开插件配置中指定的文件,读取其内容,并将内容作为响应返回。如果文件无法打开,它应该记录错误并返回 404 Not Found。
function _M.access(conf, ctx)
local fd = io.open(conf.path, "rb")
if fd then
local content = fd:read("*all")
fd:close()
ngx.header.content_length = #content
ngx.say(content)
ngx.exit(ngx.OK)
else
ngx.exit(ngx.HTTP_NOT_FOUND)
core.log.error("File is not found: ", conf.path, ", error info: ", err)
end
end
定义日志逻辑
log 函数在 log 阶段执行。日志记录是可选的,但有助于调试和检查插件是否正常工作。
实现日志逻辑以记录插件配置、对插件的请求和响应:
function _M.log(conf, ctx)
core.log.warn("conf: ", core.json.encode(conf))
core.log.warn("ctx: ", core.json.encode(ctx, true))
end
组合所有内容
当你组合上面的所有代码时,file-proxy.lua 应该如下所示:
-- 引入该插件所需的模块/库
local core = require("apisix.core")
local io = require("io")
local ngx = ngx
-- 声明插件名称
local plugin_name = "file-proxy"
-- 定义插件的 schema 格式
local plugin_schema = {
type = "object",
properties = {
path = {
type = "string" -- 要提供服务的文件路径
},
},
required = {"path"} -- path 为必填字段
}
-- 定义插件,包括版本号、优先级、名称和 schema
local _M = {
version = 1.0,
priority = 1000,
name = plugin_name,
schema = plugin_schema
}
-- 校验插件配置是否正确的函数
function _M.check_schema(conf)
-- 根据 schema 校验配置
local ok, err = core.schema.check(plugin_schema, conf)
-- 如果校验失败,返回 false 和错误信息
if not ok then
return false, err
end
-- 如果校验成功,返回 true
return true
end
-- 在 access 阶段调用的函数
function _M.access(conf, ctx)
-- 打开配置中指定的文件
local fd = io.open(conf.path, "rb")
-- 如果文件成功打开,读取内容并作为响应返回
if fd then
local content = fd:read("*all")
fd:close()
ngx.header.content_length = #content
ngx.say(content)
ngx.exit(ngx.OK)
else
-- 如果文件无法打开,记录错误并返回 404 Not Found 状态
ngx.exit(ngx.HTTP_NOT_FOUND)
core.log.error("File is not found: ", conf.path, ", error info: ", err)
end
end
-- 在 log 阶段调用的函数
function _M.log(conf, ctx)
-- 记录插件配置和请求上下文
core.log.warn("conf: ", core.json.encode(conf))
core.log.warn("ctx: ", core.json.encode(ctx, true))
end
-- 返回插件对象,使其可被 APISIX 使用
return _M
加载自定义插件
- Docker
- Kubernetes
有两种方法可以将自定义插件代码加载到 APISIX 中:
- 将插件源代码放在默认的 APISIX 插件目录
/apisix/plugins中,与其他 APISIX 插件放在一起。 - 将插件源代码放在单独的目录中,并在
config.yaml中的extra_lua_path中指定搜索路径。
以下部分提供了第二种方法的说明,因为建议在单独的目录中管理自定义代码。
创建自定义插件文件
APISIX 在你的自定义目录中的 /apisix/plugins 中查找 L7 插件,在 /apisix/stream/plugins 中查找 L4 插件。
对于自定义插件,创建一个具有自定义名称的目录,并在该目录中创建 /apisix/plugins:
docker exec apisix-quickstart /bin/sh -c "mkdir -p custom-plugin/apisix/plugins"
将插件文件保存到该目录:
docker exec apisix-quickstart /bin/sh -c "echo '
local core = require(\"apisix.core\")
local io = require(\"io\")
local ngx = ngx
local plugin_name = \"file-proxy\"
local plugin_schema = {
type = \"object\",
properties = {
path = {
type = \"string\"
},
},
required = {\"path\"}
}
local _M = {
version = 1.0,
priority = 1000,
name = plugin_name,
schema = plugin_schema
}
function _M.check_schema(conf)
local ok, err = core.schema.check(plugin_schema, conf)
if not ok then
return false, err
end
return true
end
function _M.access(conf, ctx)
local fd = io.open(conf.path,\"rb\")
if fd then
local content = fd:read(\"*all\")
fd:close()
ngx.header.content_length = #content
ngx.say(content)
ngx.exit(ngx.OK)
else
ngx.exit(ngx.HTTP_NOT_FOUND)
core.log.error(\"File is not found: \", conf.path, \", error info: \", err)
end
end
function _M.log(conf, ctx)
core.log.warn(\"conf: \", core.json.encode(conf))
core.log.warn(\"ctx: \", core.json.encode(ctx, true))
end
return _M
' > /usr/local/apisix/custom-plugin/apisix/plugins/file-proxy.lua"
更新 APISIX 配置文件
更新 APISIX 配置文件,在 extra_lua_path 中指定自定义插件的搜索路径,并将插件名称添加到插件列表中:
docker exec apisix-quickstart /bin/sh -c "echo '
apisix:
extra_lua_path: "/usr/local/apisix/custom-plugin/\?.lua"
enable_control: true
control:
ip: 0.0.0.0
port: 9092
deployment:
role: traditional
role_traditional:
config_provider: etcd
admin:
admin_key_required: false
allow_admin:
- 0.0.0.0/0
plugin_attr:
prometheus:
export_addr:
ip: 0.0.0.0
port: 9091
plugins:
- file-proxy
' > /usr/local/apisix/conf/config.yaml"
❶ extra_lua_path:自定义插件的搜索路径。
请注意,仅将 file-proxy 插件添加到插件列表将覆盖所有现有的默认插件。要使 file-proxy 插件成为现有插件的补充,你应该复制现有插件的名称并将它们添加到列表中。
重新加载 APISIX 以使配置更改生效:
docker exec apisix-quickstart apisix reload
从自定义插件文件生成 ConfigMap:
# 替换为你的文件路径
FILE_PATH="/path/to/file-proxy.lua"
kubectl create configmap custom-file-proxy --from-file="$FILE_PATH"
升级 APISIX 安装以挂载包含 file-proxy.lua 插件的自定义 ConfigMap,并将其与默认插件一起启用:
helm upgrade apisix apisix/apisix \
-n ingress-apisix \
--set apisix.deployment.role=traditional \
--set apisix.deployment.role_traditional.config_provider=yaml \
--set etcd.enabled=false \
--set ingress-controller.enabled=true \
--set ingress-controller.config.provider.type=apisix-standalone \
--set ingress-controller.apisix.adminService.namespace=ingress-apisix \
--set ingress-controller.gatewayProxy.createDefault=true \
--set "apisix.customPlugins.enabled=true" \
--set "apisix.customPlugins.plugins[0].name=file-proxy" \
--set "apisix.customPlugins.plugins[0].attrs={}" \
--set "apisix.customPlugins.plugins[0].configMap.name=custom-file-proxy" \
--set "apisix.customPlugins.plugins[0].configMap.mounts[0].key=file-proxy.lua" \
--set "apisix.customPlugins.plugins[0].configMap.mounts[0].path=/usr/local/apisix/apisix/plugins/file-proxy.lua" \
--set "apisix.plugins={file-proxy}"
apisix.plugins={file-proxy} 的警告此标志仅将 file-proxy 插件添加到插件列表,覆盖所有默认插件。要添加自定义插件而不删除现有插件,请将现有插件名称包含在 apisix.plugins 列表中。
或者,你也可以按如下方式更新 values 文件:
apisix:
...
customPlugins:
enabled: true
plugins:
- name: "file-proxy"
attrs: {}
configMap:
name: "file-proxy-config"
mounts:
- key: "file-proxy.lua"
path: "/usr/local/apisix/apisix/plugins/file-proxy.lua"
plugins:
...
- file-proxy
测试自定义插件
存储测试文件
在 APISIX 实例中存储一个静态 openapi.yaml 文件:
- Docker
- Kubernetes
docker exec apisix-quickstart /bin/sh -c "echo '
openapi: 3.0.1
info:
title: OpenAPI Spec
description: OpenAPI Spec file description.
' > /usr/local/apisix/openapi.yaml"
kubectl exec -it <your-apisix-pod-name> -- /bin/sh -c "echo '
openapi: 3.0.1
info:
title: OpenAPI Spec
description: OpenAPI Spec file description.
' > /usr/local/apisix/openapi.yaml"
创建路由
要使用 file-proxy 自定义插件,你需要在 APISIX 中创建一个使用该插件的路由:
- Admin API
- ADC
- Ingress Controller
curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT -d '
{
"id":"openapi-file-proxy",
"uri":"/openapi.yaml",
"plugins":{
"file-proxy":{
"path":"/usr/local/apisix/openapi.yaml"
}
}
}'
services:
- name: OpenAPI File Proxy Service
routes:
- uris:
- /openapi.yaml
name: openapi-file-proxy
plugins:
file-proxy:
path: /usr/local/apisix/openapi.yaml
将 配置同步到 APISIX:
adc sync -f adc.yaml
- Gateway API
- APISIX CRD
apiVersion: apisix.apache.org/v1alpha1
kind: PluginConfig
metadata:
name: file-proxy-plugin-config
spec:
plugins:
- name: file-proxy
config:
path: /usr/local/apisix/openapi.yaml
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: openapi-file-proxy
spec:
parentRefs:
- name: apisix
rules:
- matches:
- path:
type: Exact
value: /openapi.yaml
filters:
- type: ExtensionRef
extensionRef:
group: apisix.apache.org
kind: PluginConfig
name: file-proxy-plugin-config
apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
name: openapi-file-proxy
spec:
ingressClassName: apisix
http:
- name: openapi-file-proxy
match:
paths:
- /openapi.yaml
plugins:
- name: file-proxy
enable: true
config:
path: /usr/local/apisix/openapi.yaml
将配置应用到你的集群:
kubectl apply -f route-file-proxy.yaml
验证插件
向路由发送请求:
curl "http://127.0.0.1:9080/openapi.yaml"
响应应该是 openapi.yaml 文件的内容:
openapi: 3.0.1
info:
title: OpenAPI Spec
description: OpenAPI Spec file description.
下一步
使用 Lua 为 APISIX 开发自定义插件是扩展 API 网关功能的强大方法。在插件运行器的支持下,你还可以使用 Java、Go 和 Python 等其他编程语言开发插件(即将推出)。