Yukang's Page

Kong源码分析: 启动

2017-07-07

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是可能有的启动时数据库改动。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代码。后面继续分析其可扩展的插件机制。

Tags: Lua
使用微信打赏

若你觉得我的文章对你有帮助,欢迎点击上方按钮对我打赏

扫描二维码,分享此文章