欢迎光临
我们一直在努力

Ingress-Nginx 偶发 502 Bad Gateway 与上游 Keep-Alive 踩坑

Ingress-Nginx 偶发 502 Bad Gateway 与上游 Keep-Alive 踩坑

封面信息图

在 Kubernetes 生产环境中,Ingress-Nginx 偶发 502 Bad Gateway 属于排查难度最高、也最容易引发前后端推诿的经典顽疾之一。

故障的典型表象是:业务系统整体运行平稳,全站 QPS 和 CPU 均在正常水位,但前端或客户端在每分钟数十万次请求中,总会极其随机地遭遇几十个 502 错误。查看 Ingress-Nginx 的 access.log,记录着 upstream_status: 502 以及 upstream_response_time: 0.001(耗时仅 1 毫秒);而下游业务容器(如 Spring Boot / Go Web)的日志里却干干净净,完全找不到这笔请求的到达痕迹。

这种“毫秒级 502 假死”,根源通常不在于后端服务挂了,而是由于 Ingress-Nginx 与后端业务 Pod 在 TCP HTTP Keep-Alive 空闲连接超时时间(Idle Timeout)上的配置错位与竞态竞争(Race Condition)。

深入网络栈:Keep-Alive 超时错位引发的“幽灵关闭”

为了提高性能、减少频繁三次握手的开销,Ingress-Nginx 默认会与后端的 Upstream Pod 维持一组持久的 TCP 长连接池(Keep-Alive)。

当一条连接处于空闲状态(没有数据传输)时,连接双方各自维护着一个空闲超时定时器(Idle Timeout):

  • Ingress-Nginx 端:proxy_connect_timeout 默认为 60s,长连接空闲保持时间默认通常较长。
  • 下游后端应用端:Node.js 默认 keepAliveTimeout 为 5 秒,Spring Boot (Tomcat/Netty) 默认空闲超时通常在 20 秒到 60 秒之间,Go http.Server 默认 IdleTimeout 通常设为 30 秒。

问题就发生在**“后端刚刚发起 TCP FIN 挥手,而 Nginx 恰好在同一微秒将新请求塞进该连接”**的极端竞态瞬间:

Ingress-Nginx (客户端) Upstream Pod / Tomcat (服务端)
│ │
│ ─── (该 TCP 长连接已空闲 20.001 秒) ─────────────── │ (Tomcat 空闲超时到达: 20s)
│ │
│ ├── 发送 TCP FIN (主动关闭连接)
│ │
├── [恰好此时有新请求到达] ─── │
│ Nginx 从空闲连接池取出该 Socket │
│ 发送 HTTP GET /api/pay ──► │
│ │ (在网络光纤中发生数据包交叉)
│ ▼
│ ◄── 收到 Tomcat 发来的 FIN 报文 │
│ │ 收到 Nginx 发来的 HTTP 数据包
│ │ 但本地 Socket 状态已变为 CLOSE_WAIT
│ ◄── 发送 TCP RST (重置连接) ────────────────────────┤
▼
[ Nginx 收到 RST: 抛出 Connection reset by peer ]
[ Nginx 判定上游不可用,直接向真实客户端返回 502 Bad Gateway ]

在整个过程中,业务应用进程在收到数据包前就已经把 Socket 关闭了,因此应用代码层完全没有这笔请求的日志;而 Nginx 发送数据瞬间遭遇 RST,在耗时 0.001 秒内直接向客户端抛出 502。

根治方案一:严格校准 Keep-Alive 超时梯队(Nginx 必须小于 Backend)

要彻底根除这种竞态关闭,必须遵循一条不可逾越的黄金法则:Ingress-Nginx(调用方)主动关闭空闲连接的超时时间,必须严格小于后端业务 Pod 的 Idle Timeout(建议留出至少 5~10 秒的安全差额)!

这样可以确保空闲连接永远由前端 Ingress-Nginx 主动关闭,后端永远不会在空闲时单方面抢先发送 FIN 包。

1. 在 Ingress 资源中配置 Upstream Keep-Alive

在 Ingress 的 Annotations 中精细化调整反向代理长连接参数:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-ingress
namespace: prod
annotations:
kubernetes.io/ingress.class: "nginx"
# 启用与后端的 Keep-Alive 长连接复用
nginx.ingress.kubernetes.io/upstream-keepalive-connections: "200"
nginx.ingress.kubernetes.io/upstream-keepalive-time: "1h"
# 核心调优: 将 Nginx 对后端的空闲超时设为 15 秒
nginx.ingress.kubernetes.io/upstream-keepalive-timeout: "15"
# 单条连接最多处理的请求数 (达到后主动平滑关闭重建)
nginx.ingress.kubernetes.io/upstream-keepalive-requests: "10000"

2. 将后端业务容器的 Keep-Alive 空闲超时调大至 30 秒以上
  • Spring Boot (application.yml):server:
    tomcat:
    connection-timeout: 30000
    keep-alive-timeout: 30000

  • Go 语言服务 (main.go):server := &http.Server{
    Addr: ":8080",
    Handler: router,
    ReadTimeout: 10 * time.Second,
    WriteTimeout: 10 * time.Second,
    IdleTimeout: 60 * time.Second, // 远大于 Nginx 的 15 秒
    }

根治方案二:开启 Nginx 针对特定错误的自动幂等重试

即使网络偶发抖动导致连接被断开,Ingress-Nginx 也可以通过配置 proxy_next_upstream 在毫秒级内自动挑选另一个健康的 Pod 进行无感重试,对外部用户完全抹平 502:

metadata:
annotations:
# 当遭遇连接重置或 502/504 时,自动向下一个 upstream 副本重试
nginx.ingress.kubernetes.io/proxy-next-upstream: "error timeout invalid_header http_502 http_504"
nginx.ingress.kubernetes.io/proxy-next-upstream-tries: "3"
nginx.ingress.kubernetes.io/proxy-next-upstream-timeout: "10"

根治方案三:优雅停机(Graceful Shutdown)与 preStop Hook

除了空闲超时错位外,Pod 缩容或滚动更新时 Endpoint 尚未从 Ingress 摘除、而应用容器已经退出的短暂窗口也会引发 502。

必须在 Pod Spec 中注入 preStop 睡眠钩子,给 Endpoint 传播留足缓冲时间:

lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]

总结

通过“对齐 Keep-Alive 超时梯队(Nginx 15s < App 60s) + 开启 proxy_next_upstream 自动重试 + 注入 preStop 优雅停机”的三重防线,我们在核心生产集群上彻底根治了困扰已久的随机 502 幽灵故障,全站非业务 5xx 错误率直接归零。

赞(0)
未经允许不得转载:171主机测评 » Ingress-Nginx 偶发 502 Bad Gateway 与上游 Keep-Alive 踩坑
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址