文章

502排查总结

502 排查总结

1. 最初现象

日志里看到:

http_code: 502
upstream_addr: health-ack
upstream_response_time: 0.000
upstream_connect_time: -
upstream_header_time: -
user_req: POST /portal/content/list HTTP/1.1

初步判断:Nginx 没有从 upstream 拿到有效响应,所以返回 502


2. 确认 upstream 配置

相关配置是:

proxy_pass http://health-ack;

upstream health-ack {
    server 10.111.138.23:80  weight=50;
    server 10.111.160.172:80 weight=50;
}

另一个几乎一样的 upstream:

upstream go-member-manager {
    server 10.111.160.172:80 weight=50;
    server 10.111.138.23:80 weight=50;
}

因此一开始可以排除明显的 IP、端口配置错误。


3. 网络层验证

telnet 正常,说明:

Nginx -> 10.111.138.23:80
Nginx -> 10.111.160.172:80

TCP 层是通的。

所以不是:

端口没开
安全组拦截
网络不通
connect refused

4. curl ingress 节点返回 404 的分支

测试过:

curl http://10.111.138.23:80/portal/content/list

返回 404。

一开始怀疑过 Ingress Host 没带导致 default backend 404,因为 Ingress 通常按 Host + path 匹配。后面确认实际域名是:

api-health.qingsongjkkj.com

正确测试应该是:

curl -v -H "Host: api-health.qingsongjkkj.com" \
http://10.111.138.23:80/portal/content/list

但后续根据数据看,这不是主因,因为问题不是大量 404,而是极少量 502。


5. 关键突破:异常日志里的 $upstream_addrhealth-ack

正常请求 upstream 会显示:

10.111.138.23:80
10.111.160.172:80

但异常 502 显示:

upstream_addr: health-ack

然后确认 log_format 里确实是:

$upstream_addr
$upstream_response_time
$upstream_connect_time
$upstream_header_time

所以不是日志字段写错。

Nginx 官方文档说明,$upstream_addr 正常记录 upstream server 地址;如果没有选出 server,它会保留 upstream group 的名字。因此 upstream_addr = health-ack 的含义不是”转发到了叫 health-ack 的机器”,而是:这次请求进入 health-ack 组后,没有选出 10.111.138.2310.111.160.172。(Nginx)


6. 核心结论

更精确的直接原因是:

health-ack upstream group 在某些极短时间窗口里没有可用 peer,所以 Nginx 直接返回 502。

也就是类似:

请求进入 health-ack
-> 没有选出 10.111.138.23:80
-> 也没有选出 10.111.160.172:80
-> upstream_addr 显示 health-ack
-> 返回 502

7. 为什么不是全量失败,而是极少量失败

你补充的数据是:

两个 upstream 节点都有大量请求
总量很大
502 约万分之二

这说明不是配置写死错误。如果配置错、Host 错、端口错,应该是大面积失败。

更符合 Nginx 被动健康检查机制:

某个 peer 出现 error / timeout / invalid_header
-> 被临时标记 failed

另一个 peer 也在短时间内出现失败
-> 也被临时标记 failed

刚好有请求进来
-> health-ack 组内没有可用 peer
-> 返回 502

Nginx upstream server 默认 max_fails=1fail_timeout=10s,也就是在 fail_timeout 时间窗口内达到失败次数后,server 会被临时认为不可用。(Nginx)


8. 哪些错误会把 peer 打 failed

真正会导致 peer 被判失败的,不是普通 404,而是这些连接级/协议级错误:

connect() failed
upstream timed out
upstream prematurely closed connection
recv() failed / connection reset by peer
upstream sent invalid header

Nginx proxy_next_upstream 文档说明,errortimeoutinvalid_header 会被视为 unsuccessful attempt;而 http_403http_404 永远不会被视为 unsuccessful attempt。(Nginx)

所以之前看到 404,不是核心。真正应该查 error log 里的:

grep -E "health-ack|no live upstreams|upstream timed out|prematurely closed|invalid header|connect\(\) failed|recv\(\(\) failed|reset by peer" \
/data/qsc/openresty/nginx/logs/health/*error.log

其中:

no live upstreams

是结果;

timeout / reset / prematurely closed / invalid header

才是把 peer 打 failed 的原因。


9. “改 upstream 名就好了”的解释

你后面说:

改了个 upstream 名就没出现过了。

这个现象支持”upstream group 运行时状态问题”。

因为:

upstream health-ack { ... }

和:

upstream health-ack-new { ... }

即使里面 server 一模一样,对 Nginx 来说也是两个不同的 upstream group。

改名会带来两个效果:

1. reload Nginx
2. 创建一个新的 upstream group,旧 health-ack 的运行时失败状态/连接状态不再影响它

所以不是后端 IP 变好了,而是绕开了旧 health-ack 这个共享 upstream group 的运行时状态。


10. 最关键发现:health-ack 被 9 处共用

查到:

260:  proxy_pass http://health-ack;
493:  proxy_pass http://health-ack;
509:  proxy_pass http://health-ack;
525:  proxy_pass http://health-ack;
613:  proxy_pass http://health-ack;
1024: proxy_pass http://health-ack;
1056: proxy_pass http://health-ack;
4293: proxy_pass http://health-ack;
4321: proxy_pass http://health-ack;

这个是目前最关键的线索。

说明 health-ack 不是一个业务单独使用,而是:

多个 server / 多个 location / 多个域名
共同使用同一个 upstream group

所以其中任何一个 location 出现连接级失败,都可能影响同一个 health-ack 组的 peer 状态。然后其他正常业务刚好撞到”无可用 peer”的短窗口,也会出现 502。

这也解释了为什么:

go-member-manager 配置一样但没问题
health-ack 有问题

因为它们不是同一个 upstream group,运行时失败状态是分开的。


11. api.duoerpharmacy.com 废弃域名的判断

你贴了:

server_name api.duoerpharmacy.com;

location / {
    proxy_pass http://health-ack;
}

它确实是 health-ack 的其中一个引用点。

但你补充说:

这个域名解析都没了

所以判断是:

如果 access log 最近没有请求,它不是这次问题来源。
如果 access log 还在写,即使 DNS 没了,也可能是 hosts、缓存、内网解析、直接 IP + Host 访问导致。

这个域名不一定是根因,但属于废弃域名仍然代理到共享 upstream 的风险点。建议清理或改成 return 444/404,不要继续打 health-ack


当前结论

目前最合理的结论是:

502 的直接原因是:health-ack upstream group 在极少数时间窗口里无可用 peer,Nginx 选不出具体 IP,所以 $upstream_addr 显示 health-ack 并返回 502。

更深层原因倾向于:

health-ack 被 9 个 location/server 共用,其中某些请求路径偶发产生 timeout/reset/prematurely closed/invalid_header 等连接级失败,导致同一个 upstream group 的 peer 被短暂标记 failed;由于默认 max_fails=1/fail_timeout=10s 较敏感,两个 peer 的失败窗口偶发重叠,就产生了万分之二左右的 502。


建议处理

先做三件事。

第一,查 9 个引用点对应的完整 server/location:

nginx -T | sed -n '240,275p'
nginx -T | sed -n '475,535p'
nginx -T | sed -n '595,625p'
nginx -T | sed -n '1005,1070p'
nginx -T | sed -n '4275,4335p'

第二,查 error log,找真正把 peer 打 failed 的错误:

grep -E "health-ack|no live upstreams|upstream timed out|prematurely closed|invalid header|connect\(\) failed|recv\(\(\) failed|reset by peer" \
/data/qsc/openresty/nginx/logs/health/*error.log

第三,把共享 upstream 拆开,不要 9 个业务共用一个 health-ack

upstream health_ack_portal {
    server 10.111.138.23:80  weight=50 max_fails=3 fail_timeout=10s;
    server 10.111.160.172:80 weight=50 max_fails=3 fail_timeout=10s;
}

对应业务改成:

proxy_pass http://health_ack_portal;

同时把默认的敏感策略调宽一点:

max_fails=3 fail_timeout=10s

不建议一上来就:

max_fails=0

因为那会关闭失败统计,后端真坏时 Nginx 还会继续打坏节点。