我把 LLM Gateway Lite 从本地开发做到可开源发布:一篇完整实战记录
关键词:OpenResty、LLM 网关、配置可视化、故障转移、429 重试、统计口径、开源脱敏
最近把一个轻量级 LLM 网关项目从"能跑"打磨到"可运营、可观测、可开源发布",过程中踩了不少坑。
这篇文章不讲空话,直接给你一套可落地的改造清单 和实战代码思路。
1. 先说目标:这个网关要解决什么问题?
一个能在本地和生产都好用的 LLM 网关,我认为至少要做到:
- 统一 OpenAI 兼容入口(上层业务不关心底层渠道差异)
- 多 Provider 路由和故障转移(429/5xx 自动切换)
- 配置可视化可编辑(不是每次都手改 yaml)
- 有管理后台(能看到调用、失败、冷却、风险)
- 可持久化关键统计(容器重建不丢)
- 最终代码可开源(不带密钥、不带公司内网信息)
2. 本地启动与基础配置(Mac + Docker)
2.1 一次性准备
bash
cp configs/providers.yaml.example configs/providers.yaml
cp configs/models.yaml.example configs/models.yaml
cp .env.example .env
docker compose up -d --build
2.2 鉴权配置建议
- 客户端调用网关:
x-api-key(来自configs/gateway_keys.yaml) - 管理后台:
GATEWAY_ADMIN_TOKEN(来自.env)
建议在 docker-compose.yml 里这样写:
yaml
- GATEWAY_ADMIN_TOKEN=${GATEWAY_ADMIN_TOKEN:-change-me-admin-token}
这样本地有默认值,部署时也能由环境变量覆盖。
3. 管理后台重构:不是"好看"这么简单
我做后台时重点不是换皮,而是把"排障信息"放到第一屏。
3.1 页面结构
admin/dashboard:运行概览(调用、成功率、状态码、Provider 健康、Key 风险)admin/dashboard/topology:模型-Provider 拓扑 + 运行状态admin/dashboard/config:配置编辑 + 版本历史admin/login:浏览器输入 admin token,不再手拼 URL
3.2 关键改造点
- 移除对用户无价值的信息(如配置 hash)
- 统一视觉语言(卡片、趋势图、状态徽标)
- 增强错误兜底(JS 渲染失败不再整页空白)
- 导航栏自动透传
admin_token
4. 配置中心:可视化编辑 + 版本管理 + 原子回滚
很多团队配置治理的痛点,不是"不会改",而是"改坏了怎么回滚"。
我在配置中心里做了这几件事:
- 支持编辑
models.yaml/providers.yaml - 保存后自动生效
- 自动记录版本历史(按北京时间显示)
- 保存失败自动回滚(原子写入 + 校验)
4.1 版本存储设计
采用文件版本,不上数据库,先保证简单可控:
- 当前配置:
configs/*.yaml - 历史版本:
logs/config-versions/*.yaml
后续如果量级上来,再切数据库也不迟。
5. 路由策略实战:default_providers 变"链式轮询"
这个点是核心需求之一:
当 default_providers 配多个时,不是 A 全挂才到 B,而是把 A/B 的可用 key 扁平化轮询。
目标链路类似:
providerA-key1 -> providerA-key2 -> providerB-key1 -> providerB-key2 -> ...
5.1 实现思路
- 在 keypool 层新增 provider chain 选择函数
- router 返回时带上具体 key(不是只返回 provider)
- 重试时排除已尝试 key / 已耗尽 provider
- stream / non-stream 统一走重试路径
6. 429 自动故障转移验证:流式和非流式都要测
只测 stream=false 不够,线上最容易出问题的是 stream=true。
我补了两套脚本:
scripts/test_stream_failover_429.pyscripts/test_nonstream_failover_429.py
6.1 测试方法
- 启本地 mock upstream(固定返回 429)
- 临时把首跳 provider 指向 mock
- 发
claw-primary请求 - 校验上游序列是否从 mock 自动切到 fallback provider
- 校验客户端是否仍拿到正常 200(无感切换)
一句话:失败发生在网关内部,上层应用不感知。
7. 统计窗口"看着像假数据"的根因和修复
这个坑很典型,很多看板都会踩:
- 你选了"最近 15 分钟"
- 卡片显示的是累计总调用
- 用户直觉就是:这数据是假的
7.1 修复原则
- KPI、榜单、状态分布全部使用窗口口径
- 累计值只作为辅助提示,不和窗口值混用
- 对历史缺失分桶数据做回填兜底(比如 Provider 榜从 key_stats 回填)
- 增加"未归属请求"指标,解释窗口总量与 Provider 汇总差值
8. 统计持久化:容器重建后不丢
默认共享内存统计在容器重建后会丢,这对排障很痛。
我落地了一个轻量方案:
- 每 10 秒快照
ngx.shared_dict到logs/stats-snapshot.json - worker 启动时自动恢复
- 文件在挂载目录,容器重建不丢
适合本地和中小环境。
如果是大规模生产,建议接 Prometheus/ClickHouse/Redis 做指标链路。
9. 开源前必做:脱敏清理(非常关键)
这是最后一步,也是最容易翻车的一步。
9.1 必查项
- 所有真实 API key、token
- 内网 IP、公司域名、代理地址
- 日志和快照里的敏感内容
- 脚本默认参数里的真实凭据
9.2 我做的处理
configs/gateway_keys.yaml改为 demo 占位值.env.example提供默认模板,不提交真实.env.gitignore忽略logs/**- 文档和示例中的特定 provider 命名改为通用命名
- 删除代码中的公司特定调试分支
10. 一套可复用的提交策略
建议拆两次提交:
- 功能改造提交(UI、配置中心、路由、统计、测试脚本)
- 开源清理提交(脱敏、ignore、文档占位)
这样后续 review、回溯、开源审计都会轻松很多。
结语
做网关这类基础组件,最怕"功能有了,运维不可用;能跑起来,不能开源"。
这次实践下来最深的感受是:
- 功能正确只是起点
- 可观测、可回滚、可解释才是工程化
- 脱敏和开源规范必须前置,不要最后临时补
如果你也在做类似网关,我建议你优先把这三件事做扎实:
- 故障转移可验证(含 stream)
- 统计口径一致且可解释
- 开源脱敏流程自动化
这样你的项目才真的能"从本地 demo 走到可持续维护"。
开源项目地址:https://github.com/answerlink/llm-gateway-lite
附:文中提到的关键文件
lua/handlers/admin_dashboard.lualua/handlers/admin_topology.lualua/core/stats.lualua/core/router.lualua/core/keypool.lualua/handlers/openai_compat.lualua/core/config_manager.luascripts/test_stream_failover_429.pyscripts/test_nonstream_failover_429.pydocker-compose.yml.env.example