标签归档:Api网关

使用 konga 来管理微服务 API 网关 kong

微服务网关kong有比较多个后台管理面板,比如比较简单的kong-dashboard,还有konga,之前在初探kong的时候,使用的就是比较简单的kong-dashboard,很多功能都没有,而且最近由于kong官方更新比较频繁,1.0之后的kong-dashboard就已经不兼容了,频繁报错,所有今天我就来使用一下另一款kong的后台管理面板:konga

安装konga

在开始安装之前,需要准备的有:

  • 一个已经安装好的kong环境
  • Nodejs >= 8 (推荐8.11.3 LTS)
  • npm

关于这三者的安装,我这里就不赘述了,kong的安装可以在博客的相关文章查看。在做好准备工作之后,就开始安装:

安装npm依赖

$ git clone https://github.com/pantsel/konga.git
$ cd konga
$ npm i

在执行npm i之后,由于会引入很多的npm包,所以可能会有报错,比如我这里就遇到了一些权限问题,比如:

Unable to save binary /home/qianyugang/soft/konga/node_modules/node-sass/vendor/linux-x64-64 : { Error: EACCES: permission denied, mkdir '/home/thinkpad/soft/konga/node_modules/node-sass/vendor'

如果出现了如上错误,可以把最后一个命令修改为

sudo npm i --unsafe-perm=true --allow-root

执行之后,可能会提示:

bower bootstrap-switch extra-resolution Unnecessary resolution: bootstrap-switch#~3.3.4
added 69 packages from 77 contributors and audited 3120 packages in 4.654s
found 275 vulnerabilities (125 low, 24 moderate, 126 high)
  run `npm audit fix` to fix them, or `npm audit` for details

意思是有一些包没有执行好,那么我们就按照它的提示执行一下:

sudo npm audit fix --unsafe-perm=true --allow-root

基本就可以解决npm依赖包的问题了。总之这里是有可能出现各种各样的npm问题,依次解决即可。

初始化数据库

npm的依赖都安装完成之后,就需要复制一下konga目录下的.env_example文件

cp .env_example env

然后把其中的一些配置项目都填写上去。具体的配置项可以查看 https://github.com/pantsel/konga#environment-variables 。这里就主要把以下三项配置好:

DB_ADAPTER=postgres
PORT=1337
DB_URI=postgresql://kong_user:kong_pass@localhost:5432/konga

注意这个DB_URI一定要填写完整,这里的数据库我用的是postgres,konga其实还支持mysql,mongo等多种数据库,这里就不赘述了。配置完成之后,需要登上你自己的postgres数据库,然后执行如下命令新建数据库:

CREATE DATABASE "konga" WITH ENCODING='UTF8';

新建了数据库之后,,执行如下命令,来初始化数据库:

node ./bin/konga.js prepare --adapter postgres --uri postgresql://kong_user:kong_pass@localhost:5432/konga

出现如下提示之后,数据库这一块就完成:

Preparing database...
debug: Hook:api_health_checks:process() called
debug: Hook:health_checks:process() called
debug: Hook:start-scheduled-snapshots:process() called
debug: Hook:upstream_health_checks:process() called
debug: Hook:user_events_hook:process() called
debug: Seeding User...
debug: User seed planted
debug: Seeding Kongnode...
debug: Kongnode seed planted
debug: Seeding Emailtransport...
debug: Emailtransport seed planted
debug: Database migrations completed!

启动konga

执行如下命令:

npm start

看到如下小帆船的图,成功启动


   Sails              <|    .-..-.
   v0.12.14            |\
                      /|.\
                     / || \
                   ,'  |'  \
                .-'.-==|/_--'
                `--'-------' 
   __---___--___---___--___---___--___
 ____---___--___---___--___---___--___-__

最后,打开浏览器输入http://localhost:1337/,就可以进入konga的管理界面了。会出现一个让你注册的界面,注册登录一下,然后配置一下kong的admin-api链接,大功告成,打完收工。

微服务 API 网关 Kong 中文文档发布

由于项目的原因,最近的几个月一直在学习微服务的API网关 Kong ,在这里做一个简单介绍,是一个云原生,高效,可扩展的分布式 API 网关。 自 2015 年在 github 开源后,广泛受到关注,目前已收获 1.68w+ 的 star,其核心价值在于高性能和可扩展性。由于对项目的积极维护,Kong被广泛用于从初创公司到全球5000强以及政府机构的生产中。从技术角度来说,Kong是基于Openresty的一个莹莹,Openresty是基于Nginx的,使用的语言是Lua。

所以在学习过程中,首要是需要看官方文档,由于项目比较新,所以暂时没有中文文档,我就想着,反正文档总是要全部看一遍的,不如自己翻译一份好了,于是乎就有了这个项目:Kong的文档中文版。欢迎大家star&fork。

由于自己不是英语专业,而且主要目的是学习Kong,所以采用的是人工+机翻结合的方式,如果有遇到翻译的不够通顺,或者对于翻译的语句有歧义的地方,麻烦一定点击官网英文文档https://docs.konghq.com/ 查看,并且欢迎提 PR 提修改意见。另,由于kong的文档本身也在不断增加和完善当中,如果有遇到没有即使更新翻译的状况欢迎提issue,我会不断补充的。

todo:

  • 目前文档中的超链接都是链接的英文原文,后续会慢慢改成中文内链。
  • 会在每一页文档里面附上单独的英文原文链接,以便做对照。
  • 会添加kong自带的插件文档。

本文档是基于 https://docs.konghq.com/1.1.x/ 1.1.x 版本,目前官网已经更新至 1.2.x 版本,如果使用的最新版本,请查看 https://docs.konghq.com 并注意差别。

Lua OpenResty 使用 protobuf 和 rabbitmq AMQP 发送和接收消息

项目中有个需求,需要使用Google的protobuf作为压缩协议,然后使用rabbitmq AMQP来发送和接收消息,在研究使用这两个工具中,遇到了有一些坑,之前有写了两篇来介绍分别使用,《 在 lua 中使用 protobuf》和《Lua OpenResty 使用rabbitmq AMQP协议发送和接收消息》 ,这里我们来结合使用一下,然后顺便解决一下lua的相关库的使用问题。

protobuf 相关步骤

1.安装 protoc

首先安装依赖库

sudo apt-get install autoconf automake libtool curl make g++ unzip

下载、解压安装包

curl -L -o protobuf-all-3.6.1.tar.gz  https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protobuf-all-3.6.1.tar.gz
tar -xzvf protobuf-all-3.6.1.tar.gz

进入安装包安装

cd protobuf-all-3.6.1
./configure
make
make check
sudo make install
sudo ldconfig

最后检查是否安装成功

$ protoc --version
libprotoc 3.6.1

2.生成pb文件

需要定义你自己的标准proto文件 xxx.proto 文件(可能不止一个文件,不止一个目录),定义好了之后,使用protoc命令生成.pb文件,命令如下:

protoc --proto_path=proto --descriptor_set_out=common.pb proto/xxxx/*.proto proto/xxxxx/*.proto

其中 --proto_path 参数是你的proto文件目录,--descriptor_set_out 是你需要输出.pb文件的目录,后面几个参数就是具体要引入的.proto文件。这里需要注意的是,有时候会有多个proto文件并且多目录import的情况,这个时候,就需要在参数中都体现出来(命令的最后两个参数),这条命令是把所有的 .proto 文件生成了一个 common.pb 文件方便引入。注意这个common.pb的路径,后面需要用到。

3.安装lua的protobuf库

这里我们使用的是lua-protobuf库。 lua-protobuf实际上是一个纯C的protobuf协议实现,和对应的Lua绑定。

项目地址:https://github.com/starwing/lua-protobuf

可以使用 luarocks 安装lua-protobuf

luarocks install lua-protobuf

如果没有安装 luarocks 可以安装一下luarocks。

rabbitmq AMQP 相关步骤

1.安装第三方库

如果想要在openresty中使用AMQP协议发送和接收消息的话,需要使用到一个第三方库。地址为:https://github.com/4mig4/lua-amqp 。这里你会发现,这个库和《Lua OpenResty 使用rabbitmq AMQP协议发送和接收消息》文章中介绍使用的库不一样,确实是的,因为在使用过程中我发现,之前的那个库还有一些功能不完善,这里使用的这个库是作者fork了之前的那个项目,并改进了里面许多功能。所以最终我选用了这个库来开发。

安装方式为,使用luarocks安装,由于这个库没有push到https://luarocks.org/ 仓库当中,所以我们需要使用到其中的声明文件安装。把项目clone下来之后,找到这个文件 amqp-1.0-4.rockspec 这个文件,然后执行:

luarocks build amqp-1.0-4.rockspec

这里直接执行,可能会报错,报错信息为unrecognized filename extension,意思是识别不了文件格式之类的,这里需要把这个文件中的

source = {
   url = "https://github.com/4mig4/lua-amqp.git",
   tag = "",
}

修改为:

source = {
   url = "https://github.com/4mig4/lua-amqp",
   tag = "",
}

然后继续执行,执行完成之后,就可以在/usr/local/share/lua/5.1目录中看到这个第三方库了,如果整理的文件夹名字不是amqp,而是一个amqp加版本号,可以手动直接把文件夹修改为amqp即可。到此第三方库安装完成。

在使用过程中,我发现一个问题,作者在fork了项目之后,增加了CQUEUES, NGX.SOCKET, SOCKET一些通讯协议功能,但是我本地环境直接运行会报错,查看了一些源码,发现是我的本地环境的cqueues支持有点问题,而作者把这个作为最优先的协议,那么我就把源码文件https://github.com/4mig4/lua-amqp/blob/master/amqp/init.lua 中的local use_cqueues = true改为local use_cqueues = false,即关闭cqueues来使用了。

发送和接收消息

假设你的proto结构体是这样的:

syntax = "proto3";
package aa.bb;
message EventEnvelope {
  string id = 1; 
  int64 created_ts = 2; 
  string server_hash = 3;
  int64 happened_ts = 4; 
  oneof body {
    aa.bb.LoggedOut logged_out = 1501;
  }
message LoggedOut {
  int64 union_id = 1;
  string app_key = 2;
}

发送消息

local function send_pb()

    -- 引入pb库
    local pb   = require "pb"
    -- 加载pb文件
    assert(pb.loadfile("xxxxx/common.pb"))

    -- 这里是你需要发送的pb消息,注意这里是支持嵌套的,如果你的protobuf文件里面有one of,可以直接使用多层嵌套
    -- 这里就根据你自己protobuf结构来就好
    local data    = {
        created_ts  = ngx.now() * 1000,
        server_hash = 'localhost',
        happened_ts = ngx.now() * 1000,
        id          = 8376548368364,
        logged_out  = {
            union_id = 123455666,
            app_key  = xxxxxxxx
        }
    }

    -- 生成需要发送的消息,这个消息是proto压缩之后的,这个`EventEnvelope` 就是最外层的结构体
    local messages = assert(pb.encode("aa.bb.EventEnvelope", data))

    -- 引入amqp第三方库
    local amqp        = require "amqp"

    -- 里面的一些参数就不再赘述了,都是rabbitmq的一些参数
    local ctx = amqp.new({
        role        = "publisher",
        exchange    = "exchangexxxx",
        ssl         = false,
        user        = "guest",
        password    = "guest",
        auto_delete = false,
        routing_key = "routing_keyxxxxx",
        passive     = true,
        no_ack      = true,
        no_wait     = false,
    })
    ctx:connect("127.0.0.1", port)
    ctx:setup()
    local ok, err = ctx:publish(messages)
    if not ok then
        ngx.log(ngx.ERR, "[ -- rabbitmq send failed : -- ] " .. err)
    else
        ngx.log(ngx.ERR, "[ -- rabbitmq send success ]")
    end
end

send_pb("this is a message")

接收消息

可以新建一个consume_queue.lua文件,然后如下代码:


-- 依旧是引入
local amqp            = require "amqp"
local pb              = require "pb"

-- 这里是回调函数
local function consume_local(f)
    print(f) -- 这里就是消息的所有信息,里面包含了properties ,body,frame等信息 
    print(f.body) -- 这里就是消息的主体

       -- 加载pb文件
    assert(pb.loadfile("xxxxx/common.pb"))
    -- 解析消息
    local data = assert(pb.decode("aa.bb.EventEnvelope", f.body))
    print(data) 

end

local amqp        = require "amqp"

local ctx = amqp.new({
    role        = "consumer",
    queue       = "eventbus1", -- 这里可以自定义
    exchange    = "exchangexxxx",
    ssl         = false,
    user        = "guest",
    password    = "guest",
    no_wait     = false,
    routing_key = "routing_keyxxxxx",
    auto_delete = false, -- 是否自动删除消息
    no_ack      = true,
    exclusive   = false, -- 是否为排他队列
    callback    = consume_local, -- 回调函数
    durable     = true,
    passive     = false,
    type        = "topic"

})

ctx:connect("127.0.0.1", port)

local ok, err = ctx:consume()

执行命令开始消费消息

/usr/local/openresty/bin/resty consume_queue.lua

微服务 API 网关 Kong 日志中文文档

日志

原文链接: https://docs.konghq.com/1.1.x/logging/ (不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

日志等级

日志级别在Kong的配置中设置。以下是日志级别,按照严重程度顺序递增,debug, info, notice, warn, error and crit

  • debug:它提供有关插件的runloop和每个插件或其他组件的调试信息。只是在调试期间使用,因为它的消息量太多了。
  • info/notice:kong没有在这两个级别上产生很大的差异。提供有关正常行为的信息,其中大多数行为可以忽略。
  • warn:要记录任何不会导致事务丢失但需要进一步调查的异常行为,应使用警告级别。
  • error:用于记录导致请求被停止的错误(例如,获取HTTP 500错误)。需要监控此类日志的速率。
  • crit:当Kong在紧急条件下工作而不能正常工作从而影响多个客户时,使用此级别。

默认情况下,notice是使用和建议的日志级别。然而,如果日志变得过于繁琐,他们可能会被提升到更高的水平,就像warn一样。

从Kong日志中删除某些元素

随着围绕保护私人数据(如GDPR)的新规定,您可能需要改变您的日志记录习惯。如果您使用Kong作为API网关,则可以在一个位置完成此操作以使所有API生效。本指南将引导您完成一个实现此目的的方法,但总有不同的方法来满足不同的需求。请注意,这些更改将影响NGINX访问日志的输出。这对Kong的日志插件没有任何影响。

举个例子,假设您要从kong日志中删除任何电子邮件地址实例。电子邮件地址可能以不同的方式出现,例如/apiname/v2/verify/alice@example.com 或者 /v3/verify?alice@example.com。为了防止这些被添加到日志中,我们需要使用自定义NGINX模板。

要开始使用自定义NGINX模板,请先获取我们模板的副本。 这可以在 https://docs.konghq.com/latest/configuration/#custom-nginx-templates-embedding-kong 找到或从下面复制

# ---------------------
# custom_nginx.template
# ---------------------

worker_processes $; # can be set by kong.conf
daemon $;                     # can be set by kong.conf

pid pids/nginx.pid;                      # this setting is mandatory
error_log logs/error.log $; # can be set by kong.conf

events {
    use epoll; # custom setting
    multi_accept on;
}

http {
    # include default Kong Nginx config
    include 'nginx-kong.conf';

    # custom server
    server {
        listen 8888;
        server_name custom_server;

        location / {
          ... # etc
        }
    }
}

为了控制日志中的内容,我们将在模板中使用NGINX 的map模块。有关使用map指令的更多详细信息,请参阅本指南。这将创建一个新变量,其值取决于第一个参数中指定的一个或多个源变量的值。格式为:

map $paramater_to_look_at $variable_name {
    pattern_to_look_for 0;
    second_pattern_to_look_for 0;

    default 1;
}

举个例子,我们将映射一个名为keeplog的新变量,该变量依赖于$request_uri中出现的某些值。我们将把map指令放在http块的开头,这必须在 include'nginx-kong.conf' 之前。因此,对于我们的示例,我们将添加以下内容:

map $request_uri $keeplog {
    ~.+\@.+\..+ 0;
    ~/servicename/v2/verify 0;
    ~/v3/verify 0;

    default 1;
}

您可能会注意到这些行中的每一行都以波形符号开头。这就是NGINX在评估生产线时使用RegEx的原因。 在这个例子中我们有三件事需要寻找:

  • 第一行使用正则表达式查找x@y.z格式的任何电子邮件地址
  • 第二行查找URI的任何部分,即/servicename/v2/verify
  • 第三行查看包含/v3/verify的URI的任何部分

因为所有这些都具有0以外的值,如果请求具有其中一个元素,则不会将其添加到日志中。

现在,我们需要为日志中保留的内容设置日志格式。我们将使用log_format模块并为我们的新日志指定show_everything的名称。日志的内容可以根据您的需要进行定制,但在这个例子中,我会简单地将一切改回kong标准,要查看可以使用的完整选项列表,请参阅本指南

log_format show_everything '$remote_addr - $remote_user [$time_local] '
    '$request_uri $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent"';

现在,我们的自定义NGINX模板已经可以使用了。如果您一直观察,您的文件现在应该如下所示:

# ---------------------
# custom_nginx.template
# ---------------------

worker_processes $; # can be set by kong.conf
daemon $;                     # can be set by kong.conf

pid pids/nginx.pid;                      # this setting is mandatory
error_log stderr $; # can be set by kong.conf



events {
    use epoll; # custom setting
    multi_accept on;
}

http {


    map $request_uri $keeplog {
        ~.+\@.+\..+ 0;
        ~/v1/invitation/ 0;
        ~/reset/v1/customer/password/token 0;
        ~/v2/verify 0;

        default 1;
    }
    log_format show_everything '$remote_addr - $remote_user [$time_local] '
        '$request_uri $status $body_bytes_sent '
        '"$http_referer" "$http_user_agent"';

    include 'nginx-kong.conf';
}

我们需要做的最后一件事是告诉Kong使用新创建的日志,show_everything,为此,我们将改变Kong变量prpxy_access_log。通过打开和编辑etc/kong/kong.conf或使用环境变量KONG_PROXY_ACCESS_LOG=来修改默认位置以显示。

proxy_access_log=logs/access.log show_everything if=$keeplog

最后一步,重启kong,使修改东西都生效,你可以使用kong restart命令来操作。

现在,将不再记录使用其中的电子邮件地址发出的任何请求。 当然,我们可以使用此逻辑以条件方式从日志中删除任何我们想要的内容。

微服务 API 网关 Kong 代理中文文档

原文链接: https://docs.konghq.com/1.1.x/proxy/ (不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

代理

简介

在本文档中,我们将通过详细解释其路由功能和内部工作原理,来了解Kong的代理功能。

Kong公开了几个可以通过两个配置属性调整的接口:

  • proxy_listen:它定义了一个地址/端口列表,Kong将接受来自客户端的公共流量并将其代理到您的上游服务(默认为8000)。
  • admin_listen:它还定义了一个地址和端口列表,但是这些应该被限制为仅由管理员访问,因为它们暴露了Kong的配置功能:Admin API(默认为8001)。

注意:从1.0.0开始,API实体已被删除。本文档将介绍使用新路由和服务实体进行代理。
如果您使用的是0.12或更低版本,请参阅本文档的旧版本。

相关术语

  • client:指下游 downstream 客户向Kong的代理端口发出请求。
  • upstream service:指位于Kong后面的您自己的 API/service ,转发客户端请求。
  • Service:顾名思义,服务实体是每个上游服务的抽象。比如说服务可以是数据转换微服务,一个计费API等。
  • Route:这是指Kong Routes实体。路由是进入Kong的入口点,并定义要匹配的请求的规则,并路由到给定的服务。
  • Plugin:这指的是Kong“插件”,它们是在代理生命周期中运行的业务逻辑。可以通过Admin API配置插件 – 全局(所有传入流量)或特定路由和服务。

概览

从高层次的角度来看,Kong在其配置的代理端口上监听HTTP流量(默认情况下为80008443)。Kong将根据您配置的路由评估任何传入的HTTP请求,并尝试查找匹配的路由。如果给定的请求与特定Route的规则匹配,Kong将处理代理请求。由于每个Route都链接到一个Service,因此Kong将运行您在Route及其相关服务上配置的插件,然后在上游代理请求。

您可以通过Kong的Admin API管理Routes。Routes 的机 hostspsths methods 属性定义用于匹配传入HTTP请求的规则。

如果Kong收到的请求无法与任何已配置的路由匹配(或者如果没有配置路由),它将响应:

HTTP/1.1 404 Not Found
Content-Type: application/json
Server: kong/<x.x.x>

{
    "message": "no route and no Service found with those values"
}

提醒:如何配置服务

配置服务快速入门指南介绍了如何通过[Admin API]配置Kong。

通过向Admin API发送HTTP请求来向Kong添加服务:

curl -i -X POST http://localhost:8001/services/ \
    -d 'name=foo-service' \
    -d 'url=http://foo-service.com'
HTTP/1.1 201 Created
...

{
    "connect_timeout": 60000,
    "created_at": 1515537771,
    "host": "foo-service.com",
    "id": "d54da06c-d69f-4910-8896-915c63c270cd",
    "name": "foo-service",
    "path": "/",
    "port": 80,
    "protocol": "http",
    "read_timeout": 60000,
    "retries": 5,
    "updated_at": 1515537771,
    "write_timeout": 60000
}

该请求指示Kong注册一个名为“foo-service”的服务,该服务指向http://foo-service.com(上游)。

注意:url参数是一个简化参数,用于一次性添加protocolhostportpath

现在,为了通过Kong向这个服务发送流量,我们需要指定一个Route,它作为Kong的入口点:

curl -i -X POST http://localhost:8001/routes/ \
    -d 'hosts[]=example.com' \
    -d 'paths[]=/foo' \
    -d 'service.id=d54da06c-d69f-4910-8896-915c63c270cd'
HTTP/1.1 201 Created
...

{
    "created_at": 1515539858,
    "hosts": [
        "example.com"
    ],
    "id": "ee794195-6783-4056-a5cc-a7e0fde88c81",
    "methods": null,
    "paths": [
        "/foo"
    ],
    "preserve_host": false,
    "priority": 0,
    "protocols": [
        "http",
        "https"
    ],
    "service": {
        "id": "d54da06c-d69f-4910-8896-915c63c270cd"
    },
    "strip_path": true,
    "updated_at": 1515539858
}

我们现在已经配置了一个Route来匹配与给定hostpath匹配的传入请求,并将它们转发到我们配置的foo-service,从而将此流量代理到http://foo-service.com

路由和匹配功能

现在让我们讨论Kong如何匹配针对路由的已配置hostpathmethods属性(或字段)的请求。请注意,所有这三个字段都是可选的,但必须至少指定其中一个。

对于匹配路线的请求:

  • 请求必须包含所有已配置的字段
  • 请求中的字段值必须至少与其中一个配置值匹配(当字段配置接受一个或多个值时,请求只需要其中一个值被视为匹配)

我们来看几个例子。考虑如下配置的路由:

{
    "hosts": ["example.com", "foo-service.com"],
    "paths": ["/foo", "/bar"],
    "methods": ["GET"]
}

与此Route匹配的一些可能请求如下所示:

GET /foo HTTP/1.1
Host: example.com
GET /bar HTTP/1.1
Host: foo-service.com
GET /foo/hello/world HTTP/1.1
Host: example.com

所有这三个请求都满足路径定义中设置的所有条件。但是,以下请求与配置的条件不匹配:

GET / HTTP/1.1
Host: example.com
POST /foo HTTP/1.1
Host: example.com
GET /foo HTTP/1.1
Host: foo.com

所有这三个请求仅满足两个配置条件。第一个请求的路径不匹配配置的路径,第二个请求的HTTP方法和第三个请求的host头也均不匹配。

现在我们了解了hosts, paths, 和 methods属性如何协同工作,让我们分别来看每个属性。

host 请求头

基于其host header 来路由请求是通过Kong代理流量的最直接方式,特别是因为这是HTTP host header 的预期用途。Kong可以通过Route实体的hosts字段轻松完成。

hosts接受多个值,在通过Admin API指定它们时必须以逗号分隔:

hosts接受多个值,这些值很容易在JSON有效负载中表示:

curl -i -X POST http://localhost:8001/routes/ \
    -H 'Content-Type: application/json' \
    -d '{"hosts":["example.com", "foo-service.com"]}'
HTTP/1.1 201 Created
...

但由于Admin API还支持form-urlencoded内容类型,因此您可以通过[]表示法指定数组:

curl -i -X POST http://localhost:8001/routes/ \
    -d 'hosts[]=example.com' \
    -d 'hosts[]=foo-service.com'
HTTP/1.1 201 Created
...

要满足此Route的hosts条件,来自客户端的任何传入请求现在必须将其Host header 设置为以下之一:

Host: example.com

或者

Host: foo-service.com

使用通配符主机名

为了提供灵活性,Kong允许您在hosts字段中指定带通配符的主机名。通配符主机名允许任何匹配的host满足条件,从而匹配给定的Route。

通配符主机名必须在域的最左侧或最右侧标签中仅包含一个星号。例子:

  • *.example.com将匹配诸如a.example.com  x.y.example.com
  • example.* 将匹配诸如example.com  example.org

一个完整的例子如下所示:

{
    "hosts": ["*.example.com", "service.com"]
}

将允许以下请求匹配此路由:

GET / HTTP/1.1
Host: an.example.com
GET / HTTP/1.1
Host: service.com

preserve_host属性

代理时,Kong的默认行为是将上游请求的主机头设置为服务主机中指定的hostpreserve_host字段接受一个布尔标志,指示Kong不要这样做。

例如,当preserve_host属性未更改且Route配置如下:

{
    "hosts": ["service.com"],
    "service": {
        "id": "..."
    }
}

client对Kong的可能请求可能是:

GET / HTTP/1.1
Host: service.com

Kong将从Service的主机属性中提取Host头值,并将发送以下上游请求:

GET / HTTP/1.1
Host: <my-service-host.com>

但是,通过使用preserve_host=true配置Route:

{
    "hosts": ["service.com"],
    "preserve_host": true,
    "service": {
        "id": "..."
    }
}

并假设来自客户的相同请求:

GET / HTTP/1.1
Host: service.com

Kong将根据客户端请求保留Host,并将发送以下上游请求:

GET / HTTP/1.1
Host: service.com

请求路径

路由匹配的另一种方式是通过请求路径。 要满足此路由条件,客户端请求的路径必须以paths属性的值之一为前缀。 例如,使用如下配置的Route:

{
    "paths": ["/service", "/hello/world"]
}

以下请求将被匹配:

GET /service HTTP/1.1
Host: example.com
GET /service/resource?param=value HTTP/1.1
Host: example.com
GET /hello/world/resource HTTP/1.1
Host: anything.com

对于这些请求中的每一个,Kong检测到其URL路径以路由的paths之一为前缀。默认情况下,Kong会在不更改URL路径的情况下代理上游请求。

使用路径前缀进行代理时,首先评估最长路径。这允许您定义两个具有两个路径的Routes:/service/service/resource,并确保前者不会“遮蔽”后者。

在路径中使用正则表达式

Kong通过PCRE(Perl兼容正则表达式)支持Route的路径字段的正则表达式模式匹配。您可以同时将路径作为前缀和正则表达式分配给Route。例如,如果我们考虑以下Route:

{
    "paths": ["/users/\d+/profile", "/following"]
}

此Route将匹配以下请求:

GET /following HTTP/1.1
Host: ...
GET /users/123/profile HTTP/1.1
Host: ...

使用PCRE标志(PCRE_ANCHORED)评估提供的正则表达式,这意味着它们将被约束为在路径中的第一个匹配点(root/character)匹配。

评估顺序

如前所述,Kong按长度评估前缀路径:首先评估最长前缀路径。 但是,Kong将根据路由的regex_priority属性从最高优先级到最低优先级来评估正则表达式路径。这意味着考虑以下Routes:

[
    {
        "paths": ["/status/\d+"],
        "regex_priority": 0
    },
    {
        "paths": ["/version/\d+/status/\d+"],
        "regex_priority": 6
    },
    {
        "paths": ["/version"],
    },
    {
        "paths": ["/version/any/"],
    }
]

在这种情况下,Kong将按以下顺序评估针对以下定义的URI的传入请求:

  1. /version/any/
  2. /version
  3. /version/\d+/status/\d+
  4. /status/\d+

始终在正则表达式路径之前评估前缀路径。

像往常一样,请求仍然必须匹配Route的机hostsmethods属性,并且Kong将遍历您的Routes,直到找到匹配最多规则的路由(请参阅[路由优先级] [代理路由优先级])。

捕获组

也支持正则的捕获组,匹配的组将从路径中提取并可用于插件使用。 如果我们考虑以下正则表达式:

/version/(?<version>\d+)/users/(?<user>\S+)

以及以下请求路径:

/version/1/users/john

Kong会将请求路径视为匹配,如果匹配整个Route(考虑hostsmethods字段),则可以从ngx.ctx变量中的插件获取提取的捕获组:

local router_matches = ngx.ctx.router_matches

-- router_matches.uri_captures is:
-- { "1", "john", version = "1", user = "john" }
规避特殊字符

接下来,值得注意的是,根据RFC 3986,在正则表达式中找到的字符通常是保留字符,因此应该是百分比编码(URL编码)。通过Admin API配置具有正则表达式路径的路由时,请务必在必要时对您的有效负载进行URL编码。例如,使用curl并使用application/x-www-form-urlencodeMIME类型:

curl -i -X POST http://localhost:8001/routes \
    --data-urlencode 'uris[]=/status/\d+'
HTTP/1.1 201 Created
...

请注意,curl不会自动对您的有效负载进行URL编码,并注意使用--data-urlencode,它可以防止Kong的Admin API对+字符进行URL解码,并将其解码为一个空的“。

strip_path属性

可能需要指定路径前缀以匹配路由,但不将其包括在上游请求中。为此,请通过配置如下所示的Route来使用strip_path布尔属性:

{
    "paths": ["/service"],
    "strip_path": true,
    "service": {
        "id": "..."
    }
}

启用此标志会指示Kong在匹配此路由并继续代理服务时,不应在上游请求的URL中包含URL路径的匹配部分。例如,以下客户端对上述路由的请求:

GET /service/path/to/resource HTTP/1.1
Host: ...

将导致Kong发送以下上游请求:

GET /path/to/resource HTTP/1.1
Host: ...

同样,如果在启用了strip_path的Route上定义了正则表达式路径,则将剥离整个请求URL匹配序列。 例:

{
    "paths": ["/version/\d+/service"],
    "strip_path": true,
    "service": {
        "id": "..."
    }
}

以下HTTP请求与提供的正则表达式路径匹配:

GET /version/1/service/path/to/resource HTTP/1.1
Host: ...

请求HTTP方法

methods字段允许根据HTTP方法匹配请求。它接受多个值。其默认值为空(HTTP方法不用于路由)。 以下路由允许通过GETHEAD进行路由:

{
    "methods": ["GET", "HEAD"],
    "service": {
        "id": "..."
    }
}

这样的Route将符合以下要求:

GET / HTTP/1.1
Host: ...
HEAD /resource HTTP/1.1
Host: ...

但它与POSTDELETE请求不匹配。在路由上配置插件时,这允许更多粒度。例如,可以想象两个指向同一服务的路由:一个具有无限制的未经身份验证的GET请求,另一个仅允许经过身份验证和速率限制的POST请求(通过将身份验证和速率限制插件应用于此类请求)。

匹配优先事项

Route可以基于其hosts, paths, 和 methods字段定义匹配规则。要使Kong匹配到路由的传入请求,必须满足所有现有字段。 但是,通过允许两个或多个路由配置包含相同值的字段,Kong允许相当大的灵活性 – 当发生这种情况时,Kong应用优先级规则。

规则是:在评估请求时,Kong将首先尝试匹配具有最多规则的路由。

例如,如果两个路由配置如下:

{
    "hosts": ["example.com"],
    "service": {
        "id": "..."
    }
},
{
    "hosts": ["example.com"],
    "methods": ["POST"],
    "service": {
        "id": "..."
    }
}

第二个Route有一个hosts字段和一个methods字段,因此它将首先由Kong评估。通过这样做,我们避免了第一个用于第二个路径的“阴影”调用。

因此,此请求将匹配第一个Route

GET / HTTP/1.1
Host: example.com

这个请求将匹配第二个:

POST / HTTP/1.1
Host: example.com

遵循这个逻辑,如果要使用hosts字段,methods字段和uris字段配置第三个Route,它将首先由Kong评估。

代理行为

上面的代理规则详细说明了Kong如何将传入请求转发到您的上游服务。下面,我们详细说明Kong与HTTP请求与注册路由匹配的时间与请求的实际转发之间内部发生的情况。

1.负载均衡

Kong实现负载平衡功能,以跨上游服务实例池分发代理请求。

您可以通过查看负载平衡来查找有关配置负载平衡的更多信息

2.插件执行

Kong可通过“插件”进行扩展,这些“插件”将自己挂载在代理请求的请求/响应生命周期中。插件可以在您的环境中执行各种操作 和/或 在代理请求上进行转换。

可以将插件配置为全局(针对所有代理流量)或特定 Routes 和 Services运行。 在这两种情况下,您都必须通过Admin API创建插件配置

一旦路由匹配(及其关联的服务实体),Kong将运行与这些实体中的任何一个相关联的插件。在路由上配置的插件在服务上配置的插件之前运行,否则,通常的插件关联规则适用。

这些配置的插件将运行其access阶段,您可以在插件开发指南中找到更多相关信息。

3.代理和上游超时

一旦Kong执行了所有必要的逻辑(包括插件),它就可以将请求转发给您的上游服务。这是通过Nginx的ngx_http_proxy_module完成的。 您可以通过以下服务属性为Kong和给定上游之间的连接配置所需的超时:

  • upstream_connect_timeout:以毫秒为单位定义建立与上游服务的连接的超时。默认为60000
  • upstream_send_timeout:以毫秒为单位定义用于向上游服务发送请求的两个连续写入操作之间的超时。默认为60000
  • upstream_read_timeout:以毫秒为单位定义用于接收来自上游服务的请求的两个连续读取操作之间的超时。默认为60000

Kong将通过 HTTP/1.1 发送请求,并设置以下headers:

  • Host: <your_upstream_host>,如前文所述。
  • Connection: keep-alive,允许重用上游连接。
  • X-Real-IP: <remote_addr>,其中$remote_addr是ngx_http_core_module提供的名称相同的变量。请注意,$remote_addr可能被ngx_http_realip_module覆盖。
  • X-Forwarded-For: <address>,其中<address>是由附加到具有相同名称的请求标头的ngx_http_realip_module提供的$realip_remote_addr的内容。
  • X-Forwarded-Proto: <protocol>,其中<protocol>是客户端使用的协议。在$realip_remote_addr是可信地址之一的情况下,如果提供,则转发具有相同名称的请求头。否则,将使用ngx_http_core_module提供的$scheme变量的值。
  • X-Forwarded-Host: <host>,其中<host>是客户端发送的主机名。在$realip_remote_addr是可信地址之一的情况下,如果提供,则转发具有相同名称的请求头。否则,将使用ngx_http_core_module提供的$host变量的值。
  • X-Forwarded-Port: <port>,其中<port>是接受请求的服务器的端口。在$realip_remote_addr是可信地址之一的情况下,如果提供,则转发具有相同名称的请求头。否则,将使用ngx_http_core_module提供的$server_port变量的值。

所有其他请求headers都由Kong转发。

使用WebSocket协议时会出现一个例外。如果是这样,Kong将设置以下标头以允许升级客户端和上游服务之间的协议:

  • Connection: Upgrade
  • Upgrade: websocket

有关此主题的更多信息,请参见[Proxy WebSocket流量] [proxy-websocket]部分。

4.错误和重试

每当代理期间发生错误时,Kong将使用底层的Nginx重试机制将请求传递给下一个上游。

这里有两个可配置元素:

  1. 重试次数:可以使用retries属性为每个服务配置。有关详细信息,请参阅Admin API
  2. 究竟是什么构成错误:这里Kong使用Nginx默认值,这意味着在与服务器建立连接,向其传递请求或读取响应头时发生错误或超时。

第二个选项基于Nginx的proxy_next_upstream指令。此选项不能通过Kong直接配置,但可以使用自定义Nginx配置添加。有关详细信息,请参阅配置参考。

5.响应

Kong接收来自上游服务的响应,并以流方式将其发送回下游客户端。此时,Kong将执行添加到 Route 和/或 Service 的后续插件,这些插件在header_filter阶段实现一个钩子。

一旦执行了所有已注册插件的header_filter阶段,Kong将添加以下headers,并将完整的headers发送到客户端:

  • Via: kong/x.x.x,其中x.x.x是正在使用的Kong版本。
  • X-Kong-Proxy-Latency: <latency>,其中latency是Kong收到客户端请求和向上游服务发送请求之间的时间(以毫秒为单位)。
  • X-Kong-Upstream-Latency: <latency>,其中latency是Kong等待上游服务响应的第一个字节的时间(以毫秒为单位)。

将标题发送到客户端后,Kong将开始为实现body_filter钩子的 Route和/或Service 执行已注册的插件。由于Nginx的流媒体特性,可以多次调用此钩子。由这样的body_filter挂钩成功处理的上游响应的每个块被发送回客户端。您可以在插件开发指南中找到有关body_filter钩子的更多信息。

配置一个备用路由

作为Kong的代理功能提供的灵活性的实际用例和示例,让我们尝试实现“后备路线”,因此,为了避免Kong响应HTTP 404,“找不到路由”,我们可以捕获这些请求并将它们代理到特殊的上游服务,或者向它应用插件(例如,这样的插件可以使用不同的状态代码或响应终止请求,而不代理请求)。

以下是此类后备路由的示例:

{
    "paths": ["/"],
    "service": {
        "id": "..."
    }
}

正如您所猜测的,任何向Kong发出的HTTP请求实际上都会匹配此Route,因为所有URI都以根字符/为前缀。正如我们从[请求路径] [代理请求路径]部分所知,最长的URL路径首先由Kong评估,因此/路径最终将由Kong最后评估,并有效地提供“后备”路由,仅作为最后的手段。

为路由配置SSL

Kong提供了一种基于每个连接动态提供SSL证书的方法。SSL证书由核心直接处理,并可通过Admin API进行配置。通过TLS连接到Kong的客户端必须支持服务器名称指示扩展才能使用此功能。

SSL证书由Kong Admin API中的两个资源处理:

  • /certificates,存储您的密钥和证书。
  • /snis,将注册证书与Server Name 指示相关联。

您可以在Admin API参考中找到这两种资源的文档。

以下是在给定路由上配置SSL证书的方法:首先,通过Admin API上传您的SSL证书和密钥:

curl -i -X POST http://localhost:8001/certificates \
    -F "cert=@/path/to/cert.pem" \
    -F "key=@/path/to/cert.key" \
    -F "snis=ssl-example.com,other-ssl-example.com"
HTTP/1.1 201 Created
...

snis表单参数是糖参数,直接插入SNI并将上传的证书与其关联。

您现在必须在Kong内注册以下Route。 为方便起见,我们仅使用Hos header 匹配对此Route的请求:

curl -i -X POST http://localhost:8001/routes \
    -d 'hosts=ssl-example.com,other-ssl-example.com' \
    -d 'service.id=d54da06c-d69f-4910-8896-915c63c270cd'
HTTP/1.1 201 Created
...

您现在可以期望Kong通过HTTPS提供路由:

curl -i https://localhost:8443/ \
  -H "Host: ssl-example.com"
HTTP/1.1 200 OK
...

建立连接并协商SSL握手时,如果您的客户端发送ssl-example.com作为SNI扩展的一部分,Kong将提供先前配置的cert.pem证书。

限制客户端协议(HTTP/HTTPS/TCP/TLS)

路由具有protocols属性,以限制他们应该侦听的客户端协议。此属性接受一组值,可以是“http”“https”“tcp”“tls”

具有httphttps的路由将接受两种协议中的流量。

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["http", "https"],
    "service": {
        "id": "..."
    }
}

未指定任何协议具有相同的效果,因为路由默认为[“http”,“https”]

但是,仅使用https的路由接受通过HTTPS的流量。如果以前从受信任的IP发生SSL终止,它也会接受未加密的流量。当请求来自trusted_ip中的一个配置的IP并且如果设置了X-Forwarded-Proto:https header时,SSL终止被认为是有效的:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["https"],
    "service": {
        "id": "..."
    }
}

如果上述路由与请求匹配,但该请求是纯文本而没有有效的先前SSL终止,则Kong响应:

HTTP/1.1 426 Upgrade Required
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: Upgrade
Upgrade: TLS/1.2, HTTP/1.1
Server: kong/x.y.z

{"message":"Please use HTTPS protocol"}

从Kong 1.0开始,可以使用protocols属性中的“tcp”为原始TCP(不一定是HTTP)连接创建路由:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["tcp"],
    "service": {
        "id": "..."
    }
}

同样,我们可以使用“tls”值创建接受原始TLS流量(不一定是HTTPS)的路由:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["tls"],
    "service": {
        "id": "..."
    }
}

仅具有TLS的路由仅接受通过TLS的流量。 也可以同时接受TCP和TLS:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["tcp", "tls"],
    "service": {
        "id": "..."
    }
}

代理WebSocket流量

由于底层的Nginx实现,Kong支持WebSocket流量。如果希望通过Kong在客户端和上游服务之间建立WebSocket连接,则必须建立WebSocket握手。这是通过HTTP升级机制完成的。这是您的客户要求对Kong的看法:

GET / HTTP/1.1
Connection: Upgrade
Host: my-websocket-api.com
Upgrade: WebSocket

这将使Kong将ConnectionUpgrade header 转发到您的上游服务,而不是由于标准HTTP代理的逐跳特性而将其解除。

WebSocket和TLS

Kong将在其各自的httphttps端口接受wswss连接。要从客户端强制执行TLS连接,请将Route的protocols属性设置为https

将 Service 设置为指向上游WebSocket服务时,应仔细选择要在Kong和上游之间使用的协议。如果要使用TLS(wss),则必须使用服务protocol属性中的https协议和正确的端口(通常为443)定义上游WebSocket服务。要在没有TLS(ws)的情况下进行连接,则应在协议中使用http协议和端口(通常为80)。

如果您希望Kong终止SSL/TLS,您只能从客户端接受wss,而是通过纯文本或ws代理上游服务。

结论

通过本指南,我们希望您了解Kong的基础代理机制,从请求如何匹配路由到其关联的服务,到如何允许使用WebSocket协议或设置动态SSL证书。

该网站是开源的,可以在 https://github.com/Kong/docs.konghq.com 找到。 如果您还没有,我们建议您还阅读负载平衡参考,因为它与我们刚刚介绍的主题密切相关。

微服务 API 网关 Kong 插件开发 – 插件配置

原文地址:https://docs.konghq.com/1.1.x/plugin-development/plugin-configuration/(不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

简介

大多数情况下,您的插件可以配置为满足您的所有用户需求。当插件被执行的时候,您的插件的配置存储在Kong的数据存储区中,以检索它并将其传递给handler.lua方法。

配置由Kong中的Lua表组成,我们称之为 schema。它包含用户在通过Admin API启用插件时将设置的键/值属性。Kong为您提供了一种验证用户插件配置的方法。

当用户向Admin API发出请求以启用或更新给定Service,Route和/或Consumer上的插件时,将根据您的架构schema插件的配置。

例如,用户执行以下请求:

$ curl -X POST http://kong:8001/services/<service-name-or-id>/plugins/ \
    -d "name=my-custom-plugin" \
    -d "config.foo=bar"

如果配置对象的所有config都根据您的模式有效,则API将返回201 Created,并且插件将与其配置一起存储在数据库中(在这种情况下为{foo =“bar”})。如果配置无效,Admin API将返回400 Bad Request和相应的错误消息。

模块

kong.plugins.<plugin_name>.schema

schema.lua规范

此模块将返回一个Lua表,其中包含将定义用户以后如何配置插件的属性的属性。 可用的属性是:

属性名称 Lua type 默认值 描述
no_consumer Boolen false 如果为true,则无法将此插件应用于特定的Consumer。此插件必须仅应用于服务和路由。例如:身份验证插件。
fields Table {} 你插件的schema,可用属性及其规则的键/值表。
self_check Function nil 如果要在接受插件配置之前执行任何自定义验证,则要实现的功能。

self_check函数必须按如下方式实现:

-- @param `schema` 描述插件配置的架构(规则)的表。
-- @param `config` 当前插件配置的键/值表。
-- @param `dao` DAO的一个实例 (查看 DAO 章节).
-- @param `is_updating` 一个布尔值,指示是否在更新的上下文中执行此检查。
-- @return `valid` 一个布尔值,指示插件的配置是否有效。
-- @return `error` 一个 DAO 错误 (查看 DAO 章节)

以下是一个可能的schema.lua文件的示例:

return {
  no_consumer = true, -- 此插件仅适用于服务或路由,
  fields = {
    -- 在此处描述您的插件配置架构。
  },
  self_check = function(schema, plugin_t, dao, is_updating)
    -- 执行任何自定义验证
    return true
  end
}

描述您的配置schema

schema.lua文件的fields自选描述了插件配置的schema。它是一个灵活的键/值表,其中每个键都是插件的有效配置属性,每个键都是一个描述该属性规则的表。例如:

 fields = {
    some_string = {type = "string", required = true},
    some_boolean = {type = "boolean", default = false},
    some_array = {type = "array", enum = {"GET", "POST", "PUT", "DELETE"}}
  }

以下是属性的规则列表:

规则 LUA TYPE(S) 可使用的值 描述
type string “id”, “number”, “boolean”, “string”, 
“table”, “array”, “url”, “timestamp”
验证属性的类型。
required boolean 默认值:false。
如果为true,则该属性必须存在于配置中。
unique boolean 默认值:false。
如果为true,则该值必须是唯一的(请参阅下面的注释)。
default any 如果未在配置中指定该属性,则将该属性设置为给定值。
immutable boolean 默认值:false。
如果为true,则在创建插件配置后将不允许更新该属性。
enum table 属性的可接受值列表。不接受此列表中未包含的任何值。
regex string 用于验证属性值的正则表达式。
schema table 如果属性的类型是table,则定义用于验证这些子属性的模式。
func function 用于对属性执行任何自定义验证的函数。请参阅后面的示例,了解其参数和返回值。
  • type:将转换从请求参数中检索的值。如果类型不是本机Lua类型之一,则会对其执行自定义验证:
    • id:必须是string
    • timestamp:必须是nember
    • uri:必须是有效的URL
    • array:必须是整数索引表(相当于Lua中的数组)。在Admin API中,可以通过在请求的正文中使用不同值的属性键的多次来发送这样的数组,或者通过单个body参数以逗号分隔。
  • unique:此属性对插件配置没有意义,但在插件需要在数据存储区中存储自定义实体时使用。
  • schema:如果您需要对嵌套属性进行深化验证,则此字段允许您创建嵌套模式。模式验证是递归的。任何级别的嵌套都是有效的,但请记住,这会影响插件的可用性。
  • 附加到配置对象但schema中不存在的任何属性也将使所述配置无效。

例子

key-auth插件的schema.lua文件定义了API密钥的可接受参数名称的默认列表,以及默认设置为false的布尔值:

-- schema.lua
return {
  no_consumer = true,
  fields = {
    key_names = {type = "array", required = true, default = {"apikey"}},
    hide_credentials = {type = "boolean", default = false}
  }
}

于是,当在handler.lua中实现插件的access()函数并且用户使用默认值启用插件时,您可以如下:

-- handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local CustomHandler = BasePlugin:extend()

function CustomHandler:new()
  CustomHandler.super.new(self, "my-custom-plugin")
end

function CustomHandler:access(config)
  CustomHandler.super.access(self)

  kong.log.inspect(config.key_names)        -- {"apikey"}
  kong.log.inspect(config.hide_credentials) -- false
end

return CustomHandler

请注意,上面的示例使用插件开发工具包(PDK)kong.log.inspect函数将这些值打印到Kong日志中。

一个更复杂的示例,可用于最终日志记录插件:

-- schema.lua

local function server_port(given_value, given_config)
  -- 自定义验证
  if given_value > 65534 then
    return false, "port value too high"
  end

  -- 如果环境是“开发”,8080将是默认端口
  if given_config.environment == "development" then
    return true, nil, {port = 8080}
  end
end

return {
  fields = {
    environment = {type = "string", required = true, enum = {"production", "development"}}
    server = {
      type = "table",
      schema = {
        fields = {
          host = {type = "url", default = "http://example.com"},
          port = {type = "number", func = server_port, default = 80}
        }
      }
    }
  }
}

这样的配置将允许用户将配置发布到您的插件,如下所示:

curl -X POST http://kong:8001/services/<service-name-or-id>/plugins \
    -d "name=my-custom-plugin" \
    -d "config.environment=development" \
    -d "config.server.host=http://localhost"

以下内容将在handler.lua中提供:

-- handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local CustomHandler = BasePlugin:extend()

function CustomHandler:new()
  CustomHandler.super.new(self, "my-custom-plugin")
end

function CustomHandler:access(config)
  CustomHandler.super.access(self)

  kong.log.inspect(config.environment) -- "development"
  kong.log.inspect(config.server.host) -- "http://localhost"
  kong.log.inspect(config.server.port) -- 8080
end

return CustomHandler

您还可以在Key-Auth插件源代码中查看schema的真实示例。

微服务 API 网关 Kong 插件开发 – 文件结构

原文地址:https://docs.konghq.com/1.1.x/plugin-development/file-structure/ (不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

本章假定你已经会使用Lua语言

介绍

将您的插件视为一组Lua模块。本章中描述的每个文件都被视为一个单独的模块。如果他们的名字遵循这个约定,Kong将检测并加载你的插件的模块:

kong.plugins.<plugin_name>.<module_name>

您的模块当然需要通过package.path变量访问,可以通过lua_package_path配置属性调整您的需求。但是,安装插件的首选方法是通过LuaRocks,它与Kong本身集成。有关LuaRocks安装的插件的更多信息,请参阅本指南后面的内容。

为了让Kong意识到必须查找插件的模块,你必须将它添加到配置文件中的plugins属性中,这是一个以逗号分隔的列表。例如

plugins = bundled,my-custom-plugin # 你的插件名称

或者,如果您不想加载任何自带的插件:

plugins = my-custom-plugin  # 你的插件名称

现在,Kong将尝试从以下命名空间加载几个Lua模块:

kong.plugins.my-custom-plugin.<module_name>

其中一些模块是必需的(例如handler.lua),有些是可选的,并且允许插件实现一些额外的功能(例如api.lua以扩展Admin API)。现在让我们准确描述您可以实现的模块以及它们的用途。

基本插件模块

在最基本的形式中,插件包含两个必需的模块:

simple-plugin
├── handler.lua
└── schema.lua
  • 每个函数将在请求的生命周期中的所需时刻运行。
  • 由用户。此模块保存该配置的模式并在其上定义规则,以便用户只能输入有效的配置值。

高级插件模块

有些插件可能需要与Kong更深入地集成:在数据库中拥有自己的表,在Admin API中公开端点等等……每个插件都可以通过向插件添加新模块来完成。如果它实现了所有可选模块,那么插件的结构如下:

complete-plugin
├── api.lua
├── daos.lua
├── handler.lua
├── migrations
│   ├── cassandra.lua
│   └── postgres.lua
└── schema.lua

以下是要实施的可能模块的完整列表以及其目的的简要说明。 本指南将详细介绍,让您掌握其中的每一个文件。

模块文件名称 是否必须 描述
api.lua No 定义Admin API中可用的端点列表,以与插件处理的实体自定义实体进行交互。
daos.lua No 定义DAO(数据库访问对象)列表,这些DAO是插件所需并存储在数据存储区中的自定义实体的抽象。
handler.lua Yes 一个接口的实现。每个函数都由Kong在请求的生命周期中的所需时刻运行。
migrations/xxxx.lua No 给定数据存储的相应迁移。只有当您的插件必须在数据库中存储自定义实体并通过daos.lua定义的其中一个DAO与它们进行交互时,才需要进行迁移。
schema.lua Yes 保存插件配置的架构,以便用户只能输入有效的配置值。

Key-Auth 插件是具有此文件结构的插件的示例。 有关详细信息,请参阅其源代码

微服务 API 网关 Kong 插件开发 – 安装/卸载插件

原文地址:https://docs.konghq.com/1.1.x/plugin-development/entities-cache/ (不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

介绍

Kong的自定义插件由Lua源文件组成,这些源文件需要位于每个Kong节点的文件系统中。本指南将为您提供逐步说明,使Kong节点了解您的自定义插件。这些步骤应该应用于Kong集群中的每个节点,以确保每个节点上都有自定义插件。

打包源

您可以使用常规打包策略(例如tar),也可以使用LuaRocks包管理器为您执行此操作。我们推荐使用LuaRocks,因为它在使用其中一个官方分发包时与Kong一起安装。

使用LuaRocks时,您必须创建一个rockspec文件,用来指定包的内容。有关示例,请参阅Kong插件模板,有关该格式的更多信息,请参阅有关rockspecs的LuaRocks文档

使用以下命令打包你的rock(来自插件仓库):

# install it locally (based on the `.rockspec` in the current directory)
$ luarocks make

# 打包已安装的rock
$ luarocks pack <plugin-name> <version>

假设你的插件rockspec的名字为kong-plugin-myPlugin-0.1.0-1.rockspec,上面就会变成:

$ luarocks pack kong-plugin-myPlugin 0.1.0-1

LuaRocks pack命令现在已经创建了一个.rock文件(这只是一个包含安装rock所需内容的zip文件)。

如果您不使用或不能使用LuaRocks,则使用tar将插件所包含的.lua文件打包到.tar.gz存档中。 如果目标系统上有LuaRocks,也可以包含.rockspec文件。

该插件的内容应该接近以下内容:

$ tree <plugin-name>
<plugin-name>
├── INSTALL.txt
├── README.md
├── kong
│   └── plugins
│       └── <plugin-name>
│           ├── handler.lua
│           └── schema.lua
└── <plugin-name>-<version>.rockspec

安装插件

要使Kong节点能够使用自定义插件,必须在主机的文件系统上安装自定义插件的Lua源。有多种方法:通过LuaRocks,或手动。 选择一个,然后跳转到第3部分。

  1. 来自新建的’rock’的LuaRocks。
    .rock文件是一个自包含的软件包,可以在本地安装,也可以从远程服务器安装。
    如果您的系统中安装了luarocks实用程序(如果使用其中一个官方安装包,可能就是这种情况),您可以在LuaRocks树(LuaRocks安装Lua模块的目录)中安装“rock”。
    它可以通过以下方式安装:

     $ luarocks install <rock-filename>
    

    文件名可以是本地名称,或任何支持的方法。
    例如:http://myrepository.lan/rocks/myplugin-0.1.0-1.all.rock

  2. 从源档案中通过LuaRocks安装。 如果您的系统中安装了luarocks实用程序(如果使用其中一个官方安装包,可能就是这种情况),您可以在LuaRocks树(LuaRocks安装Lua模块的目录)中安装Lua源代码。
    您可以通过将当前目录更改为提取的存档来实现,其中rockspec文件是:

     $ cd <plugin-name>
    

    然后运行以下命令:

     $ luarocks make
    

    这将在系统的LuaRocks树中的kong/plugins/<plugin-name>中安装Lua源代码,其中所有的Kong源都已存在。

  3. 手动
    安装插件源的一种更保守的方法是避免“污染”LuaRocks树,而是将Kong指向包含它们的目录。
    这是通过调整Kong配置的lua_package_path属性来完成的。如果你熟悉它,那么这个属性是Lua VM的LUA_PATH变量的别名。
    这些属性包含以分号分隔的目录列表,用于搜索Lua源。它应该在您的Kong配置文件中设置如下:

     lua_package_path = /<path-to-plugin-location>/?.lua;
    

    继续:
    4./<path-to-plugin-location>是包含提取的存档的目录的路径。它应该是归档中kong目录的位置。
    5.?是一个占位符,将被kong.plugins替换。<plugin-name>当Kong将尝试加载你的插件。
    6.;;“默认Lua路径”的占位符。不要改变它。
    例如:
    插件位于文件系统上,使处理程序文件为:

     /usr/local/custom/kong/plugins/<something>/handler.lua
    

    kong目录的位置是:/usr/local/custom,因此正确的路径设置将是:

     lua_package_path = /usr/local/custom/?.lua;;
    

    多个插件:

    如果您希望以这种方式安装两个或更多自定义插件,可以将变量设置为:

      lua_package_path = /path/to/plugin1/?.lua;/path/to/plugin2/?.lua;;
    
     7.`;`是目录之间的分隔符。
     8.`;;`仍然意味着“默认的Lua路径”。
    

    注意:您还可以通过其等效的环境变量KONG_LUA_PACKAGE_PATH设置此属性。

提醒:无论您使用哪种方法来安装插件的源,您仍必须为Kong群集中的每个节点执行此操作。

加载插件

您现在必须将自定义插件的名称添加到Kong配置中的插件列表中(在每个Kong节点上):

plugins = bundled,<plugin-name>

或者,如果您不想包含默认捆绑的插件:

plugins = <plugin-name>

或者

plugins = plugin1,plugin2

注意:您还可以通过其等效的环境变量KONG_PLUGINS来设置此属性。 提醒:不要忘记更新Kong群集中每个节点的plugins指令。 提醒:插件重启后会生效:

kong restart

但是,如果你想在kong永不停止时应用插件,你可以使用:

kong prepare
kong reload

验证加载插件

你现在应该能够毫无问题地启动Kong。 请参阅自定义插件有关如何在服务,路由或消费者实体上启用/配置插件的说明。

为确保您的插件由Kong加载,您可以使用调试日志级别启动Kong:

log_level = debug

或者

KONG_LOG_LEVEL=debug

然后,您应该看到正在加载的每个插件的以下日志:

[debug] Loading plugin <plugin-name>

删除插件

完全删除插件有三个步骤。

  1. 从您的Kong Service或Route配置中删除插件。确保它不再适用于全局,也不适用于任何服务,路由或使用者。对于整个Kong集群,只需执行一次,不需要重新启动/重新加载。此步骤本身将使插件不再使用。但它仍然可用,仍然可以重新应用插件。
  2. plugins指令中删除插件(在每个Kong节点上)。确保在执行此操作之前已完成步骤1。在此步骤之后,任何人都无法将插件重新应用于任何Kong Service,Route,Consumer甚至全局。此步骤需要重新启动/重新加载Kong节点才能生效。
  3. 要彻底删除插件,请从每个Kong节点中删除与插件相关的文件。在删除文件之前,请确保已完成步骤2,包括重新启动/重新加载Kong。如果你使用LuaRocks来安装插件,你可以使用luarocks remove <plugin-name>来删除它。

分发插件

这样做的首选方法是使用LuaRocks,Lua模块的包管理器。它称这些模块为“rocks”。 您的模块不必存在于Kong存储库中,但如果您希望维护Kong设置,则可能就是这样。

通过在rockspec文件中定义模块(及其最终依赖项),您可以通过LuaRocks在您的平台上安装这些模块。

您也可以在LuaRocks上传模块并将其提供给所有人!

有关示例,请参阅Kong插件模板,有关该格式的更多信息,请参阅有关rockspecs的LuaRocks文档

故障排除

由于以下几个原因,配置错误的自定义插件可能无法启动:

  • “plugin is in use but not enabled” -> 您从另一个节点配置了一个自定义插件,并且该插件配置在数据库中,但您尝试启动的当前节点在其plugins指令中没有它。要解决此问题,请将插件的名称添加到节点的plugins指令中。
  • “plugin is enabled but not installed” -> 插件的名称出现在plugins指令中,但是Kong无法从文件系统加载handler.lua源文件。要解决此问题,请确保正确设置lua_package_path指令以加载此插件的Lua源。
  • “no configuration schema found for plugin” -> 插件已在plugins指令中安装,但是Kong无法从文件系统加载schema.lua源文件。要解决此问题,请确保schema.lua文件与插件的handler.lua文件一起存在。

微服务 API 网关 Kong 插件开发 – 缓存自定义实体

原文地址:https://docs.konghq.com/1.1.x/plugin-development/entities-cache/ (不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

介绍

您的插件可能需要经常访问每个请求 和/或 响应的自定义实体(在前一章中可见)。通常,加载它们一次并将它们缓存在内存中会显着提高性能,同时确保数据存储区不会因负载增加而受到压力。

考虑一个api-key身份验证插件,需要在每个请求上验证api-key,从而在每个请求中从数据存储区加载自定义凭据对象。当客户端提供api-key以及请求时,通常你会查询数据存储区以检查该密钥是否存在,然后,阻止请求或检索 Consumer ID以识别用户,这将在每个请求上发生,并且效率非常低:

  • 查询数据存储会增加每个请求的延迟,使请求处理速度变慢。
  • 数据存储区也会受到负载增加,可能崩溃或减速的影响,这反过来会影响每个Kong节点。

为避免每次都查询数据存储区,我们可以在节点上缓存内存中的自定义实体,这样频繁的实体查找不会每次都触发数据存储区查询(仅限第一次),而是在内存中查询,从数据存储区查询它(特别是在高负载下)更快更可靠。

Modules

kong.plugins.<plugin_name>.daos

缓存自定义实体

一旦定义了自定义实体,就可以使用插件开发工具包提供的 kong.cache 模块将它们缓存在代码中的内存中:

local cache = kong.cache

有两级缓存:

  1. L1:Lua内存缓存 – nginx worker的本地缓存这可以包含任何类型的Lua值。
  2. L2:共享内存缓存(SHM) – nginx节点的本地缓存,但在所有工作者之间共享。这只能保存标量值,因此需要(反)序列化。

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

该模块公开以下功能:

FUNCTION NAME DESCRIPTION
value, err = cache:get(key, opts?, cb, ...) 从缓存中检索值。如果缓存没有值(未命中),在保护模式下调用cbcb必须返回一个(并且只有一个)将被缓存的值。它可能会抛出错误,因为这些错误会被Kong捕获并正确记录在ngx.ERR级别。 此函数会缓存否定结果(nil)。因此,在检查错误时必须依赖其第二个参数err
ttl, err, value = cache:probe(key) 检查是否缓存了值。如果是,则返回其剩余的TTL。它没有,返回零。缓存的值也可以是负缓存。第三个返回值是被缓存的值本身。
cache:invalidate_local(key) 从节点缓存中移除一个值。
cache:invalidate(key) 从节点的缓存中删除一个值,并将删除事件传播到集群中的所有其他节点。
cache:purge() 从节点缓存中删除所有值。

回到我们的身份验证插件示例,要查找具有特定api-key的凭据,我们将编写类似于:

-- access.lua

local function load_entity_key(api_key)
  -- 重要: 回调是在锁中执行的,因此我们不能在这里终止请求,我们必须始终返回。
  local apikeys, err = kong.dao.apikeys:find_all({key = api_key}) -- Lookup in the datastore
  if err then
    error(err) -- 被kong.cache捕获并记录
  end

  if not apikeys then
    return nil -- 没有找到 (cached for `neg_ttl`)
  end

  -- 假设键是唯一的,我们总是只有一个值…
  return apikeys[1] -- cache the credential (cached for `ttl`)
end

-- 从请求querystring检索apikey
local querystring = kong.request.get_query()
local apikey = querystring.apikey

-- 我们使用缓存。首先检查apikey是否已经存在
-- 存储在内存缓存中的键值为:“apikeys”。. .apikey
-- 如果不是,则查找数据存储并返回凭据对象。
-- 内部缓存。get将把值保存在内存中,然后返回凭据。
local credential, err = kong.cache:get("apikeys." .. apikey, nil,
                                       load_entity_key, apikey)
if err then
  return kong.response.exit(500, "Unexpected error: " .. err)
end

if not credential then
  -- 缓存和数据存储中没有凭据
  return kong.response.exit(403, "Invalid authentication credentials")
end

-- 如果凭据存在且有效,则设置上游标头
kong.service.request.set_header("X-API-Key", credential.apikey)

注意,在上面的示例中,我们使用插件开发工具包中的各种组件与请求、缓存模块交互,甚至从插件生成响应。
现在,有了上面的机制,一旦使用者使用API键发出请求,缓存就会被认为是热的,随后的请求不会导致数据库查询。 在Key-Auth插件处理程序中,缓存用于多个位置。让我们来看看官方插件是如何使用缓存的。

更新或者删除一个缓存实体

每次在数据存储中更新或删除缓存的自定义实体(即使用Admin API)时,都会在数据存储中的数据与缓存在Kong节点内存中的数据之间产生不一致。为了避免这种不一致,我们需要从内存存储中删除缓存的实体,并强制Kong从数据存储中再次请求它。我们将此过程称为缓存失效。

缓存失效

如果希望缓存的实体在CRUD操作时失效,而不是等待它们到达TTL,则必须执行以下步骤。对于大多数实体,这个过程都可以自动化,但是手动订阅一些CRUD事件可能需要使一些具有更复杂关系的实体失效。

自动缓存失效

如果依赖于实体模式的cache_key属性,则可以为实体提供开箱即用的缓存失效。例如,在下面的模式中:

local SCHEMA = {
  primary_key = { "id" },
  table = "keyauth_credentials",
  cache_key = { "key" }, -- 此实体的缓存键
  fields = {
    id = { type = "id" },
    created_at = { type = "timestamp", immutable = true },
    consumer_id = { type = "id", required = true, foreign = "consumers:id"},
    key = { type = "string", required = false, unique = true }
  }
}

return { keyauth_credentials = SCHEMA }

我们可以看到,我们将这个API key实体的缓存键声明为它的key属性。这里使用key是因为它有一个唯一的约束。因此,添加到cache_key的属性应该产生唯一的组合,这样就不会有两个实体产生相同的缓存键。
添加此值允许您在该实体的DAO上使用以下函数:

cache_key = kong.db.<dao>:cache_key(arg1, arg2, arg3, ...)

其中参数必须是模式的cache_key属性中指定的属性,按照指定的顺序,然后,此函数计算确保唯一的字符串值cache_key
例如,如果我们要生成API密钥的cache_key:

local cache_key = kong.db.keyauth_credentials:cache_key("abcd")

这将为API密钥“abcd”(从查询的一个参数中检索)生成一个cache_key,我们可以使用它来从缓存中检索密钥(如果缓存是未命中,则从数据库中获取):

local apikey = kong.request.get_query().apikey
local cache_key = kong.db.keyauth_credentials:cache_key(apikey)

local credential, err = kong.cache:get(cache_key, nil, load_entity_key, apikey)
if err then
  return kong.response.exit(500, "Unexpected error: " .. err)
end

-- do something with the credential

如果cache_key是这样生成的并且在实体的模式中指定,则缓存失效将是一个自动过程:影响此API密钥的每个CRUD操作都将使make生成受影响的cache_key,并将其广播到群集上的所有其他节点,以便他们可以从缓存中逐出该特定值,并在下一个请求中从数据存储区中获取新值。

当父实体正在接收CRUD操作时(例如,拥有此API密钥的消费者,根据我们的模式的consumer_id属性),Kong为父实体和子实体执行缓存失效机制。

注意:请注意Kong提供的负面缓存。在上面的示例中,如果给定密钥的数据存储区中没有API密钥,则缓存模块将存储未命中,就像它是命中一样。这意味着Kong也会传播“创建”事件(使用此给定密钥创建API密钥的事件),以便存储未命中的所有节点都可以驱逐它,并从数据存储中正确地获取新创建的API密钥。

请参阅群集指南以确保为此类失效事件正确配置了群集。

手动缓存失效

在某些情况下,实体架构的cache_key属性不够灵活,并且必须手动使其缓存无效。原因可能是插件没有通过传统的foreign =“parent_entity:parent_attribute”语法定义与另一个实体的关系,或者因为它没有使用来自其DAO的cache_key方法,或者甚至因为它以某种方式滥用缓存机制。

在这些情况下,您可以手动将自己的订户设置为Kong正在收听的相同失效频道,并执行您自己的自定义失效工作。

要监听Kong内部的失效通道,请在插件的init_worker处理程序中实现以下内容:

function MyCustomHandler:init_worker()
  -- listen to all CRUD operations made on Consumers
  kong.worker_events.register(function(data)

  end, "crud", "consumers")

  -- or, listen to a specific CRUD operation only
  kong.worker_events.register(function(data)
    kong.log.inspect(data.operation)  -- "update"
    kong.log.inspect(data.old_entity) -- old entity table (only for "update")
    kong.log.inspect(data.entity)     -- new entity table
    kong.log.inspect(data.schema)     -- entity's schema
  end, "crud", "consumers:update")
end

一旦上述侦听器适用于所需的实体,您就可以根据需要对插件已缓存的任何实体执行手动失效。 例如:

kong.worker_events.register(function(data)
  if data.operation == "delete" then
    local cache_key = data.entity.id
    kong.cache:invalidate("prefix:" .. cache_key)
  end
end, "crud", "consumers")

扩展Admin API

您可能已经知道,Admin API是Kong用户与Kong通信以设置其API和插件的地方。他们可能还需要能够与您为插件实现的自定义实体进行交互(例如,创建和删除API密钥)。这样做的方法是扩展Admin API,我们将在下一章详细介绍:扩展Admin API

微服务 API 网关 Kong 插件开发 – 实现自定义逻辑

简介

Kong插件允许您在 request/response 的生命周期中的几个入口点注入自定义逻辑(使用 Lua 语言),因为它由Kong代理。为此,必须实现base_plugin.lua 接口的一个或多个方法。这些方法将在命名空间下的模块中实现:kong.plugins。<plugin_name> .handler

Module

kong.plugins.<plugin_name>.handler

可用的请求上下文

插件接口允许您覆盖handler.lua文件中的以下任何方法,以在Kong的执行生命周期的各个入口点实现自定义逻辑:

函数名 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在调用时给出:插件的配置。此参数是Lua表,并包含用户根据插件的架构(在schema.lua模块中描述)定义的值。有关插件模式的更多信息将在下一章中介绍。

handler.lua规范

handler.lua文件必须返回一个表,该表实现了您希望执行的函数。为简洁起见,这里有一个注释示例模块,它实现了所有可用的方法:

-- 扩展基本插件处理程序是可选的,因为Lua中没有真正的接口概念,
-- 但是基本插件处理程序的方法可以从子实现中调用,
-- 并将在“error.log”中打印日志(其中打印所有日志)。
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)

  -- Implement any custom logic here
end

function CustomHandler:rewrite(config)
  -- Eventually, execute the parent implementation
  -- (will log that your plugin is entering this context)
  CustomHandler.super.rewrite(self)

  -- Implement any custom logic here
end

function CustomHandler:access(config)
  -- Eventually, execute the parent implementation
  -- (will log that your plugin is entering this context)
  CustomHandler.super.access(self)

  -- Implement any custom logic here
end

function CustomHandler:header_filter(config)
  -- Eventually, execute the parent implementation
  -- (will log that your plugin is entering this context)
  CustomHandler.super.header_filter(self)

  -- Implement any custom logic here
end

function CustomHandler:body_filter(config)
  -- Eventually, execute the parent implementation
  -- (will log that your plugin is entering this context)
  CustomHandler.super.body_filter(self)

  -- Implement any custom logic here
end

function CustomHandler:log(config)
  -- Eventually, execute the parent implementation
  -- (will log that your plugin is entering this context)
  CustomHandler.super.log(self)

  -- Implement any custom logic here
end

-- 该模块需要返回创建的表
-- 让Kong 可以执行这些功能。
return CustomHandler

当然,插件本身的逻辑可以抽象到另一个模块中,并从处理程序模块调用。许多现有的插件在逻辑冗长时已经选择了这种模式,但它是完全可选的:

local BasePlugin = require "kong.plugins.base_plugin"

-- 实际的逻辑是在这些模块中实现的
local access = require "kong.plugins.my-custom-plugin.access"
local body_filter = require "kong.plugins.my-custom-plugin.body_filter"

local CustomHandler = BasePlugin:extend()

function CustomHandler:new()
  CustomHandler.super.new(self, "my-custom-plugin")
end

function CustomHandler:access(config)
  CustomHandler.super.access(self)

  -- 从“access”中加载的模块执行任何函数,
  -- 例如,`execute()`并将插件的配置传递给它。
  access.execute(config)
end

function CustomHandler:body_filter(config)
  CustomHandler.super.body_filter(self)

  -- Execute any function from the module loaded in `body_filter`,
  -- for example, `execute()` and passing it the plugin's configuration.
  body_filter.execute(config)
end

return CustomHandler

有关实际处理程序代码的示例,请参阅Key-Auth插件的源代码

插件开发套件

在这些阶段中实现的逻辑很可能必须与请求/响应对象或核心组件交互(例如访问缓存、数据库……)。Kong提供了一个插件开发套件(简称PSK),一组Lua函数和变量,插件可以使用这些Lua函数和变量来执行各种网关操作,确保它们与将来的Kong版本向前兼容。

当您试图实现一些需要与Kong交互的逻辑时(例如检索请求头、从插件生成响应、记录一些错误或调试信息……),您应该参考插件开发工具包

插件执行顺序

一些插件可能依赖于其他插件的执行来执行一些操作。例如,依赖于消费者身份的插件必须在身份验证插件之后运行。考虑到这一点,Kong定义了插件执行之间的优先级,以确保顺序得到遵守。

你的插件的优先级可以通过一个属性来配置,在返回的handler table 中一个数字:

CustomHandler.PRIORITY = 10

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

插件 优先级
pre-function +inf
zipkin 100000
ip-restriction 3000
bot-detection 2500
cors 25000
jwt 1005
oauth2 1004
key-auth 1003
ldap-auth 1002
basic-auth 1001
hmac-auth 1000
request-size-limiting 951
acl 950
rate-limiting 901
response-ratelimiting 900
request-transformer 801
response-transformer 800
aws-lambda 750
azure-functions 749
prometheus 13
http-log 12
statsd 11
datadog 10
file-log 9
udp-log 8
tcp-log 7
loggly 6
syslog 4
galileo 3
request-termination 2
correlation-id 1
post-function -1000

在Kong的插件中使用自建数据表的问题

目前正在开发的kong 插件中需要用到自己新建表,然后对其进行读写,但是由于kong的版本升级原因,有一些问题在官方文档中没有提到,导致一直读取失败,主要注意的点如下:

1.迁移文件的差异

kong的版本>= 1.0

迁移文件的名字为000_base_xxx.lua,其中格式格式如下:

return {
   postgres = {
       up = [[
         CREATE TABLE IF NOT EXISTS "xxxxx" (
           "id"	uuid	NOT NULL   DEFAULT uuid_generate_v4()   PRIMARY KEY,
           "xx"	int	NOT NULL,
           "xx"	bool,
           "xx"	json,
           "xx"	json
         );
       ]],
   },
   cassandra ={
       up =[[

       ]]
   }
}

kong的版本小于1.0版本

迁移文件的名字为postgres.luacassandra.lua,其中格式如下: 此为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)
      );

      DO $$
      BEGIN
        IF (SELECT to_regclass('public.keyauth_key_idx')) IS NULL THEN
          CREATE INDEX keyauth_key_idx ON keyauth_credentials(key);
        END IF;
        IF (SELECT to_regclass('public.keyauth_consumer_idx')) IS NULL THEN
          CREATE INDEX keyauth_consumer_idx ON keyauth_credentials(consumer_id);
        END IF;
      END$$;
    ]],
    down = [[
      DROP TABLE keyauth_credentials;
    ]]
  }
}

2.daos.lua的差异

daos.lua的格式也是有差异的,但是这部分的差异在文档中是没有体现的

kong的版本>= 1.0

local typedefs = require "kong.db.schema.typedefs"

local xxx = {
   primary_key = { "id" },
   name = "app_config",
   fields = {
       { id = typedefs.uuid },
       { xx = { type = "string", required = false, unique = true }, },
       { xx = { type = "string", required = false, unique = true } },
       { xx = { type = "string", required = false } },
       { xx = { type = "string", required = false } },
       { xx = { type = "string", required = false } }
   }
}
return {
   zz = xxx,
}

kong的版本小于1.0版本

-- daos.lua
local SCHEMA = {
  primary_key = {"id"},
  table = "keyauth_credentials", -- the actual table in the database
  fields = {
    id = {type = "id", dao_insert_value = true}, -- a value to be inserted by the DAO itself (think of serial ID and the uniqueness of such required here)
    created_at = {type = "timestamp", immutable = true, dao_insert_value = true}, -- also interted by the DAO itself
    consumer_id = {type = "id", required = true, foreign = "consumers:id"}, -- a foreign key to a Consumer's id
    key = {type = "string", required = false, unique = true} -- a unique API key
  }
}
return {keyauth_credentials = SCHEMA} -- this plugin only results in one custom DAO, named `keyauth_credentials`

尤其注意fields字段的结构差异,不然很容易弄混

3.调用差异

kong的版本>= 1.0

-- 通过某个字段查询
local app_configs, err = kong.db.app_config:select_by_app_key(app_key)
-- 通过主键查询
local app_group_configs, err = kong.db.app_group_config:select({ id = group_id })

kong的版本小于1.0版本

local app_configs, err = kong.dao.app_config:find()
local app_configs, err = kong.dao.app_config:find_all()

微服务 API 网关 Kong 插件开发 – 访问数据存储区

原文地址:https://docs.konghq.com/1.1.x/plugin-development/access-the-datastore/ (不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

简介

Kong通过我们称为“DAOs”的类与模型层交互。本章将详细介绍与数据存储交互的可用API。Kong支持两个主数据存储:Cassandra 3.x.xPostgreSQL 9.5+

kong.db

Kong 的所有实例表示为:

  • 描述实体在数据存储区中与哪个表相关的模式,对其字段的约束,如外键,非空约束等…此schema是插件配置章节中描述的表。
  • DAO类的一个实例映射到当前正在使用的数据库(Cassandra或PostgreSQL)。此类的方法使用模式并公开方法来插入,更新,查找和删除该类型的实体。

Kong的核心实体是:Services, Routes, Consumers 和 Plugins。所有这些都可以作为数据访问对象(DAO)通过kong.db全局单例访问:

-- Core DAOs
local services_dao = kong.db.services
local routes_dao = kong.db.routes
local consumers_dao = kong.db.consumers
local plugins_dao = kong.dao.plugins

来自Kong的核心实体和插件中的自定义实体都可以通过kong.db.*

The DAO Lua API

DAO类负责在数据存储区中的给定表上执行的操作,通常映射到Kong的实体。所有底层支持的数据库(目前是Cassandra和PostgreSQL)都遵循相同的接口,从而使DAO与所有这些兼容。 例如,插入服务和插件非常简单:

local inserted_service, err = kong.db.services:insert({
  name = "mockbin",
  url = "http://mockbin.org",
})

local inserted_plugin, err = kong.db.plugins:insert({
  name = "key-auth",
  service_id = { id = inserted_service.id },
})

有关在插件中使用的DAO的实际示例,请参阅 Key-Auth plugin 的源代码