美文网首页工作专题
Kong插件开发指南

Kong插件开发指南

作者: RavenZZ | 来源:发表于2018-11-28 20:05 被阅读162次

    What is Kong

    OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。
    Kong 核心基于OpenResty构建, 拥有强大的插件扩展功能, 可通过Restful API对API,Service,Upstream等进行管理

    本文将讲述如何编写kong插件

    编写插件

    插件目录结构

    Kong所有的插件都位于kong/plugins目录下, 如下在plugins目录我们创建一个名为custom-plugin, 目录结构及推荐命名如下

    custom-plugin
    ├── api.lua            # 用于扩展Admin API 
    ├── daos.lua           # 数据访问层
    ├── handler.lua        # (必需)包含请求的生命周期, 提供接口来实现插件逻辑
    ├── migrations         # 插件的表结构定义语句
    │   ├── cassandra.lua  
    │   └── postgres.lua
    └── schema.lua         # (必需)插件配置参数定义, 可加入自定义校验函数
    

    插件入口文件

    下面我们来看插件的入口文件handler.lua, 你可以重写以下方法用以在kong的生命周期内执行想要的自定义逻辑;

    -- 继承BasePlugin
    local BasePlugin = require "kong.plugins.base_plugin"
    local CustomHandler = BasePlugin:extend()
    
    -- 插件构造函数
    function CustomHandler:new()
      CustomHandler.super.new(self, "my-custom-plugin")
    end
    
    function CustomHandler:init_worker()
      CustomHandler.super.init_worker(self)
      -- 在这里实现自定义的逻辑
    end
    
    function CustomHandler:certificate(config)
      CustomHandler.super.certificate(self)
      -- 在这里实现自定义的逻辑
    end
    
    function CustomHandler:rewrite(config)
      CustomHandler.super.rewrite(self)
      -- 在这里实现自定义的逻辑
    end
    
    function CustomHandler:access(config)
      CustomHandler.super.access(self)
      -- 在这里实现自定义的逻辑
    end
    
    function CustomHandler:header_filter(config)
      CustomHandler.super.header_filter(self)
      -- 在这里实现自定义的逻辑
    end
    
    function CustomHandler:body_filter(config)
      CustomHandler.super.body_filter(self)
      -- 在这里实现自定义的逻辑
    end
    
    function CustomHandler:log(config)
      CustomHandler.super.log(self)
      -- 在这里实现自定义的逻辑
    end
    
    return CustomHandler
    

    如下表格, 为kong插件中支持重写的函数列表

    函数名 Lua-Nginx-Module 上下文 描述
    :init_worker() init_worker_by_lua 在每个Nginx Worker启动时执行
    :certificate() ssl_certificate_by_lua_block 在SSL握手的SSL证书服务阶段执行
    :rewrite() rewrite_by_lua_block 每个请求中的rewrite阶段执行
    :access() access_by_lua 在被代理至上游服务前执行
    :header_filter() header_filter_by_lua 从上游服务器接收所有Response headers后执行
    :body_filter() body_filter_by_lua 从上游服务接收的响应主体的每个块时执行。 由于响应被流回客户端,因此它可以超过缓冲区大小并按块进行流式传输。 因此,如果响应很大,则会多次调用此方法
    :log() log_by_lua 当最后一个响应字节输出完毕时执行

    另外还有一点, 插件的执行有时是依赖于特定的顺序的, 比如认证的插件应当先于部分业务插件执行, 可以通过CustomHandler.PRIORITY = 10在handler.lua中配置

    插件配置

    -- schema.lua
    return {
      no_consumer = true, -- this plugin will only be applied to Services or Routes,
      fields = {
        kafka_brokers = {type="array"},
        kafka_topic = {type = "string"}
      },
      self_check = function(schema, plugin_t, dao, is_updating)
        -- 自定义的验证函数
        return true
      end
    }
    
    • no_consumer: 如果为true, 则插件只能被应用于Service和Routes
    • fields: 一个field数组, field中可定义type,required,unique,default,immutable,enum,regex 等属性
    • self_check: 在安装时, 执行的自定义校验函数

    PDK(Plugin Development Kit)

    kong 的插件开发套件包含了一些常用的Lua函数和变量, 如kong.client,kong.request,kong.log,kong.table 等

    数据访问

    Kong自身存储可选PostgreSQL和Cassandra, 在开发插件时, kong提供了一个数据库抽象层用于存储自定义的实体, 也就是dao层.

    要完成数据访问, 需要两步:

    1. 编写Migration文件, 用于数据库DDL操作, 在kong migrations up时执行
    2. 编写daos.lua, 用于映射你的数据表

    如下为PostgreSQL示例:

    -- 步骤一:postgres.lua
    return {
      {
        name = "2015-07-31-172400_init_keyauth",
        up = [[
          CREATE TABLE IF NOT EXISTS keyauth_credentials(
            id uuid,
            consumer_id uuid REFERENCES consumers (id) ON DELETE CASCADE,
            key text UNIQUE,
            created_at timestamp without time zone default (CURRENT_TIMESTAMP(0) at time zone 'utc'),
            PRIMARY KEY (id)
          );
        ]],
        down = [[
          DROP TABLE keyauth_credentials;
        ]]
      },
      .....
    }
    

    postgres.lua 由一个Migration数组组成, 每个Migration包含三个字段:name,up,down, 其中name须唯一, up和down分别代表升级和降级时执行的SQL语句.

    -- 步骤二:daos.lua
    local SCHEMA = {
      primary_key = {"id"},
      table = "keyauth_credentials", -- 数据库表名
      fields = {
        id = {type = "id", dao_insert_value = true}, 
        created_at = {type = "timestamp", immutable = true, dao_insert_value = true}, 
        consumer_id = {type = "id", required = true, foreign = "consumers:id"}, 
        key = {type = "string", required = false, unique = true} 
      }
    }
    
    return {keyauth_credentials = SCHEMA}  --keyauth_credentials即为在DAO中加入的自定义schema
    

    完成以上两步后, 即可通过如下所示代码在我们的插件中对数据库进行操作

    local singletons = require "kong.singletons"
    singletons.dao.keyauth_credentials.find_all({key="ravenzz"})
    singletons.dao.keyauth_credentials.insert(...)
    singletons.dao.keyauth_credentials.update(...)
    ....
    

    缓存

    作为网关代理层, 缓存一定是必不可少的一环, 在kong的PDK中, 封装了lua-resty-mlcache.
    kong的缓存分为两级:

    • L1: Lua memory cache - 在nginx worker中共享, 可以存储任何Lua值
    • L2: Shared memory cache (SHM) - 在nginx node的所有worker中共享. 可以存储任何标量值, 但它需要序列化和反序列化, 所以性能会有所下降

    注: 从数据库中提取数据后,它将同时存储于上述两级缓存中。现在,如果同一个工作进程再次请求数据,它将从Lua内存缓存检索数据。 如果同一个Nginx节点中的不同工作者请求该数据,它将在SHM中找到数据,对其进行反序列化(并将其存储在自己的Lua内存缓存中),然后将其返回。

    一个典型的使用方式如下:

    -- 通过一个唯一值获取Cache Key
    cache_key = singletons.dao.keyauth_credentials:cache_key("api_key")
    value, err = singletons.cache:get(cache_key ,nil, load_from_db_func, ....)
    
    

    函数: value, err = cache:get(key, opts?, cb, ...) , 如果cache没有值(miss), 则会调用函数cb, cb 必须返回一个返回值, 可返回需要缓存的值或者nil, 需要注意的是返回值为nil时, get函数依旧会执行缓存, 但我们可以通过cache:get()的第二个参数控制缓存的TTL和negative TTL. 如下代码即代表有数据时, 我们缓存600s, 没有数据时, 缓存nil结果40s

    value, err = singletons.cache:get(cache_key ,{
                ttl = 600, --如果有数据, 则缓存的时间, 单位:秒
                neg_ttl = 40 -- 如果没有数据, 单位:秒
            }, load_from_db_func, ....)
    

    通过上述API开发者可轻松实现缓存懒加载功能

    Docker化

    这里没有用kong官方提供的image, 而是通过源码安装方式自定义安装kong, 将修改的代码放入custom目录, 编译时, 将custom目录覆盖至kong目录, 这样做的好处在于无需修改kong目录的代码, 可轻松保持kong为最新社区版本

    MY-PROXY
    ├── custom         # 自定义的代码及配置(包含插件), 用于覆盖kong的代码及配置文件
    ├── kong           # git clone git@github.com:Kong/kong.git
    

    修改后的Dockerfile如下

    ARG RESTY_IMAGE_BASE="centos"
    ARG RESTY_IMAGE_TAG="7"
    
    FROM ${RESTY_IMAGE_BASE}:${RESTY_IMAGE_TAG}
    
    LABEL maintainer="RavenZZ <ravenzz@qq.com>"
    
    RUN yum update -y 
    RUN yum install -y pcre-devel openssl openssl-devel gcc curl perl make unzip gettext git
    
    ARG RESTY_VERSION="1.13.6.2"
    ARG RESTY_LUAROCKS_VERSION="2.4.4"
    
    # 安装OpenResty和LuaRocks
    RUN cd /tmp \
        && curl -LO https://openresty.org/download/openresty-${RESTY_VERSION}.tar.gz \
        && tar -xf openresty-${RESTY_VERSION}.tar.gz \
        && cd /tmp/openresty-${RESTY_VERSION} \
        && ./configure \
        --prefix=/usr/local/openresty \
        --with-pcre-jit \
        --with-http_ssl_module \
        --with-http_realip_module \
        --with-http_stub_status_module \
        --with-http_v2_module \
        -j2 \
        && make -j2 \
        && make install \
        && curl -fSL https://github.com/luarocks/luarocks/archive/${RESTY_LUAROCKS_VERSION}.tar.gz -o luarocks-${RESTY_LUAROCKS_VERSION}.tar.gz \
        && tar xzf luarocks-${RESTY_LUAROCKS_VERSION}.tar.gz \
        && cd luarocks-${RESTY_LUAROCKS_VERSION} \
        && ./configure \
        --prefix=/usr/local/openresty/luajit \
        --with-lua=/usr/local/openresty/luajit \
        --lua-suffix=jit-2.1.0-beta3 \
        --with-lua-include=/usr/local/openresty/luajit/include/luajit-2.1 \
        && make build \
        && make install \
        && cd /tmp \
        && rm -rf luarocks-${RESTY_LUAROCKS_VERSION} luarocks-${RESTY_LUAROCKS_VERSION}.tar.gz  \
        && rm -rf /tmp/openresty-${RESTY_VERSION} /tmp/openresty-${RESTY_VERSION}.tar.gz
    
    ENV PATH=$PATH:/usr/local/openresty/luajit/bin:/usr/local/openresty/nginx/sbin:/usr/local/openresty/bin
    ENV LUA_PATH="/usr/local/openresty/site/lualib/?.ljbc;/usr/local/openresty/site/lualib/?/init.ljbc;/usr/local/openresty/lualib/?.ljbc;/usr/local/openresty/lualib/?/init.ljbc;/usr/local/openresty/site/lualib/?.lua;/usr/local/openresty/site/lualib/?/init.lua;/usr/local/openresty/lualib/?.lua;/usr/local/openresty/lualib/?/init.lua;./?.lua;/usr/local/openresty/luajit/share/luajit-2.1.0-beta3/?.lua;/usr/local/share/lua/5.1/?.lua;/usr/local/share/lua/5.1/?/init.lua;/usr/local/openresty/luajit/share/lua/5.1/?.lua;/usr/local/openresty/luajit/share/lua/5.1/?/init.lua"
    ENV LUA_CPATH="/usr/local/openresty/site/lualib/?.so;/usr/local/openresty/lualib/?.so;./?.so;/usr/local/lib/lua/5.1/?.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so;/usr/local/lib/lua/5.1/loadall.so;/usr/local/openresty/luajit/lib/lua/5.1/?.so"
    
    # 编译kong
    COPY kong /kong
    RUN cd /kong \
        && make install 
    
    
    # 使用custom覆盖/kong目录后, 再次编译
    COPY custom /kong
    RUN cd /kong \
        && make install 
    
    ENV PATH=$PATH:/kong/bin
    
    RUN ln -sf /dev/stdout /usr/local/openresty/nginx/logs/access.log \
        && ln -sf /dev/stderr /usr/local/openresty/nginx/logs/error.log
    
    COPY docker-entrypoint.sh /docker-entrypoint.sh
    ENTRYPOINT ["/docker-entrypoint.sh"]
    EXPOSE 8000 8443 8001 8444
    STOPSIGNAL SIGTERM
    
    CMD ["kong", "docker-start"]
    

    运行

    在首次运行或更新代码时, 执行kong migrations up 对数据库进行DDL操作, 完成后启动

    # 执行Migration
    docker run --rm \
        --network=kong-net \
        -e "KONG_DATABASE=postgres" \
        -e "KONG_PG_HOST=xxxxxxx" \
        -e "POSTGRES_DB=kong" \
        -e "KONG_PG_USER=kong_user" \
        -e "KONG_PG_PASSWORD=kong_pwd" \
        ravenzz/kong:latest kong migrations --vv up
    
    # 启动kong
    docker run -d --rm --name kong \
        --network=kong-net \
        -e "KONG_DATABASE=postgres" \
        -e "KONG_PG_HOST=xxxxxxx" \
        -e "KONG_PG_USER=kong_user" \
        -e "KONG_PG_PASSWORD=kong_pwd" \
        -e "KONG_PROXY_ACCESS_LOG=/dev/stdout" \
        -e "KONG_ADMIN_ACCESS_LOG=/dev/stdout" \
        -e "KONG_PROXY_ERROR_LOG=/dev/stderr" \
        -e "KONG_ADMIN_ERROR_LOG=/dev/stderr" \
        -e "KONG_ADMIN_LISTEN=0.0.0.0:8001, 0.0.0.0:8444 ssl" \
        -p 8000:8000 \
        -p 8443:8443 \
        -p 8001:8001 \
        -p 8444:8444 \
        ravenzz/kong:latest
    

    在生产环境时, 若使用docker部署, 避免对性能产生影响, --network参数需设置为host

    Kong GUI

    Konga
    kong-dashboard

    参考资料

    相关文章

      网友评论

        本文标题:Kong插件开发指南

        本文链接:https://www.haomeiwen.com/subject/ejozqqtx.html