一. 背景
DNS 作为互联网中最基础,最重要的服务之一,如何保证其稳定是非常重要的一项工作。我司使用多套语言栈,php,java,go 和 python。30%的业务部署在虚拟机,70%的业务部署在 k8s 中。大量的短链接存在于各个语言栈中,考虑后使用了系统级别的 DNS 缓存。
二. 我们遇到的问题
1. 自建 dns server 故障
# cat /etc/resolv.conf
#nameserver是自建的powerdns地址
nameserver 192.168.1.2
nameserver 192.168.1.3
options timeout:5
复制代码
第一台 DNS server 故障后,业务出现大量 dns 请求超时。可以看到服务器上请求 5s 后才能响应。一般业务程序设置 dns 超时都在毫秒级别,所以势必导致大量请求超时。
$ curl -o /dev/null -s -w '%{time_namelookup}:%{time_connect}:%{time_total}\n' https://baidu.com
5.514:5.542:5.749
复制代码
2. 云厂商私域故障
云厂商提供了稳定性比较高的 DNS 服务器,我们尝试使用了私域做内网解析。/etc/resolv.conf
配置为如下
; generated by /usr/sbin/dhclient-script
nameserver 183.60.82.98
nameserver 183.60.83.19
复制代码
但实际上还是出现了问题,连续两天内不间断报错no such host
,对业务产生影响。
$ curl -XPOST http://xxx-service-admin.xxx.com
no such host
复制代码
三. 问题分析
第一台 DNS 服务器故障后,为什么会导致整个请求失败?那么配置 3 台有什么意义?
DNS 解析超时时间默认 5s,最小设置到 1s, 但是业务设置的请求 dns 超时都在毫秒,这个设置如何破?
自建的 DNS 和云厂商的私有域名都会出现问题,如何保证 DNS 服务器稳定性达到 99.9999%呢?
要回答前两个问题,需要先了解/etc/resolv.conf
中的含义,其他的可以参考resolv.conf配置参考
nameserver
# DNS服务器的ip地址,最大可以设置3个。使用的算法是尝试一个DNS服务器,如果查询超时,尝试下一个,直到没有DNS服务器,然后重复尝试所有DNS服务器,直到达到最大值重试次数。
timeout
# 尝试一个DNS服务器的超时时间,默认是5s.
复制代码
# cat /etc/resolv.conf
nameserver 192.168.1.2
nameserver 192.168.1.3
nameserver 192.168.1.4
options timeout:1 rotate
复制代码
# strace -f -e trace=connect curl -s -I baidu.com 2>&1 | grep 'htons(53)'
[pid 5172] connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.1.4")}, 16) = 0
[pid 5172] connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.1.2")}, 16) = 0
[pid 5172] connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.1.3")}, 16) = 0
# strace -f -e trace=connect curl -s -I baidu.com 2>&1 | grep 'htons(53)'
[pid 5201] connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.1.2")}, 16) = 0
[pid 5201] connect(4, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.1.3")}, 16) = 0
[pid 5201] connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("192.168.1.4")}, 16) = 0
复制代码
四. 解决方案
使用 dnsmasq 作为缓存
我司应用是主机+k8s 部署,且部分机器被部署上了 dnsmasq,考虑 dnsmasq 轻量级,易部署和可维护性,选择了 dnsmasq。整体架构如下:
PowerDNS 使用主从模式,部署多个 Recursor 转发和缓存 dns record.每个主机上(包括 k8s node 节点)安装 dnsmasq。每个 dnsmasq 客户端连接不同的 Recursor。
dnsmasq 开启 all-server,请求同时发给所有配置的上游 DNS 服务器。
将 pdns recursor 分成两组集群,通过判断 1000+台的主机的 ip 地址最后一位是奇数还是偶数来分给不同的集群,近似做到负载均衡。
每台 dnsmasq 设置不同的 cache 时间,在[240,300]秒中取随机数。缓存期内,不会请求 DNS 服务器。
k8s 的域 cluster.local 转发给 coredns.使用 dnsmasq 后请求 DNS 走向变成
client -> dnsmasq(缓存过期向后走) -> dns servers(使用响应最快的那个)
复制代码
安装
可以参考https://doc.powerdns.com/authoritative/installation.html
使用主从安装参考这两篇博文
https://zhuanlan.zhihu.com/p/265959768
https://blog.xuegaogg.com/posts/1655/
配置
# 在主机部署时用
# cat /etc/dnsmasq.conf
port=53
domain-needed
all-servers #请求发给所有resolv-file配置的dnsserver中,默认配置
#strict-order #严格按照/etc/resolv.conf中给出的顺序使用名称服务器,只有当第一台超时后,才会读取下一个。
neg-ttl=300 #如果没有设置ttl则使用这个值
max-cache-ttl=261 #dnsmasq缓存的时间
resolv-file=/etc/resolv.dnsmasq.conf # dnsmasq上游dns配置
cache-size=10240 # 缓存条目
复制代码
# k8s node上部署的,需要额外增加server字段,指向coredns地址。CORE_DNS_IP为k8s集群coredns的地址。
server=/cluster.local/CORE_DNS_IP
# k8s上部署后,pod中的dnsPolicy要设置为Default,指向pod所在的node
dnsPolicy: Default
复制代码
# cat /etc/resolv.dnsmasq.conf
# 设置DNS服务器地址
nameserver 192.168.1.2 # pdns recursor server
nameserver 192.168.1.3 # pdns recursor server
复制代码
考虑 k8s 部署,此处 nameserver 应该设置为当前主机的 ip 地址,设置 127.0.0.1 只是在非 k8s 上演示使用
# cat /etc/resolv.conf
nameserver 127.0.0.1
复制代码
验证
# 启动
$ systemctl start dnsmasq
$ ps -ef | grep dnsmasq
nobody 24511 1 0 Jun08 ? 00:00:12 /usr/sbin/dnsmasq -k
root 24975 20530 0 21:40 pts/1 00:00:00 grep --color=auto dnsmasq
$ netstat -tunlp | grep dnsmasq
tcp 0 0 0.0.0.0:53 0.0.0.0:* LISTEN 24511/dnsmasq
tcp6 0 0 :::53 :::* LISTEN 24511/dnsmasq
udp 0 0 0.0.0.0:53 0.0.0.0:* 24511/dnsmasq
udp6 0 0 :::53 :::* 24511/dnsmasq
复制代码
# cat /etc/resolv.conf
nameserver 127.0.0.1
复制代码
# cat /etc/resolv.dnsmasq.conf
nameserver 192.168.1.2 # 随便写的,不通的
nameserver 192.168.1.3 # 随便写的,不通的
nameserver 183.60.82.98 # 云厂商DNS地址,通的
复制代码
确认前两个 DNS 服务器不通
$ nc -w 2 -v -z 192.168.1.3 53
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connection timed out.
$ nc -w 2 -v -z 192.168.1.2 53
Ncat: Version 7.50 ( https://nmap.org/ncat )
Ncat: Connection timed out.
复制代码
$ curl -o /dev/null -s -w '%{time_namelookup}:%{time_connect}:%{time_total}\n' https://baidu.com
复制代码
抓包查看,有一台正常的 DNS 服务器(183.60.82.98)响应即完成本次 dns 请求。
# 频繁请求baidu.com
$ for i in {1..1000};do curl -o /dev/null -s -w '%{time_namelookup}:%{time_connect}:%{time_total}\n' https://baidu.com;sleep 1s;done
复制代码
可以看到缓存时间`max-cache-ttl=261`,请求没有到达 DNS 服务器。
# cat /etc/dnsmasq.conf
port=53
domain-needed
all-servers
#strict-order
neg-ttl=300
max-cache-ttl=261
resolv-file=/etc/resolv.dnsmasq.conf
cache-size=10240
复制代码
监控
之前一台 recursor 机器最大 50 万 PPS,且出现打满的情况。
现在,最高的只有 6k+ PPS。
安装 process_exporter,配置 prometheus 采集规则,当 dnsmasq 进程数小于 1 时告警。
namedprocess_namegroup_num_procs{groupname="dnsmasq",instance="x.x.6.80:9256"}
复制代码
疑问
如果 dnsmasq 客户端挂掉,那么此时该主机所有的 dns 请求都会失败,如下所示:
# systemctl stop dnsmasq
#
# netstat -tunlp | grep 53
#
# cat /etc/resolv.conf
nameserver 127.0.0.1
# curl baidu.com
curl: (6) Could not resolve host: baidu.com; Unknown error
复制代码
在实际项目落地中测试发现:当/etc/resolv.conf
中第一个 nameserver 设置为当前主机时,如果当前主机 DNS 服务器挂掉,会立刻请求第二个 nameserver,如下:
# cat /etc/resolv.conf
nameserver 127.0.0.1
nameserver 183.60.82.98 # 云厂商DNS地址
#
# netstat -tunlp | grep 53
#
复制代码
$ time strace -f -e trace=connect curl -s -I baidu.com 2>&1 | grep 'htons(53)'
[pid 30982] connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("127.0.0.1")}, 16) = 0
[pid 30982] connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("183.60.82.98")}, 16) = 0
real 0m0.099s
user 0m0.007s
sys 0m0.034s
复制代码
同时抓包确认,可以看到请求没有发给本地的 DNS 服务器,直接请求都了下一个nameserver
.
至于为何不会请求本地 DNS 服务器,我查了一些资料,没有看到具体的回答,有知道烦请解释下。
基于这个验证,在每个主机的/etc/resolv.conf
中增加 1 个云厂商的nameserver
就可以了。
# cat /etc/resolv.conf
nameserver 127.0.0.1
nameserver 183.60.82.98 # 云厂商DNS地址
复制代码
评论