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层.
要完成数据访问, 需要两步:
- 编写Migration文件, 用于数据库DDL操作, 在
kong migrations up
时执行 - 编写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 [email protected]:Kong/kong.git
修改后的Dockerfile如下
ARG RESTY_IMAGE_BASE="centos"
ARG RESTY_IMAGE_TAG="7"
FROM ${RESTY_IMAGE_BASE}:${RESTY_IMAGE_TAG}
LABEL maintainer="RavenZZ "
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 Plugin Development
- Docker OpenResty
- Docker kong
- Github kong
- lua-resty-mlcache