Kong 的初始化过程
安装好 Kong 之后我们是用命令sudo ./bin/kong start -c kong.conf -vv
来启动。其中 kong.conf 为配置文件,-vv
选项打印出一些重要信息展示出来,方便发现问题。
可以看到./bin/kong
是一个脚本,是用的#!/usr/local/openresty/bin/resty
程序来执行,而 resty 是 OpenResty 的一个 Perl 可执行脚本。kong 的内容很简单,就是一个入口函数调用:require("kong.cmd.init")(arg)
所以我们可以从 cmd/init.lua 这个文件开始入手看启动过程。一翻开 init.lua 这个文件,发现其实不过是个 wrapper,解析了 args 之后就是调用 start,stop,quit 等命令。然后我们顺藤摸瓜找 cmd/start.lua 文件,整个启动过程就在这里了:
local conf = assert(conf_loader(args.conf, {
prefix = args.prefix
}))
local err
local dao = assert(DAOFactory.new(conf))
xpcall(function()
assert(prefix_handler.prepare_prefix(conf, args.nginx_conf))
assert(dao:run_migrations())
assert(serf_signals.start(conf, dao))
assert(nginx_signals.start(conf))
log("Kong started")
end, function(e)
err = e -- cannot throw from this function
end)
从代码上来看很直观,首先 conf_loader 载入配置文件,DAOFactory 构建数据库连接层,prefix_handler.prepare_prefix 是准备一些由程序生成的配置文件。dao:run_migrations 是迁移表结构到数据库,类似其他 Web 框架。serf_signals 是启动 serf 程序,nginx_signals 是启动 nginx 进程。
读取配置文件 conf_loader
conf_loader 读取的当然是命令行里面传入的 kong.conf 文件,打开 conf_loader.lua 看了看,是是用一个 lua 第三方库来做文件解析的。local pl_config = require "pl.config"
,最开始不太知道这个 pl 是什么,经过搜索后才知道是这里定义的,在 kong.rockspec 里面有定义了该库的依赖"penlight == 1.4.1"
。读取配置的整个过程比较琐碎,最后回构建一个解析好的 conf 表。这里学到了 Lua 里面的 setmetatable 设置元表的方法,table 作为 Lua 里面的最基本数据结构,setmetatable 可以方便的绑定一个 key 和其对应的方法。看起来也像是面向对象的风格,在 conf_loader 的最后部分是:
return setmetatable({
load = load,
add_default_path = function(path)
DEFAULT_PATHS[#DEFAULT_PATHS+1] = path
end,
......
}, {
__call = function(_, ...)
return load(...)
end
})
这样其他地方调用的时候local conf, err, errors = conf_loader(args.conf)
自然就把 args.conf 传入 load,返回解析后的结果。
prepare_prefix 动态生成 Nginx 和 serf 的配置
prefix_handler.lua 这个文件主要在准备一些 Nginx 的配置文件和 serf 的配置文件。prepare_prefix 函数前半部分在创建各个子目录,logs、serf、pids、以及各个日志文件。关于 Kong 的 config 部分需要参考一下这里。这个函数比较长,重要的部分是生成 Nginx 的配置文件。
可以看到 compile_kong_conf 函数其实是是用 kong/templates 目录下的 nginx_kong.lua 和 nginx.lua 分别生成两个文件,其中 nginx_kong.lua 里面包含了嵌入 Kong 的 Lua 代码的逻辑。
init_by_lua_block {
require 'luarocks.loader'
require 'resty.core'
kong = require 'kong'
kong.init()
}
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()
}
}
因此我们可以知道 Kong 每次 reload 或者启动的时候会生成新的 Nginx 配置文件,所以我们如果要加入自己的配置可以直接修改 nginx_kong.lua 文件。另外我在使用的时候发现一个小问题,Kong 把 serf 的 node_id 存在一个文件里,如果我们把之前跑过 Kong 的机器做了镜像,然后再启动一个新的实例时,这个 node_id 文件既然存在则没有重新生成,最终导致两台 kong 实例并没有相互通信形成一个集群。我认为这里其实可以再检查一下 node_id 的文件和本机的 ip 是否一致,如果不一致则重新生成。
dao:run_migrations()
初始化过程的下一步则是执行数据库操作,Kong 目前只支持 cassandra 和 Postgres,个人认为应该增加 Redis 的支持。
serf_signals.start
之前提到过 serf 是用来保证 kong instance 之间的通信的,启动的时候的一个很重要参数是--event-handler
,参数的内容是一个可执行脚本 (通常叫做 serf_event.sh),文件的内容是前面生成配置文件的时候写入的。默认情况下 serf 会监听在 7946 端口,如果多台 server 需要形成一个集群,这个端口之间需要能相互通信。这里就有一个问题,在一个新的 server 刚启动的时候,该 server 是如何发现其他节点的呢?我们可以看到 serf_signals.lua 里的 start 函数调用了 serf:autojoin() 函数,跟踪到 autojoin 里面看代码,其实是从数据库里读取出其他 nodes 的信息,然后依次告诉对方新同志加入了,然后把自己的节点信息写入到数据库里。自然如果要退出也需要把自己的信息从数据库里删掉。
nginx_signals.start
启动的最后一步即是 Nginx 的启动,其实最终执行的命令就是:
/usr/local/openresty/nginx/sbin/nginx -p /usr/local/kong -c nginx.conf
总结
通过上面的分析,可以总结 Kong 的启动过程即是:解析输入参数,验证参数合法性并生成必要的目录和配置文件,执行数据库操作,启动 serf,启动 Nginx。最终其实就是一个 OpenResty 启动过程,嵌入 Kong 里面的 core 部分的 Lua 代码。后面继续分析其可扩展的插件机制。