记一次因后端框架断开连接引发的Nginx502


异常现场

  • 影响问题
    • 因客户端恶意请求,引发后端框架直接关闭连接,导致Nginx对后端检测误判为异常,下线 upstream 影响正常业务请求。
  • 访问日志
[31/Mar/2022:10:00:05 +0800] "GET /vod/live HTTP/1.1" 502 552 1206 "https://map.sina.com" "Mozilla/5.0 (Windows NT 6.1; WOW64) Appl
eWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36" upserver_online 502 0.000 0.001 news.baidu.com " "1.2.3.4"

日志大致说明:

已对日志敏感信息处理(URI、Host、upstream、客户端IP)。

  • 请求时间: [31/Mar/2022:10:00:05 +0800]
  • 请求方式: GET
  • 请求URI: /vod/live
  • 请求协议: HTTP/1.1
  • 响应状态码: 502
  • 响应大小: 552 字节
  • 请求大小: 1206 字节
  • Referer: https://map.sina.com
  • Uer-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) Appl
    eWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
  • Upstream: upserver_online
  • Upstream响应状态码: 502
  • upstream_response_time: 0.000
  • request_time: 0.001
  • Host: news.baidu.com
  • 客户端IP: 1.2.3.4

大致可以知道后端异常导致 502 ;

接下来查看 error_log , 等级: error

2022/03/31 10:00:05 [error] 38243#0: *50216349623 no live upstreams while connecting to upstream, client: 1.2.3.4, server: news.baidu/com, request: "GET /vod/live HTTP/1.1", upstream: "http://upserver_online/vod/live", host: "news.baidu.com", referrer: "https://map.sina.com"

大致可以知道此时 Nginx 认为检测不到存活的后端;

/api/ URI 的 api_server 检测正常,请求正常。

现有配置

upstream api_server {
    server 10.23.44.55:12311;
    server 10.23.43.55:12311;
    }

upstream upserver_online {
    server 10.23.51.1:22013;
    server 10.23.55.30:12311;
    }


server {
    listen    80;
    listen    443 ssl;
    server_name news.baidu.com ;
    charset utf-8;
    access_log  logs/$http_host.access.main.log main;
    error_log  logs/news.baidu.com.error.crit.log error;

    ssl_certificate      keys/server.cer;
    ssl_certificate_key  keys/server.key;

    ssl_session_timeout  5m;
    ssl_prefer_server_ciphers on;
    add_header Strict-Transport-Security "max-age=31536000";

    location  /api/ {
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Real-Port $remote_port;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass http://api_server;
    }

    location / {
        proxy_redirect off;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $clientRealIp;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Real-Port $remote_port;
        proxy_pass http://upserver_online;
    }
}

排查过程

首先进行常规网络检查,登陆机器 telnet upstream 对应端口,并 curl upstream ,确认后端一切正常,但为什么 Nginx 会检查失败?

于是尝试将 location / 中的 proxy_pass upserver_online 写死为 10.23.51.20:12311

location / {
    ...
    proxy_pass http://10.23.51.1:22013;
}

该 upstream 请求大部分恢复,access_log 但仍有少量 502 ,且集中客户端IP,遂进行抓包查看。

Nginx: 172.17.10.123

Upstream: 10.23.51.1:22013

  • 与服务端(upstream)建连成功
  • 向服务端发送数据成功,有 ACK 应答
  • 在 ACK 应答后,服务端读取请求内容,Cookie 中包含特殊字符
  • 服务端框架抛出异常,无数据响应,走断连流程,关闭连接
  • 造成 “upstream prematurely closed connection while reading response header from upstream”

对应后端存在异常日志

初步判定为疑似客户端恶意请求,而非正常业务请求。

尝试复现

后端为Nginx并 return 444

...
   location /error_broken {
        return 444;
    }
...
  • 无法复现,抓包无法体现建连过程

后端为简单 socket 服务,并对请求直接关闭连接

#!/usr/bin/python3
# -*- coding: UTF-8 -*-

import socket

sock = socket.socket()
sock.bind(("0.0.0.0", 8800)) # 绑定监听的IP地址与端口8800
sock.listen(5) # 设置最大监听数

while True:
    conn, addr = sock.accept()
    data = conn.recv(1024)
    print(data) # 打印查看请求头与请求体

    # 必须以HTTP响应头的格式返回数据,否则浏览器无法正常解析
    # 同时注意send的数据不能是str字符串,必须是bytes,否则会报错。
    # conn.send(b'HTTP/1.1 200 OK\r\n\r\n <h1>7.27 Response!</h1>')

    # 不做内容响应,直接断开连接
    conn.close()
  • 得以复现,在建连成功后,没有响应数据,走正常关闭连接流程

原因分析

  1. 造成 502 的原因

    服务后端框架直接关闭连接,导致 Nginx 认为后端服务不可用,且健康检查默认为 1 次失败,冷却时间 10 秒,同时因为频繁夹杂异常请求,最终导致影响正常请求。

  2. 502时,日志为 upstream_server 名称,而非 server IP 的原因

    当 upstream 为多个 server 时,所有后端检测不可用时 ( default: max_fails=1 , fail_timeout=10 ) Nginx 就会认为后端 502,在日志输出就会变成 upstram_server 名称,例如: upserver_online 502

    如果 upstream 只有一个 server 时,在后端 502 时,仍为 upstream_server_IP: Port 格式,例如: 192.168.1.2:1234 502

解决方式

  • 临时方法

异常期间写死 proxy_pass 为 upstream_server_IP 即可

  • 长远方案

让后端服务正常处理异常情况,例如响应400,而非直接关闭连接。

遗留问题

为什么当 upstream server 数量为 1 且状态 502 或其他异常情况时,日志记录的是具体 upstream server IP;
但当数量大于 1 时,是 upstream server 名称?
待深入源码分析。


相关文档

http://nginx.org/en/docs/http/ngx_http_upstream_module.html#server


文章作者: NesTeaLin
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 NesTeaLin !
  目录