一. 问题和需求
需求分析
1. 使用ELB要支持http,https访问,同时要支持websocket , 这个需求好办,直接开通http/https模式ELB用于支持普通http协议访问,开通TCP模式ELB用于支持websocket。
2. 通过ELB的访问要禁止部分国家访问, 这个需求如果使用http/https 的ELB,很好实现,直接创建web acl并应用上ELB即可,但由于有websocket需求,这部分访问就不能走http/https模式的ELB,需要走TCP模式的ELB,而TCP模式的ELB是基于4层实现的,不能使用web acl策略,因此只能在nginx层面使用geoip模块做限制 ,但nginx层面要求能获取用户真实IP,TCP模式的ELB给出的IP $remote_addr 为ELB本身的IP,而 $http_x_forwarded_for是空的,完全不符合要求,这就是本次操作的难点。
为了让ELB支持给出用户真实IP,我搜遍了全网,都只找到如下教程,但这些教程都是elb老版本的,而且操作难度高,而目前AWS上的elb都已经升到了v2版本,原来的老版本已经不支持了,目前最新的elbv2无法使用下面这些方法来实现效果:
http://www.ttlsa.com/nginx/aws-elb-nginx-enable-proxy-protocol/
https://www.jb51.net/article/76750.htm
https://blog.csdn.net/fgf00/article/details/80164200
经过两天的研究和搜索,终于找到了如下方法,大体思路如下:
1. nginx启用http_realip_module 模块,用于支持 real_ip_header proxy_protocol 和 set_real_ip_from
2. 由于要支持websocket, 创建TCP模式的elb ,也就是 network Load Balancer, 此ELB不支持web acl,无法从ELB层面限制源IP国家地区访问
3. 对于要支持http/https访问的,创建http/https模式的elb ,也就是 application Load Balancer,此ELB可直接使用web acl 限制国家访问,很好实现,如下图
二. 安装相关组件
cd /root
##先上传openresty-1.17.8.2.tar.gz 包到/root下
tar zxvf openresty-1.17.8.2.tar.gz
cd openresty-1.17.8.2
/usr/local/openresty/nginx/sbin/nginx -V
./configure --prefix=/usr/local/openresty --with-http_stub_status_module --with-http_geoip_module --with-http_realip_module # 注意要加上realip_module模块,用于获取ELB用户IP,geoip用于nginx层面限制国家地区访问
make
mv /usr/local/openresty/nginx/sbin/nginx /usr/local/openresty/nginx/sbin/nginx.old
cp -pf /root/openresty-1.17.8.2/build/nginx-1.17.8/objs/nginx /usr/local/openresty/nginx/sbin/
systemctl restart openresty
/usr/local/openresty/nginx/sbin/nginx -V
三. 踩坑过程
1. 创建ELB
分别创建两个ELB,一个http模式,一个TCP模式,前者直接用上web acl,后者无法使用web acl,此时难题出现了,访问tcp模式的ELB, access日志里显示的IP不对
2. 使用网上教程过程中碰到的坑
网上说在awscli使用如下命令来启用相关代理协议,就可以获取到真实IP了,实际操作不可行,因为现在ELB默认已经升级到了elbv2, 原来的命令和方法全部作废, aws官方也没有详细说明
# aws elb create-load-balancer-policy --load-balancer-name YOU_ELB_NAME --policy-name EnableProxyProtocol --policy-type-name ProxyProtocolPolicyType --policy-attributes AttributeName=ProxyProtocol,AttributeValue=True
3. 解决方法
(1) nginx上加配置
nginx.conf里http块加上如下内容,注意 set_real_ip_from一定要设置ELB所在的IP段,用于排除掉ELB的IP,取出用户真实IP
#此功能是设置拒绝CN(china)区访问,无此需求的不用加,geoip相关安装配置不在此展开
geoip_country /usr/share/GeoIP/GeoIP.dat;
map $geoip_country_code $is_cn_country {
default yes;
CN no;
}
# 开启websocket
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
#启用proxy_protocol,用于读取ELB后端目标组的代理协议v2的头数据,抓取用户真实IP
set_real_ip_from 127.0.0.1;
set_real_ip_from 172.31.0.0/16;
# 表示把来在172.31.0.0/16 段(TCP负载均衡器的IP段)的所有请求的来源地址,都改成 $proxy_protocol_addr,并且记录在 $remote_addr 变量里。
real_ip_header proxy_protocol;
real_ip_recursive on;
在要用户websocket的网站conf文件里配置:
server {
listen 80 proxy_protocol; #注意这里一定要加上 proxy_protocol
server_name api-ws.domain.com;
access_log logs/api-ws.domain.com-access.log access;
error_log logs/api-ws.domain.com-error.log;
location / {
#限制CN区访问
if ($is_cn_country = no) {
return 403;
}
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 启用支持websocket连接
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://apiws7777/;
}
}
(2)AWS后台的负载均衡配置里的目标群组里,启用代理协议v2, 注意,这一步最为关键!必须启用。
(3)重启nginx,开始测试,经测试,用户IP抓取到了,大功告成!
(4)测试不同国家访问,CN区访问返回403,符合预期,国外能正常打开,功能已实现。
四. 经验总结
1. 用百度搜索aws相关的问题资料特别少,可能是国人用AWS用得相对少一些,最近几年才开始用得多,问题总结用百度在网上很难搜到,很多问题只能自己摸索,或者搜索国外英文网站。
2. aws 的elb老版本现在已经没有了,现在都是elbv2, 相关技术文档没有跟上,只能借鉴前人的技术文档,不能直接套用。
3. 碰到问题一定要多动脑筋,多尝试。
4. 此问题是站在前人的肩膀上解决的,再次感谢如下链接的作者
http://www.ttlsa.com/nginx/aws-elb-nginx-enable-proxy-protocol/
https://www.jb51.net/article/76750.htm
https://blog.csdn.net/fgf00/article/details/80164200
5. 中间遇到的报错
" while reading PROXY protocol, client: 127.0.0.1, server: 0.0.0.0:80
2021/01/28 17:56:47 [error] 3206#0: *24714 broken header: "GET / HTTP/1.1^M
Host: 172.31.101.19
Connection: close
User-Agent: ELB-HealthChecker/2.0
Accept-Encoding: gzip, compressed
报错的原因是因为有部分网站server conf文件里的 linsten 80这里加上了 proxy_protocol, 一旦nginx下某一个网站启用proxy_protocol, 其他站点都会自动启用 proxy_protocol头,如果同一个nginx下其他网站不是TCP ELB模式代理进来,就会报错,导致其他网站都打不开,显示 502 Bad Gateway, 解决办法是其他网站也要使用TCP模式ELB,后端服务组开启代理模式v2, 此问题参考说明 https://trac.nginx.org/nginx/ticket/1048