背景描述
proxy_pass 后面直接配置的是域名
故障现象
访问站点出错 504 Gateway Time-out, 通过监控查到有部分请求打了一个下线的 IP 上。
使用nginx做反向代理,将请求发送到一个域名(例如: proxy_pass http://www.test.com 该域名对应的 IP 是 A) ,刚开始运行一切正常,但是当运行了一段时间以后,域名对应的 IP 变了(例如 http://www.test.com 对应的 IP 由 A 变为 B),nginx 的转发仍然还在向原先的 IP 发送请求,导致业务中断,此时reload nginx 后才会重新恢复正常,且日志显示数据转发到新的 IP B。
故障分析
此处只针对 nginx 向后端做代理,且后端代理为域名形式的这种情况做分析
- 1、正常情况下启动 nginx 后(或者 -t / reload nginx 时),nginx 会通过操作系统配置的 DNS 服务器去解析域名对应的 IP
- 2、当 nginx 配置文件中的所有涉及到的域名都可以被正常解析到以后,才能启动(或者检查/重新加载)通过
- 3、这里需要提醒一点,在 nginx -t 或者 nginx -s reload 只是检查域名是否可以解析通过,并不会在此时缓存域名对应 IP,只有在通过 nginx 第一次向 proxy_pass 后端对应的域名做代理数据转发时,这里 nginx 会通过操作系统配置的 DNS 服务器解析域名,此时才会缓存域名对应的 IP,且会缓存很长时间,甚至一个月(整个过程均有生产实例证明,且抓包验证)
如何解决
- 1、既然是因为 nginx 缓存域名对应 IP 的 DNS 记录造成的,那么怎么才能解决呢,方法有两种:
- (1)、手动 reload nginx,让 nginx 重新解析域名,这个时候解析到域名对应的 IP 是最新的,不会包含已经被废弃的 IP
- (2)、设置 nginx 的 DNS 缓存时间,比如 600s 失效,然后重新去解析
- 2、方法(2)当然是最好的,但是 nginx 的 DNS 缓存时间在哪里设置呢,我没有找到!
- 3、但是我找到另外一种方法 – nginx 的 resolver
nginx 的 resolver 解决方案
- 1、默认 nginx 会通过操作系统设置的 DNS 服务器(/etc/resolv.conf)去解析域名
- 2、其实 nginx 还可以通过自身设置 DNS 服务器,而不用去找操作系统的 DNS
- 3、下面来讲一个这个 resolver
示例配置如下:
server {
listen 8080;
server_name localhost;
resolver 114.114.114.114 223.5.5.5 valid=3600s;
resolver_timeout 3s;
set $qq "www.qq.com";
location / {
proxy_pass http://$qq;
}
}
参数说明:
- resolver 可以在 http 全局设定,也可在 server 里面设定
- resolver 后面指定 DNS 服务器,可以指定多个,空格隔开
- valid 设置 DNS 缓存失效时间,自己根据情况判断,建议 600 以上
- resolver_timeout 指定解析域名时,DNS 服务器的超时时间,建议 3 秒左右
注意:当 resolver 后面跟多个 DNS 服务器时,一定要保证这些 DNS 服务器都是有效的,因为这种是负载均衡模式的,当 DNS 记录失效了(超过 valid 时间),首先由第一个 DNS 服务器(114.114.114.114)去解析,下一次继续失效时由第二个 DNS 服务器(223.5.5.5)去解析,亲自测试的,如有任何一个 DNS 服务器是坏的,那么这一次的解析会一直持续到 resolver_timeout ,然后解析失败,且日志报错解析不了域名,通过页面抛出502错误。
重点:如上例,在代理到后端域名 http://www.qq.com
时,千万不要直接写在 proxy_pass 中,因为 server 中使用了 resolver,所以必须先把域名定义到一个变量里面,然后在 proxy_pass http://$变量名
,否则 nginx 语法检测一直会报错,提示解析不了域名。
延展阅读
这里列举几个 proxy_pass、upstream 与 reslover 的应用场景
proxy_pass + upstream
upstream foo.example.com {
server 127.0.0.1:8001;
}
server {
listen 80;
server_name localhost;
location /foo {
proxy_pass http://foo.example.com;
}
}
访问 http://localhost/foo,proxy 模块会将请求转发到 127.0.0.1 的 8001 端口上。
只有 proxy_pass,没有 upstream 与 resolver
server {
listen 80;
server_name localhost;
location /foo {
proxy_pass http://foo.example.com;
}
}
实际上是隐式创建了 upstream,upstream 名字就是 foo.example.com。upstream 模块利用本机设置的 DNS 服务器(或/etc/hosts),将 foo.example.com 解析成 IP,访问 http://localhost/foo,proxy
模块会将请求转发到解析后的 IP 上。
如果本机未设置 DNS 服务器,或者 DNS 服务器无法解析域名,则 nginx 启动时会报类似如下错误:
nginx: [emerg] host not found in upstream "foo.example.com" in /path/nginx/conf/nginx.conf:110
proxy_pass + resolver(变量设置域名)
server {
listen 80;
server_name localhost;
resolver 114.114.114.114;
location /foo {
set $foo foo.example.com;
proxy_pass http://$foo;
}
}
访问 http://localhost/foo,nginx 会动态利用 resolver 设置的 DNS 服务器(本机设置的 DNS 服务器或 /etc/hosts 无效),将域名解析成 IP,proxy 模块会将请求转发到解析后的 IP 上。
proxy_pass + upstream(显式) + resolver(变量设置域名)
upstream foo.example.com {
server 127.0.0.1:8001;
}
server {
listen 80;
server_name localhost;
resolver 114.114.114.114;
location /foo {
set $foo foo.example.com;
proxy_pass http://$foo;
}
}
访问 http://localhost/foo 时,upstream 模块会优先查找是否有定义 upstream 后端服务器,如果有定义则直接利用,不再走 DNS 解析。所以 proxy 模块会将请求转发到127.0.0.1 的 8001 端口上。
proxy_pass + upstream(隐式) + resolver(变量设置域名)
server {
listen 80;
server_name localhost;
resolver 114.114.114.114;
location /foo {
set $foo foo.example.com;
proxy_pass http://$foo;
}
location /foo2 {
proxy_pass http://foo.example.com;
}
}
location /foo2 实际上是隐式定义了 upstream foo.example.com,并由本地 DNS 服务器进行了域名解析,访问 http://localhost/foo 时,upstream 模块会优先查找 upstream,即隐式定义的 foo.example.com,proxy 模块会将请求转发到解析后的 IP 上。
proxy_pass + resolver(不用变量设置域名)
server {
listen 80;
server_name localhost;
resolver 114.114.114.114;
location /foo {
proxy_pass http://foo.example.com;
}
}
不使用变量设置域名,则 resolver 的设置不起作用,此时相当于场景 2,只有 proxy_pass 的场景。
proxy_pass + upstream + resolver(不用变量设置域名)
upstream foo.example.com {
server 127.0.0.1:8001;
}
server {
listen 80;
server_name localhost;
resolver 114.114.114.114;
location /foo {
proxy_pass http://foo.example.com;
}
}
不使用变量设置域名,则 resolver 的设置不起作用,此时相当于场景 1 proxy_pass + upstream。
roxy_pass 直接指定 IP 加端口号
server {
listen 80;
server_name localhost;
location /foo {
proxy_pass http://127.0.0.1:8001/;
}
}
实际上是隐式创建了 upstream,proxy_pass 会将请求转发到 127.0.0.1 的 8001 端口上。