
1. 为什么内网自建DNS比你想的更值得折腾很多团队在规模小的时候习惯直接用路由器或者运营商下发的DNS机器少、访问目标单一确实感觉不出问题。可一旦内网设备超过二三十台或者开始有微服务、容器、测试环境、预发布环境这些需要靠域名互相寻址的场景靠hosts文件和IP直连就会迅速变成一场灾难。改一台机器的hosts要登录一次换一个IP要通知一圈人测试环境域名和线上冲突更是家常便饭。这时候自建一套内网DNS几乎是绕不开的选择。在Linux上做内网DNSBINDBerkeley Internet Name Domain是最经典也最稳的方案。它足够老老到几乎所有Linux发行版都自带或者一条命令就能装它也足够强强到全球根域名体系里相当一部分环节都在跑它。对于内网场景BIND提供的主从Master/Slave复制能力尤其关键——主节点负责写记录从节点自动同步主节点挂了从节点还能继续提供解析这就是内网DNS高可用的最低成本实现。这篇内容面向的是有一定Linux基础、想在内网快速搭起一套可用DNS主从架构的运维或开发人员。我会从BIND的安装、主从配置、区域文件编写、同步验证一路讲到实际踩过的坑和排查思路。整套流程在几台普通Linux机器上就能复现不需要额外硬件也不需要复杂的编排系统。读完你至少能做到内网域名自己说了算主从自动同步出问题知道从哪查。需要先说明一点本文所有示例中的IP段、域名、主机名都是虚构的占位符你在实际部署时替换成自己环境的真实值即可。下面进入正题。2. 动手之前先把BIND主从的账算清楚2.1 主从模式到底解决了什么问题单台DNS服务器最大的问题是单点故障。一旦这台机器宕机、网络抖动或者BIND进程崩溃整个内网所有依赖域名解析的服务都会跟着遭殃。你可能会说那我配两个DNS地址不就行了问题在于如果两台DNS各自独立维护记录不一致客户端查询结果就会随机漂移排查起来极其痛苦。主从模式的核心价值就在于写入只发生在主节点从节点通过区域传送Zone Transfer自动拉取全量记录。这样既保证了数据一致性又实现了冗余。客户端配置里把主从两个IP都写上主节点不可用时从节点顶上解析不中断。从实现角度看BIND的主从同步依赖的是SOA记录中的serial字段。从节点定期检查主节点的serial一旦发现主节点的serial比自己大就触发一次区域传送。这个机制简单但有效理解它是后面排查同步问题的关键。2.2 部署前需要确认的几件事在真正敲命令之前有几项准备工作必须先确认否则后面会反复返工。第一是网络规划。你需要明确主从两台机器的IP、内网要使用的域名后缀比如internal.example.lan这种虚构后缀、以及客户端会用哪些网段。域名后缀建议不要用公网真实存在的顶级域避免解析时产生歧义。第二是端口和防火墙。DNS标准端口是53TCP和UDP都要放行。主从之间还需要允许TCP 53用于区域传送。很多同步失败最后查出来都是防火墙只放了UDP没放TCP。第三是时间同步。主从机器的系统时间如果偏差太大日志时间戳会对不上排查问题时非常难受。建议部署前先确认两台机器时间基本一致。第四是SELinux和AppArmor。部分发行版默认开启的安全模块会阻止BIND绑定端口或读取区域文件如果遇到莫名其妙的权限拒绝先检查这里。下面这张表把关键准备项列清楚方便对照检查准备项主节点从节点说明静态IP需要需要避免DHCP导致地址漂移53端口TCP/UDP放行放行区域传送依赖TCP 53时间同步建议建议日志排查更顺畅安全模块确认放行确认放行避免权限拒绝域名后缀统一规划统一规划主从必须一致2.3 安装BIND不同发行版的差异BIND在不同发行版上的包名和配置文件路径略有差异这是新手最容易困惑的地方。我把它整理成一张对照表发行版系安装命令主配置路径区域文件目录Debian/Ubuntuapt install bind9/etc/bind/named.conf/etc/bind/RHEL/CentOSyum install bind/etc/named.conf/var/named/通用源码编译安装自定义自定义以Debian系为例安装完成后BIND会自动注册为系统服务服务名通常是bind9或named。安装后先别急着改配置用named -v确认版本用systemctl status看服务状态确保基础环境是干净的。提示如果你的发行版默认装了systemd-resolved并占用了53端口BIND会启动失败。这种情况需要先停用或调整该服务把53端口让出来。3. 主节点配置从named.conf到区域文件3.1 主配置文件的整体结构BIND的主配置文件通常由三部分组成全局选项options、日志配置logging、区域声明zone。对于内网场景我们重点关注options和zone两块。一个精简但完整的主节点配置大致长这样以虚构域名internal.example.lan为例options { listen-on port 53 { 127.0.0.1; 10.10.0.1; }; listen-on-v6 { none; }; directory /var/named; allow-query { 10.10.0.0/24; 127.0.0.1; }; allow-transfer { 10.10.0.2; }; recursion yes; forwarders { 8.8.8.8; }; }; zone internal.example.lan IN { type master; file internal.example.lan.zone; allow-update { none; }; };这里有几个参数值得展开说。listen-on指定BIND监听的地址内网场景建议只监听内网IP和本地回环不要监听any减少暴露面。allow-query限制哪些网段可以查询防止被当成开放解析器滥用。allow-transfer是主从同步的关键只允许从节点IP发起区域传送这是安全底线。forwarders的作用是当查询的域名不在本地区域内时转发给上游DNS。内网场景下这个配置让BIND既能解析内网域名又能解析公网域名一举两得。3.2 区域文件怎么写才不出错区域文件是真正存放记录的地方格式上最容易出错。一个标准的正向区域文件包含SOA、NS、A、CNAME、MX等记录类型。下面是一个可用的示例$TTL 3600 IN SOA ns1.internal.example.lan. admin.internal.example.lan. ( 2024010101 ; serial 3600 ; refresh 1800 ; retry 604800 ; expire 86400 ; minimum ) IN NS ns1.internal.example.lan. IN NS ns2.internal.example.lan. ns1 IN A 10.10.0.1 ns2 IN A 10.10.0.2 web IN A 10.10.0.10 db IN A 10.10.0.11 cache IN CNAME web.internal.example.lan.这里有几个细节必须强调。serial字段是主从同步的触发器每次修改记录后必须手动递增否则从节点不会同步。建议用日期加序号的方式比如2024010101表示2024年1月1日第1次修改这样既直观又不容易重复。SOA记录末尾的域名必须带点admin.internal.example.lan.最后那个点表示这是完全限定域名漏掉点会导致解析异常。同理NS记录里的域名也要带点。这是新手最常踩的坑之一。CNAME记录不能和其他记录共存。比如你不能给cache同时配CNAME和A记录这是DNS协议层面的限制不是BIND的问题。3.3 配置检查与启动改完配置别急着重启服务先用BIND自带的检查工具验证语法named-checkconf /etc/named.conf named-checkzone internal.example.lan /var/named/internal.example.lan.zonenamed-checkconf检查主配置语法named-checkzone检查区域文件语法。两个命令都通过后再重启服务systemctl restart named systemctl status named服务起来后用dig或nslookup在本地验证dig 127.0.0.1 web.internal.example.lan如果返回正确的A记录说明主节点基本可用。如果返回SERVFAIL或REFUSED先看日志/var/log/messages或journalctl -u named日志里通常有明确原因。4. 从节点配置让同步真正跑起来4.1 从节点的zone声明与主节点不同从节点的配置结构和主节点类似但zone部分的type要改成slave并且要指定masters指向主节点zone internal.example.lan IN { type slave; file slaves/internal.example.lan.zone; masters { 10.10.0.1; }; };注意file的路径。从节点的区域文件是自动生成的不需要你手动创建BIND从主节点拉取后会自动写入这个路径。所以这个目录必须对BIND进程可写否则同步会失败。Debian系默认在/var/cache/bind/RHEL系在/var/named/slaves/具体以你的配置为准。从节点的options里allow-transfer可以设为none因为从节点不需要再向别人传送。allow-query则要和主节点保持一致保证客户端能正常查询。4.2 区域传送的触发与验证从节点启动后会主动向主节点发起一次区域传送。触发条件是从节点本地没有区域文件或者主节点的serial比本地大。传送成功后从节点会在指定路径生成区域文件。验证同步是否成功有几个层次的方法。最直接的是看从节点上区域文件是否生成ls -l /var/named/slaves/如果文件存在且大小不为零说明至少传送过一次。再进一步用dig直接查从节点dig 10.10.0.2 web.internal.example.lan返回和主节点一致的结果说明从节点已经在正常提供服务。还可以在主节点上主动触发一次传送来测试dig 10.10.0.1 internal.example.lan AXFR这个命令会列出整个区域的所有记录。如果返回完整记录列表说明区域传送通道是通的。如果返回Transfer failed就要去查allow-transfer配置和防火墙。4.3 同步失败时按这个顺序排查主从同步失败是内网DNS最常见的故障我把它整理成一个排查顺序照着走基本能定位排查步骤检查内容常见问题1主节点allow-transfer从节点IP没加进去2防火墙TCP 53只放了UDP没放TCP3从节点file目录权限BIND进程无写权限4serial是否递增改了记录没改serial5主从时间偏差时间差过大导致判断异常6日志报错看named日志具体原因这个顺序的逻辑是先查权限和网络这些外部因素再查配置和协议层面的因素最后看日志兜底。实际排查中前两步能解决大部分问题。注意修改主节点记录后一定要记得递增serial。我见过太多次改了记录从节点没同步最后发现就是serial没动。养成改完就改serial的习惯能省下大量排查时间。5. 客户端接入与解析验证的完整链路5.1 客户端怎么指向内网DNS客户端接入有两种方式。一种是手动改/etc/resolv.conf把主从两个IP都写上nameserver 10.10.0.1 nameserver 10.10.0.2另一种是通过DHCP下发适合机器多的场景。不管哪种方式主从两个IP都要配这样主节点挂了客户端会自动切到从节点。resolv.conf的查询顺序是从上到下第一个不通才用第二个所以主节点放前面。需要提醒的是很多发行版的/etc/resolv.conf会被网络管理工具覆盖。如果你手动改了发现重启后失效要去检查NetworkManager或systemd-resolved的配置从源头改。5.2 用dig验证解析链路客户端配好后用dig验证。先查内网域名dig web.internal.example.lan再查公网域名验证forwarders是否生效dig www.example.com两个都返回正常结果说明整条链路是通的。如果内网域名能解析但公网不行检查forwarders配置如果都不行检查客户端到DNS服务器的网络连通性。还可以用dig trace看解析的完整路径不过内网场景下这个命令用处不大因为内网域名不会走根服务器。更实用的是dig short只看结果脚本里常用。5.3 主从切换的实测验证高可用最直接的方法就是手动停掉主节点看客户端是否还能正常解析。操作如下# 在主节点上 systemctl stop named # 在客户端上 dig web.internal.example.lan如果客户端仍然返回正确结果说明已经切到从节点。这时候再查从节点的日志能看到它接管了查询请求。恢复主节点后从节点会继续定期检查serial保持同步。这个测试建议在正式上线前做一次确认切换逻辑符合预期。有些环境因为客户端只配了一个DNS地址主节点一停就全挂了这种问题只有实测才能发现。6. 那些文档里不会写的踩坑经验6.1 serial忘记递增引发的灵异事件这是我最想强调的一个坑。主节点改了记录dig 主节点能查到新记录但dig 从节点还是旧记录而且等多久都不更新。新手这时候容易怀疑网络、怀疑配置其实原因就是serial没改。BIND判断是否需要同步唯一依据就是serial。serial不变从节点就认为数据没变化不会触发传送。所以每次修改区域文件第一件事就是改serial。我现在的习惯是改记录前先改serial形成肌肉记忆。6.2 区域文件里的点号陷阱区域文件里域名末尾的点号表示完全限定域名。带点和不带点BIND的理解完全不同。比如web IN A 10.10.0.10这里web不带点BIND会自动补上当前区域的域名变成web.internal.example.lan。而如果你写web.internal.example.lan IN A 10.10.0.10不带末尾的点BIND会再补一次区域名变成web.internal.example.lan.internal.example.lan这就错了。所以写完整域名时一定要带末尾的点写相对名时不要带。这个规则记牢能避免大量解析异常。6.3 日志级别与排查效率BIND默认的日志级别比较保守出问题时信息不够。可以在配置里加一段logging把查询日志和区域传送日志打开logging { channel default_log { file /var/log/named/named.log versions 3 size 20m; severity info; print-time yes; }; category default { default_log; }; category xfer-out { default_log; }; category xfer-in { default_log; }; };xfer-out和xfer-in分别对应区域传送的发出和接收排查同步问题时这两个category的日志最有价值。日志文件目录要确保BIND可写否则日志打不出来。6.4 内网DNS的安全边界内网DNS虽然在内网但安全配置不能省。几个关键点allow-query限制查询来源别写成anyallow-transfer只允许从节点防止区域数据被随意拉取recursion如果不需要对外提供递归可以关掉或者限制来源。这些配置在options里都能控制。另外BIND的版本要及时更新。老版本可能存在已知问题内网环境虽然相对封闭但保持更新是基本习惯。7. 后续可以怎么扩展这套架构主从模式跑通之后这套架构还有不少可以延伸的方向。比如增加第二个从节点进一步提升冗余配置方式和第一个从节点完全一样只要在主节点的allow-transfer里加上它的IP即可。再比如引入TSIG密钥让区域传送带认证安全性更高适合对安全要求更严的环境。如果内网规模继续扩大可以考虑拆分多个区域按业务线或环境划分每个区域独立做主从避免单区域文件过大。还可以结合配置管理工具把区域文件的变更纳入版本控制每次改动有记录、可回滚。我自己在实际维护中体会最深的一点是内网DNS的稳定性八成取决于规范两成取决于技术。serial递增、点号规则、allow-transfer限制这些看似琐碎的规范才是长期不出问题的关键。技术方案本身不复杂难的是每次都按规矩来。把这套主从架构搭起来只是开始真正让它稳定服务靠的是日常的规范操作和定期检查。