在团队规模尚小,业务尚较为简单的前提条件下,我们常常将多个功能集中在一个应用中,进行统一化的部署和测试。随着业务的发展,功能模块日益增多。如需更新单一模块,都会需要对整个程序进行更新,如此下来,长期以往系统维护将会变得愈发费时费力。
针对以上问题,我们将单体应用进行拆分,变成多个自成一体的模块,每个模块有各自自成体系的发布和运维等功能,由此解决了单体应用的弊端,将应用微服务化。当我们拆分出多个模块之后,各个模块需要统一的出入口,这里我们需要使用网关解决统一调用和接入问题。
在这样的背景之下,我们通过利用Kong这一开源API网关,将请求转发到上游服务之前,进行一系列的管理。
本文针对其核心应用,首先简要阐明Nginx、Openresty与Kong三者只间的关系,然后实际介绍如何使用Kong的插件机制来添加一个自定义插件。
1. 基本概念
Kong是一个开源的API网关,它是一个针对API的一个管理工具。你可以在那些上游服务之前,额外地实现一些功能。
Kong本身是一款基于OpenResty(Nginx + Lua模块)编写的高可用、易扩展的,由Mashape公司开源的API Gateway项目。Kong是基于NGINX和Apache Cassandra或PostgreSQL构建的,能提供易于使用的RESTful API来操作和配置API管理系统,所以它可以水平扩展多个Kong服务器,通过前置的负载均衡配置把请求均匀地分发到各个Server,来应对大批量的网络请求。
Nginx
Nginx是模块化设计的反向代理软件,由C语言开发,是多进程(单线程) & 多路IO复用模型(高并发)。
多进程
Nginx 在启动后,会有一个 master 进程和多个相互独立的 worker 进程。接收来自外界的信号,向各worker进程发送信号,每个进程都有可能来处理这个连接。master 进程能监控worker 进程的运行状态,当 worker 进程退出后(异常情况下),会自动启动新的 worker 进程。一个请求,完全由 worker 进程来处理,而且只能在一个 worker 进程中处理请求。
多路复用模型
epoll通过在Linux内核中申请一个简易的文件系统,只要有 fd 上事件发生,epoll_wait() 就能检测到并返回给用户,用户就能”非阻塞“地进行 I/O 了。在内核,select中采用轮训的方法来查看是由有fd文件描述符准备好。在内核,epoll根据每个sockfd上面与设备趋同程序建立站起来的回调函数,当某个sockfd上的时间发生,与之对应的回调函数就会被调用。
Openresty
OpenResty是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。Openresty中的 Ngx_Lua_Module 使得开发人员能够使用 Lua 脚本调动 Nginx 支持的各种模块,让 web 服务直接跑在 Nginx 内部。
Ngx_Lua_Module
把 Lua5.1 解释器 或 LuaJIT 2.0/2.1 解释器嵌入到 nginx 中,将 Lua 线程(Lua threads)与 nginx 事务模型(Nginx event model)结合,更改变子请求(subrequests)的处理过程。在 nginx 的一个 worker 里,所有请求共享一个Lua 解释器或 LuaJIT 实例,即一个 nginx worker,一个 Lua 解释器或 LuaJIT 实例。每个请求的上下文(context)是通过轻量级的 Lua 协程(coroutines)相互隔离的。
Kong
Kong 可以认为是一个 OpenResty 应用程序,而OpenResty 运行在 Nginx 之上,使用 Lua 扩展了 Nginx。Kong = OpenResty + Nginx + Lua
我们可以通过 HTTP Restful API 来动态管理 Kong 配置,8001是默认管理端口,8000/8443则分别是 Http 和 Https 的转发端口,我们可以通过 HTTP Restful 来动态管理Kong配置。
# 配置一个上游服务
curl -X POST http://localhost:8001/upstreams --data "name=helloUpstream"
Kong插件机制
Kong具有极高的可扩展性,而它的高扩展性便来源于他的插件机制。
首先,先介绍如何在Kong中添加插件。
插件的作用范围非常灵活,可作用于一个服务或者路由之上,也可作用于整个Kong服务。我们可以为一个服务添加50次/秒的官方限流插件,通过如下所示的Restful API方式:
curl -X POST http://localhost:8001/services/hello/plugins \
--data "name=rate-limiting" \
--data "config.second=50"
2. 开发Kong自定义插件
这里用添加一个名为key-auth-redis的自定义插件为例,详细介绍如何在kong开发一个自定义插件。
├── kong_components
│ └── kong_plugins
│ └── key-auth-redis
│ ├── handler.lua # 请求生命周期
│ ├── access.lua # 逻辑实现部分
│ ├── schema.lua # 插件配置参数定义,或自定义校验函数
│ ├── migrations
│ │ ├── 000_base_qingke-auth.lua # 数据库结构信息
│ │ └── init.lua # 初始化数据结构信息
首先,进入Kong_plugins目录,新建key-auth-redis的文件夹,并创建handler.lua文件和schema.lua文件,handler.lua文件中是插件主要的逻辑,需要继承baseplugin,根据不同阶段完成需要的逻辑。
Handler.lua 文件
local BasePlugin = require 'kong.plugins.base_plugin'
local access = require 'kong.plugins.key-auth-redis.access'
local AuthNotEncryption = BasePlugin:extend()
function AuthNotEncryption:access(conf)
access.execute(conf)
end
AuthNotEncryption.PRIORITY = 1000
AuthNotEncryption.VERSION = '1.0.0'
return AuthNotEncryption
这里注意
AuthNotEncryption.PRIORITY 是插件执行顺序。AuthNotEncryption.VERSION是插件版本。local AuthNotEncryption= BasePlugin:extend(),Kong 的插件使用一个叫 Classic 的 class 机制。所有的插件都从 base_plugin.lua 基类上继承而来。base_plugin.lua 定义了插件在各个阶段被执行的方法名,我们在这里自定义变量AuthNotEncryption并继承baseplugin,定义了这个Kong插件在执行中调用方法顺序,最后return自定义变量。
Access.lua 文件
key-auth-redis这个插件需要访问redis数据库,进行访问用户鉴权,因此,我们只需要在访问上游服务器前执行该插件。为了达到这个操作,我们需要重写BasePlugin在访问上有服务之前的生命周期函数,完成对应的逻辑。
下表这里为Kong各个生命周期函数的具体描述。
通过此表,我们可以定位到想要实现key-auth-redis的功能,我们需要复写access方法。完成插件的大致逻辑如下。
function _M.execute(conf)
local params
local method = kong.request.get_method()
if method == 'POST' then
params = kong.request.get_body()
else
return Response.exit(100010, params.data_id)
end
if (params == nil) then
return Response.exit(201500, "")
end
validate_params(params, conf)
do_authentication(conf, cache, params)
end
return _M
这里通过kong.request.get_body()获取访问带有参数,然后调用access.lua中带有的访问校验函数do_authentication。
Schema.lua 文件
schema.lua是插件在使用自定义配置参数的主要脚本。在schema文件中,我们可以进行对key-auth-redis插件中访问redis数据库的配置。
local typedefs = require 'kong.db.schema.typedefs'
local utils = require "kong.tools.utils"
return {
name = 'key-auth-redis',
fields = {
{consumer = typedefs.no_consumer},
{run_on = typedefs.run_on_first},
{protocols = typedefs.protocols_http},
{
config = {
type = 'record',
fields = {
{redis_host = {type = 'string', default = '0.0.0.0'}},
{redis_port = {type = 'number', default = 6379}},
{redis_password = {type = 'string', default = ''}},
{redis_timeout = {type = 'number', default = 200000}},
{redis_connections = {type = 'number', default = 1000}},
{fault_tolerant = {type = 'boolean', default = true}},
{redis_database = {type = 'number', default = 0}},
{clock_skew = {type = 'number', default = 300, gt = 0}}
}
}
}
}
}
在field中,成员类型可以是string、boolean、array、number、timestamp,我们可以根据插件所需要的参数类型,进行参数配置。
到此为止,key-auth-redis插件代码部分已经介绍完毕。在完成代码修改后需要重启Kong,重启命令。
kong restart -c kong.conf --vv
然后,通过如下命令重启启动kong。
kong start
PS:
我们是行者AI,我们在“AI+游戏”中不断前行。
快来【公众号 | xingzhe_ai】,和我们讨论更多技术问题吧!