快速阅读一下kong 1.0.3的代码,第一是重新梳理思路,之前折腾0.14.1的时候是摸黑探路,相关笔记比较散乱,第二是kong 0.x的版本已经不再维护了,1.x相比0.x有一些比较显著的变化,譬如原先让人感到混乱的 kong/db和kong/dao现在只有一个了,插件实现全部切换成pdk了。
2019-05-06 16:28:56:kong 1.1.x有了一个重大变换,实现了db-less模式,可以不使用数据库了,见笔记二十六:查看全部笔记。
如果是刚开始学习kong,直接从1.x开始,0.x已经不再维护,0.15是0.x的最后一个版本。
前19篇笔记是刚开始接触kong时记录的,使用的版本是0.14.1,当时对kong一知半解,笔记比较杂乱。第二十篇开始是再次折腾时的笔记,使用的版本是1.0.3,笔记相对条理一些。
从0.x到1.x需要关注的变化有:
这里查阅的nginx.conf是《使用kong源代码生成镜像的Dockerfile》中生成的nginx.conf,nginx.conf只是引用nginx-kong.conf:
worker_processes auto;
daemon off;
pid pids/nginx.pid;
error_log logs/error.log info;
worker_rlimit_nofile 1048576;
events {
worker_connections 16384;
multi_accept on;
}
http {
include 'nginx-kong.conf';
}
如果对nginx.conf、nginx-kong.conf感到疑惑、不明所以,推荐阅读:API网关Kong学习笔记(六):Kong数据平面的事件、初始化与插件加载。
如果对nginx-kong.conf中的内容感到疑惑,推荐阅读:API网关Kong学习笔记(一):Nginx、OpenResty和Kong入门,基础概念和安装部署和Web开发平台OpenResty(一):学习资料、基本组成与使用方法。
nginx-kong.conf的内容结构和以前一致:
...
init_by_lua_block {
Kong = require 'kong'
Kong.init()
}
init_worker_by_lua_block {
Kong.init_worker()
}
upstream kong_upstream {
server 0.0.0.1;
balancer_by_lua_block {
Kong.balancer()
}
keepalive 60;
}
server {
ssl_certificate_by_lua_block {
Kong.ssl_certificate()
}
}
server {
server_name kong;
listen 0.0.0.0:8000;
listen 0.0.0.0:8443 ssl;
...
ssl_certificate_by_lua_block {
Kong.ssl_certificate()
}
...
location / {
...
rewrite_by_lua_block {
Kong.rewrite()
}
access_by_lua_block {
Kong.access()
}
...
header_filter_by_lua_block {
Kong.header_filter()
}
body_filter_by_lua_block {
Kong.body_filter()
}
log_by_lua_block {
Kong.log()
}
}
location = /kong_error_handler {
...
content_by_lua_block {
Kong.handle_error()
}
header_filter_by_lua_block {
Kong.header_filter()
}
body_filter_by_lua_block {
Kong.body_filter()
}
log_by_lua_block {
Kong.log()
}
}
}
server {
server_name kong_admin;
listen 0.0.0.0:8001;
listen 0.0.0.0:8444 ssl;
...
location / {
default_type application/json;
content_by_lua_block {
Kong.serve_admin_api()
}
}
...
}
OpenResty的XX_by_lua_block指令的作用时机见Web开发平台OpenResty(二):组成、工作过程与原理: NginxLuaModule实现的指令与作用位置。XX_by_lua_block指令中调用的函数在 kong/init.lua
中实现。
找到入口后剩下的事情就是读代码。笔记里没法记得太详细,只记录一些关键点。
Kong读取的配置文件时prefix目录下的.kong_env
,这个文件是kong prepare的时候生成的,是kong启动时真正使用的配置文件,如果这文件不存在,先查找/etc/kong/kong.conf
,然后查找/etc/kong.conf
。
从kong 1.x开始操作数据库的代码全部都在kong/db中,kong/db/init.lua中的 DB.new()
实例化一个DB对象。
-- kong/db/init.lua: 45
function DB.new(kong_config, strategy)
-- ...省略--
local self = {
daos = daos, -- each of those has the connector singleton
strategies = strategies,
connector = connector,
strategy = strategy,
errors = errors,
infos = connector:infos(),
kong_config = kong_config,
}
-- ...省略--
关于DB对象的时候要注意,它的元方法__index被更改过,不是默认的__index:
-- kong/db/init.lua: 39
local DB = {}
DB.__index = function(self, k)
return DB[k] or rawget(self, "daos")[k]
end
-- kong/db/init.lua: 45
function DB.new(kong_config, strategy)
-- 省略 ---
return setmetatable(self, DB)
因此有些操作读取的变量是DB对象的成员daos的field,譬如db.plugins
:
-- kong/db/init.lua: 344
loaded_plugins = assert(db.plugins:load_plugin_schemas(config.loaded_plugins))
daos
中存放的每个表的操作句柄,是在kong/db/init.lua中用kong/db/dao.lua中的new函数实例化的:
-- kong/db/init.lua: 108
for _, schema in pairs(schemas) do
local strategy = strategies[schema.name]
if not strategy then
return nil, fmt("no strategy found for schema '%s'", schema.name)
end
daos[schema.name] = DAO.new(self, schema, strategy, errors)
end
DAO.new()是kong/db/dao.lua中的_M.new()
。
被实例化的表,即shcema中存放的entity有下面几个:
-- kong/db/init.lua: 23
local CORE_ENTITIES = {
"consumers",
"services",
"routes",
"certificates",
"snis",
"upstreams",
"targets",
"plugins",
"cluster_ca",
}
-- kong/db/init.lua: 67
for _, entity_name in ipairs(CORE_ENTITIES) do
local entity_schema = require("kong.db.schema.entities." .. entity_name)
-- validate core entities schema via metaschema
local ok, err_t = MetaSchema:validate(entity_schema)
if not ok then
return nil, fmt("schema of entity '%s' is invalid: %s", entity_name,
tostring(errors:schema_violation(err_t)))
end
local entity, err = Entity.new(entity_schema)
if not entity then
return nil, fmt("schema of entity '%s' is invalid: %s", entity_name,
err)
end
schemas[entity_name] = entity
end
entity的初始化以及dao的实现以后有时间再记录, API网关Kong学习笔记(六)- 数据库操作封装和API网关Kong学习笔记(十三):向数据库中插入记录的过程分析中有0.x版本的分析,那时候还有两套dao实现,混在一起没有现在看起来清晰,当时的笔记是在一知半解的情况记录的,不是特别条理,但思路和方向基本正确 。
关心的功能都是用插件实现的,从这个角度来看,kong就是一堆插件的集合。对kong进行定制开发主要也是开发新的插件或者更改已有的插件。
配置文件中的plugins配置为bundled
时,加载kong/constants.lua的plugins中的所有插件,否则只加载指定的插件。
.kong_env中的配置:
plugins = bundled
kong/constans.lua中的plugins:
-- kong/constans.lua
local plugins = {
"jwt",
"acl",
"correlation-id",
"cors",
"oauth2",
"tcp-log",
"udp-log",
"file-log",
"http-log",
"key-auth",
"hmac-auth",
"basic-auth",
"ip-restriction",
"request-transformer",
"response-transformer",
"request-size-limiting",
"rate-limiting",
"response-ratelimiting",
"syslog",
"loggly",
"datadog",
"ldap-auth",
"statsd",
"bot-detection",
"aws-lambda",
"request-termination",
-- external plugins
"azure-functions",
"zipkin",
"pre-function",
"post-function",
"prometheus",
}
加载的插件保存在conf.loaded_plugins中:
-- kong/constans.lua
conf.loaded_plugins = setmetatable(plugins, _nop_tostring_mt)
在初始化函数Kong.init()中,插件是最后加载的,保存在init.lua的局部变量loaded_plugins
中:
-- kong/init.lua: 343
-- Load plugins as late as possible so that everything is set up
loaded_plugins = assert(db.plugins:load_plugin_schemas(config.loaded_plugins))
sort_plugins_for_execution(config, db, loaded_plugins)
初始化完成之后,用插件时都是从loaded_plugins
中取出,例如在Kong.rewrite()
中:
-- kong/init.lua: 645
-- we're just using the iterator, as in this rewrite phase no consumer nor
-- route will have been identified, hence we'll just be executing the global
-- plugins
for plugin, plugin_conf in plugins_iterator(ctx, loaded_plugins,
configured_plugins, true) do
kong_global.set_named_ctx(kong, "plugin", plugin_conf)
kong_global.set_namespaced_log(kong, plugin.name)
plugin.handler:rewrite(plugin_conf)
kong_global.reset_log(kong)
end
插件的代码结构和以前相同,都是位于kong/plugins目录中,每个插件一个目录,目录结构如下:
▾ plugins/
▾ acl/
▾ migrations/
000_base_acl.lua
001_14_to_15.lua
init.lua
acls.lua
api.lua
daos.lua
groups.lua
handler.lua
schema.lua
▾ aws-lambda/
handler.lua
schema.lua
v4.lua
handlers.lua
中实现被在kong/init.lua
中调用的方法:
plugin.handler:init_worker()
plugin.handler:certificate(plugin_conf)
plugin.handler:rewrite(plugin_conf)
plugin.handler:preread(plugin_conf)
local err = coroutine.wrap(plugin.handler.access)(plugin.handler, plugin_conf)
plugin.handler:header_filter(plugin_conf)
plugin.handler:body_filter(plugin_conf)
plugin.handler:log(plugin_conf)
如果插件要存取数据库,在migrations目录中创建包含数据库表创建和更新语句
的lua文件。
schema.lua
格式有变化,比方说以前是这样的:
local Errors = require "kong.dao.errors"
return {
no_consumer = true,
fields = {
regex = { type = "string",required = true },
replacement = { type = "string", required = true },
flag = {type = "string", default="redirect", required =true},
},
self_check = function(schema, plugin_t, dao, is_update)
if plugin_t.regex == "" or plugin_t.replacement == "" then
return false, Errors.schema "must set regex and replacement"
end
if plugin_t.flag ~= "redirect" and plugin_t.flag ~= "permanent" then
return false, Errors.schema "flag should be redirect or permanent"
end
return true
end
}
现在是:
local typedefs = require "kong.db.schema.typedefs"
return {
name = "http-redirect",
fields = {
{ consumer = typedefs.no_consumer },
{ run_on = typedefs.run_on_first },
{ config = {
type = "record",
fields = {
{ regex = { type = "string",required = true },},
{ replace = { type = "string",required = true },},
{ flag = {type="string", default="redirect", required =true},},
}
}
}
},
}
Kong的插件机制单独写一篇分析。
前面只是简单浏览了kong的数据平面的实现代码,没有涉及kong的管理平面。kong的管理平面提供了kong的管理API,管理平面使用另一个端口,它的入口函数是Kong.serve_admin_api()
:
server {
server_name kong_admin;
listen 0.0.0.0:8001;
listen 0.0.0.0:8444 ssl;
...
location / {
default_type application/json;
content_by_lua_block {
Kong.serve_admin_api()
}
}
...
}
Kong.serve_admin_api位于kong/init.lua中:
-- kong/init.lua: 792
function Kong.serve_admin_api(options)
kong_global.set_phase(kong, PHASES.admin_api)
options = options or {}
header["Access-Control-Allow-Origin"] = options.allow_origin or "*"
if ngx.req.get_method() == "OPTIONS" then
header["Access-Control-Allow-Methods"] = "GET, HEAD, PUT, PATCH, POST, DELETE"
header["Access-Control-Allow-Headers"] = "Content-Type"
return ngx.exit(204)
end
return lapis.serve("kong.api")
end
这里不分析管理平面的实现,有时间的时候单独记录一篇,这篇笔记到此为止。