APISIX 简单的自定义插件开发步骤

小得盈满

2023/10/31

本文基于 APISIX 3.2 版本进行插件开发并运行通过。

APISIX 目前开发插件比较简单,只需要编写 Lua 源代码并放到默认的插件目录下,然后通过配置文件开启插件即可,我们如果使用 Docker 运行 APISIX 那么默认的安装目录是:/usr/local/apisix,插件目录是:/usr/local/apisix/apisix/plugins ,我们只需要修改这个目录已有的插件源码或者自己编写新的插件放到这个目录,编写完成并启用后,重启 APISIX 将会自动加载。因为使用 Docker 启动,我们可以直接在外部编写插件并且映射到容器内部指定的位置即可。

如果是在第三方的目录编写插件,那么 APISIX 要求插件父目录必须包含:apisix/plugins 子目录,也就是和默认的层级结构一致,比如插件的目录为:/opt/apisix-plugins ,那么实际编写插件位置应该是在:/opt/apisix-plugins/apisix/plugins 。同样是对于容器来说外面并不需要建这么多的层级,映射进去的时候指定就可以,然后需要修改配置文件,添加外部路径:

apisix:
    # ...
    extra_lua_path: "/opt/apisix-plugins/?.lua"

外部路径中相同名称的插件会覆盖现有的插件。

如果选择在默认目录下开发,那么不需要进行上面的配置,开发可以参考内部的 example-plugin.lua 这个插件,这是一个示例代码,默认也启用了,直接复制这个改即可,比如我们创建一个 example-plugin2.lua,代码如下:

--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements.  See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License.  You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local ngx = ngx
local core = require("apisix.core")
local plugin = require("apisix.plugin")

local schema = {
    type = "object",
    properties = {
        name = {type = "string"},
        version = {type = "integer"},
    },
    required = {"name"},
}

local plugin_name = "example-plugin2"

local _M = {
    version = 0.1,
    priority = 99,
    name = plugin_name,
    schema = schema,
}

function get_username()
    local req_headers = ngx.req.get_headers()
    local username = req_headers.user
    if username ~= "" then
        return username
    end
    return nil
end

function _M.check_schema(conf, schema_type)
    if schema_type == core.schema.TYPE_METADATA then
        return core.schema.check(metadata_schema, conf)
    end
    return core.schema.check(schema, conf)
end


function _M.init()
    -- call this function when plugin is loaded
    local attr = plugin.plugin_attr(plugin_name)
    if attr then
        core.log.info(plugin_name, " get plugin attr val: ", attr.val)
    end
end


function _M.destroy()
    -- call this function when plugin is unloaded
end

function _M.rewrite(conf, ctx)
    core.log.warn("plugin rewrite phase, conf: ", core.json.encode(conf))
    core.log.warn("plugin rewrite phase, ctx: ", core.json.encode(ctx, true))
    core.log.warn("plugin rewrite phase, username: ", get_username())
end


function _M.access(conf, ctx)
    core.log.warn("plugin access phase, conf: ", core.json.encode(conf))
    core.log.warn("plugin access phase, ctx: ", core.json.encode(ctx, true))
    core.log.warn("plugin access phase, ngx headers: ", core.json.encode(ngx.req.get_headers()))
end

local function hello()
    local args = ngx.req.get_uri_args()
    if args["json"] then
        return 200, {msg = "world"}
    else
        return 200, "world\n"
    end
end


function _M.control_api()
    return {
        {
            methods = {"GET"},
            uris = {"/v1/plugin/example-plugin2/hello"},
            handler = hello,
        }
    }
end

return _M

这就是一个比较简单的插件,其中没设置元数据,其余的部分简单说下:

  1. schema 表示插件的配置需要在启用时指定
  2. _M 表示一个插件实例,可以指定名称、版本、优先级、schema 等,优先级建议设置 0~ 100 之间的值。
  3. 然后我们就可以给 _M 绑定不同的方法,按照请求阶段分别为:init、check_schema、rewrite、access、before_proxy、header_filter、body_filter、log 等阶段,我们需要选择适合的阶段进行处理。另外还有 control_api 方法,由插件提供并定义响应结果,可以监控插件的一些信息等。

我们上面这个插件就是简单打印了一些信息,编写了一个函数获取 Header 里面的用户,然后我们需要复制 config-default.yaml 配置文件中的 plugins 配置项并填写到 config.yaml 中,因为之前的插件都要保留着不要给覆盖掉了,具体 config.yaml 中的内容如下:

plugins:                           # plugin list (sorted by priority)
  - real-ip                        # priority: 23000
  - ai                             # priority: 22900
  - client-control                 # priority: 22000
  - proxy-control                  # priority: 21990
  - request-id                     # priority: 12015
  - zipkin                         # priority: 12011
  #- skywalking                    # priority: 12010
  #- opentelemetry                 # priority: 12009
  - ext-plugin-pre-req             # priority: 12000
  - fault-injection                # priority: 11000
  - mocking                        # priority: 10900
  - serverless-pre-function        # priority: 10000
  #- batch-requests                # priority: 4010
  - cors                           # priority: 4000
  - ip-restriction                 # priority: 3000
  - ua-restriction                 # priority: 2999
  - referer-restriction            # priority: 2990
  - csrf                           # priority: 2980
  - uri-blocker                    # priority: 2900
  - request-validation             # priority: 2800
  - chaitin-waf                    # priority: 2700
  - openid-connect                 # priority: 2599
  - cas-auth                       # priority: 2597
  - authz-casbin                   # priority: 2560
  - authz-casdoor                  # priority: 2559
  - wolf-rbac                      # priority: 2555
  - ldap-auth                      # priority: 2540
  - hmac-auth                      # priority: 2530
  - basic-auth                     # priority: 2520
  - jwt-auth                       # priority: 2510
  - key-auth                       # priority: 2500
  - consumer-restriction           # priority: 2400
  - forward-auth                   # priority: 2002
  - opa                            # priority: 2001
  - authz-keycloak                 # priority: 2000
  #- error-log-logger              # priority: 1091
  - proxy-cache                    # priority: 1085
  - body-transformer               # priority: 1080
  - proxy-mirror                   # priority: 1010
  - proxy-rewrite                  # priority: 1008
  - workflow                       # priority: 1006
  - api-breaker                    # priority: 1005
  - limit-conn                     # priority: 1003
  - limit-count                    # priority: 1002
  - limit-req                      # priority: 1001
  #- node-status                   # priority: 1000
  - gzip                           # priority: 995
  - server-info                    # priority: 990
  - traffic-split                  # priority: 966
  - redirect                       # priority: 900
  - response-rewrite               # priority: 899
  - degraphql                      # priority: 509
  - kafka-proxy                    # priority: 508
  #- dubbo-proxy                   # priority: 507
  - grpc-transcode                 # priority: 506
  - grpc-web                       # priority: 505
  - public-api                     # priority: 501
  - prometheus                     # priority: 500
  - datadog                        # priority: 495
  - loki-logger                    # priority: 414
  - elasticsearch-logger           # priority: 413
  - echo                           # priority: 412
  - loggly                         # priority: 411
  - http-logger                    # priority: 410
  - splunk-hec-logging             # priority: 409
  - skywalking-logger              # priority: 408
  - google-cloud-logging           # priority: 407
  - sls-logger                     # priority: 406
  - tcp-logger                     # priority: 405
  - kafka-logger                   # priority: 403
  - rocketmq-logger                # priority: 402
  - syslog                         # priority: 401
  - udp-logger                     # priority: 400
  - file-logger                    # priority: 399
  - clickhouse-logger              # priority: 398
  - tencent-cloud-cls              # priority: 397
  - inspect                        # priority: 200
  #- log-rotate                    # priority: 100
  # <- recommend to use priority (0, 100) for your custom plugins
  - example-plugin                 # priority: 0
  #- gm                            # priority: -43
  - aws-lambda                     # priority: -1899
  - azure-functions                # priority: -1900
  - openwhisk                      # priority: -1901
  - openfunction                   # priority: -1902
  - serverless-post-function       # priority: -2000
  - ext-plugin-post-req            # priority: -3000
  - ext-plugin-post-resp           # priority: -4000
  - example-plugin2                # priority: 99

上面最后一项就是我们自定义的插件,然后如果是使用 Docker Compose 启动,插件可以映射如下:

version: "3"

services:
  apisix:
    # ...
    volumes:
      - ./conf/config.yaml:/usr/local/apisix/conf/config.yaml:ro
      # 自定义插件映射
      - ./plugins/example-plugin2.lua:/usr/local/apisix/apisix/plugins/example-plugin2.lua

然后我们重启启动 APISIX,启动没有报错可以尝试添加路由:

curl -XPUT 127.0.0.1:9180/apisix/admin/routes/example2 -H 'X-API-KEY: <admin-key>' -d '
{
	"plugins": {
	    "example-plugin2": {
	        "name": "example2"
	    }
	},
	"upstream": {
	    "type": "roundrobin",
	    "nodes": {
            "127.0.0.1:1980": 1
        }
	},
	"uri": "/hello"
}'

然后我们访问数据平面服务即可:

curl -XGET 127.0.0.1:9080/hello

这时候我们看 APISIX 的日志就可以看到我们插件打印出的详细信息了,基于上面的插件开发运行的原理可以根据需要编写更多的代码来扩展功能。

Reference:

  1. https://apisix.apache.org/docs/apisix/3.2/plugin-develop/
  2. https://www.cnblogs.com/loseself/p/16151876.html