Apache APISIX:启动流程
前言
本系列博客旨在通过手撕源码的方式理解 APISIX 的核心运行原理,将涵盖 APISIX 的启动流程、请求的生命周期、插件体系等关键技术点。
如无特殊说明,本文以 APISIX 3.13 版本为例,可通过以下命令获取源码:
1
git clone https://github.com/apache/apisix.git --branch=release/3.13
作为系列博客的第一篇,我们将聚焦 APISIX 的启动流程,了解它是如何启动的,知道其底层是运行了什么服务。
构建 APISIX DEV 环境
在解读整体流程前,推荐搭建好 APISIX 的开发环境。
可参考官方文档:Devcontainer 构建 APISIX 开发环境
启动流程
启动命令
在 devcontainer 中可以通过以下方式启动 APISIX 服务:
1
2
3
make run
# 或者
./bin/apisix start
APISIX 默认会监听 9080 端口,可以用 curl 验证:
1
curl localhost:9080
返回结果
1
{"error_msg":"404 Route Not Found"}
出现这个提示说明 APISIX 已经成功启动。
如果查看 Makefile,可以发现 make run 最终调用的也是:
1
2
3
4
5
6
7
8
ENV_APISIX ?= $(CURDIR)/bin/apisix
### run : Start the apisix server
.PHONY: run
run: runtime
@$(call func_echo_status, "$@ -> [ Start ]")
$(ENV_APISIX) start // 最终也是 ./bin/apisix start
@$(call func_echo_success_status, "$@ -> [ Done ]")
因此,APISIX 的真正启动入口就是 bin/apisix 脚本,它负责后续的初始化流程。
bin/apisix
bin/apisix
脚本主要做了以下几件事:
- 根据安装方式定位
apisix.lua
文件1 2 3
./apisix/cli/apisix.lua # 源码安装 /usr/local/share/lua/5.1/... # Luarocks 安装 /usr/local/apisix/... # 官方 RPM 或 Docker
- 查找 OpenResty 的可执行路径,并判断版本是否 >= 1.19:
1 2 3
OR_BIN=$(command -v openresty || exit 1) OR_EXEC=${OR_BIN:-'/usr/local/openresty-debug/bin/openresty'} OR_VER=$(openresty -v 2>&1 | awk -F '/' '{print $2}' | awk -F '.' '{print $1 * 100 + $2}')
- 核心启动命令
1
exec $LUAJIT_BIN $APISIX_LUA $*
其中:
- $LUAJIT_BIN:luajit 二进制文件路径
- $APISIX_LUA:./apisix/cli/apisix.lua
- $*:传入的命令参数(例如 start)
也就是说,bin/apisix 本身只是一个启动入口脚本,最终是通过 luajit 执行 Lua 代码来启动的,即执行 apisix/cli/apisix.lua。
apisix/cli/apisix.lua
我们看下 apisix.lua
有哪些内容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
local pkg_cpath_org = package.cpath
local pkg_path_org = package.path
local _, find_pos_end = string.find(pkg_path_org, ";", -1, true)
if not find_pos_end then
pkg_path_org = pkg_path_org .. ";"
end
local apisix_home = "/usr/local/apisix"
local pkg_cpath = apisix_home .. "/deps/lib64/lua/5.1/?.so;"
.. apisix_home .. "/deps/lib/lua/5.1/?.so;"
local pkg_path_deps = apisix_home .. "/deps/share/lua/5.1/?.lua;"
local pkg_path_env = apisix_home .. "/?.lua;"
-- modify the load path to load our dependencies
package.cpath = pkg_cpath .. pkg_cpath_org
package.path = pkg_path_deps .. pkg_path_org .. pkg_path_env
-- pass path to construct the final result
local env = require("apisix.cli.env")(apisix_home, pkg_cpath_org, pkg_path_org)
local ops = require("apisix.cli.ops")
ops.execute(env, arg)
关键点解析
设置 Lua 依赖路径
将 /usr/local/apisix/deps 下的 Lua 文件和 .so 动态库加入到 package.path 与 package.cpath,以确保依赖可加载。
构建运行环境
apisix.cli.env 返回启动所需环境信息,其中最核心的是 openresty_args:
1 2 3 4
local res, err = util.execute_cmd("command -v openresty") local openresty_path_abs = util.trim(res) local openresty_args = openresty_path_abs .. " -p " .. apisix_home .. " -c " .. apisix_home .. "/conf/nginx.conf"
这里用到了
util.execute_cmd
:1 2 3 4 5 6 7 8
local function execute_cmd(cmd) local t, err = popen(cmd) -- 运行系统命令 if not t then return nil, err end local data, err = t:read("*all") -- 读取输出 t:close() return data end
它的作用就是 执行 shell 命令并返回结果。
最终 openresty_args 形如:
1 2 3
/usr/bin/openresty -p /usr/local/apisix -c /usr/local/apisix/conf/nginx.conf /usr/bin/openresty -p /workspace -c /workspace/conf/nginx.conf ...
这里目录的差别主要是 apisix_home 变量在起作用:
apisix_home 的定位
apisix.cli.env 中有如下逻辑:
1 2 3 4 5 6
local script_path = arg[0] if script_path:sub(1, 2) == './' then apisix_home = util.trim(util.execute_cmd("pwd")) if not apisix_home then error("failed to fetch current path") end
- arg[0] 就是 bin/apisix 脚本传给 luajit 的入口参数(当源码安装即 ./apisix/cli/apisix.lua)
- 当入口是 ./apisix/cli/apisix.lua 这种相对路径时,apisix_home 会被重写为 当前工作目录(通过 pwd 获取)。
- 在 devcontainer 场景下,这个目录就是 /workspace。
执行命令分发
调用 apisix.cli.ops.execute 来处理参数,例如 start、stop、reload 等。
apisix.cli.ops
ops.execute
负责处理命令行参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
function _M.execute(env, arg)
local cmd_action = arg[1] -- arg[1] 也就是传递给 bin/apisix 的第一个参数
if not cmd_action then
return help()
end
if not action[cmd_action] then
stderr:write("invalid argument: ", cmd_action, "\n")
return help()
end
action[cmd_action](env, arg[2]) -- 将剩余参数传递给具体的方法中
end
execute
会根据 bin/apisix
接受到第一个参数来执行预定义的方法。
1
2
3
4
5
6
7
8
9
10
11
12
local action = {
help = help,
version = version,
init = init,
init_etcd = etcd.init,
start = start,
stop = stop,
quit = quit,
restart = restart,
reload = reload,
test = test,
}
可以在action table 中查看 apisix 支持哪些指令。
在启动流程中,我们重点关注 start
方法。
start
start
方法核心流程如下:
清理 env
1
cleanup(env)
- 简单校验
- 确保 APISIX 不能运行在 /root 文件夹下
- 确保旧的 APISIX 进程结束运行
处理命令行参数,支持自定义配置文件:
1 2 3
local parser = argparse() parser:option("-c --config", "location of customized config.yaml") local args = parser:parse()
若提供了 -c 参数,会将自定义配置写入
profile:customized_yaml_index()
。初始化环境和 ETCD:
1 2 3 4
init(env) if env.deployment_role ~= "data_plane" then init_etcd(env, args) end
启动 openresty
1
util.execute_cmd(env.openresty_args)
util.execute_cmd
前文已经分析过了,这里就是将openresty_args
作为命令执行。最终,APISIX 是通过启动一个 OpenResty 进程来运行的,使用指定的程序目录和 nginx.conf 配置文件。
我们可以通过
ps -ef
查看所有运行中的进程1 2 3
root 25144 1 0 07:17 ? 00:00:00 nginx: master process /usr/bin/openresty -p /workspace -c /workspace/conf/nginx.conf nobody 25145 25144 0 07:17 ? 00:00:02 nginx: worker process root 25157 25144 0 07:17 ? 00:00:02 nginx: privileged agent process
可以看到三类nginx相关的进程正在运行,熟悉 nginx 的肯定对这些信息不陌生:
- master 进程:主进程,管控 + 配置管理
- worker 进程:真正处理流量
- privileged agent 进程:安全地执行特权操作
可以看到 master 进程输出的信息正是 openresty_args 变量的内容。
停止服务
分析到这里,停止 APISIX 服务的分析就十分简单了,我们直接找到 apisix/cli/ops.lua
文件,里面的 stop
方法就是关闭的指令:
1
2
3
4
5
6
local function stop(env)
cleanup(env)
local cmd = env.openresty_args .. [[ -s stop]]
util.execute_cmd(cmd)
end
可以看出只是给 openresty_args 拼接了 -s stop
参数,这是 openresty 退出的指令,执行它 openresty 将会有序停止服务。
总结
本文从 bin/apisix 脚本入手,逐层分析了 APISIX 的启动逻辑。最终可以看到,APISIX 实际上是通过执行 openresty -p <工作目录> -c
在下一篇文章中,我们会接着探讨:APISIX如何管理自身的配置文件,配置文件是如何被翻译成 OpenResty 可以识别的配置。