运维踩坑:nginx 缓存上游容器旧 IP,请求转发到了错误的服务

容器化微服务环境,前置 nginx 反代到 Spring Cloud Gateway。某次 gateway 容器重建后,所有经 nginx 的 /api/** 请求开始返回 500,而 gateway 自身、服务发现、各业务服务均正常。

排查绕了一大圈,根因是 nginx 的一个经典行为:proxy_pass 写主机名时只在启动时解析一次,之后永久缓存。上游容器重建换 IP 后,nginx 仍在连旧 IP——而旧 IP 已被 Docker 分配给了另一个服务。

现场

前置 nginx + Spring Cloud Gateway + Nacos 架构,全部跑在同一个 Docker 自定义网络(172.18.0.0/16)里:

请求链路本该是 客户端 → nginx → gateway → 业务服务,gateway 再按 Nacos 的实例列表把请求路由到 project-order / project-auth。但出问题时 nginx 根本没把请求交给 gateway——某个经 nginx 的接口请求报错:

几个看似矛盾的现象,把排查方向带偏了数次:

  • 服务发现正常:目标服务已注册到 Nacos,gateway 也订阅到了实例。
  • gateway 容器内直连正常:curl gateway:8080/xxx 返回 200。
  • 但经 nginx 的请求,gateway 一条日志都没有——请求根本没到 gateway。
  • nginx 配置看不出问题:location /api/ { proxy_pass http://project-gateway:8080/; },proxy_pass 带尾部 / 会剥前缀,没毛病。
  • docker exec nginx curl project-gateway:8080 也是通的。

配置对、直连通、exec 也通,唯独经 nginx 就 500 且 gateway 收不到——问题出在 nginx worker 实际把请求转发到了哪。抓 nginx worker 的真实 TCP 连接目标(/proc/net/tcp 解码):

对照各容器当前 IP:

nginx 把请求转发到了 172.18.0.20(业务服务 project-order),而非 gateway(.21)。该服务没有对应路由,抛异常被框架兜底成 500。

为什么 docker exec 通、worker 却不通?exec 启动的新 shell 用当前 DNS(解析到 .21),而 nginx worker 用的是启动时缓存的旧 IP(.20)。同一容器、同一网络栈,DNS 解析的时机不同。

根因

  • proxy_pass 写的是主机名,且全配置没有 resolver 指令 → nginx 在启动时解析一次并永久缓存,之后不再重新解析。
  • gateway 容器重建后 IP 由 .20 变 .21,旧 IP .20 被 Docker 重新分配给了 project-order。
  • nginx 仍在连旧 IP,于是所有 /api/** 请求都打到了 project-order。

一句话:proxy_pass 写主机名 = 启动期解析一次后缓存,容器 IP 一变就连错。

修复

resolver + 变量版 proxy_pass,让 nginx 按 TTL 动态解析:

三个关键点:

  1. resolver 127.0.0.11 valid=30s —— Docker 内置 DNS,解析结果 30 秒过期。
  2. set $upstream + 变量版 proxy_pass —— proxy_pass 一旦含变量,nginx 就不再启动期静态解析,而是运行时走 resolver。这是社区版 nginx 动态解析上游的唯一方式。
  3. rewrite ^/api/(.*)$ /$1 break —— 必须显式剥前缀(原因见下)。

连环坑:变量版 proxy_pass 不剥前缀

从纯文本 proxy_pass http://x:8080/; 改成变量版后,原来自动剥 /api/ 前缀的能力消失了,后端会收到错误的 path(实测 gateway 收到的是 /)。所以必须配 rewrite ... break 显式剥,并且 proxy_pass 去掉尾部 /(配 rewrite break 时必须是纯地址,带 URI 会二次改写)。

别走这条路

upstream {} 块里写主机名同样是启动期一次性解析,不动态刷新(只有 Nginx Plus 的 resolve 参数才动态)。upstream 块解决不了这个问题。

改完 nginx -t && nginx -s reload,容器怎么重建、IP 怎么变,nginx 都会按 30 秒 TTL 自动跟上。

添加评论
点赞收藏
点踩分享查看原文
评论
?
参与讨论