1. 项目缘起与整体设计思路
1.1 为什么单台DNS服务器扛不住真实场景
很多人在实验环境里搭DNS,习惯性地只配一台服务器,解析记录一写,systemctl restart named,能ping通域名就算完事。这种玩法在自家局域网里确实够用,但只要放到稍微正规一点的场景里,问题立刻暴露出来。
我最早踩的坑是在一个内部测试环境里,当时所有机器都指向同一台DNS服务器做域名解析。某天那台机器磁盘满了,named进程直接挂掉,结果整个内网所有依赖域名访问的服务全部瘫痪——不是网络断了,是域名没人给解析了。那次之后我才真正理解,DNS作为基础设施,单点故障的代价是整个环境一起陪葬。
主从域名服务器要解决的核心问题就是高可用。主服务器负责写记录、改配置,从服务器自动同步主服务器的区域数据,对外提供查询服务。主挂了,从还能顶;从挂了,主继续扛。两台机器互为备份,查询请求不会因为一台故障而中断。
而分离解析解决的是另一个维度的问题:同一台DNS服务器,面对不同的查询来源,返回不同的解析结果。最典型的场景是内网用户访问某个服务走内网地址,外网用户访问同一个域名走公网地址。如果不做分离解析,要么内网用户绕一圈公网再回来,延迟高得离谱;要么外网用户拿到内网地址,根本连不上。
这两个功能组合在一起,就是一套能打的中小型DNS架构。下面我把整套操作拆开,从环境规划到配置落地,再到排错,一步步说清楚。
1.2 整体架构与角色划分
在动手之前,先把架构想明白。我习惯用两台机器做基础配置,如果条件允许可以再加一台做备用从服务器,但两台已经能覆盖绝大多数需求。
| 角色 | 作用 | 关键特征 |
|---|---|---|
| 主域名服务器 | 区域数据的权威来源,负责记录增删改 | 允许从服务器同步,自身可查询 |
| 从域名服务器 | 自动同步主服务器区域数据,提供查询 | 只读,不手动改记录 |
| 分离解析 | 根据客户端来源IP返回不同结果 | 依赖视图(view)功能实现 |
主从之间通过区域传送机制同步数据。主服务器在区域配置里声明“允许谁来拉数据”,从服务器在区域配置里声明“我从哪里拉数据”。同步触发有两种:主服务器序列号变化时主动通知从服务器,或者从服务器定时轮询主服务器检查序列号。
分离解析则依赖view配置块。每个view里定义一组客户端匹配规则(比如某个网段),然后在这个view内部单独写区域和解析记录。客户端请求进来时,服务器从上到下匹配view,命中哪个就用哪个view里的数据回答。
注意:view功能在部分精简版或特定发行版的DNS软件中可能被裁剪,动手前先确认你的软件版本支持view。
1.3 环境规划与前置准备
我这次用的环境是两台Linux机器,操作系统选的是主流的企业级发行版,DNS软件用BIND。IP规划如下:
- 主域名服务器:192.168.10.11
- 从域名服务器:192.168.10.12
- 内网客户端网段:192.168.10.0/24
- 外网模拟网段:172.16.20.0/24
域名我用的内部测试域名example.lab,这个域名不会和公网真实域名冲突,适合做实验。如果你要在生产环境用,换成自己实际持有的域名即可。
前置准备清单:
- 两台机器时间同步,时间偏差太大会影响区域传送的序列号判断
- 防火墙放行53端口(TCP和UDP都要),TCP用于区域传送和长响应,UDP用于常规查询
- 关闭或配置好SELinux,避免它拦截named写文件
- 确认
/etc/resolv.conf指向正确的DNS服务器
# 时间同步检查 timedatectl status # 防火墙放行(以firewalld为例) firewall-cmd --permanent --add-service=dns firewall-cmd --reload # SELinux相关布尔值(如果开启) setsebool -P named_write_master_zones on这几步看着简单,但我见过太多人卡在防火墙上——从服务器拉不到数据,查半天配置没问题,最后发现是TCP 53被挡了。区域传送走的是TCP,不是UDP,这点务必记住。
2. 主域名服务器配置全流程
2.1 安装与基础配置文件定位
BIND的安装没什么好说的,用包管理器直接装。装完之后先别急着改配置,把默认配置文件的位置和结构摸清楚,后面改起来才不慌。
# 安装BIND yum install bind bind-utils -y # 或 apt install bind9 bind9utils # 主要配置文件 /etc/named.conf # 主配置 /etc/named.rfc1912.zones # 区域声明(部分发行版) /var/named/ # 区域数据文件目录named.conf是入口,里面通过include引入其他配置片段。我习惯把自定义的区域配置单独写一个文件,然后在named.conf里include进来,这样升级或迁移时不会和系统默认配置混在一起。
主配置里需要关注几个关键项:
options { listen-on port 53 { 127.0.0.1; 192.168.10.11; }; listen-on-v6 port 53 { ::1; }; directory "/var/named"; allow-query { any; }; recursion yes; allow-recursion { 192.168.10.0/24; 172.16.20.0/24; }; notify yes; };listen-on指定监听哪些地址,生产环境不要图省事写any,明确列出本机IP更安全。allow-query控制谁能来查,实验环境可以放开,正式环境要收紧。allow-recursion控制谁可以使用递归查询,这个比allow-query更关键,因为开放递归会被利用做放大攻击。
提示:
recursion yes配合宽松的allow-recursion是典型的配置隐患。对外服务的权威服务器应该关闭递归,只做权威解析。
2.2 区域声明与正向解析文件编写
区域声明告诉BIND“我负责哪个域名的解析”。主服务器的区域类型是master,同时要声明允许哪些从服务器来拉数据。
zone "example.lab" IN { type master; file "example.lab.zone"; allow-transfer { 192.168.10.12; }; also-notify { 192.168.10.12; }; };allow-transfer是白名单,只有列表里的IP能拉区域数据。不设这个或者写成any,等于把整个域的记录公开给任何人,这是很常见的低级错误。also-notify让主服务器在序列号变化时主动通知从服务器,加快同步速度。
正向解析文件放在/var/named/目录下,文件名和上面file字段一致。内容结构如下:
$TTL 1D @ IN SOA ns1.example.lab. admin.example.lab. ( 2024010101 ; serial 1H ; refresh 15M ; retry 1W ; expire 1D ) ; minimum @ IN NS ns1.example.lab. @ IN NS ns2.example.lab. ns1 IN A 192.168.10.11 ns2 IN A 192.168.10.12 www IN A 192.168.10.100 mail IN A 192.168.10.101 @ IN MX 10 mail.example.lab.几个关键点展开说。SOA记录里的serial是区域传送的核心,从服务器通过比较这个数字判断要不要同步。每次改完记录必须递增这个值,我习惯用日期加序号的方式,比如2024010101表示2024年1月1日第1次修改。忘了改serial是从服务器不同步的第一大原因。
refresh告诉从服务器多久检查一次主服务器,retry是检查失败后多久重试,expire是从服务器联系不上主服务器多久后放弃数据,minimum是负缓存时间。这几个值在实验环境用默认的就行,生产环境根据更新频率调整。
NS记录声明这个域有哪些域名服务器,必须和实际的主从机器对应。A记录是具体的地址映射。MX记录是邮件交换记录,数字越小优先级越高。
写完文件后检查语法:
named-checkzone example.lab /var/named/example.lab.zone named-checkconf /etc/named.conf两个命令都通过再重启服务。我见过有人跳过检查直接重启,结果服务起不来,日志里一堆语法错误,回头一个个找很浪费时间。
2.3 反向解析配置的必要性与写法
反向解析是用IP查域名,很多人觉得用不上就跳过。但在一些场景里它是刚需:邮件服务器做反垃圾检查时会查发件IP的反向记录,某些日志分析工具需要把IP翻译成域名。
反向区域的命名规则是把网段倒过来加.in-addr.arpa。比如192.168.10.0/24网段,反向区域名是10.168.192.in-addr.arpa。
zone "10.168.192.in-addr.arpa" IN { type master; file "192.168.10.rev"; allow-transfer { 192.168.10.12; }; also-notify { 192.168.10.12; }; };反向解析文件:
$TTL 1D @ IN SOA ns1.example.lab. admin.example.lab. ( 2024010101 1H 15M 1W 1D ) @ IN NS ns1.example.lab. @ IN NS ns2.example.lab. 11 IN PTR ns1.example.lab. 12 IN PTR ns2.example.lab. 100 IN PTR www.example.lab. 101 IN PTR mail.example.lab.PTR记录的主机号部分只写最后一段,BIND会自动补全前面的网段。反向区域同样要配allow-transfer,否则从服务器拉不到反向数据。
2.4 主服务器启动与自检
配置写完,语法检查通过,启动服务:
systemctl start named systemctl enable named systemctl status named状态显示active running之后,用dig或nslookup在本机测试:
dig @192.168.10.11 www.example.lab dig @192.168.10.11 -x 192.168.10.100第一条查正向,第二条查反向。返回结果里看ANSWER SECTION有没有正确的记录,flags里有没有aa(权威回答)标志。如果返回SERVFAIL或REFUSED,去/var/log/messages或journalctl -u named看日志。
我自己的习惯是每改一次配置就测一次,不要攒一堆改动一起重启。出问题的时候,改动越少越容易定位。
3. 从域名服务器配置与同步验证
3.1 从服务器区域类型与配置差异
从服务器的配置和主服务器有对称的美感。区域类型从master变成slave,不需要file字段指定数据文件(BIND会自动在/var/named/slaves/下生成),但需要masters字段指定主服务器地址。
zone "example.lab" IN { type slave; masters { 192.168.10.11; }; file "slaves/example.lab.zone"; }; zone "10.168.192.in-addr.arpa" IN { type slave; masters { 192.168.10.11; }; file "slaves/192.168.10.rev"; };从服务器的options里allow-transfer可以留空或只允许特定地址,因为它本身不需要向别人传送数据。allow-query和allow-recursion按实际需求配。
从服务器的named.conf里同样要配listen-on,监听自己的IP。directory指向/var/named,这样file字段的相对路径才能正确解析。
3.2 区域传送的触发与验证
从服务器启动后,会自动向主服务器发起区域传送。验证方法:
# 在从服务器上查看数据文件是否生成 ls -l /var/named/slaves/ # 查询从服务器 dig @192.168.10.12 www.example.lab如果slaves目录下出现了区域文件,说明传送成功。如果目录为空,按以下顺序排查:
- 主服务器
allow-transfer是否包含从服务器IP - 防火墙TCP 53是否放行
- 主服务器区域文件serial是否正常
- 从服务器
masters地址是否正确 - 两台机器时间是否同步
我遇到过一次传送失败,日志里报transfer of 'example.lab/IN' from 192.168.10.11#53: failed while receiving responses: permission denied。查了半天发现是主服务器allow-transfer里写的是从服务器的另一个网卡地址,而实际通信走的是这个网段。这种细节很容易忽略,配的时候要确认通信实际使用的源地址。
3.3 主从同步的主动通知与被动轮询
主服务器改完记录、递增serial之后,有两种方式让从服务器知道:
主动通知:主服务器配置了also-notify,serial变化时主动发NOTIFY消息给从服务器,从服务器收到后立即发起传送。这是推荐方式,同步延迟最小。
被动轮询:从服务器按照SOA里的refresh时间定时检查主服务器的serial。如果主服务器没配also-notify,就只能等轮询周期到了才同步。
验证通知是否生效,可以在主服务器改一条记录,递增serial,重启named,然后在从服务器上观察:
# 从服务器上实时看日志 journalctl -u named -f正常的话几秒内就能看到transfer of 'example.lab/IN' from 192.168.10.11#53: Transfer completed之类的日志。
注意:
also-notify只在主服务器重启或重新加载配置后生效。如果只是改了区域文件没重启,NOTIFY不会发出去。所以改完记录记得rndc reload或重启服务。
3.4 从服务器只读特性与故障切换
从服务器的区域数据是只读的,手动改slaves目录下的文件没有意义,下次同步会被覆盖。所有记录变更都在主服务器做,从服务器只负责同步和应答。
故障切换方面,客户端通常配置两个DNS服务器地址,一个主一个从。当主服务器不可达时,客户端会自动切换到从服务器查询。这个切换是客户端行为,不需要在DNS服务器上做额外配置。
但要注意,如果客户端只配了主服务器地址,主挂了就没人应答了。所以客户端的/etc/resolv.conf里应该同时写主和从的地址:
nameserver 192.168.10.11 nameserver 192.168.10.12这样主服务器故障时,客户端查询会自动落到从服务器上。实测下来,切换通常在几秒内完成,对业务影响很小。
4. 分离解析的视图配置与落地
4.1 视图功能的工作原理
分离解析的核心是view。BIND收到查询请求后,按照named.conf里view定义的顺序,用每个view的match-clients规则去匹配请求来源IP。第一个匹配上的view被选中,后续view不再检查。然后在选中的view内部查找对应的区域和记录。
这个“从上到下、先匹配先赢”的机制意味着view的顺序很重要。通常把范围最小的view放最前面,范围大的放后面,最后放一个匹配所有地址的默认view兜底。
每个view内部是独立的命名空间,可以有自己的区域声明、自己的根提示、自己的转发配置。同一个域名可以在不同view里有不同的解析记录,互不干扰。
4.2 内网视图与外网视图的配置
假设需求是:内网用户(192.168.10.0/24)访问www.example.lab返回192.168.10.100,外网用户(其他所有)访问同一个域名返回172.16.20.100。
配置结构如下:
view "internal" { match-clients { 192.168.10.0/24; }; recursion yes; allow-recursion { 192.168.10.0/24; }; zone "example.lab" IN { type master; file "internal/example.lab.zone"; allow-transfer { 192.168.10.12; }; also-notify { 192.168.10.12; }; }; zone "10.168.192.in-addr.arpa" IN { type master; file "internal/192.168.10.rev"; allow-transfer { 192.168.10.12; }; also-notify { 192.168.10.12; }; }; }; view "external" { match-clients { any; }; recursion no; zone "example.lab" IN { type master; file "external/example.lab.zone"; allow-transfer { 192.168.10.12; }; also-notify { 192.168.10.12; }; }; };内网视图的recursion yes允许内网用户做递归查询,外网视图recursion no只做权威解析,避免被利用。两个视图各自有独立的区域文件,放在不同子目录下,互不覆盖。
内网区域文件里www指向192.168.10.100,外网区域文件里www指向172.16.20.100。其他记录如NS、MX在两个视图里保持一致。
4.3 视图配置的常见陷阱与顺序问题
view配置有几个容易翻车的地方,我逐个说。
陷阱一:view和全局options冲突。一旦使用了view,所有区域声明都必须放在view内部,不能再有全局的zone声明。全局options里的一些指令也会被view内的同名指令覆盖。如果混用,BIND启动时会报错。
陷阱二:match-clients顺序。如果把any的view放在前面,后面所有view都匹配不上了,因为第一个view已经把所有请求都截胡了。所以any的view必须放最后。
陷阱三:根区域缺失。如果内网视图需要递归,必须提供根提示文件(named.ca),否则递归查询找不到根。外网视图如果只做权威解析,可以不需要根区域。
陷阱四:从服务器同步。从服务器也要配对应的view结构,否则主服务器传过来的数据没法正确落到对应视图里。从服务器的view里区域类型是slave,masters指向主服务器。
陷阱五:日志和调试。view配置出问题时,日志里会提示哪个view匹配失败或区域加载失败。开大日志级别有助于定位:
rndc querylog on tail -f /var/log/messages4.4 分离解析的效果验证
配置完成后,从不同网段的客户端测试。如果手头没有多个网段,可以用dig的-b参数指定源地址模拟:
# 模拟内网来源 dig -b 192.168.10.50 @192.168.10.11 www.example.lab # 模拟外网来源(需要本机有对应网段地址) dig -b 172.16.20.50 @192.168.10.11 www.example.lab返回结果里ANSWER SECTION的地址应该不同。内网来源返回192.168.10.100,外网来源返回172.16.20.100。
如果两个来源返回相同结果,检查view的match-clients是否写对,以及view顺序是否正确。用rndc dumpdb -all可以导出当前缓存和视图状态,帮助分析。
5. 常见问题排查与实操避坑指南
5.1 区域传送失败排查速查表
区域传送是从服务器配置里最容易出问题的环节。我把常见现象和对应原因整理成表,方便对照排查。
| 现象 | 可能原因 | 排查方法 |
|---|---|---|
| slaves目录为空 | allow-transfer未包含从IP | 检查主服务器区域配置 |
| 日志报permission denied | 防火墙拦截TCP 53 | 检查防火墙规则 |
| 日志报connection refused | 主服务器named未运行 | 检查主服务器服务状态 |
| 传送成功但记录不更新 | serial未递增 | 检查SOA serial值 |
| 传送超时 | 网络不通或路由问题 | ping测试连通性 |
| 部分区域传送失败 | 单个区域配置错误 | 检查对应区域文件语法 |
我自己的经验是,传送问题九成出在allow-transfer和防火墙上。先把这两个确认了,再去看其他。
5.2 分离解析不生效的排查思路
分离解析不生效,表现是不同来源返回相同结果。排查顺序:
- 确认BIND版本支持view,
named -v查看版本 - 确认view配置在
named.conf里,且没有全局zone声明冲突 - 确认
match-clients的网段写法正确,比如192.168.10.0/24不能写成192.168.10.0/24以外的形式 - 确认view顺序,
any的view在最后 - 确认区域文件路径正确,不同view指向不同文件
- 重启服务后测试,改完view必须重启,
rndc reload有时不生效
有一次我改完view配置,rndc reload之后测试没变化,折腾半天发现必须systemctl restart named才行。view结构的变更比普通区域变更影响大,建议直接重启。
5.3 序列号管理与记录变更规范
serial管理是主从同步的命脉。我总结了一套自己的规范:
- 格式用
YYYYMMDDNN,NN是当天修改次数,从01开始 - 每次改区域文件必须递增serial,哪怕只改一个字符
- 改完先
named-checkzone检查语法,再rndc reload - reload后去从服务器确认同步完成,再通知相关人员
如果多人维护同一个区域,serial冲突是常见问题。建议指定一个人负责递增serial,或者用版本控制工具管理区域文件,避免覆盖。
提示:
rndc reload只重新加载配置和区域文件,不会中断服务。但view结构变更、监听地址变更等需要restart。区分清楚,能减少不必要的服务中断。
5.4 性能与安全加固建议
基础功能跑通之后,可以考虑几个加固点。
限制查询来源:allow-query不要写any,明确列出允许查询的网段。对外权威服务器只允许查询自己负责的域,拒绝其他查询。
关闭不必要的递归:对外服务器recursion no,内网服务器按需开启并限制来源。
启用响应速率限制:BIND支持RRL(Response Rate Limiting),可以缓解放大攻击。配置在options里:
rate-limit { responses-per-second 10; window 5; };日志分离:把查询日志和安全日志分开,便于审计和排错。logging配置块里定义channel和category。
定期备份区域文件:区域文件是核心资产,改之前先备份。我习惯用cp example.lab.zone example.lab.zone.bak.$(date +%Y%m%d),出问题随时回滚。
5.5 我踩过的几个典型坑
最后分享几个我实际踩过的坑,都是文档里不会写但实际会遇到的。
坑一:从服务器时间不同步导致传送异常。两台机器时间差超过refresh周期时,从服务器可能反复发起传送或判断serial出错。用NTP同步时间是基础操作,别省。
坑二:区域文件权限问题。手动创建的区域文件如果属主不是named用户,BIND读不了。chown named:named /var/named/*.zone是常规操作。SELinux开启时还要注意上下文,restorecon -Rv /var/named/可以修复。
坑三:view里忘了配反向区域。正向分离解析配好了,反向忘了配,结果内网用户nslookupIP时拿到的是外网视图的反向记录,对不上。正向反向要成对配置。
坑四:also-notify写了但从服务器没反应。检查从服务器防火墙是否放行来自主服务器的UDP 53,NOTIFY消息走UDP。TCP 53放行了但UDP没放,传送能成但通知收不到,同步延迟变大。
坑五:serial递增了但忘了reload。改完文件直接测试,发现从服务器没同步,查半天发现主服务器根本没重新加载区域文件。改完必须rndc reload或重启,这个动作不能省。
这套主从加分离解析的配置,我在几个不同规模的环境里都落地过,两台机器加合理的配置,撑住一个中小型内网的域名解析需求完全没问题。关键是把serial管理、allow-transfer白名单、view顺序这几个点盯住,剩下的就是熟练度问题。