APISIX 目前内置了各类插件,覆盖了 API 网关的各种领域,如认证鉴权、安全管理、流量管理、可观测性、多协议接入等,内置插件是通过lua实现的。
Apisix是基于Openresty+Lua实现的,Openresty处理http请求的执行阶段来自于Nginx,Nginx的HTTP框架依据常见的处理流程将处理阶段划分成了11个阶段,其中每个处理阶段可以由任意多个模块流水式处理请求。Openresty通过ngx_http_lua_module将lua脚本嵌入到各个处理阶段,执行顺序如下图(具体各个阶段的工作可以参考文档https://github.com/openresty/lua-nginx-module)。Apisix利用各个阶段执行lua脚本指令,完成插件的初始化,加载,执行,卸载功能。
插件初始化
nginx中的配置
init_worker_by_lua_block {
require("apisix").http_init_worker()
}
文件apisix/init.lua中源码:
function _M.http_init_worker()
local seed, err = core.utils.get_seed_from_urandom()
if not seed then
core.log.warn('failed to get seed from urandom: ', err)
seed = ngx_now() * 1000 + ngx.worker.pid()
end
math.randomseed(seed)
-- Because go's scheduler doesn't work after fork, we have to load the gRPC module
-- in each worker.
core.grpc = require("apisix.core.grpc")
if type(core.grpc) ~= "table" then
core.grpc = nil
end
local we = require("resty.worker.events")
local ok, err = we.configure({shm = "worker-events", interval = 0.1})
local discovery = require("apisix.discovery.init").discovery
......
if core.config.init_worker then
local ok, err = core.config.init_worker()
if not ok then
core.log.error("failed to init worker process of ", core.config.type,
" config center, err: ", err)
end
end
plugin.init_worker() #apisix自有的插件初始化
router.http_init_worker()
require("apisix.http.service").init_worker()
plugin_config.init_worker()
require("apisix.consumer").init_worker()
consumer_group.init_worker()
apisix_secret.init_worker()
apisix_upstream.init_worker()
require("apisix.plugins.ext-plugin.init").init_worker() # 第三方插件初始化
...
end
从源码可看,插件进程初始化实在工作进程中,在负载均衡,服务发现模块等模块之后才进行插件初始化。插件初始化包括各种插件的配置初始化以及加载到工作进程中:
function _M.init_worker()
local _, http_plugin_names, stream_plugin_names = get_plugin_names()
-- some plugins need to be initialized in init* phases
if is_http and core.table.array_find(http_plugin_names, "prometheus") then
local prometheus_enabled_in_stream =
core.table.array_find(stream_plugin_names, "prometheus")
require("apisix.plugins.prometheus.exporter").http_init(prometheus_enabled_in_stream)
elseif not is_http and core.table.array_find(stream_plugin_names, "prometheus") then
require("apisix.plugins.prometheus.exporter").stream_init()
end
-- someone's plugin needs to be initialized after prometheus
-- see https://github.com/apache/apisix/issues/3286
_M.load()
if local_conf and not local_conf.apisix.enable_admin then
init_plugins_syncer()
end
local plugin_metadatas, err = core.config.new("/plugin_metadata",
{
automatic = true,
checker = check_plugin_metadata
}
)
if not plugin_metadatas then
error("failed to create etcd instance for fetching /plugin_metadatas : "
.. err)
end
_M.plugin_metadatas = plugin_metadatas
end
插件执行
nginx.conf配置:
access_by_lua_block {
apisix.http_access_phase()
}
插件处理流程图如下:
插件处理流程包括根据请求中的路由信息和用户相关信息匹配插件,经过筛选过滤插件以及插件信息合并后再执行插件,apisix/init.lua中http_access_phase 涉及到插件的主要代码如下:
......
local route = api_ctx.matched_route
if not route then
-- run global rule when there is no matching route
plugin.run_global_rules(api_ctx, router.global_rules, nil)
core.log.info("not find any matched route")
return core.response.exit(404,
{error_msg = "404 Route Not Found"})
end
core.log.info("matched route: ",
core.json.delay_encode(api_ctx.matched_route, true))
local enable_websocket = route.value.enable_websocket
if route.value.plugin_config_id then
local conf = plugin_config.get(route.value.plugin_config_id)
if not conf then
core.log.error("failed to fetch plugin config by ",
"id: ", route.value.plugin_config_id)
return core.response.exit(503)
end
route = plugin_config.merge(route, conf)
end
......
-- run global rule
plugin.run_global_rules(api_ctx, router.global_rules, nil)
local plugins = plugin.filter(api_ctx, route)
plugin.run_plugin("rewrite", plugins, api_ctx)
run_plugin的核心代码:
for i = 1, #plugins, 2 do
local phase_func = plugins[i][phase]
local conf = plugins[i + 1]
if phase_func and meta_filter(api_ctx, plugins[i]["name"], conf) then
plugin_run = true
api_ctx._plugin_name = plugins[i]["name"]
phase_func(conf, api_ctx)
api_ctx._plugin_name = nil
end
end