kong笔记——自定义插件开发

kong笔记 目录导航
本文通过一个自定义插件的编写,来了解Kong的插件机制
插件功能:

  • 原本返回百度首页,在添加插件之后,返回插件自定义的内容;

前提

  1. 使用kong代理百度的接口,配置route路径;
  2. 了解kong 插件的目录结构及部署(上一篇说到的)

实战

上一篇我们学到,一个插件必须包含两个文件:

  • handler.lua
  • schema.lua

handler.lua主要负责业务逻辑功能编写,schema.lua主要负责插件参数定制,类似将handler.lua中写死的变量抽出来写到schema.lua中,变为动态注入。

既然是自定义插件,又想应用到kong中,这期间肯定会有一些规范做适配,kong也不例外,在kong中,我们可以通过继承一个base插件:
kong.plugins.base_plugin

这个base插件提供了一些方法待实现,这些方法是基于 openresty的http模块来定义的

函数名LUA-NGINX-MODULE Context描述
:init_worker()init_worker_by_lua在每个 Nginx 工作进程启动时执行
:certificate()ssl_certificate_by_lua在SSL握手阶段的SSL证书服务阶段执行
:rewrite()rewrite_by_lua从客户端接收作为重写阶段处理程序的每个请求执行。在这个阶段,无论是API还是消费者都没有被识别,因此这个处理器只在插件被配置为全局插件时执行
:access()access_by_lua为客户的每一个请求而执行,并在它被代理到上游服务之前执行(路由)
:header_filter()header_filter_by_lua从上游服务接收到所有响应头字节时执行
:body_filter()body_filter_by_lua从上游服务接收的响应体的每个块时执行。由于响应流回客户端,它可以超过缓冲区大小,因此,如果响应较大,该方法可以被多次调用
:log()log_by_lua当最后一个响应字节已经发送到客户端时执行

在各个阶段分别可以处理响应的业务。

另外,kong自身也提供了一些方法,有上下文的,有request、response等等的工具类,便于我们使用

PDK名称功能描述
kong.client提供客户端的ip, 端口等信息
kong.ctx提供了插件之间共享并传递参数的桥梁
kong.ip提供了kong.ip.is_trusted(address)IP白名单检测方法
kong.log日志方法
kong.node返回此插件的UUID信息
kong.request提供request信息的读取功能,access()中可读
kong.response提供response信息的读写功能, access()中不可用
kong.router返回此请求关联的router信息
kong.service返回此请求关联的service,可以动态修改后端服务信息
kong.service.request仅用于access()方法中,可以读写请求信息
kong.service.response仅可用于header_filter(), body_filter()方法中,只提供header信息的读取功能
kong.tablekong提供的一套数据结构功能

更多方法参考:PDK - v2.5.x | Kong Docs (konghq.com)

插件代码

插件名称为:demo

  • handler.lua

    local BasePlugin = require "kong.plugins.base_plugin"
    
    local DemoHandler = BasePlugin:extend()
    
    DemoHandler.VERSION = "0.1.0"
    DemoHandler.PRIORITY = 2000
    
    -- 在 'init_by_lua_block' 中运行
    function DemoHandler:new()
        DemoHandler.super.new(self, "demo")
    end
    
    -- 在 'init_worker_by_lua_block' 中运行
    function DemoHandler:init_worker()
        DemoHandler.super.init_worker(self)
    end
    
    -- 在 'ssl_certificate_by_lua_block' 中运行
    function DemoHandler:certificate(conf)
        DemoHandler.super.certificate(self)
    end
    
    -- 在 'rewrite_by_lua_block' 中运行
    function DemoHandler:rewrite(conf)
        DemoHandler.super.rewrite(self)
    end
    
    -- 在 'access_by_lua_block' 中运行
    function DemoHandler:access(conf)
        DemoHandler.super.access(self)
        return kong.response.exit(200,conf.content)
    end
    
    -- 在 'header_filter_by_lua_block' 中运行
    function DemoHandler:header_filter(conf)
        DemoHandler.super.header_filter(self)
    end
    
    -- 在 'body_filter_by_lua_block' 中运行
    function DemoHandler:body_filter(conf)
        DemoHandler.super.body_filter(self)
    end
    
    -- 在 'log_by_lua_block' 中运行
    function DemoHandler:log(conf)
        DemoHandler.super.log(self)
    end
    
    return DemoHandler
    

    可以看到, 主要内容就是实现一个BasePlugin:extend(),实现一系统生命周期对应的方法后,返回这个实例即可.

    注意看一下 access()方法那块,我添加了一些内容!!!!

    而且, 所有方法在实现的时候, 都要先调用一下父类方法.

  • schema.lua

    return {
        no_consumer = true,
        fields = {
            content = { type = "string", default = "success!!" },
        }
    }
    

部署

  1. 先把代码放置到/opt/share/kong/plugins/demo位置

  2. 修改/etc/kong/kong.conf文件,加载插件

    lua_package_path = /opt/share/?.lua;;
    plugins = bundled,demo
    
  3. 重启Kong服务: kong restart

验证

  1. 打开konga,可以看到插件的部署情况:

    image-20220113113639368.png

    底色是绿色的代表正在使用的插件!

  2. 添加插件

    在这里我需要说明一下,kong插件的作用域范围有4个

    • Consumer作用域

    • route作用域
      仅针对route配置起作用(一个route可以配置多个path,也就是说这个route下的所有path插件都生效,不是这个route的path不生效)

    • service作用域

      针对service配置起作用(一个service有多个route,只要是属于这个service下的route,插件都生效)

    • global作用域

      所有请求,插件都生效

    目前我们演示route作用域,其他感兴趣的自己可以尝试一下:

    • 添加插件

      image-20220113114746442.png

      参数详情:

      image-20220113114835770.png

    • 访问结果

      image-20220113114934463.png

拓展

上述的实战比较简单,相信你也可以顺利完成,接下来我来说几个参坑点吧

  1. lua库扫描以及引入第三方lua库

    • 在我们编写lua脚本的时候,可能需要引入其他的库(kong自身没有提供的库),这个时候当我们在handler.lua中require的时候,则需要在kong.conf配置untrusted_lua_sandbox_requires 属性,例如:

      在handler.lua中

      local template = require "resty.template"
      local split = require "kong.tools.utils".split
      

      那么我们必须在kong.conf中配置

      untrusted_lua_sandbox_requires = resty.template, kong.tools.utils
      
    • 还有一种方案是,直接将自己写好的库跟handler.lua,schema.lua放到一个文件夹里面,然后通过下列方式来引入

      local xxx     = require "kong.plugins.插件名称.自己库的名称"
      

      例如:插件名称叫demo,自己写的库名称是redisUtil.lua,那么引入方式为:

      local redisUtil     = require "kong.plugins.demo.redisUtil"
      

      这种方案有个前提是!

      你的redisutil 库必须有返回,如果是函数,想做到通用,就必须return,并且函数不要用local修饰,例如:我想让get,put,incr这三个函数通用,我的redisutil就必须返回这三个函数

      return {
          get = get;
          put = put;
          incr = incr;
      }
      

      如果全部封装到table里面,在最后返回时,也可以这么写:

      return _M
      
  2. schema文件拓展内容

    在schema文件中,支持的类型共有下面这些类型

     map, number, array, foreign, function, integer, boolean, record, string, set
    

    其中,在type=record的时候,这种就必须定义field,类似java中的JavaBean一样,定义属性,以及属性值

    比如:

    return {
      name = "ip-restriction",
      fields = {
        { protocols = typedefs.protocols_http },
        { config = {
          type = "record",
          fields = {
            { allow = { type = "array", elements = typedefs.ip_or_cidr, }, },
            { deny = { type = "array", elements = typedefs.ip_or_cidr, }, },
          },
        },
        },
      },
    }
    

    这种就相当与是有一个config对象,包含两个属性,这个两个属性都是数组类型,数组内部的元素必须符合ip校验规则,其数据结构对应为:

    {
        "name": "ip-restriction",
        "config": {
            "allow": [
                "127.0.0.1"
            ],
            "deny": [
                "127.0.0.1"
            ]
        }
    }
    

    在type=map的时候

    比如:

    return {
      name = "response-ratelimiting",
      fields = {
        { protocols = typedefs.protocols_http },
        { config = {
            type = "record",
            fields = {
              { limits = {
                  type = "map",
                  required = true,
                  len_min = 1,
                  keys = { type = "string" },
                  values = {
                    type = "record",
                    required = true,
                    fields = {
                      { second = { type = "number", gt = 0 }, },
                      { minute = { type = "number", gt = 0 }, },
                      { hour = { type = "number", gt = 0 }, },
                      { day = { type = "number", gt = 0 }, },
                      { month = { type = "number", gt = 0 }, },
                      { year = { type = "number", gt = 0 }, },
                    },
                  },
                },
              },
            },
          },
        },
      },
    }
    

    这种就相当于是JsonObject结构,其数据结构为:

    {
        "name": "response-ratelimiting",
        "config": {
            "limits": {
                "second":1,
                "minute":1,
                "hour":1,
                "day":1,
                "month":1,
                "year":1
            }
        }
    }
    

    还有一些比较复杂的嵌套对象,比如:

    return {
        name = "demo-ip-restriction",
        fields = {
            { protocols = typedefs.protocols_http },
            { config = {
                type = "record",
                fields = {
                    { rfilter = {
                        type = "array",
                        elements = {
                            type = "record",
                            fields = {
                                { appkey = { type = "string", required = true }, },
                                { deny = { type = "array", elements = typedefs.ip_or_cidr, }, },
                            },
                        },
                    }
                    },
                },
            },
            },
        },
    }
    

    这种对应的数据结构为:

    {
        "name": "demo-ip-restriction",
        "config": {
            "rfilter": [
                {
                    "appkey": "1111",
                    "deny": [
                        "142.0.0.1/24"
                    ]
                },
                {
                    "appkey": "2222",
                    "deny": [
                        "132.0.0.1/32"
                    ]
                }
            ]
        }
    }
    
  3. 部署中常见的问题
    在部署插件中我们可能会遇到这么几个问题“

    • plugin is in use but not enabled;

      这个是因为你之前配置了一个自定义插件,并在某种场合使用了该插件,后来由于某种原因,你下架了该自定义插件,重启kong后,就会报该错误;

      想解决很简单,在数据库plugins中删除使用添加该插件的节点

    • plugin is enabled but not installed;

      这个是因为你在kong.conf中配置了自定义插件(plugins = bundled,demo),没有配置lua源(lua_package_path = /opt/share/?.lua;;)

  4. 多个作用域插件的执行顺序

    在上文我们提到插件可以作用域在consumer,route,service,global

    当多个作用域的插件出现在一个场景时,他们的优先级法则为:一个插件相对于它所配置的实体数量越具体,它的优先级就越高。

    多次配置插件时的完整优先级顺序为:

    1. 在以下组合上配置的插件:route、service和consumer。(consumer表示请求必须经过身份验证)。
    2. 在route和consumer的组合上配置的插件。(consumer表示请求必须经过身份验证)。
    3. 在service和consumer的组合上配置的插件。(consumer表示请求必须经过身份验证)。
    4. 在route和service的组合上配置的插件。
    5. 在consumer上配置的插件。(consumer表示请求必须经过身份验证)。
    6. 在route上配置的插件。
    7. 在service上配置的插件。
    8. 配置为global运行的插件。

    示例:如果插件应用两次(具有不同的配置):对于service(插件配置 A)和对于consumer(插件配置 B),则验证此consumer身份的请求将运行插件配置 B 并忽略 A。但是,不验证此consumer身份的请求将回退到运行插件配置 A。请注意,如果禁用了配置 B(其标志设置为 ),则配置 A 将应用于与配置 B 匹配的请求。rate-limiting``enabled``false

    官网解释:Admin API - v2.7.x | Kong Docs (konghq.com)

  5. 多个插件的执行顺序

    当一个路径涉及到多个插件时,有时候后一个插件需要依赖前一个插件的数据,所以出现了优先级问题,这个时候你可以通过

    CustomHandler.PRIORITY = 10
    

    来设置你的插件优先级

    优先级越高,相对于其他插件的阶段(如:access()、:log()等),插件的阶段执行得越快。 已有捆绑插件的当前执行顺序为:

    插件优先级
    pre-function+inf
    zipkin100000
    ip-restriction3000
    bot-detection2500
    cors25000
    jwt1005
    oauth21004
    key-auth1003
    ldap-auth1002
    basic-auth1001
    hmac-auth1000
    request-size-limiting951
    acl950
    rate-limiting901
    response-ratelimiting900
    request-transformer801
    response-transformer800
    aws-lambda750
    azure-functions749
    prometheus13
    http-log12
    statsd11
    datadog10
    file-log9
    udp-log8
    tcp-log7
    loggly6
    syslog4
    galileo3
    request-termination2
    correlation-id1
    post-function-1000
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值