Day 19 / 共 20 天 · 第 4 周 高级子系统与运维

SSL / secret / wasm / pubsub

收官前把剩余几个子系统扫一遍:动态 TLS 证书(按 SNI 选、热更新)、密钥管理(对接 Vault 等)、Wasm 插件支持(呼应上一站 wasm-go)、pubsub 发布订阅。它们各自解决一类问题。

📍 你在整条链的位置(第 4 周 高级子系统与运维)
stream L4 D18 SSL/secret/wasm/pubsub(剩余子系统) 部署收官 D20 毕业 🎓
L01

动态证书

🤔 痛点:几百个域名的 HTTPS 证书,续期一次就要 reload 一次? Let's Encrypt 证书 90 天就过期,几百个域名此起彼伏地续期。传统 Nginx 把证书写在配置文件里,换一张就得 reload,抖一下断连接——运维天天提心吊胆。
💡 本质:证书也"全动态",握手时现查 把证书当成小区门口随时能换的门牌:不重修大门(不 reload),要换哪块牌子直接换。APISIX 把证书(cert/key/snis)存 etcd,TLS 握手时按客户端要访问的域名(SNI)现查现用。加新域名、续期换证,写 etcd 即生效,零重启。这就是 Day 07"全动态"理念延伸到了 TLS。

init.lua:173-182ssl_phase:TLS 握手时用 router.router_ssl(Day 06 的 radixtree_sni)选证书。apisix/ssl/ + ssl.lua 管证书。

"动态证书"= 加/换证书不重启 传统 Nginx 证书写在配置文件里,换证书要 reload。APISIX 把证书存 etcd(SSL 对象),握手时动态查——加新域名的证书、续期换证书,写 etcd 即生效,零重启。对"几百个域名、证书频繁续期(Let's Encrypt 90 天)"的场景至关重要。这是"全动态"理念延伸到了 TLS 证书。
L02

ssl_client_hello_phase

init.lua:184-224ssl_client_hello_phase——在 TLS 的 ClientHello 阶段(握手最早期)就拿到 SNI:

function _M.ssl_client_hello_phase()
    local sni, err = apisix_ssl.server_name(true)   -- 从 ClientHello 取 SNI
    if not sni then ngx_exit(-1) end
    local api_ctx = core.tablepool.fetch("api_ctx", 0, 32)
    router.router_ssl.match_and_set(api_ctx, true, sni)  -- 按 SNI 匹配证书并设置
end
ClientHelloSNI=a.com radixtree_sni按域名查(Day06) a.com → 证书A *.b.com → 通配证书 c.com → 证书C 用证书A完成握手
握手最早期拿到 SNI → radixtree_sni 按域名找证书(复用 Day 06 的路由能力)→ 用对应证书完成握手。
为什么在 ClientHello 阶段? SNI(客户端想访问的域名)在 TLS 握手的第一个包 ClientHello 里。网关必须在这时就知道用哪张证书——因为握手要用对应域名的证书回应。OpenResty 的 ssl_client_hello_by_lua 让你在这个最早期介入。拿到 SNI → radixtree_sni 匹配 → 设置本次握手用的证书。之后 ssl_phase 完成握手。
L03

SNI 选证书

SSL 对象(schema_def.lua:765)含 cert(证书)、key(私钥)、snis(这张证书服务哪些域名)。router_ssl 按 SNI 建 radixtree(Day 06),握手时 match_and_set 匹配。

读法:一个网关服务几百个域名 = 几百张证书,radixtree_sni 让"按域名找证书"和"按 uri 找路由"一样快。回想 Day 06——路由匹配的能力被复用到了证书选择。证书还支持通配符(*.example.com)、多 SNI 共享一张证书。
L04

secret 密钥管理

apisix/secret.lua + apisix/secret/:对接外部密钥管理系统(Vault、AWS Secrets Manager、环境变量),让配置里不写死敏感信息。secret.lua:77init_worker watch /secrets

别把密钥明文写进配置 插件配置里常有敏感信息(数据库密码、API key、JWT secret)。直接写进 etcd/配置文件不安全(谁能看配置谁就看到密钥)。secret 机制让你写"引用"(如 $secret://vault/1/mysql-pass),运行时从 Vault 等动态解析真实值。密钥集中在 Vault 管理、轮换,配置里只留引用。这是生产安全的重要一环——和上一站 Higress Console 的"证书/密钥存 Secret"是同类思想。
📝 举个例子(保险箱取件码) 插件配置里 redis 密码不写明文,写引用:"redis_password": "$secret://vault/1/redis/pass"
运行时 secret 层照这个"取件码"去 Vault 取真实密码交给插件。配置(etcd)里永远只有取件码、没有真密码——谁翻到配置也偷不走密码。密码轮换只在 Vault 改,配置一字不动。
L05

密钥引用

配置里用 $secret://<manager>/<id>/<key>$env://VAR 引用。secret.luafetch_by_uri 解析这些引用,从对应后端取真实值(带缓存 + TTL)。

读法:插件读配置时,如果值是 secret 引用,secret 层透明地解析成真实值再给插件。插件无感——它拿到的就是明文密码,不知道背后从 Vault 取的。又是"透明适配"的设计。支持 vault/aws/gcp/环境变量等多种 manager(secret/ 目录)。
L06

wasm 支持

apisix/wasm.lua:APISIX 也支持 Wasm 插件(通过 proxy-wasm)——除了 Lua 插件,还能跑 Wasm 插件(Go/Rust/C++ 编译的)。wasm.lua:158require 把 wasm 插件包装成标准插件对象。

Lua 之外的扩展路线 APISIX 主打 Lua 插件(开发快、热加载)。但如果你已有 Wasm 插件(比如上一站 wasm-go 写的),或想用 Rust 写高性能插件,APISIX 也能加载 proxy-wasm 格式的 Wasm 插件。wasm.lua 把 Wasm 插件适配成 APISIX 的插件接口(priority/schema/阶段方法),和 Lua 插件一视同仁地跑。这意味着 APISIX 和 Envoy/Higress 在插件层有了共同语言——proxy-wasm 是跨网关的插件标准(呼应上一站 wasm-go Day 03 说的"USB 标准")。

👶 小白:APISIX 都用 Lua 写插件了,为什么还要费劲支持 Wasm?

👨‍🏫 老师:三个理由:① 复用已有资产——你在 Envoy/Higress 上写的 proxy-wasm 插件(上一站 wasm-go)能直接搬过来;② 语言自由——想用 Rust/Go 写高性能或复杂逻辑插件;③ 生态互通——proxy-wasm 是跨网关标准("USB 接口"),支持它就等于和 Envoy 系有了共同语言。Lua 主打"快和热加载",Wasm 补上"跨生态和多语言",两条路并存。

L07

pubsub

apisix/pubsub/ + core/pubsub.lua:APISIX 能当"消息网关"——代理 Kafka、MQTT、NATS 等发布订阅协议,让 WebSocket 客户端订阅消息流。

读法:这是把网关能力扩展到"消息"领域——比如浏览器通过 WebSocket 连 APISIX,APISIX 代理到 Kafka 消费消息推给浏览器。属于较专门的能力,了解即可。它体现了 APISIX 从"HTTP API 网关"向"统一流量网关"(HTTP + L4 + 消息 + AI)扩张的野心。
L08

今日小结 + 动手

🧠 今天你应该能回答

  • 动态证书为什么要在 ClientHello 阶段按 SNI 选?
  • secret 机制怎么让配置不写死密钥?谁来解析引用?
  • APISIX 除了 Lua 插件为什么还支持 Wasm?和上一站什么关系?
  • pubsub 体现了 APISIX 什么野心?

✋ 动手

cd /Users/bitmart/work/codes/github/apisix
sed -n '173,225p' apisix/init.lua
sed -n '68,90p' apisix/secret.lua
ls apisix/secret/ apisix/ssl/ apisix/pubsub/
明天预告 · Day 20(收官)部署与三网关大对比——bin/apisix 部署、config.yaml 关键配置,最后把 APISIX 和 higress-group(Envoy/Higress)三种网关架构做终极对比,理解不同技术路线的取舍。
← Day 18 Day 20 · 部署收官 →