本文主要是介绍nginx在CDN加速或使用SLB代理后,获取真实IP,做并发访问限制的方法(限流),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
本文80%转载于张戈博客,后续加入了自己的理解和想法
原文地址:https://zhangge.net/4879.html
站点在运行时,为了防止DDoS 攻击、或内部接口调用造成的数据迸发,nginx提供了limit限流模块:
HttpLimitZoneModule 限制同时并发访问的数量
HttpLimitReqModule 限制访问数据,每秒内最多几个请求
一、普通配置:
什么叫普通配置?
普通配置就是针对【用户浏览器】→【网站服务器】这种常规模式的 nginx 配置。那么,如果我要对单 IP 做访问限制,绝大多数教程都是这样写的:
## 用户的 IP 地址 $binary_remote_addr 作为 Key,每个 IP 地址最多有 50 个并发连接
## 你想开 几千个连接 刷死我? 超过 50 个连接,直接返回 503 错误给你,根本不处理你的请求了
limit_conn_zone $binary_remote_addr zone=TotalConnLimitZone:10m ;
limit_conn TotalConnLimitZone 50;
limit_conn_log_level notice;## 用户的 IP 地址 $binary_remote_addr 作为 Key,每个 IP 地址每秒处理 10 个请求
## 你想用程序每秒几百次的刷我,没戏,再快了就不处理了,直接返回 503 错误给你
limit_req_zone $binary_remote_addr zone=ConnLimitZone:10m rate=10r/s;
limit_req_log_level notice;## 具体服务器配置
server {listen 80;location ~ \.php$ {## 最多 5 个排队, 由于每秒处理 10 个请求 + 5个排队,你一秒最多发送 15 个请求过来,再多就直接返回 503 错误给你了limit_req zone=ConnLimitZone burst=5 nodelay;fastcgi_pass 127.0.0.1:9000;fastcgi_index index.php;include fastcgi_params;} }
这样一个最简单的服务器安全限制访问就完成了,这个基本上你 Google 一搜索能搜索到 90% 的网站都是这个例子,而且还强调用“$binary_remote_addr”可以节省内存之类的云云。
二、CDN 或 SLB 代理之后
为了增加安全、性能,许多站点都用到了CDN加速,或者其他的二级代理,例如阿里的SLB负载均衡等等。
于是,网站的访问模式就变为:
用户浏览器 → CDN 节点 / SLB 节点→ 网站源服务器
甚至是更复杂的模式:
用户浏览器 → CDN/SLB 节点(CDN 入口、CC\DDoS 攻击流量清洗等) → 阿里云盾 → 源服务器
可以看到,我们的网站中间经历了好几层的透明加速和安全过滤, 这种情况下,我们就不能用上面的“普通配置”。因为普通配置中基于【源 IP 的限制】的结果就是,我们把【CDN /SLB节点】或者【阿里云盾】给限制了,因为这里“源 IP”地址不再是真实用户的 IP,而是中间 CDN /SLB节点的 IP 地址。
我们需要限制的是最前面的真实用户,而不是中间为我们做加速的加速服务器。
其实,当一个 CDN /SLB或者透明代理服务器把用户的请求转到后面服务器的时候,这个 CDN /SLB服务器会在 Http 的头中加入一个记录
X-Forwarded-For : 用户 IP, 代理服务器 IP
如果中间经历了不止一个代理服务器,这个记录会是这样
X-Forwarded-For : 用户 IP, 代理服务器 1-IP, 代理服务器 2-IP, 代理服务器 3-IP, ….
可以看到经过好多层代理之后, 用户的真实 IP 在第一个位置, 后面会跟一串中间代理服务器的 IP 地址,从这里取到用户真实的 IP 地址,针对这个 IP 地址做限制就可以了。
那么针对 CDN /SLB模式下的访问限制配置就应该这样写:
http{## 这里取得原始用户的IP地址,没走CDN/SLB的,给到$remote_addrmap $http_x_forwarded_for $clientRealIp {default $remote_addr;~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr;}#设置IP白名单,对内部的IP不设限map $clientRealIp $limit{default $clientRealIp;xx.xx.xx.xx "";}#以真实IP为单位,限制请求数,并返回429状态;limit_req_status 429;limit_req_zone $limit zone=ConnLimitZone:20m rate=5r/s;limit_req_log_level notice;#以真实IP为单位,限制该IP的并发连接数,并返回429状态;limit_conn_status 429;limit_conn_zone $limit zone=TotalConnLimitZone:20m ;limit_conn TotalConnLimitZone 100;limit_conn_log_level notice;#以访问域名为单位,限制总并发链接数;limit_conn_zone $server_name zone=SumConnLimitZone:20m;
}## 具体Server:如下在监听php/go/java部分新增限制规则即可,或直接放在域名下面,限制全部访问
server {listen 80;location ~ \.php$ {#限制总并发连接数limit_conn SumConnLimitZone 10000;#最多5个排队, 由于每秒处理 10 个请求 + 5个排队,你一秒最多发送 15 个请求过来,再多就直接返回 429 错误给你了limit_req zone=ConnLimitZone burst=5 nodelay;fastcgi_pass 127.0.0.1:9000;fastcgi_index index.php;include fastcgi_params;}
}
三、如何验证
根据以上配置,我们知道nginx,每秒最多允许通过10+5个请求,在压测时,就会有两种情况:
- 在白名单内(到白名单的服务器测试),压测该站点,应该全部通过
- 不在白名单内,最多只允许通过10 +5 个请求,余下部分应该返回429
前提:压测总次数超过 10 +5,否则看不出效果。
centos一般都自带有 siege压测工具,还比较好用:
yum -y install siege
使用方法:
siege -c 3 -r 10 -b https://xxxx.xx.com/api/xxxx
-c 3 表示3个用户
-r 10 表示访问10次
以上表示:3个用户,每个用户访问10次请求,共计30次
经测试,每增加一台nginx,相同的配置应该是 x 2,例如:nginx1 的配置是 10+5,nginx2的配置也是10+5,域名部署在这两台nginx上,请求数最大允许为:20+10
以上测试,如果不能通过,应该是配置问题,那么我们用echo模块来调试下:
四、echo 模块
作者原文提到了 nginx 的一个 echo 模块,特意玩了下感觉挺有意思的,下面贴一下简单集成步骤。
①、给 nginx 集成 echo 模块:
cd /usr/local/src
#下载echo模块并解压:
wget https://github.com/openresty/echo-nginx-module/archive/v0.61.tar.gz
tar zxvf v0.61.tar.gz#下载nginx并解压
wget http://nginx.org/download/nginx-1.12.2.tar.gz
tar -xzvf nginx-1.12.2.tar.gz
cd nginx-1.12.2/#查看在用nginx的编译参数(如果是全新安装则省略)
/usr/local/nginx/sbin/nginx -V
nginx version: nginx/1.12.2
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-4) (GCC) #以下这行即为旧的编译参数:
configure arguments: --user=www --group=www --prefix=/usr/local/nginx --with-http_gzip_static_module
#在旧的编译参数基础上新增【--add-module=/echo模块的解压路径】参数,开始编译
./configure --prefix=/usr/local/nginx/nginx --add-module=/usr/local/src/echo-nginx-module-0.61#make编译
make -j2#平滑升级nginx (如果是全新安装请执行:make install)
mv /usr/local/nginx/sbin/nginx /usr/local/nginx/sbin/nginx.old
cp -f objs/nginx /usr/local/nginx/sbin/
make upgrade
以上升级、编译和添加第三方模块不熟悉的朋友,可以参考我另外一篇博客:
https://mp.csdn.net/mdeditor/81136273
②、echo 用法举例:
其实就和 shell 的 echo 差不多,能否输出自定义信息。
比如,在 nginx 里面配置如下:
location /hello {echo "hello, world!";
}
访问 http://xxx.com/hello 就会在浏览器里面输出 hello, world! 了(如果域名开了 CDN 可能会报 404)。
又比如,测试本文提到的真实用户的 IP,只要在本文第二步配置基础上,加上如下规则并 reload 即可:
server {listen 80;server_name yourdomain.com;## 以下是新增规则:## 当用户访问 /ip 的时候,我们输出 $clientRealIp 和 $limit变量,看看这个变量## 值是不是真的 用户源IP 地址location /ip{echo $clientRealIp;echo $limit;}
}
认真看的朋友,会问 clientRealIp 和 limit 有什么区别:
clientRealIp 如果走 SLB/CDN,获取的就是真实IP,反之,获取的就是remote_addr
limit 是在clientRealIp的基础上,排除了“IP白名单”,也就是说,当你的源IP,是白名单时,你的limit,应该为“空”,这样就不受限流了
所以,我们可以以此来判断,IP白名单是否有效:
curl http://xxx.xxx.cn/ip
本文介绍到这就差不多结束了,也是在神作的基础上精简整理并测试的,如果看完还有些许疑问,请前往查看神作原文,也许还是大神写的比较好理解(是否是原创我就不深究了,感觉也是转来转去,都没留链接,悲哀的互联网)!
转载:https://zhangge.net/4879.html
这篇关于nginx在CDN加速或使用SLB代理后,获取真实IP,做并发访问限制的方法(限流)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!