之前踩过这个坑,在《使用 nginx 作反向代理,启用 keepalive 时,遇到 502 错误的调查过程》 中了记录调查过程,当时多个案例同时查,记录的比较乱,这里重新整理一下结论。
ingress-nginx 到 upstream 的长连接通过 configmap 中的 upstream-keepalive-connections 等参数设置,注意与 keep-alive 区分(见文末)。另外 ingress-nginx 0.20 之前的版本有 bug,即使配置了也不生效:ingress-nginx upstream 的 keep-alive 不生效。
这里主要解释结论 3,这里的结论不仅适用于 ingress-nginx,也适用于其它使用 nginx 的场景。
结论1:nginx 的端口耗尽时,会返回 502 错误(和本文要讨论的内容无关)。
结论2:nginx 向已经被服务端主动断开的连接发送请求,会收到 RST,然后返回 502。
结论3:服务端先于 nginx 断开连接的情况有两种,
1)服务端的连接超时时间小于 nginx 中的配置;
2)服务端配置的单个连接的最大请求数小于 nginx 中配置。
服务端应用可能是通过本地的 tomcat 或者其它 web 框架对外暴露的,这种情况非常普遍。 这些 Web 服务或者框架通常都有默认的长连接设置。
譬如 tomcat 的 相关配置
另外曾经遇到过的 Gunicorn 超时时间只有 2 秒:
如果做反向代理的 nginx 中配置的连接断开条件比后端服务设置的条件宽松,那么就容易出现后端服务先断开连接的情况, 这时候 nginx 转发请求到 upstream,upstream 会返回 RST,nginx 打印下面的错误日志,给客户端返回 502:
2019/06/13 04:57:54 [error] 3429#3429: *21983075 upstream prematurely closed connection while reading
response header from upstream, client: 10.19.167.120, server: XXXX.com, request: "POST XXXX HTTP/1.0",
upstream: "http://11.0.29.4:8080/XXXXXX", host: "XXXX.com"
2019/06/13 04:58:34 [error] 3063#3063: *21989359 recv() failed (104: Connection reset by peer) while
reading response header from upstream, client: 10.19.138.139, server: XXXX.com, request:
"POST /api/v1/XXXX HTTP/1.1", upstream: "http://11.0.145.9:8080/api/v1/XXXX", host: "XXXX.com"
可以调整 nginx 的 upstream 中 keepalive_timeout 和 keepalive_requests,确保 nginx 先于 upstream 断开连接。只有 nginx 与 upstream 之间使用长连接的时候需要考虑这种情况,并进行类似的设置。
upstream record_upstream {
server 127.0.0.1:9091;
keepalive 16;
keepalive_timeout 58s; # 默认 60 s,根据实际情况调整,建议小于 60s
keepalive_requests 98; # 默认 100 个,根据实际情况调整,建议小于 100
}
server {
...
location /http/ {
proxy_pass http://record_upstream;
proxy_http_version 1.1;
proxy_set_header Connection "";
...
}
}
nginx 的 keepalive_timeout 和 keepalive_requests 参数各有两个:一组属于 ngx_http_core_module,在 http/server/location 中使用,限制的是 client 与 nginx 之间的连接;另一组是上面使用的,属于 ngx_http_upstream_module,限制的是 nginx 与 upstream 之间的连接。
nginx 的 upstream 中没有明确配置 keepalive,那么无论 client 和 nginx 之间是否长连接,nginx 和 upstream 都是短连接。
用下面的配置观察:
upstream record_upstream {
server 127.0.0.1:9091;
#keepalive 3;
#keepalive_timeout 58s;
#keepalive_requests 98;
}
server {
listen 9000;
listen [::]:9000;
server_name echo.example;
keepalive_requests 2000;
keepalive_timeout 60s;
location / {
proxy_pass http://record_upstream;
#proxy_http_version 1.1;
#proxy_set_header Connection "";
}
}
使用长连接访问 nginx :
wrk -c 1 -t 1 -d 2s http://127.0.0.1:9000
http-record 收到的请求是 “Connection: close”:
/go/src/Server/echo.go:46: {
"RemoteAddr": "172.17.0.1:34522",
"Method": "GET",
"Host": "record_upstream",
"RequestURI": "/",
"Header": {
"Connection": [
"close"
]
},
"Body": ""
}