☰
BIND主从域名服务器与分离解析实战:从单点故障到内外网安全解析
2026/10/17 13:08:42 网站建设 项目流程

1. 为什么还要自己搭DNS:从一次内部服务发现故障说起

很多团队觉得域名解析是基础设施的事,买了个域名、配几条A记录就完事了。直到某天内部服务大面积超时,排查半天发现是单台DNS服务器扛不住并发查询,或者某条解析记录被误改导致整个测试环境瘫痪,才意识到DNS这块看着简单,真出问题就是全局性的。我这次要聊的,就是怎么用主从域名服务器加分离解析这两套机制,把DNS从"能用"做到"稳且安全"。

先说清楚这套东西是什么。主从域名服务器(Master/Slave,现在官方叫Primary/Secondary)解决的是单点故障和负载分担问题——主服务器负责写记录,从服务器自动同步主服务器的区域数据,对外提供查询。分离解析(Split DNS / View)解决的是"内外有别"的问题——同一个域名,内网用户查到的是内网地址,外网用户查到的是公网地址,两拨人互不干扰。

这套方案适合谁?我总结下来是三类人:一是中小团队里兼管网络和运维的工程师,二是需要给内部系统做私有域名解析的开发者,三是准备考网络相关认证、需要动手把DNS原理跑通一遍的学习者。哪怕你之前只会在域名服务商后台点几下鼠标加记录,跟着这篇走完,也能把DNS的底层逻辑摸个七七八八。

我下面用的环境是Linux下的BIND(Berkeley Internet Name Domain),这是目前最主流的开源DNS服务软件,绝大多数发行版的软件源里都有。整个实操我会拆成设计思路、核心配置、完整落地、排错四块来讲,每一步都告诉你为什么这么配,而不是让你照抄。

2. 整体设计思路:主从怎么分工,分离解析靠什么实现

2.1 主从架构到底解决了什么问题

单台DNS服务器的问题很直白:它挂了,所有依赖它解析的机器全部抓瞎。有人会说那我多配几个DNS地址不就行了?问题是如果这几台服务器各管各的,记录不一致,A机器查到的是新地址,B机器查到的是旧地址,那才是灾难。所以主从的核心不是"多几台",而是数据一致性前提下的冗余。

主服务器(Primary)是唯一可以修改区域数据的地方,所有记录的增删改都在这台上做。从服务器(Secondary)通过区域传送(Zone Transfer)机制,定期或在主服务器通知时,把整个区域文件拉过来。这里有个关键点:从服务器拉取的是整个区域,不是单条记录,所以它天然和主服务器保持一致。

区域传送分两种:完全区域传送(AXFR)会把整个区域文件传一遍,增量区域传送(IXFR)只传变化的部分。数据量小的时候差别不大,但区域里记录成千上万条时,IXFR能省下大量带宽。BIND默认会优先尝试IXFR,失败再退回AXFR,这个不用手动干预。

那从服务器怎么知道主服务器数据变了?靠NOTIFY通知。主服务器一改记录,序列号(Serial)变化,它就会主动给从服务器发个通知说"我更新了,你来拉"。从服务器收到通知立刻发起传送,比单纯靠定时轮询快得多。当然定时轮询(Refresh参数)还是要配的,作为通知丢失时的兜底。

2.2 分离解析的实现原理:View视图机制

分离解析在BIND里靠View(视图)实现。你可以把View理解成"给不同来源的查询者看不同的区域数据"。每个View里可以定义自己的区域文件、自己的转发策略、自己的递归权限。

判断一个查询该走哪个View,靠的是匹配顺序。BIND会从上到下依次匹配每个View的match-clients条件,第一个匹配上的就用那个View。所以顺序很重要,通常把范围小的、内网的View放前面,把any这种兜底的放最后。

这里有个新手最容易踩的坑:一旦用了View,所有区域都必须定义在某个View里面,不能有区域裸奔在View外面。因为BIND需要明确知道每个区域归哪个视图管。另外根区域(.)的定义也要注意,如果两个View都要做递归,那每个View里都得有根区域的提示文件配置。

2.3 为什么选BIND而不是别的

市面上DNS软件不少,dnsmasq轻量适合做转发和缓存,CoreDNS在容器环境里很流行,PowerDNS有数据库后端。但要做标准的主从同步加分离解析,BIND是最成熟、文档最全、行为最可预期的选择。它的配置文件语法虽然啰嗦,但每一行都有明确含义,调通了之后你对DNS协议的理解会上一个台阶。dnsmasq做分离解析就比较别扭,CoreDNS的视图功能要靠插件拼,都不如BIND直接。

提示:如果你的需求只是内网做个小缓存转发,dnsmasq足够且更省资源。但只要涉及主从同步和内外分离,直接上BIND,别绕弯路。

3. 核心配置细节:区域文件、序列号与同步参数

3.1 区域文件里那些不能乱填的参数

区域文件(Zone File)是DNS数据的实际载体,开头那段SOA记录是重中之重。我拿一个内部域名lab.internal举例,SOA长这样:

$TTL 3600 @ IN SOA ns1.lab.internal. admin.lab.internal. ( 2024011501 ; Serial 序列号 3600 ; Refresh 刷新间隔 1800 ; Retry 重试间隔 604800 ; Expire 过期时间 86400 ; Minimum 最小TTL )

逐行说。Serial序列号是主从同步的命脉,从服务器就是靠比较自己和主服务器的序列号来决定要不要更新。格式通常是年月日+两位序号,比如2024011501表示2024年1月15日的第1次修改。这里有个铁律:每次改完记录必须手动递增序列号,否则从服务器根本不知道你改了。我见过太多人改完记录发现从服务器没同步,查半天就是忘了改序列号。

Refresh是从服务器多久主动问一次主服务器"你序列号变了吗",3600秒就是1小时。Retry是传送失败后多久重试,1800秒。Expire是如果一直联系不上主服务器,从服务器多久后放弃这份数据不再对外应答,604800秒是7天。Minimum现在主要用作否定缓存的TTL,即"查不到"这个结果缓存多久。

注意:Serial只增不减。如果你手滑改小了,从服务器会认为自己的数据更新,反而不会去同步。真改错了,就把数字改得比原来更大。

3.2 主服务器配置的关键几行

主服务器的named.conf里,区域定义要标明类型是master(老版本写primary),并指定区域文件路径:

zone "lab.internal" IN { type master; file "/etc/named/zones/lab.internal.zone"; allow-transfer { 192.168.10.12; }; // 只允许从服务器拉取 also-notify { 192.168.10.12; }; // 主动通知从服务器 };

allow-transfer是安全关键,绝对不能写成any,否则任何人都能把你的整个区域文件拉走,等于把内网拓扑图拱手送人。只写从服务器的IP。also-notify让主服务器改完记录主动推通知,比等从服务器轮询快。

3.3 从服务器配置与同步验证

从服务器上对应的区域定义类型是slave(或secondary),要指定主服务器地址:

zone "lab.internal" IN { type slave; file "/var/named/slaves/lab.internal.zone"; masters { 192.168.10.11; }; };

从服务器的区域文件不用手动创建,BIND同步过来会自动生成。同步成没成,看两个地方:一是日志里有没有transfer of 'lab.internal/IN' from 192.168.10.11这样的记录,二是/var/named/slaves/目录下有没有生成文件。用dig @从服务器IP lab.internal SOA查一下,如果返回的序列号和主服务器一致,就说明同步成功了。

3.4 分离解析的View配置骨架

View的配置结构是这样的:

view "internal" { match-clients { 192.168.10.0/24; 127.0.0.1; }; recursion yes; zone "lab.internal" IN { type master; file "/etc/named/zones/lab.internal.internal.zone"; }; }; view "external" { match-clients { any; }; recursion no; zone "lab.internal" IN { type master; file "/etc/named/zones/lab.internal.external.zone"; }; };

内网View允许递归(recursion yes),因为内网用户需要查外部域名。外网View关掉递归(recursion no),只回答自己权威的区域,避免被当成开放解析器被人利用。两个View里同名区域指向不同的文件,内网文件里写内网IP,外网文件里写公网IP,这就是分离解析的本质。

提示:match-clients里一定要带上127.0.0.1,否则本机自己查自己都可能匹配不上任何View,导致解析失败。

4. 完整实操:从零把主从加分离解析跑起来

4.1 环境规划与软件安装

我规划两台机器:主服务器192.168.10.11,从服务器192.168.10.12,内网网段192.168.10.0/24。域名用lab.internal,内网解析到192.168.10.100,外网解析到203.0.113.100(这是文档专用测试网段,实际换成你的公网IP)。

两台机器都装BIND:

# 以常见的包管理为例 yum install bind bind-utils -y # 或者 apt install bind9 bind9utils -y

装完先别急着配,把防火墙放行53端口(TCP和UDP都要,UDP用于普通查询,TCP用于区域传送和大响应):

firewall-cmd --permanent --add-port=53/udp firewall-cmd --permanent --add-port=53/tcp firewall-cmd --reload

4.2 主服务器区域文件编写

先建目录和文件。内网区域文件lab.internal.internal.zone:

$TTL 3600 @ IN SOA ns1.lab.internal. admin.lab.internal. ( 2024011501 3600 1800 604800 86400 ) @ IN NS ns1.lab.internal. @ IN NS ns2.lab.internal. ns1 IN A 192.168.10.11 ns2 IN A 192.168.10.12 app IN A 192.168.10.100 www IN A 192.168.10.100

外网区域文件lab.internal.external.zone:

$TTL 3600 @ IN SOA ns1.lab.internal. admin.lab.internal. ( 2024011501 3600 1800 604800 86400 ) @ IN NS ns1.lab.internal. @ IN NS ns2.lab.internal. ns1 IN A 203.0.113.11 ns2 IN A 203.0.113.12 app IN A 203.0.113.100 www IN A 203.0.113.100

两个文件结构一样,只是IP不同。注意NS记录在两份文件里指向的地址也不同,这是符合实际的——外网用户解析ns1.lab.internal应该拿到公网地址。

4.3 主服务器named.conf完整配置

options { listen-on port 53 { any; }; directory "/var/named"; allow-query { any; }; allow-recursion { 192.168.10.0/24; 127.0.0.1; }; forwarders { 8.8.8.8; 1.1.1.1; }; dnssec-validation no; }; view "internal" { match-clients { 192.168.10.0/24; 127.0.0.1; }; recursion yes; zone "lab.internal" IN { type master; file "/etc/named/zones/lab.internal.internal.zone"; allow-transfer { 192.168.10.12; }; also-notify { 192.168.10.12; }; }; }; view "external" { match-clients { any; }; recursion no; zone "lab.internal" IN { type master; file "/etc/named/zones/lab.internal.external.zone"; allow-transfer { 192.168.10.12; }; also-notify { 192.168.10.12; }; }; };

forwarders配的是上游解析地址,内网用户查外网域名时由这台服务器代为转发。dnssec-validation no是为了简化实验,生产环境建议开启并配好信任锚。

4.4 从服务器配置

从服务器的named.conf同样用View结构,但区域类型改成slave:

options { listen-on port 53 { any; }; directory "/var/named"; allow-query { any; }; allow-recursion { 192.168.10.0/24; 127.0.0.1; }; forwarders { 8.8.8.8; 1.1.1.1; }; dnssec-validation no; }; view "internal" { match-clients { 192.168.10.0/24; 127.0.0.1; }; recursion yes; zone "lab.internal" IN { type slave; file "/var/named/slaves/lab.internal.internal.zone"; masters { 192.168.10.11; }; }; }; view "external" { match-clients { any; }; recursion no; zone "lab.internal" IN { type slave; file "/var/named/slaves/lab.internal.external.zone"; masters { 192.168.10.11; }; }; };

从服务器的区域文件路径指向slaves目录,这个目录要确保BIND进程有写权限。同步过来的文件会自动放在这里。

4.5 启动与验证

先检查配置语法,这个命令能帮你抓出大部分拼写和语法错误:

named-checkconf named-checkzone lab.internal /etc/named/zones/lab.internal.internal.zone

两个都通过后启动服务:

systemctl start named systemctl enable named

验证分三步。第一步,在主服务器上查内网视图:

dig @192.168.10.11 app.lab.internal

应该返回192.168.10.100。第二步,模拟外网查询。因为本机就在内网网段,直接查会匹配到internal视图,所以要指定一个不在内网网段的源地址来测试,或者干脆从一台外网机器上查。如果手头没有外网机器,可以临时把测试机的IP改到别的网段,或者用dig的-b参数指定源地址:

dig -b 203.0.113.50 @192.168.10.11 app.lab.internal

应该返回203.0.113.100。第三步,验证主从同步。在从服务器上查:

dig @192.168.10.12 app.lab.internal

如果返回和主服务器一致的结果,说明同步成功。再去主服务器改一条记录,递增序列号,重启或重载BIND,然后看从服务器是否自动更新。

4.6 序列号递增与重载的正确姿势

改完区域文件后,递增Serial,然后:

rndc reload lab.internal

rndc是BIND的管理工具,reload只重载指定区域,比重启整个服务温和。重载后主服务器会发NOTIFY给从服务器,从服务器自动拉取。你可以在从服务器日志里看到传送记录。如果没配rndc密钥,用systemctl reload named也行,但那样是重载全部区域。

注意:每次改区域文件,Serial必须变。我习惯在文件里写个注释记下每次改了什么,时间长了回头看能省很多事。

5. 常见问题与排查技巧实录

5.1 从服务器死活不同步

这是最高频的问题。排查顺序我整理成一张表:

现象可能原因排查方法
从服务器无区域文件生成主服务器allow-transfer没放行从服务器IP主服务器日志看有无denied transfer
序列号一致但不更新忘了递增Serial对比两边SOA序列号
传送超时53/tcp被防火墙拦telnet主服务器53端口测试
日志报refusedmasters地址写错核对从服务器masters配置
同步了但查询无结果View匹配顺序问题确认查询源IP落在哪个View

我踩过最坑的一次是allow-transfer里写了从服务器的公网IP,但从服务器实际是用内网IP去连主服务器的,导致一直被拒。后来统一用内网IP通信才解决。所以主从之间走哪个网段,allow-transfer就写哪个网段的IP,别想当然。

5.2 分离解析不生效,内外查到一样的结果

八成是View的match-clients顺序或范围写错了。BIND从上往下匹配,如果external视图写在internal前面且match-clients是any,那所有查询都会命中external。正确做法是内网View在前,外网View在后。另外检查内网View的match-clients有没有包含你测试机的实际IP段,网段写错一位就匹配不上。

还有个隐蔽的坑:如果你在options里配了allow-query或allow-recursion,View里又配了一遍,以View里的为准。别在options里配了allow-query { any; }就以为万事大吉,View里的设置会覆盖它。

5.3 递归查询被拒绝

外网View里recursion no是故意的,但内网用户如果查外网域名失败,要检查内网View的recursion yes和allow-recursion是否包含了内网网段。另外forwarders要配可用的上游地址,否则递归查询发不出去。测试递归是否正常,查一个外部域名:

dig @192.168.10.11 www.example.com

如果返回正常结果,说明转发链路通。如果超时,检查forwarders地址是否可达。

5.4 区域文件语法错误导致服务起不来

named-checkzone是你的好朋友,改完区域文件先跑一遍。最常见的语法错误是括号不配对、每行结尾漏分号、SOA记录里的邮箱格式写错(admin.lab.internal.末尾那个点不能少,少了就变成相对域名了)。还有TTL单位,直接写数字是秒,写1h这种带单位的在某些版本里不认,稳妥起见统一用秒。

5.5 日志在哪看

BIND的日志默认可能进系统日志,也可能单独配。排查时最直接的是:

journalctl -u named -f

或者看/var/log/messages里grep named。想更精细地控制日志,在named.conf里配logging块,把category设成xfer-in、xfer-out、notify这些,专门盯同步相关的事件。我调试主从同步时就把xfer-in和notify的日志级别调高,每次传送的细节都看得清清楚楚。

5.6 安全加固的几个必做项

跑通之后别就这么放着,几个安全项必须做。第一,allow-transfer只放从服务器IP,这个前面强调过了。第二,外网View关递归,防止被利用做放大攻击。第三,如果服务器有公网IP,考虑把version隐藏掉,在options里加version "not currently available";,免得被人扫出BIND版本针对性攻击。第四,主从之间的区域传送如果跨公网,考虑用TSIG密钥做传送认证,在allow-transfer里配key而不是单纯IP。

TSIG配置大概是这样,主从两边都要有相同的密钥:

key "transfer-key" { algorithm hmac-sha256; secret "你的base64密钥"; }; // 主服务器zone里 allow-transfer { key "transfer-key"; }; // 从服务器zone里 masters { 192.168.10.11 key "transfer-key"; };

密钥用tsig-keygen命令生成,两边保持一致。这样即使IP被伪造,没有密钥也拉不走区域数据。

6. 几个让我少走弯路的实操心得

主从加分离解析这套东西,配通一次之后你会发现逻辑很清晰,但第一次配的时候坑是真不少。我最大的体会是:先把单视图的主从跑通,再加View。一上来就主从加分离一起配,出问题你根本分不清是同步的问题还是视图匹配的问题。分两步走,每步验证通过再进下一步,排查成本低得多。

第二个心得是序列号管理要形成习惯。我现在改区域文件的流程固定是:改记录、改Serial、named-checkzone、rndc reload、去从服务器确认。这五步走完才算改完,少一步都可能留下隐患。尤其是团队协作时,别人不知道你改了没递增序列号,从服务器数据就是旧的,这种问题排查起来特别费时间。

第三个是测试分离解析一定要用真实来源IP。本机测试很容易因为源IP落在内网段而只测到内网视图,外网视图到底对不对根本没验证。有条件就从不同网段的机器分别测,没条件就用dig -b指定源地址,别偷懒。

最后说个扩展方向。这套主从加分离的架构,往上可以加DNSSEC签名防篡改,往旁边可以加DNS over TLS加密查询链路,往深了可以做动态更新让DHCP分配的地址自动注册到DNS。但那些都是后话,把主从同步和分离解析这两个地基打牢,后面的扩展才有意义。我见过太多人一上来就追新特性,结果基础的同步都没搞稳,出了故障连日志都不会看。基础这东西,慢就是快。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询