暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

DNS 解析问题定位分析及解决

497

现象

线上业务偶现无法域名解析的情况,具体日志如下:php_network_getaddresses: getaddrinfo failed: Name or service not known in xxx/db/Connection.php:579

抓包

第一时间绑定hosts先解决问题,保留现场一台通过抓包分析,确实存在没有解析的情况,并且多次出现的时间点与业务日志时间相同,通过抓出来的数据包把矛头指向的DNS解析;

把数据包提供给负责DNS解析的同学时,拿到数据包,第一时间看到包,也认为DNS解析存在问题,再仔细看下数据包发现,请求解析的域名有问题。我们域名都是以xxx.xxx.com结尾,但这个未解析的数据包包括了一般字符串,通过对比发现是主机名的一部分。

这样去请求DNS服务器,确实应该没有A记录响应。这样一来,DNS服务器是没有问题的。

继续排查

后面又去和研发同学确认,他们请求时,是否会直接重新拼接域名,研发同学确认不会,那为什么服务器去请求DNS解析时,会出现这个问题呢?通过查阅资料发现问题。

(来源:https://www.php.net/manual/en/function.gethostbyname.php)
DNS 解析时有 FQDN(Fully Qualified Domain Name,完全限定域名)和非完全限定域名,如果请求解析的域名中没有加最后的点的话,认为是非完全限定域名。例如请求解析www.k8s.vip这个域名时,/data/php7/bin/php -r "echo gethostbyname('abc.k8s.vip');" 这种如果DNS服务器查询失败的话,系统内核gethostbyname() 或 getaddrinfo()都会进行一次重新请求解析,重试时就会搜search域,而 dnsdomainname 命令用于定义DNS系统中FQDN名称中的域名,该命令通常用于检查主机名和域名的配置是否正确,在出问题的服务器上面运行此命令,发现如下类似配置:
[root@huyan-tiaobanji ~]# -> dnsdomainname
a.b.c.d
[root@huyan-tiaobanji ~]# -> hostname
huyan-tiaobanji.a.b.c.d
[root@huyan-tiaobanji ~]# ->

复制

发现主机名中包括有点号,此时DNS解析出现失败的时候,就会把你的请求域名中添加进去。

验证

通过解析一个错误的域名 abcd.k8s.vip,此时去抓包
[root@huyan-tiaobanji ~]# -> data/php7/bin/php -r "echo gethostbyname('abcd.k8s.vip');"
abcd.k8s.vip[root@huyan-tiaobanji ~]# ->

复制
抓包 tcpdump -i eth0 'port 53 and (udp or tcp)' -nn -X -l

结论:当DNS解析出错的时候,会通过dnsdomainname获取域,然后拼接重新访问一次。其实根本原因还是DNS服务器没有查询到A记录,gethostbyname() 或 getaddrinfo() 进行一次重试时出现抓包中的问题。

原因已经知道了,由于DNS使用udp协议,访问量大时,数据包有可能丢包,当第一次解析失败时gethostbyname() 或 getaddrinfo() 都会再重试一次,这种成功率会大增。如何解决这个问题呢?

解决方案

(1)请求域名的时候,使用完全定域名访问,在域名的最后加上点(.),可以解决问题防止他追加主机名的情况;

(2)让dnsdomainname命令执行时,不再输出任何域名格式的域名。(主机名修改掉、hostname修改掉即可),不然程序都需要加点(.)请求。

思考

目前大部分程序域名域名解析记录时,大部分都使用的是系统内核自带的库,猜想无法程序是php还是java或者Python都应该存在这个问题。通过以下程序得到猜想是正确的。

#!/usr/bin/python
# encoding: utf-8
import socket

def query_hostname(hostname):
    try:
        addr_info = socket.getaddrinfo(hostname, None)
        ip_addresses = [info[4][0] for info in addr_info]
        return ip_addresses
    except Exception as e:
        print("Error querying: ", hostname, e)
        return []

# 不完全限定域名
partial_hostname = "abcd.k8s.vip"
# 完全限定域名
full_hostname = "abcd.k8s.vip."

print("Results for PQDN: ", partial_hostname)
partial_results = query_hostname(partial_hostname)
print(partial_results)

#print("\nResults for FQDN: ", full_hostname)
#full_results = query_hostname(full_hostname)
#print(full_results)

复制

整改

把线上所有主机名带(.)的,都修改成无(.)的主机名。


文章转载自Linux点滴运维实践,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论