apisix~自定义插件的部署

打印 上一主题 下一主题

主题 885|帖子 885|积分 2655

参考

插件放置路径#

路径的相对路径是固定的,必须是apisix/plugins,例如extra_lua_path是/path/to/example,那你真实的lua文件应该放到/path/to/example/apisix/plugins/下面
Apache APISIX 提供了两种方式来添加新的功能。

  • 修改 Apache APISIX 的源代码并重新发布 (不推荐)。
  • 配置 extra_lua_path 和 extra_lua_cpath 在 conf/config.yaml 以加载你本身的代码文件。你应该给本身的代码文件起一个不包罗在原来库中的名字,而不是使用相同名称的代码文件,但是假如有需要,你可以使用这种方式覆盖内置的代码文件。
比如,你可以创建一个目录目录结构,像下面如许:
├── example
│   └── apisix
│       ├── plugins
│       │   └── 3rd-party.lua
│       └── stream
│           └── plugins
│               └── 3rd-party.lua
假如你需要自定义插件的目录,请在该目录下创建 /apisix/plugins 的子目录。

接着,在 conf/config.yaml 文件中添加如下的配置:
  1. apisix:
  2.     ...
  3.     extra_lua_path: "/path/to/example/?.lua"
  4.    
  5.     plugins:
  6.      - 3rd-party
复制代码

  • 覆盖原有插件的题目

  • 解决这个题目,可以在values.yaml的plugins节点,添加现有的默认插件
  • 默认插件列表获取方式:/apisix/admin/plugins/list
插件定名,优先级和其他#

给插件取一个很棒的名字,确定插件的加载优先级,然后在 conf/config.yaml 文件中添加上你的插件名。例如 example-plugin 这个插件, 需要在代码里指定插件名称(名称是插件的唯一标识,不可重名),在 apisix/plugins/example-plugin.lua 文件中可以看到:
  1. local plugin_name = "example-plugin"
  2. local _M = {
  3.     version = 0.1,
  4.     priority = 0,
  5.     name = plugin_name,
  6.     schema = schema,
  7.     metadata_schema = metadata_schema,
  8. }
复制代码
注:新插件的优先级(priority 属性)不能与现有插件的优先级相同,您可以使用 control API 的 /v1/schema 方法查看全部插件的优先级。别的,同一个阶段内里,优先级 ( priority ) 值大的插件,会优先实行,比如 example-plugin 的优先级是 0,ip-restriction 的优先级是 3000,所以在每个阶段,会先实行 ip-restriction 插件,再去实行 example-plugin 插件。这里的“阶段”的定义,拜见后续的 确定实行阶段 这一节。对于你的插件,发起采用 1 到 99 之间的优先级。
在 conf/config-default.yaml 配置文件中,列出了启用的插件(都是以插件名指定的):
  1. plugins:                          # plugin list
  2.   - limit-req
  3.   - limit-count
  4.   - limit-conn
  5.   - key-auth
  6.   - prometheus
  7.   - node-status
  8.   - jwt-auth
  9.   - zipkin
  10.   - ip-restriction
  11.   - grpc-transcode
  12.   - serverless-pre-function
  13.   - serverless-post-function
  14.   - openid-connect
  15.   - proxy-rewrite
  16.   - redirect
  17.   ...
复制代码
注:先后序次与实行序次无关。
特别需要注意的是,假如你的插件有新建本身的代码目录,那么就需要修改 Makefile 文件,新增创建文件夹的操作,比如:
  1. $(INSTALL) -d $(INST_LUADIR)/apisix/plugins/skywalking
  2. $(INSTALL) apisix/plugins/skywalking/*.lua $(INST_LUADIR)/apisix/plugins/skywalking/
复制代码
_M 中另有其他字段会影响到插件的行为。
  1. local _M = {
  2.     ...
  3.     type = 'auth',
  4.     run_policy = 'prefer_route',
  5. }
复制代码
run_policy 字段可以用来控制插件实行。当这个字段设置成 prefer_route 时,且该插件同时配置在全局和路由级别,那么只有路由级别的配置见效。
假如你的插件需要跟 consumer 一起使用,需要把 type 设置成 auth。详情见下文。
配置描述与校验#

定义插件的配置项,以及对应的 JSON Schema 描述,并完成对 JSON 的校验,如许方便对配置的数据规格进行验证,以确保数据的完整性以及程序的坚固性。同样,我们以 example-plugin 插件为例,看看他的配置数据:
  1. {
  2.   "example-plugin": {
  3.     "i": 1,
  4.     "s": "s",
  5.     "t": [1]
  6.   }
  7. }
复制代码
我们看下他的 Schema 描述:
  1. local schema = {
  2.     type = "object",
  3.     properties = {
  4.         i = {type = "number", minimum = 0},
  5.         s = {type = "string"},
  6.         t = {type = "array", minItems = 1},
  7.         ip = {type = "string"},
  8.         port = {type = "integer"},
  9.     },
  10.     required = {"i"},
  11. }
复制代码
这个 schema 定义了一个非负数 i,字符串 s,非空数组 t,和 ip 跟 port。只有 i 是必须的。
同时,需要实现 check_schema(conf) 方法,完成配置参数的正当性校验。
  1. function _M.check_schema(conf)
  2.     return core.schema.check(schema, conf)
  3. end
复制代码
注:项目已经提供了 core.schema.check 公共方法,直接使用即可完成配置参数校验。
别的,假如插件需要使用一些元数据,可以定义插件的 metadata_schema ,然后就可以通过 Admin API 动态的管理这些元数据了。如:
  1. local metadata_schema = {
  2.     type = "object",
  3.     properties = {
  4.         ikey = {type = "number", minimum = 0},
  5.         skey = {type = "string"},
  6.     },
  7.     required = {"ikey", "skey"},
  8. }
  9. local plugin_name = "example-plugin"
  10. local _M = {
  11.     version = 0.1,
  12.     priority = 0,        -- TODO: add a type field, may be a good idea
  13.     name = plugin_name,
  14.     schema = schema,
  15.     metadata_schema = metadata_schema,
  16. }
复制代码
你可能之前见过 key-auth 这个插件在它的模块定义时设置了 type = 'auth'。 当一个插件设置 type = 'auth',说明它是个认证插件。
认证插件需要在实行后选择对应的 consumer。举个例子,在 key-auth 插件中,它通过 apikey 请求头获取对应的 consumer,然后通过 consumer.attach_consumer 设置它。
为了跟 consumer 资源一起使用,认证插件需要提供一个 consumer_schema 来检验 consumer 资源的 plugins 属性内里的配置。
下面是 key-auth 插件的 consumer 配置:
  1. {
  2.   "username": "Joe",
  3.   "plugins": {
  4.     "key-auth": {
  5.       "key": "Joe's key"
  6.     }
  7.   }
  8. }
复制代码
你在创建 Consumer 时会用到它。
为了检验这个配置,这个插件使用了如下的 schema:
  1. local consumer_schema = {
  2.     type = "object",
  3.     properties = {
  4.         key = {type = "string"},
  5.     },
  6.     required = {"key"},
  7. }
复制代码
注意 key-auth 的 check_schema(conf) 方法和 example-plugin 的同名方法的区别:
  1. -- key-auth
  2. function _M.check_schema(conf, schema_type)
  3.     if schema_type == core.schema.TYPE_CONSUMER then
  4.         return core.schema.check(consumer_schema, conf)
  5.     else
  6.         return core.schema.check(schema, conf)
  7.     end
  8. end
  9. -- example-plugin
  10. function _M.check_schema(conf, schema_type)
  11.     return core.schema.check(schema, conf)
  12. end
复制代码
加密存储字段#

指定参数需要被加密存储(需要 APISIX 版本不小于 3.1)
有些插件需要将参数加密存储,比如 basic-auth 插件的 password 参数。这个插件需要在 schema 中指定哪些参数需要被加密存储。
  1. encrypt_fields = {"password"}
复制代码
假如是嵌套的参数,比如 error-log-logger 插件的 clickhouse.password 参数,需要用 . 来分隔:
  1. encrypt_fields = {"clickhouse.password"}
复制代码
目前还不支持:

  • 两层以上的嵌套
  • 数组中的字段
通过在 schema 中指定 encrypt_fields = {"password"},可以将参数加密存储。APISIX 将提供以下功能:

  • 通过 Admin API 来新增和更新资源时,对于 encrypt_fields 中声明的参数,APISIX 会自动加密存储在 etcd 中
  • 通过 Admin API 来获取资源时,以及在运行插件时,对于 encrypt_fields 中声明的参数,APISIX 会自动解密
如何开启该功能?
在 config.yaml 中开启 data_encryption:
  1. apisix:
  2.     data_encryption:
  3.     enable: true
  4.     keyring:
  5.         - edd1c9f0985e76a2
  6.         - qeddd145sfvddff4
复制代码
keyring 是一个数组,可以指定多个 key,APISIX 会按照 keyring 中 key 的序次,依次尝试用 key 来解密数据(只对在 encrypt_fields 声明的参数)。假如解密失败,会尝试下一个 key,直到解密乐成。
假如 keyring 中的 key 都无法解密数据,则使用原始数据。
确定实行阶段#

根据业务功能,确定你的插件需要在哪个阶段实行。key-auth 是一个认证插件,所以需要在 rewrite 阶段实行。在 APISIX,只有认证逻辑可以在 rewrite 阶段内里完成,其他需要在代理到上游之前实行的逻辑都是在 access 阶段完成的。
注意:我们不能在 rewrite 和 access 阶段调用 ngx.exit、ngx.redirect 或者 core.respond.exit。假如确实需要退出,只需要 return 状态码和正文,插件引擎将使用返回的状态码和正文进行退出。例子
APISIX 的自定义阶段#
除了 OpenResty 的阶段,我们还提供额外的阶段来满足特定的目的:

  • delayed_body_filter
  1. function _M.delayed_body_filter(conf, ctx)
  2.     -- delayed_body_filter 在 body_filter 之后被调用。
  3.     -- 它被 tracing 类型插件用来在 body_filter 之后立即结束 span。
  4. end
复制代码
编写实行逻辑#

在对应的阶段方法里编写功能的逻辑代码,在阶段方法中具有 conf 和 ctx 两个参数,以 limit-conn 插件配置为例。
  1. curl http://127.0.0.1:9180/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -d '
  2. {
  3.     "methods": ["GET"],
  4.     "uri": "/index.html",
  5.     "id": 1,
  6.     "plugins": {
  7.         "limit-conn": {
  8.             "conn": 1,
  9.             "burst": 0,
  10.             "default_conn_delay": 0.1,
  11.             "rejected_code": 503,
  12.             "key": "remote_addr"
  13.         }
  14.     },
  15.     "upstream": {
  16.         "type": "roundrobin",
  17.         "nodes": {
  18.             "127.0.0.1:1980": 1
  19.         }
  20.     }
  21. }'
复制代码
conf 参数#

conf 参数是插件的相关配置信息,您可以通过 core.log.warn(core.json.encode(conf)) 将其输出到 error.log 中进行查看,如下所示:
  1. function _M.access(conf, ctx)
  2.     core.log.warn(core.json.encode(conf))
  3.     ......
  4. end
复制代码
conf:
  1. {
  2.   "rejected_code": 503,
  3.   "burst": 0,
  4.   "default_conn_delay": 0.1,
  5.   "conn": 1,
  6.   "key": "remote_addr"
  7. }
复制代码
ctx 参数#

ctx 参数缓存了请求相关的数据信息,您可以通过 core.log.warn(core.json.encode(ctx, true)) 将其输出到 error.log 中进行查看,如下所示:
  1. function _M.access(conf, ctx)
  2.     core.log.warn(core.json.encode(ctx, true))
  3.     ......
  4. end
复制代码
注册公共接口#

插件可以注册暴露给公网的接口。以 jwt-auth 插件为例,这个插件为了让客户端能够署名,注册了 GET /apisix/plugin/jwt/sign 这个接口:
  1. local function gen_token()
  2.     -- ...
  3. end
  4. function _M.api()
  5.     return {
  6.         {
  7.             methods = {"GET"},
  8.             uri = "/apisix/plugin/jwt/sign",
  9.             handler = gen_token,
  10.         }
  11.     }
  12. end
复制代码
注意,注册的接口将不会默认暴露,需要使用public-api 插件来暴露它。
注册控制接口#

假如你只想暴露 API 到 localhost 或内网,你可以通过 Control API 来暴露它。
Take a look at example-plugin plugin:
  1. local function hello()
  2.     local args = ngx.req.get_uri_args()
  3.     if args["json"] then
  4.         return 200, {msg = "world"}
  5.     else
  6.         return 200, "world\n"
  7.     end
  8. end
  9. function _M.control_api()
  10.     return {
  11.         {
  12.             methods = {"GET"},
  13.             uris = {"/v1/plugin/example-plugin/hello"},
  14.             handler = hello,
  15.         }
  16.     }
  17. end
复制代码
假如你没有改过默认的 control API 配置,这个插件暴露的 GET /v1/plugin/example-plugin/hello API 只有通过 127.0.0.1 才能访问它。通过以下下令进行测试:
  1. curl -i -X GET "http://127.0.0.1:9090/v1/plugin/example-plugin/hello"
复制代码
查看更多有关 control API 介绍
注册自定义变量#

我们可以在 APISIX 的很多地方使用变量。例如,在 http-logger 中自定义日志格式,用它作为 limit-* 插件的键。在某些情况下,内置的变量是不够的。因此,APISIX 答应开辟者在全局范围内注册他们的变量,并将它们作为平凡的内置变量使用。
例如,让我们注册一个叫做 a6_labels_zone 的变量来获取路由中 zone 标签的值。
  1. local core = require "apisix.core"
  2. core.ctx.register_var("a6_labels_zone", function(ctx)
  3.     local route = ctx.matched_route and ctx.matched_route.value
  4.     if route and route.labels then
  5.         return route.labels.zone
  6.     end
  7.     return nil
  8. end)
复制代码
此后,任何对 $a6_labels_zone 的获取操作都会调用注册的获取器来获取数值。
注意,自定义变量不能用于依靠 Nginx 指令的功能,如 access_log_format。
编写测试用例#

针对功能,美满各种维度的测试用例,对插件做个全方位的测试吧!插件的测试用例,都在 t/plugin 目录下,可从前往了解。 项目测试框架采用的 test-nginx 。 一个测试用例 .t 文件,通常用 DATA 分割成 序言部门 和 数据部门。这里我们简单介绍下数据部门, 也就是真正测试用例的部门,仍然以 key-auth 插件为例:
  1. === TEST 1: sanity
  2. --- config
  3.     location /t {
  4.         content_by_lua_block {
  5.             local plugin = require("apisix.plugins.key-auth")
  6.             local ok, err = plugin.check_schema({key = 'test-key'}, core.schema.TYPE_CONSUMER)
  7.             if not ok then
  8.                 ngx.say(err)
  9.             end
  10.             ngx.say("done")
  11.         }
  12.     }
  13. --- request
  14. GET /t
  15. --- response_body
  16. done
  17. --- no_error_log
  18. [error]
复制代码
一个测试用例主要有三部门内容:

  • 程序代码:Nginx location 的配置内容
  • 输入:http 的 request 信息
  • 输出检查:status,header,body,error_log 检查
这里请求 /t,经过配置文件 location,调用 content_by_lua_block 指令完成 lua 的脚本,终极返回。 用例的断言是 response_body 返回 "done",no_error_log 表示会对 Nginx 的 error.log 检查, 必须没有 ERROR 级别的记录。
附上 test-nginx 实行流程#

根据我们在 Makefile 里配置的 PATH,和每一个 .t 文件最前面的一些配置项,框架会组装成一个完整的 nginx.conf 文件, t/servroot 会被当成 Nginx 的工作目录,启动 Nginx 实例。根据测试用例提供的信息,发起 http 请求并检查 http 的返回项, 包括 http status,http response header,http response body 等。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

玛卡巴卡的卡巴卡玛

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表