
ISC的Bind一直以来应该算是DNS行业的工业标准。我们的一些业务场景(自建CDN)也使用到了bind9来作为内部dns。本文主要分享一些bind9的线上业务使用经验,具体bind9的详细简介以及安装过程,则不再赘述,有兴趣的看客可以直接查看bind9官方文档。
https://www.isc.org/bind-9-11-arm/
线上采用bind9组建dns集群,为服务器节点提供解析服务。而每台服务器节点都安装配置了dnsmasq来实现本地解析的高可用性。大致架构如下:

3.1 DNSMASQ简介
一般大家都知道在resolv.conf中配置多个nameserver来实现冗余或者高可用性。
nameserver 114.114.114.114 nameserver 223.5.5.5
稍微优化下的话,调整下超时以及使用轮询
nameserver 114.114.114.114 nameserver 223.5.5.5 options timeout:1 rotate
但实际上还是有1s的timeout时间,而这在海量并发下也是存在问题。
我们希望实现的效果是,可以配置多个nameserver,其中任何一个nameserver故障都不会影响到dns的解析,且本地有缓存可以加速dns解析的速度。dnsmasq刚好完美的解决了我们的痛点。
dnsmasq是我们线上经常使用的一个软件,主要看重了dnsmasq作为dns代理时的本地缓存以及高可用性,直接部署到节点服务器上的。
例如:节点部署dnsmasq监听127.0.0.1,而resolv.conf中设置nameserver 127.0.0.1
bind-interfaces #指定监听的IP地址,一般情况下是本机服务,故只监听127.0.0.1即可 listen-address=127.0.0.1 #记录日志 log-facility=/var/log/dnsmasq.log log-queries #不读取/etc/hosts,在提供内部dns服务时可以注释掉 no-hosts #设置cache存活时间,2.64新增max-cache-ttl,2.73新增min-cache-ttl #max-cache-ttl=1800 #min-cache-ttl=600 #缓存的条目数 cache-size=2000 #不缓存失败的查询结果 no-negcache #不读取/etc/resolv.conf no-resolv #同时向所有的server发起查询请求,取响应最快的 all-servers server=114.114.114.114 server=223.5.5.5
PS:还可以使用server来将不同域名的dns请求转发到不同的上层dnsserver
server=/baidu.com/114.114.114.114 server=/taobao.com/223.5.5.5
注意:dnsmasq缓存上层dns响应是有前提条件的,如果dns响应中Recursive avaiable位为0,那么不会缓存。如果上层是tcp响应,那么也不会缓存。
3.1 mysql dlz智能解析
bind9原生支持mysql dlz。下载获得bind9的源码包后,可以编译启用mysql dlz
./configure --with-dlz-mysql -enable-threads=no #后面接着其他的选项,bind9支持各种类型的dlz,但是mysql dlz应用范围应该说相对比较广,大家也都比较熟悉mysql。
注意:myql dlz默认仅支持单进程,注意开启enable-thread=no,或者启动named进程的时候指定-n1
bind9实现dns智能解析最关键的就是acl以及view配置。通过acl将用户分成一个个独立的群组。而view中则定义匹配了哪些群组,以及针对这些群组要进行的操作(使用不同的sql查询mysql数据库)。
下面以自定义联通线路来具体展示acl和view的配置方法:
定义wt acl
#定义了wt acl,所有来源IP为120.192.0.0/16的请求都归到wt这个群组(严格意义上来,根据的是用户端出口DNS,而不是用户的IP地址) acl "wt" { 12.22.0.0/16; }
定义wt key
#wt指的是key的name,可以手工指定 rndc-confgen -r dev/urandom -k wt #生成如下key信息 key "wt" { algorithm hmac-md5; secret "XXXXX"; };
配置wt view
bind9 mysql dlz一般有两种方式,一种是采用自带的mysql驱动,通过--with-dlz-mysql启用(本文使用)。还有一种方式则是额外编译安装mysql驱动。
view "view_wt" { #匹配wt acl match-clients { key wt; wt; }; dlz "Mysql zone" { database "mysql #定义mysql的连接信息 {host=127.0.0.1 dbname=mydns ssl=false port=3306 user=root pass=XXX } {select zone from dns_records where zone = '$zone$' and view='CNC' limit 1} {select ttl, type, mx_priority, case when lower(type)='txt' then concat('\"', data, '\"') when lower(type) = 'soa' then concat_ws (' ', data, resp_person, serial, refresh, retry, expire, minimum) else data end as mydata from dns_records where zone = '$zone$' and host = '$record$' and (view = 'CNC' or view = 'DF') and in_use = 1} {} {select ttl, type, host, mx_priority, case when lower(type)='txt' then concat('\"', data, '\"') else data end as mydata, resp_person, serial, refresh, retry, expire, minimum from dns_records where zone = '$zone$' and view='CNC' and in_use = 1 }"; }; };
dns_records建表语句
CREATE TABLE `dns_records` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `zone` varchar(255) NOT NULL, `host` varchar(255) NOT NULL DEFAULT '@', `type` enum('MX','CNAME','NS','SOA','A','AAAA','PTR') DEFAULT NULL, `data` varchar(255) DEFAULT NULL, `ttl` int(11) NOT NULL DEFAULT '300', `view` char(20) DEFAULT 'DF', `mx_priority` int(11) DEFAULT NULL, `refresh` int(11) NOT NULL DEFAULT '3600', `retry` int(11) NOT NULL DEFAULT '3600', `expire` int(11) NOT NULL DEFAULT '86400', `minimum` int(11) NOT NULL DEFAULT '3600', `serial` bigint(20) NOT NULL DEFAULT '2008082700', `resp_person` varchar(64) NOT NULL DEFAULT 'root.domain.com.', `primary_ns` varchar(64) NOT NULL DEFAULT 'ns1.domain.com.', `data_count` int(11) NOT NULL DEFAULT '0', `in_use` tinyint(1) NOT NULL DEFAULT '1', PRIMARY KEY (`id`), KEY `type` (`type`), KEY `host` (`host`), KEY `zone` (`zone`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8
其他自定义线路的配置类似。
压测单进程bind9 mysql dlz的性能在3K左右,对于一般的应用场景性能已经足够了。如果对于可用性有较高的要求的话,则可以两台机器分别搭建mysql dlz,同时mysql进行主主数据同步。可以保证任何一台dns server宕机的情况下都不会影响到管理员对解析的修改以及正常查询响应。
如果对性能有较高的要求的话,则可以在一台master下面配置多个slave,从master中同步数据,同时使用lvs或者nginx udp反向代理统一对外服务。
个人感觉lvs使用还是有一定的要求的且配置较麻烦些。而bind9完全可以启用多个实例,监听不同的端口,配合前端使用nginx udp反向代理来实现bind9的高性能,高可用以及统一对外服务。
由于bind9的acl使用的是dns请求的源IP地址来作为分类依据,那么经过nginx udp反向代理一转的话,源IP地址全部会丢失,自然也无法实现智能解析了。这个问题可以使用ECS DLZ来进行规避。
4.1 ECS简介
EDNS0(Extension Mechanisms for DNS Version 0)是DNS在rfc1035基础上对DNS协议的扩展。主要在现有的DNS消息格式的基础上增加一些字段(OPT RR),来支持更多的DNS请求业务。同时保证向后兼容性。
传统的DNS智能解析,权威dns服务器实际上只能获取用户出口DNS,获取不到用户的IP地址,故智能解析实际上是按照递归DNS的IP地址进行解析的。
ECS(edns-client-subnet),由谷歌提交的DNS拓展协议,允许DNS resolver传递用户的ip地址给authoritative DNS server。使得DNS基于用户IP进行精准解析有了可能性。

4.2 ECS BIND9
edns-client-subnet(简称ecs)现在还没有正式被bind支持。需要对bind重新编译,ISC的git上有支持ecs authoritative的源码,git克隆到到本地编译即可。
git clone https://source.isc.org/git/bind9.git cd bind9/ ./configure --with-dlz-mysql --prefix=/usr/local/bind9 make && make install
注意:ecs的区别主要体现在acl规则中,在需要支持的ecs的规则前面,增加ecs关键字就可以
acl localnet{ ecs 120.0.0.1/24; };
其他配置不需要变动,使用dig命令进行测试
dig XXX.com +subnet=A.B.C.D @119.29.29.29
bind9日志

查看日志虽然记录的clientip还是dns请求的源ip地址,但是可以看到bind9已经获取到client subnet并且成功响应。
4.3 ECS DnsProxy
目前我们的bind9服务器已经配置支持了ecs解析,且前端采用nginx udp进行反向代理,负载均衡。但传统的dnsmasq不支持ecs,如果在节点继续使用dnsmasq的话,那么就无法发挥ecs的优势(bind9无法获取用户的ip,而非ecs的方式获取的是nginx代理的ip)。
我们在第三方开源库的基础上稍微修改实现了ednsproxy程序,有以下功能:
支持多个上游dns服务器
自带本地缓存,成功的响应缓存在本地,下次客户端请求,有效期内,可以直接响应给用户
支持普通dns请求到ecs dns请求的转换
单机性能>3万qps,满足单机dns缓存代理的性能要求
4.3.1 下载地址
github
https://github.com/zhxiaom5/ednsproxy
4.3.2 使用教程
Usage of ./dnsproxy: #默认跳过ipv6解析 -6 skip ipv6 record query AAAA (default true) #是否开始缓存,默认不开启 -cache enable go-cache #debug level,默认不开启 -debug int debug level #上游dns服务器,可以支持udp和tcp -dns , dns address, use , as sep (default "114.114.114.114:53:udp,119.29.29.29:53:udp") #ecs ip的地址 -ecsip string ecs ip address (default "127.0.0.1") #ecs ip的netmask -ecsnetmask int ecs netmask (default 32) #缓存过期时间 -expire int default cache expire seconds, -1 means use doamin ttl time (default 60) #缓存文件 -file string cached file (default "cache.dat") # #监听地址 -local string local listen address (default ":53") #失败响应不缓存 -negcache enable negcache #超时时间 -timeout int read/write timeout in ms (default 200)
运行示例:
./dnsproxy -dns 119.29.29.29:53:udp -cache=true -negcache=true -ecsip 121.9.212.177 -ecsnetmask 32
本文主要简述了使用bind9进行智能解析并且采用ecs进行横向扩展的一种方式。前端采用bind9作为权威dns,后端采用mysql作为记录的存储数据库。
当自建CDN量级较小的时候,内部回源完全可以采用上述的方案,即使用内部自建DNS解析源机IP进行回源,与公网解析进行隔离,来实现源机安全和灵活调度。前端可以对应开发一套DNS的管理界面。

当自建CDN量级逐渐增加,边缘节点数越来越多的时候,继续单纯采用内部bind9自建DNS来实现回源系统的方式就很难维持下去了。
一方面因为bind mysql dlz的性能原因,横向扩展不可能无限进行下去。另一方面也是随着边缘节点分布的越来越广,回源网络的差异性也越来越大。即使是同一线路的节点,也不可能将所有的边缘节点回源指向同一台源机。
后面会另出一篇文章来详细阐述大型CDN如何设计内部回源DNS系统。有兴趣的看客,可以继续关注公众号。
END
全中国只有不到1% 的人关注了运维军团
你是个有眼光的人!
(由于交流群人数已超100人,需要进群的小伙伴可以添加运维小编的微信:qq834775039)

如果你喜欢我们的文章,请转发到朋友圈



公众号
ywjtshare
运维军团
专注运维技术与传承,分享丰富原创干货






