一直做网关的人,最怕收到的反馈大概就是“你的服务挂了”。CPU和内存明明还有大量富余,日志也一直在正常滚动,但访问的人就是打不开。我那个负责转发大模型请求的网关,就曾经长期处于这种状态。折腾到最后才意识到,真正卡脖子的不是模型推理本身,而是暴露服务之前绕不开的三件小事:域名落到哪、端口能不能通、链路等多久就断。这篇文章把我这段从 Cloudflare 撞墙,一直到迁移到 OCI 免费 L7 云负载均衡器的心路历程完整摊开。适合正在自建大模型网关、在公司内网做 AI 服务接入,以及被各种 443 连接超时折磨过的人参考——涉及的超时原理、端口取舍和排障手法,都是可以直接抄作业的。
1. 大模型网关的部署难题,远不止“服务跑起来”那么简单
1.1 网关的日常职责,注定了它会被网络问题反复摩擦
大模型网关在团队内部承担的职责非常杂。它要对上统一暴露一个 OpenAI 兼容的 API 出口,把不同模型的请求路径统一起来;要对下做 API Key 鉴权、用户配额限制、请求日志审计;还要负责把同一个提问路由到不同的模型或者不同的供应商后端。
听起来就是一个标准的 API 网关,但问题是,大模型接口的生命周期跟普通 REST API 完全不是一个量级。普通接口请求 500 毫秒内能返回就算慢的,大模型接口从用户提问到完整回答,动不动就是几十秒起步。再加上现在很多模型走流式输出,用 SSE 一点一点把 Token 吐给前端,整个连接要维持很久,中间还有“模型思考过程”这种完全不产生数据的空窗期。
这种长时间、大流量、长连接的访问模式,对任何一层网络设备的要求都比普通 Web 服务高得多。一开始团队图省事,直接把网关端口映射到公网 IP 上,让同事用http://IP:8000访问。当天就得罪了所有同事——有人单位的网络策略禁止访问非标准端口;有人客户端直接报证书错误;更别提部署那台机器的 IP 地址在一个月内因为云厂商维护变了两次,所有端到端配置全废。
1.2 域名、端口、超时,三个问题其实是同一件事
很多人把域名、端口、超时拆开来看,觉得都是独立的小问题,但实际部署网关的时候,它们是一个链条:
- 域名决定了流量到达哪个“门面”。公网 IP 不是不能访问,但 IP 一旦更换就要通知所有人,而且没有证书的 HTTP 请求在 TLS 时代寸步难行。
- 端口决定了流量能不能穿透用户侧的网络策略。80 和 443 是互联网默认放行的端口,其他端口属于“允许临时用,但随时可能被掐”的状态。
- 超时决定了这条链路上的每一层设备能容忍你多久不出数据。普通网络中间的 NAT 网关、负载均衡器、反向代理,每一层都有自己的空闲超时和响应超时,超过就会被默默断开。
换句话说,大模型网关要稳定跑起来,需要的是“域名解析稳定 + 标准端口暴露 + 超时完全可控”三合一的环境。缺任何一个,你都会遇到“为什么连得上但总是断”“为什么首 Token 等了几分钟就被掐断”这类玄学问题。
2. Cloudflare 方案看上去很美,但免费层的硬限制是绕不过去的坎
最早把目标锁定在 Cloudflare 上,原因很简单:免费 CDN、免费 HTTPS、隐藏源站 IP、全球节点加速,还能用 Cloudflare Tunnel 让没有公网 IP 的机器直接发布服务。对于个人项目和中小团队来说,这几乎是零成本暴露服务的最优解,尤其是不想再折腾云厂商安全组、防火墙入站规则的时候,Tunnel 一个出站连接就把所有问题解决了。
但我很快就在实际流量压力下发现了三个无法忽视的问题,最后一一变成了阻碍网关落地的硬约束。
2.1 Cloudflare 100 秒限制是如何卡死大模型网关的
Cloudflare 免费版在通过 CDN 代理转发 HTTP 请求时,对回源连接有严格的超时限制。官方文档里写得很明确,免费套餐下请求的整体响应时间上限是 100 秒。也就是说,不管你的后端用了多少时间处理,只要从请求发起到完成响应超过这个值,连接就会被平台直接切断。
对大模型网关来说,这意味着什么?我先用一个最简单的场景做测试:让网关转发一个支持流式输出的聊天请求,后端模型在真正开始吐字之前,需要先经历一段较长的“思考”阶段。在测试里,后端第 40 秒才输出第一个 Token,第 130 秒才全部输出完。结果就是客户端在第 100 秒整收到一个502或者直接被断开,模型还没开始说话,连接就已经没了。
对比数据很直观:
| 测试项 | 直连后端 | 经过 Cloudflare 代理 |
|---|---|---|
| 首 Token 耗时 | 40 秒 | 40 秒 |
| 完整响应耗时 | 130 秒 | 100 秒被强制断开 |
| TTFB(到达首字节时间) | 正常 | 受边缘链路影响,增加 1~3 秒 |
100 秒限制对大模型场景几乎是致命的。长上下文处理、RAG 检索、多模型工具调用,这些场景动辄几十秒甚至几分钟的耗时,是没有办法压缩到 100 秒以内的。就算你把后端硬调到百秒内返回,一遇到高峰期排队,照样会被切断。
还有一个很多人不知道的点:Cloudflare Tunnel 虽然绕过了 Worker 的 CPU 时间限制,但它本质上还是走 CDN 边缘代理回源,所以同样受这 100 秒的整体响应超时约束。我当时一度以为换 Tunnel 能解决,测下来发现该断还是断,只是报错方式从 502 变成了连接重置。
2.2 为什么只剩下 443 这个端口可以选
Cloudflare 免费层在对外暴露服务的时候,支持的端口非常有限:面向公网只开放 80 和 443。80 用于 HTTP,443 用于 HTTPS,其他所有端口几乎都不在免费套餐的允许范围内。像大模型网关常用的 gRPC 接口、WebSocket 长连接、自定义 RPC 端口,在 Cloudflare 免费版里都没有办法直接暴露出去。
当时我能做的,就是所谓的“端口折中”:把所有原本想走独立端口的能力,全部硬塞进 443 端口,用 URL 路径去区分不同服务。
比如网关原来的设计里,内置了一个/metrics监控接口和一个/healthz健康检查接口,这些还好说,跟着主路径走就行。但团队里另外一个实时语音服务原本走的是独立端口,非要用自定义协议,我就只能让客户端连到 443,再在路径上做一层协议分发。从客户端视角看,所有请求都指向同一个域名同一个端口,借用的是 HTTPS 本身的相对安全性,但代价是所有服务都暴露在同一个攻击面上,一旦某个路径的鉴权出问题,整个网关注入就会被放大到全局。
端口折中并非不能跑,只是非常别扭。对于个人 demo 项目可以接受,对于要上生产的大模型网关,这是一种“看起来很美好但处处受限”的状态。
2.3 Tunnel 的体验:能用,但离“稳定”还很远
Cloudflare Tunnel 有一个非常大的优势:不需要在公网开放任何入站端口。只要在内网机器上运行一个cloudflared进程,它主动向外建立一条加密通道,外网请求经过 Cloudflare 边缘节点进入这条隧道,就能访问到你的服务。这个机制对没有公网 IP 的家庭服务器、公司内网机器来说特别合适。
但在大模型网关这个场景下,我测下来有几个很明显的问题:
- 链路稳定性不可控:Tunnel 的边缘节点选择你无法干预,有些地区连接 Tunnel 节点的延迟高得离谱,网关的 TTFB 经常被拖到 3~5 秒,用户体感上就是“点一下要转半天”。
- 没有合理的健康检查:Cloudflare Tunnel 本身不提供后端健康检查,后端挂了它仍然继续转发,客户端拿到的错误永远是“WebSocket 连接失败”或“404”,而不是一个明确的“服务不可用”。
- 多路由复用容易冲突:同一个 Tunnel 要转发多个域名、多个路径时,配置稍有不慎就会产生路由重叠。我遇到过
/api路径被另一个服务的规则抢先匹配,结果网关的登录请求全被转发到错误后端。
这套方案最后被我定位为“只适合低频、个人使用、无需强 SLA 的场景”。我并不是否定 Cloudflare Tunnel,它解决了许多“没有公网 IP 怎么暴露服务”的困境,但它的设计定位就不是给大流量、长时间连接的生产网关用的。
3. 上 OCI 免费 L7 云负载均衡器,才算真正把主动权握在手里
经过 Cloudflare 一轮折腾之后,我对“免费方案”的要求变成了:端口我可以自定义、超时我可以调参、后端我可以做健康检查、流量我可以按域名路径去路由。这时候瞄上了 Oracle Cloud(OCI)的免费 L7 负载均衡器。它属于 Always Free 资源,只要注册账号,不需要付费也能长期使用。实测下来,这是目前白嫖级别方案里最适合大模型网关上生产的。
3.1 免费 L7 负载均衡器与 Cloudflare 方案的差异
先放一张对比表,每一条都是实际踩过坑后的体感:
| 对比维度 | Cloudflare 免费版 + Tunnel | OCI 免费 L7 负载均衡器 |
|---|---|---|
| 成本 | 免费 | 免费(Always Free) |
| 对外端口 | 仅 80/443 | L7 监听器可按需配置,比如 443、8443、8080 |
| 响应超时 | 固定约 100 秒 | 可配置,空闲超时、响应超时可放宽到 300 秒以上 |
| 健康检查 | 无 | 支持 HTTP/TCP 健康检查,自动摘除异常后端 |
| 路由规则 | 仅 URL 路径 | 支持域名 + 路径 + 权重灰度 |
| TLS 终结 | 边缘节点终结 | LB 上统一终结,可上传自己的证书 |
| 多可用区高可用 | 依赖 CF 节点 | 默认双可用区部署 |
总结成一句话:Cloudflare 免费版给你的是一个“不可调参的免费代理”,而 OCI L7 负载均衡器给你的是一个“可自由调参的免费入口”。
这里需要重点说下超时的问题。大模型网关最怕的不是“响应慢”,而是“响应慢到一半被切断”。OCI L7 LB 对连接的管理方式是可控的,监听器上可以调节空闲超时(Idle Timeout)和对应后端连接的超时参数。你可以按自己网关的实际需要,把超时值调整到足够大,而不是被某个隐藏在平台深处的固定常量掐住脖子。
3.2 创建 LB 的完整实操:从 VCN 到监听器
我在 OCI 的部署过程不算复杂,但有几个环节踩了坑,把正确的步骤整理一下。
第一步:准备 VCN 与子网
登录 OCI 控制台,进入“Networking -> Virtual cloud networks”创建一个 VCN。创建时顺手添加一个公共子网,用于挂载负载均衡器的公网入口。要注意的是,LB 和后端网关实例无论是否在同一个可用区,至少要在同一个 VCN 内,否则网络 ACL 和安全列表的配置会非常绕。
第二步:创建负载均衡器
在“Networking -> Load balancers”页面创建 LB。选择 Layer 7 类型,形状选择“Always Free”对应的规格。创建的时候,控制台会要求你选择两个可用区来提供高可用,这是免费资源的一部分,不需要额外付费。策略同区域唯一需要注意的是,LB 创建完之后会分配一个公网 IP,这个 IP 是 VIP,删除 LB 才会回收,不会因为实例重启而变化。
第三步:配置监听器
监听器是面向客户端的入口。这里是关键:
- 协议选择 HTTPS,端口 443。
- 如果第一次搭,建议先用 HTTP + 80 把链路测通,再切换 HTTPS,减少排障变量。
- 在监听器上绑定 TLS 证书,证书可以用腾讯/阿里云/Let's Encrypt 签发的,后面详说。
第四步:创建后端集
后端集就是一组实际处理请求的网关实例。协议选 HTTP,端口填你网关实际监听的端口,比如8000。这里有个非常反直觉的坑:很多人的网关在云主机上监听的是 8000,但健康检查默认走 80 端口,导致健康检查一直失败,LB 把所有后端都标记为 Down。正确做法是在后端集的“健康检查”配置里,把端口改为 8000,路径设为网关的/v1/models或/healthz,并配置期望状态码是 200(有些网关对根路径返回 404,那就把期望码改成 200 和 404)。
第五步:配置路由规则
OCI L7 LB 的路由规则非常灵活。可以按域名路由,也可以按 URL 路径路由,还可以设置权重灰度。大模型网关最典型的用法是:
# 逻辑示意,实际在控制台配置 # Host 为 ai.example.com 的请求转发到 backend-ai # Host 为 metrics.example.com 的请求转发到 backend-metrics我实际将生产流量、测试流量和监控流量分别放在三个后端集里,每次发版先切少量流量到新版本,验证稳定后再把权重拉满。这在 Cloudflare 免费版里是做不到的。
3.3 证书上传与 TLS 终结的调优
如果监听器选用了 HTTPS,证书是必须配置的。我用的方案是 Let's Encrypt 免费证书,在源站主机上用 certbot 签发。签发一次有效期 90 天,通过在主机上挂 cron 定期执行续期脚本,续期完成后使用 OCI CLI 把新证书上传到 LB:
# 将新证书上传到 LB 的示例命令(命令参数以实际 CLI 版本为准) oci lb certificate create \ --load-balancer-id <lb-ocid> \ --certificate-name "cert-2025" \ --certificate-file /etc/letsencrypt/live/ai.example.com/fullchain.pem \ --private-key-file /etc/letsencrypt/live/ai.example.com/privkey.pem这里再强调一点:如果使用了 HTTPS 监听器,默认情况下客户端到 LB 之间是加密的,LB 到后端可以用 HTTP。不建议在 LB 到后端之间再开 TLS,因为没必要增加一层开销,反而会引入证书信任问题,排障时多一个变量。
安全配置方面,建议禁用 TLS 1.0 和 TLS 1.1,只开启 TLS 1.2 和 TLS 1.3。HTTP/2 可以直接在监听器上启用,对 SSE 的头部压缩和多路复用都有好处。
3.4 超时参数与长连接优化的实操清单
大模型网关绕不开的就是 SSE 流式输出。为了让流式连接在 LB 上稳定不中断,我做了一组配套调优:
- LB 监听器的空闲超时:从默认值调高到 300 秒。如果模型思考时间特别长,就再上调到 600 秒。
- 后端的 keep-alive:网关程序(比如 FastAPI 或 Node.js)要确保开启 HTTP keep-alive,避免频繁重建 TCP 连接。
- 反向代理层透传:如果网关前面还有一层 Nginx,必须把
proxy_read_timeout、proxy_send_timeout都设置为 300 秒以上,并且设置proxy_buffering off,否则流式输出会被缓冲层憋住。 - SSE 心跳:在后端代码里,每 15~20 秒在没有实际 Token 数据时,发送一个以冒号开头的注释行(
": keep-alive\n\n")。这行注释在 SSE 协议里等价于心跳,能有效防止中间 NAT 设备因为“长时间无流量”而断开连接。
具体的快速验证命令如下。先用直连 IP 测后端:
curl -v --max-time 60 http://10.0.0.4:8000/v1/models再通过 LB 测完整链路:
curl -v https://ai.example.com/v1/chat/completions \ -H "Content-Type: application/json" \ -H "Authorization: Bearer sk-xxx" \ -d '{"model":"deepseek-chat","stream":true}'如果这条链路能稳定输出到结束,说明 LB 的超时配置和后端保持机制都到位了。
3.5 域名解析:最关键的一步,别把流量绕回 Cloudflare
LB 创建完成后,控制台会给出一个公网 VIP 和一个对应的完整域名(形如xxx.<region>.oraclecloud.com)。域名解析我推荐直接做一条 CNAME 记录指向这个 LB 域名,而不是指向 IP,这样以后 LB 底层 IP 变化时,你不需要去改 DNS。
很多人在这里会栽一个隐藏的跟头:如果你的域名托管在 Cloudflare,并且 DNS 记录开启了橙色云朵(代理)模式,那么流量依然会先进入 Cloudflare 边缘,再回源到 OCI LB。这样一来,前面那个 100 秒超时限制又阴魂不散地回来了。正确做法是:在 Cloudflare DNS 管理页面,把网关记录的状态改为“仅 DNS”(灰色云朵),让流量直接从客户端DNS解析到OCI公网IP;或者干脆把域名的 DNS 托管迁到 OCI DNS 或其他不支持强制代理的解析服务。
我为什么推荐 CNAME 而不是 A 记录?因为 OCI LB 的 VIP 在极少数运维场景下可能发生变更,CNAME 指向官方域名,解析结果由 OCI 平台维护,切换对使用者完全透明。
4. 迁移过程中遇到的实际问题与排查清单
把域名、端口、超时全部理顺之后,我原以为可以安心收工了,结果还是陆陆续续碰到了一些非常典型的网络问题。这些问题的排查手法,对自建网关的人来说是通用的。
4.1 443 端口连接超时:MTU 和网络策略是两大元凶
现象:客户端执行curl https://ai.example.com,卡了很久之后报“连接超时”或“信号灯超时时间已到”。
这种问题的排查顺序,我建议固定下来:
- 先在源站外部机或本机 ping LB 的公网 IP,确认链路基础可达。
- 检查安全列表。OCI 默认的安全列表非常严格,如果你创建 VCN 时没有手动添加入站规则,443 端口实际上是被拒绝的。这是新手最容易忽略的点。
- 检查健康检查状态。如果后端的健康检查失败,LB 会把流量全部返回 503,而 503 在部分客户端里也会被误报为“连接超时”。
- 检查 MTU 一致性。OCI 实例网卡的 MTU 很可能默认设置成 9000 的巨型帧,而客户端的家用路由器和本机网卡几乎都是 1500。当大尺寸数据包(尤其是 TLS 握手阶段的 Certificate 消息)经过链路时,会因为超过中间链路 MTU 被丢弃,表现为连接直接卡死,迟迟响应失败。
MTU 问题有一个非常经典的验证方式:
# 用 ping 探测路径 MTU,-M do 表示禁止分片 ping -M do -s 1472 <目标IP>如果-s 1472不通,但-s 1000通,就说明链路上存在 MTU 限制。此时需要调整 OCI 实例网卡的 MTU 设置,把mtu从 9000 改回 1500,或者按网络环境适配一个更安全的中间值。容器场景下还要注意 Docker 的mtu参数也要一起改,否则容器内外 MTU 不一致,同样会出现能建连但握手失败的诡异现象。
4.2 Linux 127 秒超时:这不是程序 bug,是 TCP 重传机制
另一个高频现象是:后端服务明明没启动,客户端请求要等整整 127 秒才返回失败。这是因为 Linux 默认的 TCP SYN 重传次数是 6 次,每次超时时间指数递增,重试全部走完之后累计耗时大约 127 秒。
大模型网关如果某个后端实例挂了,而健康检查还没把它标记为 Down,这个 127 秒的等待会让用户觉得服务完全不可用。排查时用ss -t和traceroute确认连接在哪一跳被丢弃。如果希望失败反馈更快一点,可以在服务器上做一个保守调整:
# 降低 SYN 重传次数,让不可达主机的失败反馈从 127 秒缩短到约 7~8 秒 sudo sysctl -w net.ipv4.tcp_syn_retries=3不过这只是“让报错更快”的治标手段,真正要做的是保证 LB 的健康检查能够及时摘除异常后端,从根上避免请求被发送到一个不存在的服务上。
4.3 流式输出被中途掐断:空闲超时与心跳缺少的典型症状
现象:大模型回答到一半,客户端突然收到流结束或连接重置,但服务端日志显示整个请求是正常处理完的。
这种问题通常不是应用层逻辑错误,而是链路上的某台设备检测到“空闲时间过长”后主动断开了 TCP 连接。大模型在输出前的思考阶段,可能十几秒甚至几十秒都不产生任何 Token;中间如果模型在调用工具,也可能长时间没有数据输出。
我在采用 OCI LB 之后把监听器的空闲超时调到了 300 秒,同时在网关内部实现了 SSE 心跳注释帧,问题彻底消失。这里很想提醒一句:不要只依赖 LB 调参,后端的主动保活同样重要。客户端、LB、互联网中间设备、源站四层链路里,任何一层的“空闲判断”都会导致断流,最稳妥的做法就是让数据流里始终有“呼吸”。
4.4 健康检查的 404 陷阱与域名头透传
还有两个隐蔽问题,非常值得拿出来单独写。
健康检查 404 陷阱:很多健康检查默认探测路径是/,而网关如果不把根路径暴露为 200,而是返回 404,LB 就判断后端不健康。我遇到过运维同事把后端集配好之后,LB 面板上所有后端都红着的场景。解决办法有两个:要么把健康检查路径改成网关明确提供的一个接口,比如/v1/models;要么在健康检查配置里把“期望响应码”改成[200,404],让后端在返回 404 时也被视为存活。按照自己网关的实际情况选一个即可。
X-Forwarded-For 头信任:LB 转发请求时会在 Header 里带上真实的客户端 IP,字段是X-Forwarded-For。网关的日志系统很可能依赖这个字段做来源统计,但一定要让网关只信任来自 LB 的该字段,而不是直接信任外部客户端传入的自定义值,否则任何用户都能伪造来源 IP,日志审计直接失去意义。
我发现不少自建网关翻车,都是因为排障方向从一开始就错了。业务正常时,什么都好;业务一异常,先看是不是域名解析、是不是LB配置、是不是后端服务,而不是一上来就怀疑模型本身,这个顺序能省掉很多无效加班。
5. 一些选型上的建议和最终选择
经历了 Cloudflare 限制和 OCI L7 LB 的迁移之后,我对“大模型网关的网络层选型”有了非常明确的判断标准。
如果你只是自己小范围用,访问量极低,网关超时偶尔失败一次也能接受,那么 Cloudflare Tunnel 依然是最省事的方案:零成本、无需公网 IP、不需要管理证书。而如果你的网关要面向一支开发团队甚至外部客户,提供稳定的 OpenAI 兼容接口,需要跑流式 SSE,那请不要抱着免费 CDN 不放——OCI 的免费 L7 负载均衡器在这套场景下的价值,远不只是“免费”两个字,它把超时的控制权还给了使用者,这是能不能用、敢不敢上生产的分界线。
我目前的架构是:OCI 免费 L7 LB 作为唯一公网入口,443 终结 TLS;后端两台免费的 Arm VM 跑网关,LB 基于 Host 域名做路由,把生产流量、内部监控工具、灰度测试流量分流到不同后端集;DNS 托管在外部正常解析服务,记录直接指向 LB 的 CNAME。整套资源月成本为零,但可以获得可调的超时、实时的健康检查、双可用区的高可用,以及按域名和路径路由的能力。
最后分享一点点体会:网络层的问题,不像代码 bug 那么快地暴露,因为它在大多数时间表现得“还好”,只在流量达到某种特征时才崩溃。大流量、长连接、流式输出,恰好就是把这些问题逼出来的压力测试。遇到超时先别急着调程序的 timeout 参数,把链路里每一层设备的超时策略梳理一遍,比满屏抓日志管用得多。配置完 OCI LB 的那天晚上,我又拿一个超长的测试请求跑了一遍,看着 Token 一个接一个输出到 200 多秒仍未中断,心里那块石头才彻底落了地。