前端开发之动态管理Nginx集群的方法( 二 )


void
ngx_http_lua_inject_timer_api(lua_State *L)
{
lua_createtable(L, 0 /* narr */, 4 /* nrec */); /* ngx.timer. */
lua_pushcfunction(L, ngx_http_lua_ngx_timer_at);
lua_setfield(L, -2, "at");
lua_setfield(L, -2, "timer");
}
static int
ngx_http_lua_ngx_timer_at(lua_State *L)
{
return ngx_http_lua_ngx_timer_helper(L, 0);
}
static int
ngx_http_lua_ngx_timer_helper(lua_State *L, int every)
{
ngx_event_t *ev = NULL;
ev->handler = ngx_http_lua_timer_handler;
ngx_add_timer(ev, delay);
}
因此,当我们调用 ngx.timer.at 这个 Lua 定时器时,就是在 Nginx 的红黑树定时器里加入了 ngx_http_lua_timer_handler 回调函数,这个函数不会阻塞 Nginx 。
下面我们来看看 APISIX 是怎样使用 ngx.timer.at 的 。
APISIX 基于定时器实现的 watch 机制Nginx 框架为 C 模块开发提供了许多钩子,而 OpenResty 将部分钩子以 Lua 语言形式暴露了出来,如下图所示:

前端开发之动态管理Nginx集群的方法

文章插图
APISIX 仅使用了其中 8 个钩子(注意,APISIX 没有使用 set_by_lua 和 rewrite_by_lua,rewrite 阶段的 plugin 其实是 APISIX 自定义的,与 Nginx 无关),包括:
  • init_by_lua:Master 进程启动时的初始化;
  • init_worker_by_lua:每个 Worker 进程启动时的初始化(包括 privileged agent 进程的初始化,这是实现 JAVA 等多语言 plugin 远程 RPC 调用的关键);
  • ssl_certificate_by_lua:在处理 TLS 握手时,openssl 提供了一个钩子,OpenResty 通过修改 Nginx 源码以 Lua 方式暴露了该钩子;
  • access_by_lua:接收到下游的 HTTP 请求头部后,在此匹配 Host 域名、URI、Method 等路由规则,并选择 Service、Upstream 中的 Plugin 及上游 Server;
  • balancer_by_lua:在 content 阶段执行的所有反向代理模块,在选择上游 Server 时都会回调 init_upstream 钩子函数,OpenResty 将其命名为 balancer_by_lua;
  • header_filter_by_lua:将 HTTP 响应头部发送给下游前执行的钩子;
  • body_filter_by_lua:将 HTTP 响应包体发送给下游前执行的钩子;
  • log_by_lua:记录 access 日志时的钩子 。
准备好上述知识后,我们就可以回答 APISIX 是怎样接收 etcd 数据的更新了 。
nginx.conf 的生成方式每个 Nginx Worker 进程都会在 init_worker_by_lua 阶段通过 http_init_worker 函数启动定时器:
init_worker_by_lua_block {
apisix.http_init_worker()
}
你可能很好奇,下载 APISIX 源码后没有看到 nginx.conf,这段配置是哪来的?
这里的 nginx.conf 实际是由 APISIX 的启动命令实时生成的 。当你执行 make run 时,它会基于 Lua 模板 apisix/cli/ngx_tpl.lua 文件生成 nginx.conf 。请注意,这里的模板规则是 OpenResty 自实现的,语法细节参见lua-resty-template 。生成 nginx.conf 的具体代码参见 apisix/cli/ops.lua 文件:
local template = require("resty.template")
local ngx_tpl = require("apisix.cli.ngx_tpl")
local function init(env)
local yaml_conf, err = file.read_yaml_conf(env.apisix_home)
local conf_render = template.compile(ngx_tpl)
local ngxconf = conf_render(sys_conf)
local ok, err = util.write_file(env.apisix_home .. "/conf/nginx.conf",
ngxconf)
当然,APISIX 允许用户修改 nginx.conf 模板中的部分数据,具体方法是模仿 conf/config-default.yaml 的语法修改 conf/config.yaml 配置 。其实现原理参见 read_yaml_conf 函数:
function _M.read_yaml_conf(apisix_home)
local local_conf_path = profile:yaml_path("config-default")
local default_conf_yaml, err = util.read_file(local_conf_path)
local_conf_path = profile:yaml_path("config")
local user_conf_yaml, err = util.read_file(local_conf_path)
ok, err = merge_conf(default_conf, user_conf)
end
可见,ngx_tpl.lua 模板中仅部分数据可由 yaml 配置中替换,其中 conf/config-default.yaml 是官方提供的默认配置,而 conf/config.yaml 则是由用户自行覆盖的自定义配置 。如果你觉得仅替换模板数据还不够,大可以直接修改 ngx_tpl 模板 。
APISIX 获取 etcd 通知的方式APISIX 将需要监控的配置以不同的前缀存入了 etcd,目前包括以下 11 种: