如何配置Nginx的Dynamic Upstream指向ELB
起因
公司有一个项目,部署有多个AWS环境,但由于一系列复杂的原因,需要先在账号A下备案的域名来使用在账号B中部署的服务,后续再将域名备案转到账号B中。
临时的解决办法就是在账号A下起一个Instance,通过Nginx反向代理到账号B的一个Classic ELB中。
起初服务跑的很顺畅,后来一天突然服务不可用,页面显示504 Gateway Timeout,查看了Nginx的日志,发现有多条upstream timed out (110: Connection timed out) while connecting to upstream
的错误日志。
调查
AWS 的ELB是一个托管的负载均衡器,通过对外提供的域名来进行访问,北京Region的域名类似service-3332222.cn-north-1.elb.amazonaws.com.cn
,但实际上底层还是会有对应的ENI来承载实际的流量,这些ENI有自己临时分配的IP地址。这些ENI可能会随着流量的扩展或AWS底层服务器的变动而随时改变。所以这也是AWS要求使用域名来访问ELB的原因。
Nginx侧使用upstream时,里面如果填写的是域名,那么Nginx会在请求DNS后把对应的IP信息缓存起来,后续的请求就一直用缓存的IP。直到下次reload的时候才会再次查询domain
此时问题就出现了,ELB的域名解析是时刻会更新的,当底层有变动时,承载ELB流量的Public IP就会变化。而Nginx的upstream中,如果使用了域名,第一次DNS请求后就会一直使用缓存的Public IP。当ELB的ENI有变化时,原先的Nginx反向代理缓存的IP地址就失效了,导致连接不上服务。出现upstream timed out (110: Connection timed out) while connecting to upstream
的错误。
解决方案
有这么几个方法可以用来解决这个问题:
- 写一个监控的cron脚本,时刻检测ELB对应的IP是否有变化,一旦有变化,就reload Nginx。这个方法需要额外的开发。
- Nginx Plus支持在upstream中添加resolve,可以周期性的重新解析DNS域名。可以完美解决所遇到的问题,可一个Nginx Plus Instance的授权费用就要$2500+/year,穷人表示用不起。
- Nginx社区版的解决方案,不使用upstream,使用set将域名设为一个变量,再传递给proxy_pass。
Nginx官方这篇Using DNS for Service Discovery with NGINX and NGINX Plus的文章讲述了第二点和第三点的解决方案。
Nginx的免费解决方案的配置如下:
|
|
在 proxy_pass中使用变量来代替URI或者Upstream Server Group,Nginx就会在解析缓存过期后再次请求DNS服务器来解析域名。
注意: 此处的resolver所要填写的IP地址,取决于EC2所在的子网划分。如果网络是VPC,那需要设置为EC2所在子网的DNS地址,AWS会为每个子网保留5个IP地址,第三个IP地址用作DNS,例如如果子网CIDR为10.0.0.0/24,那10.0.0.2就是AWS保留的用作DNS的地址, 保留IP地址的官网说明VPCs and Subnets。如果是Classic的网络,那么AWS的DNS服务器地址是固定的172.16.0.23,AWS官网说明EC2-Classic Platform。
关于免费方案的缺陷
当location的参数不是‘/’,并且proxy_pass后面的参数是一个变量时,proxy_pass的转发规则和正常的规则有所不同。如下是相关的说明以及解决方案。
以下文字转自Nginx with dynamic upstreams
proxy_pass
不使用变量时的正常转发规则:
nginx配置为如下时:
如果请求的网页是/foo/bar/baz
,那么Nginx会转发请求至http://127.0.0.1:8080/foo/bar/baz
。但如果nginx的配置如下,在proxy_pass参数后面加上了‘/’时。
Nginx在将请求转发到upstream时,会将location
中匹配的部分截掉,/foo/bar/baz
会转发到http://127.0.0.1:8080/bar/baz
。
但当我们在proxy_pass中使用变量后,转发行为会发生变化。
当我们在转发的地址后面有‘/’时:
当你请求/foo/bar/baz
时,请求会转发到‘/’而不是期望的/bar/baz
解决办法:
删掉set $upstream_endpoint
后面的‘/’,手动rewrite地址,这样就可以将请求/foo/bar/baz
转发到upstream的/bar/baz
了。
实验验证
来做几个实验来验证下上面的配置是否可以生效。
环境准备
来做几个实验来验证下效果,新建几台Instance,配置好对应的SG。如下是几台实验机器的基本信息:
Instance | Public IP | Private IP | SG Rule |
---|---|---|---|
EC2-A | 18.237.0.231 | 172.31.39.103 | SSH&HTTP From 0.0.0.0/0 |
EC2-B | 52.26.25.237 | 172.31.35.141 | SSH&HTTP from 0.0.0.0/0 |
EC2-Reverse | 52.36.100.113 | 172.31.18.5 | SSH&HTTP from 0.0.0.0/0 |
一个域名: dynamic-upstream.jibing57.com
环境设置
分别在EC2-A和EC2-B上安装简单的httpd服务
登陆EC2-A中执行命令安装httpd服务
|
|
登陆EC2-B中执行命令安装httpd服务
|
|
配置好后,访问如下页面,确认网页能够正常展示
- http://18.237.0.231/
- http://18.237.0.231/foo/bar/baz/
- http://52.26.25.237/
- http://52.26.25.237/foo/bar/baz/
配置EC2-Reverse的nginx,将请求重定向到dynamic-upstream.jibing57.com去。
|
|
进行试验
基本设置设置完后,让我们来进行试验
首先将dynamic-upstream.jibing57.com解析到EC2-A的IP地址18.237.0.231
此时访问http://52.36.100.113, 可以看到的是EC2-A Instance上的主页内容 Hello, I’m EC2-A再将dynamic-upstream.jibing57.com解析修改为EC2-B的IP地址52.26.25.237
当修改后的域名解析扩散到AWS的DNS服务器后,此时访问http://52.36.100.113,可以看到的是EC2-B Instance上的主页内容Hello, I’m EC2-B
说明设置的upstream起作用了,我们并没有重新启动load Nginx,就可以将请求转发到EC2-B中。
再来修改EC2-Reverse的nginx,测试location /foo/的情形
|
|
注意此时dynamic-upstream.jibing57.com域名后面带有‘/’。
此时访问http://52.36.100.113/foo/bar/baz/,页面内容是EC2-B中/index.html的内容Hello, I'm EC2-B
。
需改Nginx配置为rewrite。
|
|
此时访问http://52.36.100.113/foo/bar/baz/, 页面内容为EC2-B中/bar/baz/index.html的内容Hello, I'm EC2-B in dir bar/baz/