docker网段冲突与内网互通.md
从 API 频繁超时到零成本内网穿透:记一次完美的 LLM 网关架构重构
最近在折腾大模型 API 的聚合与分发,遇到了一系列非常经典的“连环坑”。从最初的 CDN 缓存导致流式输出断裂,到后端架构重构,再到跨节点内网互联时的防火墙与 Docker 网络冲突,简直是步步惊心。
好在最后成功跑通了一套**“CDN 护盾 + 纯内网 API 聚合”**的完美架构,不仅延迟极低,还彻底免去了云服务商昂贵的公网流量费。特此记录一下这套标准企业级架构的配置流程,希望能帮大家少走弯路。
架构演进背景
最初,我直接用单台服务器跑 API 代理(类似 cliproxyapi),前面套了 EdgeOne CDN 加速。 但为了更好的高可用和负载均衡,我引入了聚合网关(类似 metapi)作为“大门”,将后端的 API 代理节点作为“内室”隐藏起来。
最终理想架构: 用户 ➔ EdgeOne (HTTPS/CDN) ➔ 聚合网关服务器 (公网) ➔ Azure 内网 (免费高速) ➔ API 代理节点服务器 (禁止公网访问)
第一关:流式 API 返回 0 Token 的元凶
在使用大模型流式(Stream)输出时,经常遇到前端打字机效果失效,直接返回 0 token,或者请求等了 30 秒直接超时。
排错过程: 一开始以为是 CDN 的 WAF 把高频请求拦截了,看了安全面板发现并没有。最终查明是 CDN 的缓存机制和回源超时机制在作祟。大模型思考时间长,如果 CDN 尝试缓存流式数据,或者默认回源时间只有 30 秒,就会强制掐断连接。
正确解法(EdgeOne 规则引擎配置): 进入站点的“规则引擎”,针对 API 路径(或整个加速域名)添加两条核心操作:
- 节点缓存配置: 设为**“不缓存”**(或绕过缓存)。让每一次 API 请求 100% 穿透到达源站。
- 回源超时时间: 强制修改为 300 秒或 600 秒,给足大模型思考和吐字的时间。
第二关:舍近求远?不如直接打通云厂商内网!
既然网关服务器和后端节点都在同一个云服务商的同一区域(例如都在 Azure 日本区),走公网去互相调用简直是暴殄天物:不仅延迟高、需要配置多层防火墙,还要交昂贵的公网流量费。
操作步骤:配置 Azure 虚拟网络对等互连 (VNet Peering) 如果多台机器不在同一个虚拟网络下,内网是默认隔离的。
- 在云控制台找到网关所在的“虚拟网络 (VNet)”。
- 在左侧菜单找到“对等互连 (Peerings)”。
- 添加互连,将目标指向后端节点所在的虚拟网络(双向允许即可,不需要勾选网关传输等高级选项)。
配置完成后,机器之间通过 10.x.x.x 的私有 IP 进行通信,ping 延迟通常在 1 毫秒以内,速度起飞。
第三关:iptables 的“回包陷阱”
为了保证后端节点绝对安全,必须让它们“隐身”,只允许网关服务器的内网 IP 访问。我使用了 iptables 的 DOCKER-USER 链来拦截流量,但却踩了一个大坑:请求进得去,响应出不来,导致 curl 一直卡住超时。
踩坑原因: 使用 conntrack --ctorigdstport 匹配端口时,不仅匹配了进来的请求,也匹配了出去的响应。响应数据从容器内部发回时,源 IP 变了,直接被最后的 DROP 规则无情抛弃。
终极防火墙配置脚本: 必须加上一条放行已建立连接(ESTABLISHED)的规则。在后端节点服务器执行以下命令(假设网关内网 IP 为 10.0.0.X,服务端口为 52134):
# 1. 清理旧规则,确保有 Docker 必需的 RETURN
sudo iptables -F DOCKER-USER
sudo iptables -A DOCKER-USER -j RETURN
# 2. 【核心】放行所有已经建立连接的回包(让数据能出得去)
sudo iptables -I DOCKER-USER 1 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# 3. 允许网关的内网 IP 访问特定端口
sudo iptables -I DOCKER-USER 2 -p tcp -m conntrack --ctorigdstport 52134 -s 10.0.0.X -j ACCEPT
# 4. 拦截其他所有试图访问该端口的新连接
sudo iptables -I DOCKER-USER 3 -p tcp -m conntrack --ctorigdstport 52134 -j DROP
# 5. 永久保存规则(需安装 iptables-persistent)
sudo netfilter-persistent save第四关:Docker 容器内的“网段迷路”
底层网络通了,宿主机 curl 测试也通了,但在网关的 Web 面板里测试渠道依然超时! 排查发现,网关是运行在 Docker 容器里的,而 Docker 自己分配的虚拟网段可能与云厂商的内网网段产生冲突。容器想访问 10.x.x.x 时,流量根本没发往宿主机网卡,而是在 Docker 内部迷路了。
完美解法:使用 Host 网络模式 修改网关(如 metapi)的 docker-compose.yml,让容器直接与宿主机共享网络栈。
注意避坑: 一旦开启 network_mode: "host",Docker 就会忽略 ports 映射配置!如果你保留了 ports 字段,不仅不会生效,在启动时还会引发语法错误。你需要直接通过环境变量让程序监听你想要的外部端口。
修改后的标准 docker-compose.yml 示例:
services:
metapi:
image: <your_image_name>:latest
volumes:
- ./data:/app/data
environment:
# ... 其他环境变量 ...
# 直接让内部程序运行在你需要的端口上
PORT: 42561
TZ: Asia/Shanghai
restart: unless-stopped
# 开启 host 模式,与宿主机内网彻底打通
network_mode: "host"
# 【注意】这里必须删掉或注释掉 ports 数组!保存后执行 docker compose down && docker compose up -d,网关容器瞬间就能顺滑地通过内网 IP 调用后端的 API 节点了。
总结
经过这四关的折腾,目前这套架构运行得极其稳定。前端 CDN 负责扛伤害和安全防护,公网流量被完美地限制在网关这一层;而后端真正消耗大量带宽的数据交换,全部走云厂商免费的万兆内网。
永远记住网络排错的三字真言:看日志! 从 CDN 的缓存行为,到路由的连通性,再到防火墙的包过滤逻辑,一层层剥丝抽茧,问题总会水落石出。
