写点什么

DNS 稳定性建设实战 - 从主机到 k8s

作者:boaker
  • 2022 年 7 月 12 日
  • 本文字数:3929 字

    阅读完需:约 13 分钟

DNS稳定性建设实战-从主机到k8s

一. 背景

DNS 作为互联网中最基础,最重要的服务之一,如何保证其稳定是非常重要的一项工作。我司使用多套语言栈,php,java,go 和 python。30%的业务部署在虚拟机,70%的业务部署在 k8s 中。大量的短链接存在于各个语言栈中,考虑后使用了系统级别的 DNS 缓存。

二. 我们遇到的问题

1. 自建 dns server 故障

# cat /etc/resolv.conf#nameserver是自建的powerdns地址nameserver 192.168.1.2nameserver 192.168.1.3options timeout:5
复制代码


第一台 DNS server 故障后,业务出现大量 dns 请求超时。可以看到服务器上请求 5s 后才能响应。一般业务程序设置 dns 超时都在毫秒级别,所以势必导致大量请求超时。


$ curl  -o /dev/null -s -w '%{time_namelookup}:%{time_connect}:%{time_total}\n' https://baidu.com5.514:5.542:5.749
复制代码

2. 云厂商私域故障

云厂商提供了稳定性比较高的 DNS 服务器,我们尝试使用了私域做内网解析。/etc/resolv.conf配置为如下


; generated by /usr/sbin/dhclient-scriptnameserver 183.60.82.98nameserver 183.60.83.19
复制代码


但实际上还是出现了问题,连续两天内不间断报错no such host,对业务产生影响。


$ curl -XPOST http://xxx-service-admin.xxx.comno 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.
复制代码


  • nameserver 多个配置默认是顺序读取的,只有当请求第一个 DNS 服务器超过 5s 了,才会请求第二个 DNS 服务器。设置 timeout 为最小值 1s 时,也不能解决。

  • 设置options rotate后可以轮询访问多个nameserver。虽然可以通过轮询避开故障,且保证了负载均衡,但是还有 33%的几率请求到故障的 DNS 服务器上。


# cat /etc/resolv.confnameserver 192.168.1.2nameserver 192.168.1.3nameserver 192.168.1.4options 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
复制代码


  • 结论: 现有的/etc/resolv.conf相关不能保证我们稳定性。

四. 解决方案

使用 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(使用响应最快的那个)
复制代码

安装

  • PowerDNS 安装

可以参考https://doc.powerdns.com/authoritative/installation.html

使用主从安装参考这两篇博文

https://zhuanlan.zhihu.com/p/265959768

https://blog.xuegaogg.com/posts/1655/

# yum install dnsmasq -y
复制代码

配置

  • 配置 dnsmasq

# 在主机部署时用# cat /etc/dnsmasq.confport=53domain-neededall-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所在的nodednsPolicy: Default
复制代码


  • 配置上游 DNS 服务器

# cat /etc/resolv.dnsmasq.conf# 设置DNS服务器地址nameserver 192.168.1.2 # pdns recursor servernameserver 192.168.1.3 # pdns recursor server
复制代码


  • 主机上 DNS server 设置为 dnsmasq

考虑 k8s 部署,此处 nameserver 应该设置为当前主机的 ip 地址,设置 127.0.0.1 只是在非 k8s 上演示使用

# cat /etc/resolv.confnameserver 127.0.0.1
复制代码

验证

# 启动$ systemctl start dnsmasq$ ps -ef | grep dnsmasqnobody   24511     1  0 Jun08 ?        00:00:12 /usr/sbin/dnsmasq -kroot     24975 20530  0 21:40 pts/1    00:00:00 grep --color=auto dnsmasq$ netstat -tunlp | grep dnsmasqtcp        0      0 0.0.0.0:53              0.0.0.0:*               LISTEN      24511/dnsmasqtcp6       0      0 :::53                   :::*                    LISTEN      24511/dnsmasqudp        0      0 0.0.0.0:53              0.0.0.0:*                           24511/dnsmasqudp6       0      0 :::53                   :::*                                24511/dnsmasq
复制代码


  • 确认主机/etc/resolv.conf配置

# cat /etc/resolv.confnameserver 127.0.0.1
复制代码


  • 确认 dnsmasq 上游配置

# cat /etc/resolv.dnsmasq.confnameserver 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 53Ncat: Version 7.50 ( https://nmap.org/ncat )Ncat: Connection timed out.$ nc -w 2 -v -z 192.168.1.2 53Ncat: Version 7.50 ( https://nmap.org/ncat )Ncat: Connection timed out.
复制代码


  • 模拟 2 台 DNS 服务器故障,dns 解析不受影响。

$ curl  -o /dev/null -s -w '%{time_namelookup}:%{time_connect}:%{time_total}\n' https://baidu.com
复制代码



抓包查看,有一台正常的 DNS 服务器(183.60.82.98)响应即完成本次 dns 请求。


  • 验证在 dns 设置的缓存期内,dns 请求不会到 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.confport=53domain-neededall-servers#strict-orderneg-ttl=300max-cache-ttl=261resolv-file=/etc/resolv.dnsmasq.confcache-size=10240
复制代码



监控

  • 监控 pdns recursor

之前一台 recursor 机器最大 50 万 PPS,且出现打满的情况。

现在,最高的只有 6k+ PPS。


  • 监控 dnsmasq

安装 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.confnameserver 127.0.0.1# curl baidu.comcurl: (6) Could not resolve host: baidu.com; Unknown error
复制代码


在实际项目落地中测试发现:当/etc/resolv.conf中第一个 nameserver 设置为当前主机时,如果当前主机 DNS 服务器挂掉,会立刻请求第二个 nameserver,如下:


# cat /etc/resolv.confnameserver 127.0.0.1nameserver 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.099suser 0m0.007ssys 0m0.034s
复制代码


同时抓包确认,可以看到请求没有发给本地的 DNS 服务器,直接请求都了下一个nameserver.

至于为何不会请求本地 DNS 服务器,我查了一些资料,没有看到具体的回答,有知道烦请解释下。

基于这个验证,在每个主机的/etc/resolv.conf中增加 1 个云厂商的nameserver就可以了。

# cat /etc/resolv.confnameserver 127.0.0.1nameserver 183.60.82.98 # 云厂商DNS地址
复制代码


发布于: 刚刚阅读数: 5
用户头像

boaker

关注

还未添加个人签名 2018.11.15 加入

专注云原生技术

评论

发布
暂无评论
DNS稳定性建设实战-从主机到k8s_DNS_boaker_InfoQ写作社区