加载中…
个人资料
_src
_src
  • 博客等级:
  • 博客积分:0
  • 博客访问:2,065
  • 关注人气:30
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

Nginx动态解析upstream域名

(2018-04-10 16:55:57)
标签:

nginx

upstream

动态域名解析

re-resolution

dynamic

分类: Ubuntu/Debian

说明

  • 系统:debian 8 64bit
  • Nginx版本:1.6.2-5+deb8u5

背景

需要使用反代去访问一个http站点,假设站点域名及解析IP如下:


10.0.0.10 api.example.com

因为考虑后续站点域名解析可能会变动,没有使用域名解析出来的IP来设置反代,
而是直接只用域名,简单的配置如下:


location / {
    proxy_set_header X-Real-IP  $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_pass http://api.example.com;
}

问题

使用过程中一直正常,直到站点域名解析发生了变化,并且原来解析到的IP对应服务已不可用。
新的域名解析信息如下:


10.0.0.20 api.example.com

在访问该代理服务时候,无法正常访问,返回502 Bad Gateway:


curl http://proxy.example.com

检查错误日志,发现nginx还是连到原来的解析IP上,而对应的服务已经下线:


2018/04/09 16:38:17 [error] 30038#0: *12 connect() failed (111: Connection refused) while connecting to upstream, client: 10.0.0.100, server: proxy.example.com, request: "GET / HTTP/1.1", upstream: "http://10.0.0.10:80/", host: "proxy.example.com:80"

从上面的报错日志可以看到,nginx依然将请求代理到10.0.0.10:80
因为该服务器上对应的服务已经下线,所以提示Connection refused


分析

原因

根本原因在于,nginx会在启动时候将配置中涉及到的域名请求DNS解析,
获取到解析结果后,就缓存下来一直使用,直到下次执行reload或restart时候。

Does Nginx honor DNS TTLs for proxy upstreams?
Does Nginx honor DNS changes for upstream proxies or are lookups only
done once on startup? If not, will the TTL being honor even if it has to
traverse a list of CNAME records until it hits an A record?

No, domain names statically configured in config are only looked
up once on startup (or configuration reload).

从上面nginx mailing list可以看到,确实是不会重新解析DNS,更具体信息参考以下源码分析:

所以无论是上面的例子中,直接配置代理的地址


server {
    ...
    proxy_pass http://api.example.com;
    ...
}

还是通过upstream配置代理地址:


upstream backend {
    server api.example.com;
}
server {
    ...
    proxy_pass http://backend;
    ...
}

都会存在域名在启动时候被缓存住,在域名有变更时候不会重新解析的问题。

思路

在nginx plus商业版中已经有了该特性支持,通过在upstream中对server配置项增加resolve标记,
让nginx监控域名对应的IP地址变化,再自动更新upstream配置而不需要手动重启nginx:

在社区版的nginx中,可以利用proxy_pass配置项的特性,来实现类似的解决方案:

ngx_http_proxy_module - proxy_pass
Parameter value can contain variables. In this case, if an address is specified as a domain name, the name is searched among the described server groups, and, if not found, is determined using a resolver.

也就是在proxy_pass的参数值中,如果包含了变量,不管是域名还是upstream名,
直接当作upstream来处理,直接转到upstream的模块中去解析,如果没有匹配到相应的server group,
再当做域名进行DNS解析,如果还是解析不了,则会直接报错退出。

所以,解决问题的关键就在于利用proxy_pass参数值包含变量的情况,让nginx进行动态解析。


解决

配置

要实现proxy_pass参数值动态解析,首先需要配置resolver,配置建议:

  • 可以同时配置多个DNS服务器避免单点问题;
  • 最好使用本地的、可信的DNS服务器,避免解析超时或被劫持;
  • Nginx默认会解析ipv6,如果没有ipv6环境,可以显式关闭;
  • 对于可用性要求高的服务,可以配置重写域名的TTL。

假设有目前有两个DNS服务器:10.0.0.110.0.1.1,可以配置:


resolver 10.0.0.1 10.0.1.1 valid=10s ipv6=off;
resolver_timeout 10s;

接下来就是配置proxy_pass,需要设置变量来触发动态解析:


set $backend api.example.com;
proxy_pass http://$backend;

完整的配置参考:


server {
    ...
    resolver 10.0.0.1 10.0.1.1 valid=10s ipv6=off;
    resolver_timeout 10s;
    location / {
        ...
        set $backend api.example.com;
        proxy_pass http://$backend;
    }
}

但是,通过这种方式来实现nginx动态解析代理域名,相当于放弃了upstream,
也就无法使用upstream相关配置功能,比如回话保持、健康检测等等,具体得失自行评估。


验证

如果有条件的话,可以尝试模拟场景:

  • 启动代理到api.example.com的nginx,并确认可以正常访问
  • 修改api.example.com绑定的服务器,并将老的服务停掉
  • 尝试再通过nginx代理来访问确认是否正常

这里只测试通过抓DNS请求包来验证,
为了方便测试,仅设置了单个DNS服务器10.0.0.1,抓包命令如下:


tcpdump -n -i eth0 host 10.0.0.1 and port 53

抓包结果输出:


14:50:44.072045 IP 10.0.0.100.41091 > 10.0.0.1.53: 46625+ A? api.example.com.
14:50:44.073370 IP 10.0.0.1.53 > 10.0.0.100.41091: 46625 1/3/3 A 10.0.0.20
...
14:50:59.218199 IP 10.0.0.100.41091 > 10.0.0.1.53: 16431+ A? api.example.com.
14:50:59.219053 IP 10.0.0.1.53 > 10.0.0.100.41091: 16431 1/3/3 A 10.0.0.20
...
14:51:19.905455 IP 10.0.0.100.41091 > 10.0.0.1.53: 8884+ A? api.example.com.
14:51:19.906175 IP 10.0.0.1.53 > 10.0.0.100.41091: 8884 1/3/3 A 10.0.0.20

在测试过程中还发现以下情况:

  • Nginx某个worker以固定端口来请求DNS解析

    
      # lsof -i :41091 -n
      COMMAND  PID USER   FD   TYPE    DEVICE SIZE/OFF NODE NAME
      nginx   8107 nginx  13u  IPv4 156243264      0t0  UDP 10.0.0.100:41091->10.0.0.1:domain
    
      # ps -ef|grep 810[7]
      nginx     8107  8106  0 14:50 ?        00:00:00 nginx: worker process
    
    
  • Nginx并不是每隔指定valid时间就去请求DNS

    必须要有请求指定相应的proxy_pass,才会触发域名的动态解析,
    从抓包的情况看,14:50:44第一次发送请求时会触发一次DNS:

    
      14:50:44.072045 IP 10.0.0.100.41091 > 10.0.0.1.53: 46625+ A? api.example.com.
      14:50:44.073370 IP 10.0.0.1.53 > 10.0.0.100.41091: 46625 1/3/3 A 10.0.0.20
    
    

    中间没有请求,所以及时过了设置的valid期限,nginx依然不会请求DNS解析,
    直到14:50:59第二次发送访问请求时候,才重新解析:

    
      14:50:59.218199 IP 10.0.0.100.41091 > 10.0.0.1.53: 16431+ A? api.example.com.
      14:50:59.219053 IP 10.0.0.1.53 > 10.0.0.100.41091: 16431 1/3/3 A 10.0.0.20
    
    

参考

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有