如何设计一个DNS

894 阅读12分钟
原文链接: www.infoq.com

前言

DNS 这个东西,可大可小,可简单可复杂。对于以园区网为主的传统企业 / 单位而言,要考虑多出口的链路优化、智能解析、私有域名的解析 、监控、管理等一系列问题,还是需要有一个好的设计优化的。

我们前阵子刚完成了 DNS 架构的升级,在这里整理一下做个分享。

需求分析

在讨论 DNS 的架构之前我们先分析一下我们的需求。

多链路优化

通常一个园区网都会有多个出口。我们需要通过路由策略来决定用户的请求最终落入哪个出口上。在网络层上,BGP 自然不必多说了,静态路由的话,我们也可以设法去弄到各个运营商的地址表(万能的淘宝)然后写入路由,从而让用户的请求落入最合理的出口链路上。

“让用户的请求落入最合理的出口链路上”,靠路由表就够了吗?非也,因为还有 CDN。很自然地,由于有 CDN ,大部分网站的所提供的解析,都会根据用户的 DNS 请求来源,而智能解析到对应运营商区域内的 CDN 上。

也就是说,实际上我们 DNS 递归时的网络路由策略,基本决定了用户访问网站的实际路由。例如我们的 DNS 通过电信的链路去递归解析,那么用户解析到的网站地址多半也会落在该网站的电信 CDN 上,因此最终的请求也将被路由至电信链路上。

现在的问题在于,不同网站,在不同链路的 CDN 上的访问体验是不一致的!举个例子,QQ 邮箱(mail.qq.com),如果他落在教科网的 CDN 上,附件经常就传不上去。12306 在电信的 CDN 上访问很快,而在教科网的 CDN 上就经常卡的飞起。说到底这其实就是不同链路之间的质量差异,毕竟价格也要差好多呢。

把默认路由全都给质量最好的链路就行了呗?理想很丰满,但是小钱钱很骨干呐!实际上我们拥有的链路带宽大小通常也与链路质量成反比。因此我们需要在用户的访问体验和链路的带宽利用率上寻找一个平衡。

智能解析

如同 CDN 一般。我们自己也有多条出口,因此我们自己的服务在提供 DNS 解析的时候,也得根据用户解析的请求地址,来进行智能的解析,来提高我们网站的外部访问速率。

私有域名

在我们的数据中心里,我们会需要一些私有的域名分配给服务器。这样会方便管理,也可以利用 DNS 做一些服务注册的工作。所以我们要有私有的域名,但是需要限制在数据中心之内,避免园区内的普通用户能够直接解析到。

API

刚才我们提到了服务注册。实际上无论是从运维自动化来考虑还是流程自动化来考虑,我们都需要 DNS 对应的 API 接口,能够让我们自动化的注册、修改、删除域名。

服务状态监控

如同我们所有的服务一样,DNS 我们也需要有对应的服务监控。这需要我们的 DNS 本身能够暴露出相应的状态接口,提供服务状态的查询方法。我们通过脚本等去采集状态信息推送给监控系统(Open-Falcon)。

管理

我们需要一个方便易用的前端来做 DNS 的域名管理。需要能够进行权限的控制,给不同的域分配不同的管理员。能够记录每次变更的操作记录。彻底结束 SSH 到服务器上改 ZONE 文件的历史。

同时后端数据最好落到数据库里,这样做一些查询的时候会方便很多。比如我要查所有解析的 IP 落在外部而非校内 IP 的域名,数据库里只要一条 SQL 就搞定了,而 ZONE 文件的话就得写脚本了。

传统架构

大家可能看过前阵子 GitHub 的 dns-infrastructure-at-github 这篇文章。这里面 GitHub 说他们以前的 DNS 架构就是两台服务器,包含了缓存,递归和权威服务。文章里没说用的是啥软件,不过 8 成是 Bind 吧。

我们学校之前的 DNS 架构也是如此,两台 Bind 做完所有的事情。我所了解的许多学校 DNS 架构亦是如此。很多地方的 DNS  架构都是这样子,简单,稳定。

然而对应我们刚才的需求:

  1. 多链路优化——搞不了
  2. 智能解析——这个可以用 bind views 来做
  3. 私有域名——不好控制园区普通用户的访问
  4. API——木有
  5. 服务状态监控——只能通过分析日志,有限
  6. 管理——Bind 都有对应的第三方解决办法,但是都相对比较麻烦

新架构

技术选型

GitHub 在 dns-infrastructure-at-github  里面提到,他们使用了 Unbound 作为缓存,NSD 作为边缘节点,PowerDNS 作为权威服务。

我们则选择了使用 PowerDNS  全家。即 PowerDNS-Authoritative 作为权威服务,PowerDNS-Recursor 作为递归服务,dnsdist 作为边缘节点来提供智能解析。并且使用 PowerDNS-Admin 进行权威服务的管理。

架构

先看总体架构

递归服务

我们将两台主递归服务器默认通过链路质量稍差一点,但带宽比较充裕的 ISP 进行递归。同时,在链路质量较好的 ISP 上再放两台递归服务器,将部分域名 Forward 给他去递归,从而在基本不降低用户访问体验的情况下,充分利用两条链路的带宽。

当然我们会把自身的域名直接 Forward 给我们的权威服务,避免了无谓的外部递归。

权威服务

我们根据两条不同的链路做了两套权威服务,一套解析 ISP-1 的地址,另一台则解析为 ISP-2 的地址。然后在这两套权威服务之前,放了两台 dnsdist 作为边缘节点。

dnsdist 根据请求的 IP 地址,将请求转发给对应的后端权威服务,实现智能解析。

私有域名

在数据中心内,我们需要一些私有的内部域名来做点服务注册之类的活。这些域名我们希望只能被我们数据中心内的服务器们请求到,普通的园区用户无法请求。类似的,我们在私有的权威服务之前,使用 dnsdist  进行 ACL 过滤。这两台 dnsdist  就作为数据中心的 DNS 服务提供给服务器使用。仅匹配数据中心网段的服务器允许解析,并转发私有的域名请求给权威服务器,其他请求则转发给园区的主递归服务。普通用户的请求则无法匹配 ACL 将被拒绝。

API

Powerdns 提供了简单易用的 API 调用。像这样:

__Fri Dec 29 2017 10:07:58 GMT+0800 (CST)____Fri Dec 29 2017 10:07:58 GMT+0800 (CST)__# curl -v -X PATCH -H 'X-API-Key: d37757d0adfa0d3dc26a11119134f92' --data '{"rrsets":[{"name":"testtesttest.ecnu.edu.cn.","type":"A","ttl":3600,"changetype":"REPLACE","records":[{"content":"192.168.0.1","disabled":false,"set-ptr":false}]}]}' "http://127.0.0.1:8081/api/v1/servers/localhost/zones/ecnu.edu.cn"
* About to connect() to 127.0.0.1 port 8081 (#0)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8081 (#0)
> PATCH /api/v1/servers/localhost/zones/ecnu.edu.cn HTTP/1.1
> User-Agent: curl/7.29.0
> Host: 127.0.0.1:8081
> Accept: */*
> X-API-Key: d37757d0adfa0d3dc26a11119134f92
> Content-Length: 165
> Content-Type: application/x-www-form-urlencoded
> 
* upload completely sent off: 165 out of 165 bytes
< HTTP/1.1 204 No Content
< Access-Control-Allow-Origin: *
< Connection: close
< Content-Length: 0
< Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'
< Server: PowerDNS/4.1.0-rc1
< X-Content-Type-Options: nosniff
< X-Frame-Options: deny
< X-Permitted-Cross-Domain-Policies: none
< X-Xss-Protection: 1; mode=block
< 
* Closing connection 0__Fri Dec 29 2017 10:07:58 GMT+0800 (CST)____Fri Dec 29 2017 10:07:58 GMT+0800 (CST)__

所以我们可以很容易的将他和流程系统,运维系统进行对接。实现域名注册的自动化。

监控

与 Bind 最大的区别就在监控上了。Powerdns 内置了大量的性能指标项,我们可以非常容易的去采集这些指标,然后推送给我们的监控系统。详细的指标可以参看官网说明:

docs.powerdns.com/recursor/me…

docs.powerdns.com/authoritati…

我们使用 open-falcon 作为监控系统,效果如下:

实际上我们可以看出来,对于规模不是很大的 DNS 而言,是不需要 DNS 的负载均衡的。因为在达到单机的性能瓶颈之前,cache 的命中率才是决定请求平均延迟的主要因素。我们做双机的唯一目的只是冗余而已。

Powerdns 的单机性能,根据 官网介绍,大概能 HOLD 住亿级的请求量,所以通常不会有什么压力的~

__Fri Dec 29 2017 10:07:58 GMT+0800 (CST)____Fri Dec 29 2017 10:07:58 GMT+0800 (CST)__ It is known to power the resolving needs of over 150 million internet connections.__Fri Dec 29 2017 10:07:58 GMT+0800 (CST)____Fri Dec 29 2017 10:07:58 GMT+0800 (CST)__

管理

powerdns 由于提供了丰富的 API 接口,因此社区内有很多适配的 WEB 前端可以选择。

github.com/PowerDNS/pd…

我们选择了  PowerDNS-Admin 作为管理前端。

他可以实现 DNS 的分级权限管理,同时记录每次变更的记录。

完整的变更记录在 mysql 内:

__Fri Dec 29 2017 10:07:58 GMT+0800 (CST)____Fri Dec 29 2017 10:07:58 GMT+0800 (CST)__MariaDB [powerdnsadmin]> select * from history order by created_on desc limit 1 \G;__Fri Dec 29 2017 10:07:58 GMT+0800 (CST)____Fri Dec 29 2017 10:07:58 GMT+0800 (CST)__

这样如果发生手抖事件误操作了,也抵赖不了啦,该背锅背锅。

数据存储

Powerdns 支持多种 数据后端,也包含 BIND 的 Zone 文件模式。

我们为了将来的管理方便,选择将数据迁移到 MySQL 中作为 Backend。Powerdns 自带迁移工具 zone2sql,可以很方便的把 zone 文件转换成 sql 语句然后导入 mysql 中:

__Fri Dec 29 2017 10:07:58 GMT+0800 (CST)____Fri Dec 29 2017 10:07:58 GMT+0800 (CST)__# zone2sql --help
syntax:

  --filter-duplicate-soa | --filter-duplicate-soa=yes | --filter-duplicate-soa=no
        Filter second SOA in zone
  --gmysql | --gmysql=yes | --gmysql=no
        Output in format suitable for default gmysqlbackend
  --goracle | --goracle=yes | --goracle=no
        Output in format suitable for the goraclebackend
  --gpgsql | --gpgsql=yes | --gpgsql=no
        Output in format suitable for default gpgsqlbackend
  --gsqlite | --gsqlite=yes | --gsqlite=no
        Output in format suitable for default gsqlitebackend
  --help
        Provide a helpful message
  --json-comments | --json-comments=yes | --json-comments=no
        Parse json={} field for disabled & comments
  --mydns | --mydns=yes | --mydns=no
        Output in format suitable for default mydnsbackend
  --named-conf=...
        Bind 8/9 named.conf to parse
  --on-error-resume-next | --on-error-resume-next=yes | --on-error-resume-next=no
        Continue after errors
  --oracle | --oracle=yes | --oracle=no
        Output in format suitable for the oraclebackend
  --slave | --slave=yes | --slave=no
        Keep BIND slaves as slaves. Only works with named-conf.
  --soa-expire-default=...
        Do not change
  --soa-minimum-ttl=...
        Do not change
  --soa-refresh-default=...
        Do not change
  --soa-retry-default=...
        Do not change
  --transactions | --transactions=yes | --transactions=no
        If target SQL supports it, use transactions
  --verbose | --verbose=yes | --verbose=no
        Verbose comments on operation
  --version
        Print the version
  --zone=...
        Zonefile to parse
  --zone-name=...
        Specify an $ORIGIN in case it is not present__Fri Dec 29 2017 10:07:58 GMT+0800 (CST)____Fri Dec 29 2017 10:07:58 GMT+0800 (CST)__

主从同步

传统的 DNS 主从同步是通过 AXFR 来同步。在 Powerdns 中,我们将主 / 从的 权威 DNS 分别设置为 MasterSlave 就可以实现。

然而既然我们的后端已经是 MySQL 了,所以完全可以用 MySQL 同步来做嘛。因此我们把 Powerdns 的类型设置为 Native,并且配置了两台权威服务器的 MySQL 进行主从同步。MySQL 的主从同步很可靠,同时 binlog 也能够方便的进行回退。

复杂查询

由于我们把数据放到了 MySQL 里,因此现在做一些复杂的记录查询变的非常容易了。例如我们需要查一下某个网段内的所有 A 记录解析。换以前可能得对着 Zone 文本写脚本了。现在只要一条 SQL 语句就搞定了:

__Fri Dec 29 2017 10:07:58 GMT+0800 (CST)____Fri Dec 29 2017 10:07:58 GMT+0800 (CST)__MariaDB [powerdns]> select * from records where type="A" and content>="192.168.80.1" and content<="192.168.80.255";__Fri Dec 29 2017 10:07:58 GMT+0800 (CST)____Fri Dec 29 2017 10:07:58 GMT+0800 (CST)__

数据备份

同样,由于现在数据在 MySQL 内,数据备份也变得非常容易了。我们可以使用 MySQL 的 binlog 机制,很容易的实现数据的增量备份。再辅以每天一次全量的备份,数据的备份机制也比过去来的更完善了

总结

新的架构上线之后,通过管理权限的下放和自动化的 API 调用,管理负担降低了,管理透明度提升了。整个结构清晰,易于维护。通过多链路的优化,我们在基本不降低用户的网络访问质量的同时,尽可能地利用了每一条 ISP 链路。

解放我们的双手,去做更多有意义的事情。

参考文档

作者介绍

冯骐,华东师范大学信息部门工程师,上海教育城域网管理员。对网络、运维开发等有丰富经验。热爱开源,Open-Falcon 社区成员,开发 / 维护有多个 Open-Falcon 适配的监控插件。如:交换机监控、Windows 监控、vSphere 监控等。

感谢雨多田光对本文的审校。