- 创建者: 虚拟的现实,上次更新时间:1月 24, 2024 需要 8 分钟阅读时间
说明
以下内容说明基于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_worker | init_worker_by_lua | 在每个 Nginx worker 进程启动时被执行 |
certificate | ssl_certificate_by_lua_block | 在 SSL 握手的 SSL certificate serving 阶段被执行 |
rewrite | rewrite_by_lua_block | 每个请求一到来时被执行,注意这时还没有进行路由匹配,所以识别不出来 Service 和 Consumer,所以当插件被配置为全局插件时,该方法才会被执行 |
access | access_by_lua | 请求在被转发至 upstream service 之前被执行 |
header_filter | header_filter_by_lua | 在接收到 upstream service 响应的所有头部信息后被执行 |
body_filter | body_filter_by_lua | 在 upstream service 的每个响应快(chunk)之后被执行,该方法可能会被执行多次 |
log | log_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_consumer | Boolean | false | true 表示该插件不适合应用到 Consumer 上,只能被应用在 Service 和 Route 上,例如认证类插件。 |
fields | Table | {} | 插件的 schema。描述了插件会有哪些属性,以及配置规则 |
self_check | Function | nil | 如果需要插件本身对用户的配置数据进行校验,就设置该函数 |
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 类型 | 可接受的值 | 描述 |
---|---|---|---|
type | string | “id”, “number”, “boolean”, “string”, “table”, “array”, “url”, “timestamp” | 验证属性值的类型 |
required | boolean | 默认值为false | 插件实例化时该属性是否必须被设置 |
unique | boolean | 默认值为false | 不同插件实例的值必须是不同的 |
default | any | 这个属性的默认值 | |
immutable | boolean | 创建以后不得被修改 | |
enum | table | integer indexed table | 可接受的属性值的列表,不在此列表内的设置都是错误的 |
regex | string | A valid PCRE regular expression | 正则表达式用于验证属性值的有效性 |
schema | table | A nested schema definition | 如果属性类型是表,该字段用于定义 sub-properties |
func | function | 用于验证该属性值的函数 |
最后,给出一个插件配置的小例子:
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_key | Integer indexed table | 数组中每个元素都是 schema 的一个组件,支持复合主键 |
fields.*.dao_insert_value | Boolean | 如果为 true 表示该 field 将会由系统自动填充 |
fields.*.queryable | Boolean | 如果为true表示Cassandra会为这一列建立索引,一般也是过滤的字段 |
fields.*.foreign | String | 这一列是一个外键 |
我们在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 展现图形
- 无标签
添加评论