Day 03 / 共 20 天 · 第 1 周 全景与请求生命周期

启动流程:从命令到就绪

今天看 APISIX 怎么"开机":从 apisix start,到根据 config.yaml 生成 nginx.conf,再到 init/init_worker 初始化各子系统。理解启动,才知道处理请求时那些对象是何时准备好的。

📍 你在整条链的位置
Nginx 阶段 D2 Day3 启动(开机) 请求生命周期 D4 api_ctx D5
L01

从命令到就绪:一张时序图

① apisix start(cli/ops.lua) ② 读config.yaml+校验 ③ 渲染 ngx_tpl → nginx.conf ④ 初始化 etcd + 拉起 openresty master: init_by_lua→ http_init(全局一次性) 每个 worker: init_worker_by_lua→ http_init_worker各子系统 watch etcd+起定时器 ✅ 就绪接请求→ access_phase D4 左:CLI 准备配置并拉起(跑完就退出)|右:OpenResty 长期运行处理请求
启动时序:CLI 读配置→生成 nginx.conf→拉起 OpenResty;OpenResty master 跑 http_init,每 worker 跑 http_init_worker,就绪。
L02

CLI 和"网关本体"是两回事

🤔 apisix start 背后是什么? 很多人以为 apisix 命令就是网关。其实不是。
💡 CLI 是"点火开关",OpenResty 是"发动机" apisix/cli/apisix.lua:26-31 设 Lua 包路径)是管理命令行——只在你敲命令时跑一下:生成配置、启停 OpenResty,然后退出。真正处理请求的是 OpenResty 加载的 apisix/init.lua(长期运行)。
别混淆 cli/ 的活是"准备好 nginx.conf、把 OpenResty 拉起来"就下班了;init.lua 才是长期在岗处理请求的那个。好比"点火开关"(CLI)和"发动机"(OpenResty + init.lua)。
L03

生成 nginx.conf:配置即代码

关键一步:cli/ngx_tpl.lua 是一个 Nginx 配置模板,启动时用 config.yaml 的值填充,生成真正的 conf/nginx.conf。模板里就包含昨天说的阶段绑定:

-- ngx_tpl.lua 里(简化)
init_by_lua_block { require("apisix").http_init() }
init_worker_by_lua_block { require("apisix").http_init_worker() }
access_by_lua_block { require("apisix").http_access_phase() }
log_by_lua_block { require("apisix").http_log_phase() }
💡 你不手写 nginx.conf,APISIX 帮你生成 你只关心 config.yaml(简单 YAML:用哪个 etcd、监听哪个端口、开哪些功能),复杂的 nginx.conf 由模板生成——并把每个 Nginx 阶段绑到 apisix 模块的对应函数(昨天说的"Lua 挂到阶段"的落地)。
📝 看生成结果 apisix test 只生成配置不启动 → 去 conf/nginx.conf 就能看到生成的完整配置和阶段绑定。
L04

http_init(master 一次性)

init.lua:85-104

function _M.http_init(args)
    core.resolver.init_resolver(args)   -- DNS 解析器
    core.id.init()                       -- 实例 id
    core.env.init()                      -- 环境变量
    process.enable_privileged_agent()    -- 特权 agent 进程(干需要 root/只需一个进程的活)
    if core.config.init then core.config.init() end  -- 初始化配置中心(etcd)
    xrpc.init()                          -- stream 的 RPC 框架
end
读法:master 阶段只做"全局一次性"准备:DNS、id、环境、配置中心连接、特权 agent。特权 agent 是 OpenResty 的特殊进程,干"只需一个进程做"的活(如证书自动续期)。
L05

http_init_worker:APISIX 的"零件清单"

init.lua:107-140 逐个初始化子系统(每个 worker 都跑):

function _M.http_init_worker()
    discovery.init_worker()                    -- 服务发现(Day 16)
    require("apisix.balancer").init_worker()   -- 负载均衡(Day 10)
    require("apisix.admin.init").init_worker() -- Admin API(Day 17)
    require("apisix.timers").init_worker()     -- 定时器框架
    core.config.init_worker()                  -- 配置中心 worker(etcd watch,Day 7)
    plugin.init_worker()                       -- 插件系统(Day 11)
    router.http_init_worker()                  -- 路由(Day 6)
    require("apisix.consumer").init_worker()   -- 消费者(Day 14)
    -- global_rules / upstream / control_api ... 逐个 init_worker
    plugin.init_prometheus()                   -- 放最后确保所有 worker 就绪
end
💡 这个函数几乎列全了 APISIX 的所有子系统 router、plugin、consumer、discovery、balancer、admin、timers…每个都在 worker 启动时 init_worker 一下。本课后面几乎每天都在讲这里初始化的某个子系统。读懂这个函数 = 拿到整个 APISIX 的"目录"(我在每行标了对应教学日)。
L06

子系统 init_worker 通常干两件事

  • 建立 etcd watch:订阅自己关心的配置(router 订阅 /routes、consumer 订阅 /consumers),配置变了内存更新。
  • 起后台定时任务:健康检查、指标上报、配置轮询兜底(用 timers 框架)。
读法:这就是"全动态"的实现——每个子系统在 worker 里 watch 自己那部分 etcd 数据,配置一变立刻在内存生效。Day 07 深入 config_etcd 看 watch 机制。
L07

全动态从这里起步

启动 = 铺好"读走内存、写走 etcd"的双轨 启动后:各子系统在内存里持有配置(读请求走内存 = 快),同时 watch etcd(配置变更走 etcd → watch → 内存更新 = 动态)。这条"读写分离"的双轨在 init_worker 时铺好,之后每个请求(Day 4)都跑在这条轨上。回想 Day 01 的两大支柱——OpenResty 的性能 + etcd 的动态,在启动这一步就体现了。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • CLI(cli/)和网关本体(init.lua)的区别?
  • nginx.conf 是怎么来的?阶段绑定在哪定义?
  • http_init vs http_init_worker 各做什么?
  • 子系统的 init_worker 通常干哪两件事?和"全动态"什么关系?

✋ 动手

cd /Users/bitmart/work/codes/github/apisix
ls apisix/cli/
sed -n '85,140p' apisix/init.lua
grep -n "_by_lua" apisix/cli/ngx_tpl.lua | head
明天预告 · Day 04:全课最核心的一天——精读 http_access_phase,逐段看一个请求怎么创建 ctx、匹配路由、合并配置、选并跑插件、交给上游。
← Day 02 Day 04 · 请求生命周期 →