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-servicedns 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-notifyserial变化时主动发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里区域类型是slavemasters指向主服务器。陷阱五日志和调试。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版本支持viewnamed -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管理是主从同步的命脉。我总结了一套自己的规范格式用YYYYMMDDNNNN是当天修改次数从01开始每次改区域文件必须递增serial哪怕只改一个字符改完先named-checkzone检查语法再rndc reloadreload后去从服务器确认同步完成再通知相关人员如果多人维护同一个区域serial冲突是常见问题。建议指定一个人负责递增serial或者用版本控制工具管理区域文件避免覆盖。提示rndc reload只重新加载配置和区域文件不会中断服务。但view结构变更、监听地址变更等需要restart。区分清楚能减少不必要的服务中断。5.4 性能与安全加固建议基础功能跑通之后可以考虑几个加固点。限制查询来源allow-query不要写any明确列出允许查询的网段。对外权威服务器只允许查询自己负责的域拒绝其他查询。关闭不必要的递归对外服务器recursion no内网服务器按需开启并限制来源。启用响应速率限制BIND支持RRLResponse 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 53NOTIFY消息走UDP。TCP 53放行了但UDP没放传送能成但通知收不到同步延迟变大。坑五serial递增了但忘了reload。改完文件直接测试发现从服务器没同步查半天发现主服务器根本没重新加载区域文件。改完必须rndc reload或重启这个动作不能省。这套主从加分离解析的配置我在几个不同规模的环境里都落地过两台机器加合理的配置撑住一个中小型内网的域名解析需求完全没问题。关键是把serial管理、allow-transfer白名单、view顺序这几个点盯住剩下的就是熟练度问题。