记一次nginx负载均衡健康检查引起的事故之no live upstreams while connecting to upstream

news/2024/7/10 0:26:52 标签: nginx, 负载均衡, 运维

文章目录

    • 概要
    • 一、负载均衡
        • 1.1、常用指令解析
        • 1.2 负载算法配置
        • 1.3、反向代理
    • 二、事故分析
    • 三、小结

概要

Nginx是工作中常用的HTTP服务中间件,除了提供HTTP服务,常用的还有反向代理、限流、负载均衡等功能。
负载均衡支持七层负载均衡(HTTP)和四层负载均衡(TCP),本人项目是基于七层负载的。

这次问题发生在一个阳光明丽的上午,本来正在勤勤恳恳的码字,突然大量用户反馈502,立马查监控,发现服务器资源毫无波动,说明并不还突发流量造成的。继续查错误日志,发现遭到不明攻击,有很多伪造的攻击请求错误,继续看日志,发现Nginx一直在刷no live upstreams while connecting to upstream错误,也就是说此时Nginx负载均衡的上游服务都不可用,那为什么呢?经检测我上游PHP服务运行的好好的,秒级响应呀。

下面我们先了解下Nginx负载均衡,这样才更好的理解问题发生的原因。

一、负载均衡

根据官网可知负载均衡算法有:轮询、加权轮询、hash、ip_hash、最少活跃连接数(least_conn)、随机(random)、最少平均响应时间和最少活跃连接数(least_time)。

另外也有第三方支持的库,常用的如fair(最少响应时间),url_hash(Nginx V1.7.2已支持了,不需要编译第三方库了)。

1.1、常用指令解析

负载均衡配置案例

http{
	#负载均衡配置
    upstream  test_upstreams {
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
		server 127.0.0.1:8084  backup; #备份服务
		server 127.0.0.1:8084  down; #永久性不可用的服务,为啥会有这个指令呢???
		#空闲连接池
		keepalive 30;
		keepalive_timeout 120;
		keepalive_requests 2400;
    }
	
	#限流
	limit_req_zone $binary_remote_addr zone=limit_one:10m rate=100r/s;
	
	#http服务
    server  {
        listen 80;
        server_name www.test.com;
        root  /usr/local/web;
        location  /  {
			limit_req zone=limit_one burst=5 nodelay;#限流
            proxy_pass http://test_upstreams;
			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_next_upstream error timeout http_500 http_502 http_503 http_504;#故障转移
            proxy_next_upstream_timeout 5s;
            proxy_next_upstream_tries 2;
			proxy_connect_timeout 3s;
			proxy_send_timeout 15m;
			proxy_read_timeout 15m;
        }
    }
}

正如案例所现,常用的指令都列出来了,其他不常用的请移步官网。

  • weight 指定当前上游服务器负载权重,值越大,分配的流量就越大,默认值是1;
  • max_fails 在fail_timeout 时间内失败次数达到该值则认为当前上游服务器不可用,默认值是1;
  • fail_timeout 两种作用,一种是同max_fails解释,另一种是判定当前上游服务器不可用时,经过fail_timeout时间会探测是否恢复,默认值是10s;
  • max_conns 指定与上游服务最大TCP连接数,每个worker独立计数,用于限流,默认值是0,表示不限制,注意V1.11.5后免费版才支持。
  • backup 备份服务,当其它上游服务都不可用时才会启用,可以作为容灾备用,不支持hash、 ip_hash、 random三种负载算法
  • keepalive 指定每个worker进程可以缓存的最大空闲TCP连接,所以可以称之为空闲连接池,根据官网描述其并不限制最大连接数,所以值不建议过大。默认不开启。
  • keepalive_timeout 空闲连接池中连接的最大存活时间,默认1h。
  • keepalive_requests 空闲连接池中每个TCP连接处理的最大请求数,到达后就会被释放。默认1000,注意V1.19.10之前是100。

可以看到其实现了健康检查、故障恢复等高可用特性。

1.2 负载算法配置

1、轮询
每个请求依次分配到不同的上游服务。

upstream  test_upstreams {
		server 127.0.0.1:8081  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  max_fails=3  fail_timeout=8s;
}

2、加权轮询
指定轮询权重,请求分配率和weight值成正比,用于上游服务器性能不均的情况。

upstream  test_upstreams {
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

3、hash
对某个key做hash 映射后进行请求转发。
如下配置,按请求url做hash,一般配合缓存命中来使用

upstream  test_upstreams {
    	hash $request_uri;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

4、一致性hash

upstream  test_upstreams {
    	hash $remote_addr consistent;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

5、ip_hash
适合需要客户端与服务端有一定粘性的场景,保证客户端每次都命中同一个上游服务。当然了,上游服务发生故障会引起转移的。

upstream  test_upstreams {
    	ip_hash;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

6、least_conn
考虑权重,优先将请求分配到TCP连接数最少的上游服务。一般来说连接数多的上游服务压力就大些,可以合理配请求压力。

upstream  test_upstreams {
    	least_conn;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

7、random
随机选择,不过可以配置可选项two,实现随机选两个上游服务,并从中选一个连接数少的一个上游服务。
least_conn也可以由least_time替换,但是least_time商业版才支持

upstream  test_upstreams {
    	random two least_conn;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

8、least_time
考虑最少平均响应时间和最少活跃连接数作为上游服务,更合理,奈何只有商业版才支持。

upstream  test_upstreams {
    	least_time;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s max_conns=120;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s max_conns=80;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}

9、fair(第三方)
需要编译第三方代码。
看了下源码,不支持 max_conns等其他参数。
看很多文章说是按最短响应时间的优先分配,看了源码,并没有真的用到上游服务响应时间来作为分配指标,所以保留意见
核心逻辑如下:

假设来了有两个可用的上游服务A和B。现在依次来了1、2、3、4、5、6、7、8、9共九个请求。
A 处理了 1、4、5、7 四个请求,B处理了2、3、6、8、9五个请求。那么第10个请求来的时候有:
A 有已处理请求个数nreq=4,间隔请求个数req_delta=10 - 7=3;
B 有已处理请求个数nreq=5,间隔请求个数req_delta=10 - 9=1;
有一个评分函数score_func,优先分配给score_func(nreqA,req_deltaA)/weight和score_func(nreqB,req_deltaB)/weight中最小的一方。
这种算法稳定性完全取决于评分函数,但线上网络环境复杂,请慎用

upstream  test_upstreams {
    	fair;
		server 127.0.0.1:8081  weight=5  max_fails=3  fail_timeout=8s;
		server 127.0.0.1:8082  weight=4  max_fails=3  fail_timeout=8s;
		server 127.0.0.1:8083  weight=3  max_fails=3  fail_timeout=8s;
}
1.3、反向代理

负载均衡是要配合反向代理使用的,如1.1小节中的案例所示。我们要特别注意 proxy_next_upstream 参数,本次线上事故就与它有关
官网解释如下:

Specifies in which cases a request should be passed to the next server

即指定在什么情况下将请求转移给下一个上游服务。其默认值error timeout。

其参数有:

  • error 与上游服务建立TCP连接、向上游服务传递请求或读取响应标头时出错;
  • timeout 与上游服务建立TCP连接、向上游服务传递请求或读取响应标头超时;
  • invalid_header 上游服务响应为空或无效响应;
  • http_500 上游服务响应500;
  • http_502 上游服务响应502;
  • http_503 上游服务响应503;
  • http_504 上游服务响应504;
  • http_403 上游服务响应403;
  • http_404 上游服务响应404;
  • http_429 上游服务响应429;

支持以上10种错误情况进行请求转移到下一个上游服务,另外:

  • non_idempotent 正常情况下当HTTP请求是POST, LOCK, PATCH方法时,上游失败是不会请求下一个上游服务的,需要配置上该参数就可以转移了,所以配置时要注意接口的幂等性,重试是否会造成重复提交引起业务异常,一般来说GET、OPTIONS之类的是幂等的
  • off 关闭自动转移。

所以proxy_next_upstream指令实现了高可用的另一个特性,故障转移。即如果正常请求失败了,会依次向剩余的可用上游服务进行重试,直至有一个成功或全部失败。
不过故障转移也容易造成故障扩散,本次线上故障就是这样的

proxy_next_upstream_timeout
该参数限制了请求的总时间,默认0,表示会依次向所有可用上游服务请求一次,直至有一个成功或全部失败。

以案例所示,假设8081、8082、8083三个上游服务两秒后返回http_500。如果设置proxy_next_upstream_timeout值为3s,那么只能请求两个上游服务,即除了本来正常的请求失败后还能重试一次。

proxy_next_upstream_tries
该参数限制了请求的总次数,默认0,表示会依次向所有可用上游服务请求一次,直至有一个成功或全部失败。

以案例所示,假设8081、8082、8083三个上游服务均返回http_500。如果设置proxy_next_upstream_tries值为2,那么只会请求两个上游服务,即除了本来正常的请求失败后还能重试一次。

二、事故分析

线上事故其实也有点乌龙了,虽说是因为受到恶意攻击引起的,但主要还是前人的Nginx配置有漏洞造成的,与业务无关。
大概转发路径如下:
Nginx转发示意图
经过日志追查,发现上游的两个Nginx 有这样的配置:

error_page 405 = @405
location @405 {
    proxy_pass http://localhost:80;
}

这本来是为了美化 405 错误页面的,结果不知道为什么废弃了,但Nginx配置并没有干掉,这次线上恶意攻击出大了405错误,然后转发到本机80端口,结果并没有80这个服务,就返回给入口Nginx 502了。

入口Nginx收到502,首先触发了故障转移,请求转移到另一个上游Nginx,依旧收到502,线上恶意攻击比较高频,
接着触发健康检测阈值,导致入口Nginx认为所有上游服务都故障了,就会报 no live upstreams while connecting to upstream错误,并返回客户端502,
线上恶意攻击持久且高频,导致入口Nginx的健康检测一直认为上游服务故障,无法恢复,进而正常用户也无法使用。

三、小结

经过第一章的分析,可知Nginx的负载均衡除了负载外,还有故障转移、健康检测、故障自动恢复、限流等特性。

对于健康检测,可以说其是被动检测,即需要先发请求,看从发出到收到响应整个过程的反应作为检测手段。
当然商业版ngx_http_upstream_hc_module模块也提供了主动检测的指令health_check,不差钱的可以用上。
另外淘宝技术团队也开源了一个主动检测模块,源码,有需要的可以用起来。


http://www.niftyadmin.cn/n/5047104.html

相关文章

系统架构设计师(第二版)学习笔记----系统分析与设计及测试

【原文链接】系统架构设计师(第二版)学习笔记----软件测试 文章目录 一、结构化方法1.1 结构化开发方法1.2 结构化分析使用的手段1.3 结构化分析的步骤1.4 数据流图(DFD)的基本元素1.5 数据流图(DFD)方法建…

Hive的基本SQL操作(DDL篇)

目录 ​编辑 一、数据库的基本操作 1.1 展示所有数据库 1.2 切换数据库 1.3 创建数据库 1.4 删除数据库 1.5 显示数据库信息 1.5.1 显示数据库信息 1.5.2 显示数据库详情 二、数据库表的基本操作 2.1 创建表的操作 2.1.1 创建普通hive表(不包含行定义格…

目标检测数据集:工业端面小目标缺陷计数数据集

✨✨✨✨✨✨目标检测数据集✨✨✨✨✨✨ 本专栏提供各种场景的数据集,主要聚焦:工业缺陷检测数据集、小目标数据集、遥感数据集、红外小目标数据集,该专栏的数据集会在多个专栏进行验证,在多个数据集进行验证mAP涨点明显,尤其是小目标、遮挡物精度提升明显的数据集会在该…

【1++的Linux】之进程(三)

👍作者主页:进击的1 🤩 专栏链接:【1的Linux】 文章目录 一,什么是进程地址空间?二,进程地址空间是怎么设计的?三,为什么要有进程地址空间? 一,什…

别着急,解决不了的问题,就请交给时间吧

转眼间我走出社会已过去四年之久,但很多事依旧历历在目,就好像昨天发生的一样。 我小时候,因为一场医学事故患有先天性白内障,真的是连黑板的看不清,当时自己也不太懂事,上课对我来说就是画画以及一切能够消…

jupyter notebook 运行没有反应

配置好anaconda之后打开jupyternotebook运行代码没有反应 首先配置解释器等 import webbrowser webbrowserregister("Microsoft Edge"None,webbrowser.GenericBrowser("C: Program Files (x86)Microsoft Edge Application msedge.exe"))c.NotebookApp.b…

别再费劲配音了!小说推文视频一键生成,并带全自动配音

下面教你轻松一键制作出精彩的小说推文视频。 1. 输入文案生成小说推文视频 小说推文视频可以根据你输入的文案自动生成精美的视频内容,无需手动操作。只需提供文案,小说推文视频就能为你制作出令人惊艳的视频作品。 2. 自动小说推文配音 不用再费心去…

iOS“超级签名”绕过App Store作弊解决方案

一直以来,iOS端游戏作弊问题都是游戏行业的一大痛点。在当下游戏多端互通的潮流下,游戏作为一个整体,无论哪一端出现安全问题,都会造成更加严重的影响。因此,iOS端游戏安全保护也同样十分重要。 iOS独特的闭源生态&am…