在家里搭建了一个 GitLab 服务器,由于电信家庭宽带的公网 IP 是不固定的,且443
端口是被封的,所以买了阿里云作为中转,利用 Ngnix 作 TCP Proxy 结合 DDNS ,这样就能正常上自己的 Git 了。但今天,我发现推代码怎么也推不上去,校对了密钥,服务器状态都没问题。最后上阿里云,重启了下 Nginx,发现可以了。 找了一圈原因,最后发现是 Nginx 转发缓存 DNS 解析导致的,下面我们就复现一下问题并找找为什么,以及解决方式。
复现问题
系统: CentOS 8.3.2011
Nginx : nginx/1.14.1
- 直接在默认配置中加上如下配置,其中本地的
80
端口跑的是 Nginx 默认的服务:
stream {
log_format basic '$remote_addr [$time_local] '
'$protocol $status $bytes_sent $bytes_received '
'$session_time "$upstream_addr"';
access_log /var/logs/nginx/access2.log basic;
server {
listen 8080;
proxy_pass test.razeen.me:80;
}
}
此时,test.razeen.me
并没有解析,直接启动nginx
,会报错:
nignx: [emerg] host not found in upstream "test.razeen.me" in /etc/nginx/nginx.conf:94
- 我添加解析。
再次启动,成功启动 nginx, 访问 8080
端口,页面成功展示,日志如下:
一切正常。
- 修改解析,解析到其他IP。
600s后,无论我们再次访问,查看日志,还是转到了原来的 IP。
- 重启 nginx, 再次访问,这是 IP 才变成后面修改的。
从这里可以看出,Nginx 在启动的时候就会去查询DNS记录,如果不存在解析,直接无法启动。 但,如果中间修改域名的 DNS 解析,Nginx 中的解析并不会刷新。只有 reload or restart
后才会刷新。
那么实际是什么样的呢?
解析机制
直接去查找了一下相关的文章, 从这篇Using DNS for Service Discovery with NGINX and NGINX Plus 我们知道:
As NGINX starts up or reloads its configuration, it queries a DNS server to resolve backends.example.com. The DNS server returns the list of three backends discussed above, and NGINX uses the default Round Robin algorithm to load balance requests among them. NGINX chooses the DNS server from the OS configuration file /etc/resolv.conf.
This method is the least flexible way to do service discovery and has the following additional drawbacks:
- If the domain name can’t be resolved, NGINX fails to start or reload its configuration.
- NGINX caches the DNS records until the next restart or configuration reload, ignoring the records’ TTL values.
- We can’t specify another load‑balancing algorithm, nor can we configure passive health checks or other features defined by parameters to the
server
directive, which we’ll describe in the next section.
- Nginx 在启动/重载的时候回去解析转发的域名
- 如果域名无法解析 Nginx 就无法启动
- 只有下次重启/重载的时候才会重新去解析,启动后无视TTL
同时,如果能解析到多个 IP,还会有相应的负载均衡策略哦,后面可以再研究下这个。
这下就很明白了,原来坑就在这里,那么该怎么解决呢?
解决问题
在上面的文章中其实就给出了一种解决方案。
如下,我们可以指定 DNS 解析服务器并设置 DNS 刷新频率。
http {
resolver 10.0.0.2 valid=10s;
server {
# resolver 10.0.0.2 valid=10s; # 也可以写在这里
location / {
# resolver 10.0.0.2 valid=10s; # 也可以写在这
set $backend_servers backends.example.com;
proxy_pass http://$backend_servers:8080;
}
}
}
上面,resolver
就是 DNS 服务器的地址, valid
是 DNS 刷新的频率[1]。
但,上面这种写法只适合在 http proxy
中,在stream proxy
中并没有set
关键字,如果这么写启动会直接报错。查阅一番后,如下写法是可行的:
stream {
resolver 114.114.114.114 valid=10s;
map $remote_addr $backend {
default test.razeen.me;
}
server {
listen 8080;
# resolver 114.114.114.114 valid=10s; # 也可以写在这里
proxy_pass $backend:80;
}
}
[1] 但经过验证,这个 valid
DNS 刷新频率还要取决于你resolver
中使用的解析服务器。如果resolver
不是无缓存的DNS服务器,解析生效就会受影响。
同时通过 官方文档 看到,在 stream proxy
中 resolver
既可以写在 steam
中,也可以写在server
中:
Syntax: **resolver** *address* ... [valid=*time*] [ipv6=on|off] [status_zone=*zone*];
Default: - Context: stream
,server
This directive appeared in version 1.11.3.
在 HTTP PRXOY, resolver
可以写在http
, server
, location
中, set
可以写在server
和location
中。
同时上面这样写后,即使域名解析不到也不会导致 Nginx 启动/重启 失败。
在这篇文章中还提供了其他几种设置方法,也可以参考。