从ingress的监控中心,我们看到,失败率虽然不高,但始终保持在0.05到0.1的水平:
我们用这样的条件去查询,发现绝大部分错误是502错误:
status>=500 | select status, count(*) a group by status order by a desc
那么502错误到底是个什么错误呢?百度百科给出的解释是:
502 Bad Gateway是指错误网关,无效网关;在互联网中表示一种网络错误。 表现在WEB浏览器中给出的页面反馈。 它通常并不意味着上游服务器已关闭(无响应网关/代理) ,而是上游服务器和网关/代理使用不一致的协议交换数据。 鉴于互联网协议是相当清楚的,它往往意味着一个或两个机器已不正确或不完全编程。
还有人说是超时导致的:
马上在评论区有人反驳:
百度百科对504错误的解释:
504错误代表网关超时(Gateway timeout),是指服务器作为网关或代理,但是没有及时从上游服务器收到请求。 服务器(不一定是Web 服务器)正在作为一个网关或代理来完成客户(如您的浏览器或我们的CheckUpDown 机器人)访问所需网址的请求。
显然,504错误才是超时,而502并不是。
而且从我们对502错误日志的进一步分析来看,发生502错误时的请求时间和响应时间都极短,不可能是超时。
查502与504的区别,只有这个说法相对靠谱:
也就是说我们后端的服务是能够响应的,但响应不符合要求,所以出现了502错误。但这种错误并不是必然的,如果是必然出现,则网站整体不可用,早就被发现了,正因为它是偶发的,所以有必要看一下在发生502的时候到底发生了什么。
为此我们把nginx的logtail日志的stderr输出打开:
此前这里本来是false,现在我们把它改成true,使它能够将错误日志输出出来,便于我们查找原因。
stderr错误输出之后,立刻就能在日志里看到大量的这种错误:
2022/04/02 16:59:55 [error] 11168#11168: *739601507 recv() failed (104: Connection reset by peer) while reading response header from upstream, client: 49.93.83.68, server: www.domain.com, request: "POST /myserver/service HTTP/1.1", upstream: "http://192.108.1.121:8080/myserver/service", host: "www.domain.com"
从字面意思来理解,是说上游服务器直接关闭了连接。但是上游服务器为什么要关闭连接呢?将错误信息放入搜索引擎进一步排查,很多文章将我们的思路导向了keepalive这个方向,最应该检查的是keepalive_timeout和keepalive_request这两个属性。
什么是keepalive?这是http 1.1协议的缺省配置,在http 1.0的时候,如果你的网页上有10个图片,那么浏览器和服务器之间要同时建立10个连接,把这10个图片发过去然后再关闭这10个连接,显然对于服务器来说,建立10个连接再关闭10个连接,消耗是比较大的。所以在http 1.1协议里增加了keepalive的功能,在发10张图片的时候只需要建立一个连接就够了,只要还有内容要传输,这个通道会始终保持开放状态,不会在传输完毕之后立刻关闭,这就是keepalive保活的意思。
但是keepalive不能把这个连接永远保持,如果没有内容了还继续保持,无疑也是一种浪费,所以这里就产生了超时的概念,keepalive_timeout的意思是说如果这个连接当中没有内容传输了并且超过了这个时间,那么就把这个连接断掉,keepalive_requests的意思是说我们这个连接最多允许传输多少个内容,超过这个内容那么也把它断掉。
那么这个keepalive_timout和我们的502错误之间有什么关系呢?因为所有网站的架构都不是浏览器直接连接后端的应用服务器,而一定是中间有nginx服务器做反向代理的,浏览器和nginx服务器之间建立keepalive连接,nginx再和后端的应用服务器建立keepalive连接,所以这是两种不同的keepalive连接。我们把浏览器和nginx之间的keepalive连接叫做ka1,把nginx和应用服务器之间的keepalive连接叫做ka2。
如果ka1的超时设置为100秒,也就是说如果100秒之内没有新内容要传输,就把nginx和浏览器之间的连接断掉。而同时,我们把ka2设置为50秒,也就是说如果nginx和应用服务器之间没有新内容要传输,那么就把应用服务器和nginx之间的连接断掉。那么这时候就会产生一个问题:前50秒没有传输内容,在第51秒的时候,浏览器向nginx发了一个请求,这时候ka1还没有断掉,因为没有到100秒的时间,所以这是没有问题的,但是当nginx试图向应用服务器发请求的时候就出问题了,ka2断了!因为ka2的超时设置是50秒,这时候已经超了,所以就断了,这时候nginx无法再从应用服务器获得正确响应,只好返回浏览器502错误!
但是我们根本就没有设置过这些参数啊,怎么会有这种问题呢?
这没关系,既然没有设置过,那系统肯定用的是缺省参数,我们来看一下ka1的缺省设置是多少,也就是nginx(ingress)和浏览器之间的缺省的keepalive_timeout值:
upstream-keepalive-timeout
Sets a timeout during which an idle keepalive connection to an upstream server will stay open. default: 60
ka1的缺省设置是60秒。
我们再看一下ka2的缺省设置是多少秒,Tomcat官方文档上说:
The number of milliseconds this Connector will wait for another HTTP request before closing the connection. The default value is to use the value that has been set for the connectionTimeout attribute. Use a value of -1 to indicate no (i.e. infinite) timeout.
缺省值等同于connectionTimeout的值,那connectionTimeout等于多少呢?
The number of milliseconds this Connector will wait, after accepting a connection, for the request URI line to be presented. Use a value of -1 to indicate no (i.e. infinite) timeout. The default value is 60000 (i.e. 60 seconds) but note that the standard server.xml that ships with Tomcat sets this to 20000 (i.e. 20 seconds). Unless disableUploadTimeout is set to false, this timeout will also be used when reading the request body (if any).
connectionTimeout的缺省值是60秒,但是,他们提供的标准的server.xml里把这个值设为了20秒!
那么现在问题就很清楚了,我们的ka1是60秒,而ka2是20秒,从21秒到60秒之间的任何时间有请求进来都会发生502错误。
找到了问题的根源,解决起来就好办了,我们只需要确保ka1的超时设置小于ka2的设置就够了。或者修改ka1,或者修改ka2,都是可以的。
我们先修改ka1看一下,对于ingress来说,要修改ka1需要在ingress的configMap中修改,所以我们找到configMap设置的地方,给它增加一个新的属性:
这里我们把upstream-keepalive-timeout设为4,确保它低于ka2的20,设置完之后,ingress会自动加载新设置,我们看一下结果:
原先不断产生的502错误彻底消失了!
再来看一下错误图:
注意那个黄颜色的5XX比例,从我们设置好的那一瞬间,永远趴在了地上!