说明

以下内容说明基于1.4.0版本,具体的配置情况请参考“01-kong 网关配置”。

nginx.conf

在 kong 环境中,实际的配置文件 /usr/loca/kong/nginx.conf 和 /usr/local/kong/nginx-kong.conf 都是基于模版文件 /usr/local/share/lua/5.1/kong/templates/nginx.lua 和 /usr/local/share/lua/5.1/kong/templates/nginx-kong.lua 文件结合 /etc/kong.conf 配置自动生成,属于只读型文件,对 ngingx.conf 和 nginx-kong.conf 的修改不会生效。

nginx.conf 中包含 nginx-kong.conf 文件,kong 针对 nginx 的全局配置都在 nginx-kong.conf 文件中。

注意

正常情况下 kong 的配置内容都通过 API 接口的方式完成设置,如果需要 kong 主机增加静态解析,需要修改模版文件。

nginx-kong.conf

nginx 的主体配置内容都在 nginx-kong.conf 文件中。

通过配置文件可以看到 Kong 环境的配置是针对 nginx 不同执行阶段的操作。

Kong.init()

Kong 读取的配置文件时 prefix 目录下的.kong_env,这个文件是 kong prepare 的时候生成的,是 kong 启动时真正使用的配置文件,如果这文件不存在,先查找/etc/kong/kong.conf,然后查找/etc/kong.conf。

操作数据库的代码全部都在kong/db中,kong/db/init.lua中的 DB.new()实例化一个DB对象。

daos中存放的每个表的操作句柄,是在kong/db/init.lua中用kong/db/dao.lua中的 new 函数实例化的,DAO.new()是kong/db/dao.lua中的_M.new()。

集成的插件

配置文件中的 plugins 配置为 bundled 时,加载kong/constants.lua的 plugins 中的所有插件,否则只加载指定的插件。

插件结构说明

插件通过插件开发包(Plugin Development Kit)处理用户的请求和响应。插件开发包(PDK)是 Lua 函数集,也是 Kong 系统对外暴露的接口。

文件结构

插件是由多个 Lua modules 组成,在本章节中,一个Lua文件等同于一个Lua module。 要求模块名称格式如下:kong.plugins.<plugin_name>.<module_name>

模块必须在 Lua Path(package path)路径之下,可以在 kong.conf 下通过修改 lua_package_path 来改变 Lua Path。推荐使用 LuaRocks 来安装插件,Kong 已将它集成进去。

在 kong.conf 中,通过 plugins 项添加自定义的插件名称(Comma-separated list)。plugins 的默认值是 bundled,即默认只会加载 Kong 官网发布的插件集。Kong 只是加载了插件的源代码,但这并不意味着插件的代码会被执行(称为插件可用),需要通过 Kong 的管理接口进行配置。

注意

如果插件已经绑定到了某个实体(如Service),但随后在配置文件中将它去掉了,则 Kong 会报启动失败。

基本的插件模块

插件的有些模块是必须的,如 handler.lua,而有些则是可选的,如 api.lua。

simple-plugin
├── handler.lua
└── schema.lua

这个插件由两个模块组成,都是必须的。

handler.lua: 模块中不同函数将会在请求生命周期的不同时刻被执行。

schema.lua: 该模块提供了插件功能的使用规则,比如有哪些配置,配置的值会是哪些。

高级插件模块

有些插件可能必须与Kong进行深度集成,比如插件拥有自己的数据库,需要在管理 API 中暴露自己的 endpoints。这些都可以通过在插件中增加新的模块来完成。

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

这是一个完整的模块列表,下面简单介绍每一个模块的作用。

名称要求描述
api.lua非必须自定义一个endpoints列表
daos.lua非必须定义数据库访问对象(DAO, Database Access Objects)的 schema
handler.lua必须模块中不同函数将会在请求生命周期的不同时刻被执行
sehema.lua必须该模块提供了插件功能的使用规则,比如有哪些配置,配置的值会是哪些
migrations/*.lua非必须当插件存储自定义的实体到数据库中,并通过daos.lua来进行交互,Migrations才是必须的

实现自定义的逻辑

在处理请求的各个环节,Kong 插件允许通过 Lua 代码注入自定义的逻辑。 base_plugin.lua 接口定义了多个方法,对应了处理请求的各个环节,需要在 kong.plugins.<plugin_name>.handler 模块内实现这些方法。 

名称执行阶段描述
init_workerinit_worker_by_lua在每个 Nginx worker 进程启动时被执行
certificatessl_certificate_by_lua_block在 SSL 握手的 SSL certificate serving 阶段被执行
rewriterewrite_by_lua_block每个请求一到来时被执行,注意这时还没有进行路由匹配,所以识别不出来 Service 和 Consumer,所以当插件被配置为全局插件时,该方法才会被执行
accessaccess_by_lua请求在被转发至 upstream service 之前被执行
header_filterheader_filter_by_lua在接收到 upstream service 响应的所有头部信息后被执行
body_filterbody_filter_by_lua在 upstream service 的每个响应快(chunk)之后被执行,该方法可能会被执行多次
loglog_by_lua在响应信息全部发送给客户端之后被执行

所有方法都接收一个参数,即插件配置信息。这个参数是一个 lua table,包含了用户通过管理API写进去的配置信息。handler.lua 文件必须返回一个 table,包含了你实现的函数。模板如下:


-- 通过扩展Base Plugin handler来实现是可选的,在Lua中根本没有接口的概念。
-- Base Plugin handler的方法可以在子类中被调用。它将会向error.log中打印日志。
local BasePlugin = require "kong.plugins.base_plugin"
local CustomHandler = BasePlugin:extend()

-- Your plugin handler's constructor. If you are extending the
-- 你的插件handler的构造方法。如果你通过Base Plugin handler进行扩展,实例化一个名称为插件名称的table。
function CustomHandler:new()
CustomHandler.super.new(self, "my-custom-plugin")
end

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

-- Implement any custom logic here
end

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

-- Implement any custom logic here
end

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

-- Implement any custom logic here
end

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

-- Implement any custom logic here
end

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

-- Implement any custom logic here
end

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

-- Implement any custom logic here
end

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

-- Implement any custom logic here
end

-- 最后一定要返回这个table,供Kong来调用里面的函数
return CustomHandler


如果觉得handler模块比较大,希望组织成一个函数就是一个模块,那么你可以按照下面的方法来组织自己的代码。

local BasePlugin = require "kong.plugins.base_plugin"

-- The actual logic is implemented in those modules
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(config)
end

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

return CustomHandler

插件执行顺序

有时插件的执行是有先后顺序的,比如认证类插件一般需要优先执行。在handler模块中,通过如下配置来指定优先级:

CustomHandler.PRIORITY = 10

数字越大优先级越高。

插件配置(SCHEMA.LUA)

用户通过管理API将插件和其它实体绑定在一起,这使得插件变得可用。绑定时可以为该插件实例指定参数配置。 插件实例的配置信息存储在数据库当中,Kong据此构造配置table(也称为schema, key/value格式),并将它以参数的形式传递给handler的函数。

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

这个请求是否能成功取决于config.foo=bar的配置是否符合schema的要求。

schema.lua 也返回一个 Lua table,配置详情如下:

属性名称lua 类型默认值描述
no_consumerBooleanfalsetrue 表示该插件不适合应用到 Consumer 上,只能被应用在 Service 和 Route 上,例如认证类插件。
fieldsTable{} 插件的 schema。描述了插件会有哪些属性,以及配置规则
self_checkFunctionnil如果需要插件本身对用户的配置数据进行校验,就设置该函数

self_check函数签名如下:

-- @param `schema` A table describing the schema (rules) of your plugin configuration.
-- @param `config` A key/value table of the current plugin's configuration.
-- @param `dao` An instance of the DAO (see DAO chapter).
-- @param `is_updating` A boolean indicating whether or not this check is performed in the context of an update.
-- @return `valid` A boolean indicating if the plugin's configuration is valid or not.
-- @return `error` A DAO error (see DAO chapter)

fields是描述schema的,schema是多个key-value对组成的表,其中value是一个嵌套table,如下:

fields = {
some_string = {type = "string", required = true},
some_boolean = {type = "boolean", default = false},
some_array = {type = "array", enum = {"GET", "POST", "PUT", "DELETE"}}
}
规则lua 类型可接受的值描述
typestring“id”, “number”, “boolean”, “string”,
“table”, “array”, “url”, “timestamp”
验证属性值的类型
requiredboolean默认值为false插件实例化时该属性是否必须被设置
uniqueboolean默认值为false不同插件实例的值必须是不同的
defaultany
这个属性的默认值
immutableboolean
创建以后不得被修改
enumtableinteger indexed table可接受的属性值的列表,不在此列表内的设置都是错误的
regexstringA valid PCRE regular expression 正则表达式用于验证属性值的有效性
schematableA nested schema definition 如果属性类型是表,该字段用于定义 sub-properties
funcfunction
用于验证该属性值的函数

最后,给出一个插件配置的小例子:

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

访问数据库

Kong支持两种数据库: Cassandra 3.x.x and PostgreSQL 9.5+

在Kong系统中,所有实体都有:

1. schema。插件配置最重要的是它的 schema,且 schema 是存储在数据库当中的。一个 schema 可类比为 Mysql 的一张表。

2. DAO 类的一个实例,它映射到数据库就是 schema 的一条记录。DAO 类的方法通过 schema 规则,对外暴露了插入、更新、查找、删除实体等功能。

Kong 的核心实体包括:Service,Route,Consumers,Plugin。Service, Route 和 Consume r通过 kong.db 来抽象出接口,其它实体通过 kong.dao 来抽象接口。 所以,如果 daos.lua 文件内定义的是schema,Kong 获悉后会为你提供 CRUD 操作!以下是 key-auth 插件的 daos.lua 文件。

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

return {
keyauth_credentials = {
primary_key = { "id" },
name = "keyauth_credentials",
endpoint_key = "key",
cache_key = { "key" },
fields = {
{ id = typedefs.uuid },
{ created_at = typedefs.auto_timestamp_s },
{ consumer = { type = "foreign", reference = "consumers", default = ngx.null, on_delete = "cascade", }, },
{ key = { type = "string", required = false, unique = true, auto = true }, },
},
},
}

如果插件使用多个 schema,那么每个 schema 作为最外层 table 的一个 value 而存在。schema 规范如下:

属性名称lua 类型描述
primary_keyInteger indexed table数组中每个元素都是 schema 的一个组件,支持复合主键
fields.*.dao_insert_valueBoolean如果为 true 表示该 field 将会由系统自动填充
fields.*.queryableBoolean如果为true表示Cassandra会为这一列建立索引,一般也是过滤的字段
fields.*.foreignString这一列是一个外键

我们在handler中就可以借助Kong来操作该schema,如下:

local key_credential, err = kong.dao.key_credentials:insert({
consumer_id = consumer.id,
key = "abcd"
})

kong 是 Kong 对外暴露的所有接口的命名空间,kong.db 和 kong.dao 是 Kong 的数据库实例,且是全局唯一的实例。kong.dao.key_credentials 访问插件的数据。

迁移

如果插件需要存储的不止是插件配置信息,不但要在 daos.lua 中定义 schema 信息(由它演化出来的只是 CRUD 操作),还需要 migrations 模块。 Kong 使用 migrations 模块来创建表或修改表,迁移模块包含了一组迁移操作。我们要为每个迁移操作进行唯一的命名,并且有 up 和 down 两个域。 这两个域要么是简单的字符串形式或者是 Lua 代码(执行复杂的迁移操作),up 域是前进迁移操作,down 域是回滚迁移操作。从文件结构上来说,需要定义如下两个文件:

"kong.plugins.<plugin_name>.migrations.cassandra"
"kong.plugins.<plugin_name>.migrations.postgres"

当然,如果你的插件只支持postgres,那么你没有必要定义cassandra。如下:

-- cassandra.lua
return {
{
name = "2015-07-31-172400_init_keyauth",
up = [[
CREATE TABLE IF NOT EXISTS keyauth_credentials(
id uuid,
consumer_id uuid,
key text,
created_at timestamp,
PRIMARY KEY (id)
);

CREATE INDEX IF NOT EXISTS ON keyauth_credentials(key);
CREATE INDEX IF NOT EXISTS keyauth_consumer_id ON keyauth_credentials(consumer_id);
]],
down = [[
DROP TABLE keyauth_credentials;
]]
}
}

--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;
]]
}
}

PostgreSQL 和 Cassandra 是有区别的,比如 Cassandra 不支持”NOT NULL”, “UNIQUE”或”FOREIGN KEY”等操作。但是 Kong 支持的 schema 语义是不会随着数据库的切换而改变的。 所以 Kong 为 Cassandra 提供了这些功能,这样一来,当你替换数据库的时候,几乎零成本。 在 daos.lua 中定义了 schema 的这些属性,对于 PostgreSQL 来说,同时需要在迁移操作中指定。

缓存数据库数据

如果插件在处理每个请求时都需要访问数据库,那么数据库的内容最好被缓存起来。 local cache = kong.cache 提供了缓存功能,缓存分为两级:

L1: Lua memory cache. local to an nginx worker This can hold any type of Lua value.

L2: Shared memory cache (SHM) - local to an nginx node, but shared between all workers. This can only hold scalar values, and hence requires (de)serialization.

所以,当数据从数据库中读出来以后,Kong 将会把它放到 L1 和 L2 缓存各一份。Kong 下次读取数据时会先从 L1 读取,如果不存在时再从 L2 读取。

函数名称描述
value, err = cache:get(key, opts?, cb, …) 从缓存中读取数据。如果缓存未命中,调用回调函数 cb,cb 必须返回一个值,该值被 Kong 缓存。get 函数也缓存 ni l值。
ttl, err, value = cache:probe(key) 探针,用于检查 key 是否被缓存,如果被缓存,则返回 TTL
cache:invalidate_local(key)从节点的缓存中删除 key
cache:purge()清除节点的所有缓存


-- access.lua

local function load_entity_key(api_key)
-- IMPORTANT: the callback is executed inside a lock, hence we cannot terminate
-- a request here, we MUST always return.
local apikeys, err = kong.dao.apikeys:find_all({key = api_key}) -- Lookup in the datastore
if err then
error(err) -- caught by kong.cache and logged
end

if not apikeys then
return nil -- nothing was found (cached for `neg_ttl`)
end

-- assuming the key was unique, we always only have 1 value...
return apikeys[1] -- cache the credential (cached for `ttl`)
end

-- retrieve the apikey from the request querystring
local querystring = kong.request.get_query()
local apikey = querystring.apikey

-- We are using cache.get to first check if the apikey has been already
-- stored into the in-memory cache at the key: "apikeys." .. apikey
-- If it's not, then we lookup the datastore and return the credential
-- object. Internally cache.get will save the value in-memory, and then
-- return the credential.
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
-- no credentials in cache nor datastore
return kong.response.exit(403, "Invalid authentication credentials")
end

-- set an upstream header if the credential exists and is valid
kong.service.request.set_header("X-API-Key", credential.apikey)

缓存数据失效时间

缓存数据失效有两种方式:

1. TTL

2. 一旦有 CRUD 操作,立刻使数据变成无效状态。 对于第二种缓存失效策略,对于大多数实体来说,是可以做到自动化的。

如果schema配置中有cache_key = { "key" },注意,因为key属性值具有unique属性, 所以cache_key只有一个元素即可。否则,cache_key可能要包含多个key,保证缓存键的唯一性。

cache_key = kong.dao:cache_key(arg1, arg2, arg3, ...)
local apikey = kong.request.get_query().apikey
local cache_key = kong.dao.keyauth_credentials:cache_key(apikey)
-- 缓存中key的值是通过kong.dao:cache_key计算而来的
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

自定义插件

301跳转

增加301跳转插件,将 http 的请求自动转换为 https


--------handler.lua
local plugin_name = ({...})[1]:match("^kong%.plugins%.([^%.]+)")
local plugin = require("kong.plugins.base_plugin"):extend()

function plugin:new()
    plugin.super.new(self, plugin_name)
end

function plugin:access(plugin_conf)
    plugin.super.access(self)
    schema=kong.request.get_scheme()
    test=kong.request.get_header("test")
    if ( schema == "http" )
    then
        path_with_query = kong.request.get_path_with_query()
        host = kong.request.get_host()
        https_url = "https://" .. host .. path_with_query
        kong.response.exit(301,"Redirect to https",{["Location"] = https_url });
    end
end

function plugin:header_filter(plugin_conf)
    plugin.super.header_filter(self)
end 

function plugin:body_filter(plugin_conf)
    plugin.super.body_filter(self)
end

function plugin:log(plugin_conf)
    plugin.super.log(self)
end 

plugin.PRIORITY = 1000
return plugin

配置 prometheus

Kong 服务网关支持 prometheus 插件,通过启用 prometheus 插件功能实现针对特定域名(服务或路由)的监控。有关 prometheus 的应用和操作说明请参考“1401-prometheus 配置指南”。

1、增加监控配置文件

在/etc/kong的文件夹下新建prometheus-server.conf配置文件,内容如下:


server {
    server_name kong_prometheus_exporter;
l    isten 0.0.0.0:9542; # can be any other port as well

location / {
    default_type text/plain;
    content_by_lua_block {
    local prometheus = require "kong.plugins.prometheus.exporter"
    prometheus:collect()
                                      }
               }

location /nginx_status {
      internal;
     access_log off;
     stub_status;
                       }
  }


2、在模版文件增加配置文件内容

在 kong 的配置文件中引用新增的配置文件 prometheus-server.conf。

3、为需要监控的服务增加 prometheus 插件

4、修改防火墙配置,确保可以访问

5、通过浏览器访问确认

6、集成的 prometheus 服务器

7、通过 grafana 展现图形


  • 无标签

0 评论

你还没有登录。你所做的任何更改会将作者标记为匿名用户。 如果你已经拥有帐户,请登录