1. 项目概述与核心思路做运维这些年,DNS 算是打交道最多的基础设施之一。可能有人觉得"这不就是个域名解析吗",但真到排查线上故障时,DNS 配置不规范引来的问题,往往比应用本身的问题更隐蔽、更难查。这个项目的核心目标,是从零搭建一套内网 DNS 实战环境,把正向解析、反向解析、主从同步、缓存转发这些基础能力一项项落到底,最终形成一套可以复现的配置模板,既能给团队内部用,也能作为自己理解 DNS 协议与 BIND 实现的练习场。我选择用 Linux 下的 BIND 9 作为承载服务。理由很简单:它是目前使用范围最广、文档最全、行为最符合标准实现的 DNS 服务器软件,社区踩坑记录足够多,出了问题也容易搜到答案。相比直接使用云厂商提供的 DNS 服务或路由器内置的简单转发,BIND 能让你真正掌握解析链路里的每个环节——从 named.conf 的语法、zone 文件的格式、SOA 记录的序列号机制,到 NOTIFY 通知和增量传输的工作方式。只有亲手配置过一遍,日后在处理生产环境问题时才不至于两眼摸黑。这个方案适合谁?一类是想在纯内网环境做域名管理、不想依赖公网 DNS 的团队;另一类是刚入门的运维或网络方向同学,想搞明白"输入一个网址到拿到 IP 地址"之间到底发生了什么。无论哪类读者,我都建议跟着下文把每个配置亲手敲一遍,而不是只读不练。DNS 的配置文件语法严格、错误反馈也比较"老实",用它入门 Linux 服务配置管理,其实是很合适的练手项目。1.1 为什么需要在内部搭建 DNS 服务内部搭建 DNS 的直接动力,是"机器多了以后,记 IP 记不过来了"。服务器、虚拟机、容器、网络设备、打印机……只要设备数量超过十几台,靠 hosts 文件维护映射关系就会非常痛苦。hosts 文件只能解决本机访问的问题,设备一变、IP 一变,所有机器的 hosts 都得跟着改,维护成本直线上升。而一套内部 DNS 可以做到集中管理。只需要在个别节点把 DNS 指向这台上游,新增设备时在 zone 文件里加一条 A 记录,全内网立刻生效。另一个常见场景是服务间调用:微服务架构里,服务和服务的通信往往基于域名而非 IP 地址,比如应用通过db.internal访问数据库主节点,通过api.registry访问注册中心。这些内部域名如果交给公网 DNS 解析,显然不合理,也没有必要。自建 DNS 后,还能顺手把内部服务的 TTL 调低,方便服务迁移时快速切换解析结果。1.2 这套方案要解决的核心痛点结合我实际维护过几套内网 DNS 的经验,最大的痛点往往不是"配不出来",而是"配出来以后过两周就出问题"。典型的有三类:一是配置语法写错,但服务没报错。BIND 对 zone 文件里的空行、Tab 和注释位置比较敏感,多一个空格少一个括号都可能造成解析异常。好在有named-checkconf和named-checkzone这两个工具,用它们提前校验,绝大多数低级错误都能在重启服务之前被发现。二是主从不同步。很多团队做了主从架构,但改完主服务器上的 zone 后,从服务器迟迟不更新。常见原因就是 SOA 记录里的序列号没递增,从服务器认为数据没变化,自然就不做区域传输。这个坑极其经典,下文专门有一节讲它。三是权限和防火墙问题。BIND 通常在 53 端口上监听 TCP 和 UDP,如果防火墙只放行了 TCP,UDP 查询就会全部超时。还有 SELinux 环境下,zone 文件放错目录会导致 named 进程读不到数据。这些问题排查起来不难,但没遇到过的人往往会绕圈子。这个项目就是把上述问题全部过一遍:给出一套能用的配置,指出哪里容易踩坑,再配合 dig 命令的验证方法,让整个排查链路清晰可见。2. 环境准备与基础安装动手之前,先把实验环境说清楚。这套配置适用于主流的 Linux 发行版,我用的是 RHEL 系和 Debian 系各跑过一遍。RHEL 系(比如 CentOS、Rocky Linux)用yum或dnf安装;Debian 系用apt。底层原理一致,只是包名和配置文件路径稍有差异。下文以 RHEL 系为主路径写,Debian 系的差异点我会在合适的位置单独标注。建议准备两台虚拟机或容器来完整演练主从同步。第一台作为主 DNS,第二台作为从 DNS,操作系统配置干净纯净,不要预装多余服务,避免端口冲突干扰排查。2.1 选型说明:为什么是 BIND可能有人会问,现在有很多轻量级 DNS 服务器,比如 dnsmasq、PowerDNS、CoreDNS,为什么偏偏选 BIND?我的回答是:学习阶段选 BIND,是为了学"完整"的 DNS;生产环境如果只是给开发环境做简单缓存,dnsmasq 当然更轻快,但那样学不到 DNS 的核心机制。BIND 覆盖了 DNS 服务器的完整功能面:权威解析、递归查询、转发、缓存、视图、DNSSEC、ACL、日志分类,全部都有成熟的配置语法。虽然它的配置门槛比 dnsmasq 高,但理解它的核心概念(区域、资源记录、SOA、NS)之后,再看其他 DNS 软件的文档,几乎是降维打击。并且 BIND 的性能和稳定性经过了几十年的生产环境验证,很多企业级网络设备的管理界面背后,跑的就是 BIND 或其商业版本,选它做实验和学习对象,技术投入不会浪费。2.2 安装步骤与启动安装命令很简单。RHEL 系:yum install -y bind bind-utilsDebian 系:apt update apt install -y bind9 bind9utils bind9-docRHEL 系安装后的主配置是/etc/named.conf,配置文件目录在/etc/named/,zone 文件默认放在/var/named/下;Debian 系主配置是/etc/bind/named.conf,zone 文件通常在/etc/bind/下。我习惯在 RHEL 系环境下用/etc/named/存放自定义 zone 文件,用include语句引入,保持主配置干净清晰。安装完成后,先不着急写配置,把服务启动一次确认能跑起来:systemctl start named systemctl status named如果启动失败,直接看日志:journalctl -u named -n 50一个常见情况是服务起不来但配置看着没问题,那十有八九是配置文件权限或 SELinux 上下文不对。检查一下/var/named/下的文件权限,确保 named 进程对 zone 文件有读取权限。RHEL 系建议用restorecon -Rv /var/named恢复 SELinux 上下文,这个问题我下文会再展开。提示:实验环境里如果不需要开机自启,可以保持 disabled;但作为内网基础设施,生产环境建议systemctl enable named。3. 核心配置文件拆解:named.conf 与 zone 机制BIND 的配置文件体系分为两层:第一层是named.conf,负责定义全局选项、访问控制、日志和区域声明;第二层是 zone 文件,负责存放具体域名与 IP 映射的资源记录。理解这两层的关系,Debug DNS 就等于完成了一半。3.1 named.conf 全局选项的含义打开新建的 named.conf,通常是空壳加几个默认配置。我以一个最精简的内网权威 DNS 配置为例:options { listen-on port 53 { 192.168.1.10; }; # 监听内网IP listen-on-v6 port 53 { none; }; # 不监听IPv6 directory "/var/named"; dump-file "/var/named/data/cache_dump.db"; statistics-file "/var/named/data/named_stats.txt"; allow-query { 192.168.1.0/24; }; # 只允许内网网段查询 allow-transfer { 192.168.1.20; }; # 只允许从服务器做区域传输 recursion yes; dnssec-enable yes; };这里面有几个参数要特别注意。listen-on决定了服务绑定的网卡地址。默认配置会监听本机所有 IP,但安全起见,我一般明确写到内网 IP,避免服务意外暴露到公网或测试网段。如果本机有多个网卡,不写这个参数就全部暴露了,这不算大错,但不符合最小暴露面原则。allow-query是查询权限。实验环境可以写成内网网段;如果未来要对接办公室的多个 VLAN,可以用 ACL 集中定义。注意,这里控制的是"谁能问我问题",而不是"我能回答什么问题",后者由 zone 的配置和递归行为决定。allow-transfer很关键。它限制哪些服务器可以对这台权威服务器发起区域传输请求。区域传输会把整个 zone 的数据一次性同步过去,如果这个选项配置不当,外部任何人都能把自己伪装成从服务器,把内网域名记录全部拖走,这是内外网信息泄露的高危点。生产环境务必要把这个配置成具体 IP 或专用 ACL。3.2 zone 声明与 named.rfc1912.zones 的关系在 RHEL 系默认配置里,/etc/named.conf末尾通常会 include 一个/etc/named.rfc1912.zones,里面预置了 localhost 和 loopback 等基础 zone。我的习惯是把自己管理的业务 zone 单独写一个文件(比如/etc/named/rdnz_zones.conf),再在主配置里 include 进来。这样一来,主配置只负责全局选项和 ACL,业务区域和公共配置分离,后续用配置管理工具批量下发时思路也清晰。一个典型的正向 zone 声明是:zone "example.com" IN { type master; file "example.com.zone"; allow-update { none; }; };type master表示这是权威主区域。file指向 zone 文件路径,注意这里用的是相对路径,实际根目录由directory选项指定(即/var/named)。allow-update一般直接none,避免开放动态更新。反向 zone 声明则指向 in-addr.arpa 域:zone "1.168.192.in-addr.arpa" IN { type master; file "192.168.1.zone"; };3.3 zone 文件语法与资源记录详解zone 文件是 DNS 配置里最容易被写错的部分。我先解释核心概念,再给完整示例。zone 文件至少要包含SOA记录和NS记录,然后根据业务需求添加 A、AAAA、CNAME、MX、TXT、PTR 等记录。SOA记录是整个区域的"主控记录",它包含如下字段:serial:序列号。主从同步靠它判断数据新旧,每次修改 zone 文件后必须递增,建议用日期格式2025021501。refresh:从服务器多久检查一次主服务器数据版本。retry:从服务器检查失败后重试的间隔。expire:从服务器一直连不上主服务器时,多久之后丢弃本地数据。negative TTL:无记录应答的缓存时间。把这些参数理解透,主从不同步的问题基本解决了一半。NS记录用于声明区域的权威服务器,也就是哪些服务器有资格回答这个区域的查询。实际写 zone 文件时,行首的域名可以省略不写,用 Tab 或空格开头就表示"继承上一行的域名"。这个特性让新手困惑过一阵——看起来没写域名,但记录其实属于某个明确的所有者。建议刚开始写的时候不要偷懒,每一行都写全域名,避免理解偏差。4. 正向解析与反向解析实战这一节把最常用的正向解析和反向解析完整走一遍,每一个步骤都可以照抄。4.1 正向解析 zone 配置示例业务需求如下:内网网段192.168.1.0/24,主 DNS 服务器 IP 为192.168.1.10,域名为ns1.example.com;从 DNS 服务器为192.168.1.20,域名为ns2.example.com。需要解析的机器有web1(192.168.1.11)、web2(192.168.1.12)、db1(192.168.1.13)等。在/var/named/下创建example.com.zone:$TTL 86400 @ IN SOA ns1.example.com. admin.example.com. ( 2025021501 ; serial 3600 ; refresh 600 ; retry 86400 ; expire 300 ) ; negative cache TTL ; ; Name servers @ IN NS ns1.example.com. @ IN NS ns2.example.com. ; ; DNS server hostnames ns1 IN A 192.168.1.10 ns2 IN A 192.168.1.20 ; ; Web servers web1 IN A 192.168.1.11 web2 IN A 192.168.1.12 ; ; Database server db1 IN A 192.168.1.13 ; ; Alias for load balancer www IN CNAME web1.example.com.几个要点说明。$TTL 86400表示默认 TTL 一天。内部服务建议把 TTL 调短(比如 300 秒),这样服务 IP 有变化时,内网机器能在 5 分钟内拿到新的解析结果,不需要等缓存过期。结尾的域名一定要带点号。ns1.example.com.末尾的点表示这个是完整域名(FQDN),如果不写点号,BIND 会默认把当前 zone 的域名追加上去,结果变成ns1.example.com.example.com.。这个错误非常经典,排查时dig返回的解析结果会多出一段奇怪的域名后缀,我见到过不下五次。CNAME记录指向的域名也必须是完整域名,并且最好指向一个本来就存在的 A 记录。如果指向了一个同样用 CNAME 定义的域名,就会形成 CNAME 链,链太长会影响解析效率,甚至在某些代理环境下解析失败。4.2 反向解析 zone 配置实例反向解析的作用是把 IP 解析成域名,主要用于邮件服务器反垃圾邮件校验和日志审计。内网不一定需要,但作为完整 DNS 服务,建议配置上。创建/var/named/192.168.1.zone:$TTL 86400 @ IN SOA ns1.example.com. admin.example.com. ( 2025021501 ; serial 3600 600 86400 300 ) ; @ IN NS ns1.example.com. @ IN NS ns2.example.com. ; 11 IN PTR web1.example.com. 12 IN PTR web2.example.com. 13 IN PTR db1.example.com.然后声明反向区域,在/etc/named/rdnz_zones.conf中添加:zone "1.168.192.in-addr.arpa" IN { type master; file "192.168.1.zone"; };反向 zone 的文件名格式是反向网段.zone,内容里每行开头的数字是要解析的 IP 最后一段。注意反向域名的书写顺序:网段192.168.1.0/24对应反向域是1.168.192.in-addr.arpa,IP 的最后一段写在前面,网段顺序完全反转。4.3 配置检查与加载验证写完配置后,不要急着 restart 服务,先做检查。named-checkconf /etc/named.conf named-checkzone example.com /var/named/example.com.zone没输出即代表通过。如果有问题,它会直接告诉错误在哪行哪个 token。检查通过后重载:systemctl reload named验证解析结果使用dig:dig @192.168.1.10 web1.example.com dig @192.168.1.10 -x 192.168.1.11第一条命令期望输出里有web1.example.com. 86400 IN A 192.168.1.11;第二条期望看到11.1.168.192.in-addr.arpa. 86400 IN PTR web1.example.com.。如果dig返回SERVFAIL,优先检查 zone 文件语法;返回REFUSED,则检查allow-query权限是否覆盖了来源 IP。5. 主从同步实战:从原理到落地主从架构是内网 DNS 高可用的基础。主服务器负责读写,从服务器通过区域传输获得数据副本。客户端可以随便指向任意一台,挂了另一台还能继续解析。5.1 主从同步原理与前提主从同步的触发机制是 NOTIFY 通知配合 SOA 序列号轮询。主服务器加载新 zone 后,会向 NS 记录中声明的从服务器发送 NOTIFY 消息,告知"我有新数据了";从服务器收到通知后,主动向主服务器查询 SOA 序列号。如果序列号比本地的数字大,则发起区域传输,把整个 zone 或增量数据同步过来。即便没有 NOTIFY,从服务器也会每隔 refresh 时间去主服务器查一次序列号。所以"序列号递增"不是可选项,是硬性要求。改 zone 文件后我一般手动 +1,没有用过自动生成。部分团队会用脚本来检测文件变化并自动递增序列号,生产环境那样做更省心,但手工理解机制是第一步。5.2 主服务器端配置主服务器上,在 zone 声明中添加also-notify或直接在 NS 记录中声明从服务器即可。推荐两个都做:NS 记录用来声明权威性,allow-transfer 用来开放传输权限。主服务器 zone 声明调整:zone "example.com" IN { type master; file "example.com.zone"; allow-transfer { 192.168.1.20; }; also-notify { 192.168.1.20; }; };zone 文件中要确认 NS 记录里包含ns2.example.com.和它的 A 记录。如果从服务器的 NS 声明缺失,一些严谨的客户端会拒绝使用这台从服务器做解析,因为它们认为它不是这个区域的权威。这个点之前容易漏。修改完成后照旧named-checkconf、named-checkzone,然后 reload。5.3 从服务器端配置从服务器的 zone 声明使用type slave,不需要手工创建 zone 文件,区域传输会自己把数据落盘。zone "example.com" IN { type slave; masters { 192.168.1.10; }; file "slaves/example.com.zone"; };注意file指向的位置建议放在从服务器有写权限的目录,RHEL 系默认是/var/named/slaves/,这个目录存在并且 named 有写入权限。如果不放在这个目录,区域传输可能因为权限问题失败。从服务器的named.conf全局选项中,allow-transfer就没必要留了,从服务器不向其他机器再提供区域传输。allow-query依然配置内网网段,保证客户端能查询。重启从服务器的 named,然后观察日志:systemctl restart named journalctl -u named -f如果主从配置正确,日志里会出现类似zone example.com/IN: Transfer started和transfer of 'example.com/IN' from 192.168.1.10#53: Transfer status: success的内容。客户端把 DNS 指向从服务器再 dig 一次,能拿到同样的解析结果,说明主从架构生效了。5.4 序列号更新的实际意义前面反复提到序列号,这里用一个真实场景佐证它的重要性。某次我在 A 环境改了 web1 的 IP,从 11 改成 15,修改完成后忘记递增 SOA 序列号。主管服务器重启之后,从服务器自己也正常,但客户端访问新 IP 一直不稳定,时好时坏。排查到最后才发现,从服务器的序列号还是旧值,它认为主服务器的序列号更小或相同,不触发区域传输。当客户端恰好被负载均衡指到从服务器时,拿到的还是旧的解析结果。修复方式很简单:序列号加一,reload 主服务器,然后立刻在从服务器上执行rndc reload example.com,强制刷新这个区域。几秒钟内数据就同步过来了。这个案例说明,对 SOA 序列号机制的理解,不能停留在"知道要改"。它直接影响主从同步是否触发,是整个分布式 DNS 体系里最基础也最关键的握手协议。6. 缓存与转发配置实践除了承担权威解析,DNS 服务器经常同时充当内网客户端的递归解析入口。客户端把 DNS 指向它,它负责向外部的上游 DNS 查询结果并缓存,下次再有相同查询时直接返回缓存,减少跨公网的查询延迟。6.1 缓存机制与 TTL 的关系BIND 默认自带缓存功能,不需要额外安装。缓存的主要控制参数就是 TTL:zone 文件里设置的 TTL 值决定了缓存最长保留多久。比如公网域名的 TTL 是 600 秒,那么内网 DNS 第一次递归解析后会缓存这个答案 10 分钟,期间再有客户端查询,直接从缓存返回,不产生外网流量。缓存虽好,但也要警惕"缓存污染"。如果上游返回了错误答案或者攻击者注入了一条伪造记录,内网 DNS 会把这个答案原样缓存并分发给所有查询者。基础的防御手段是开启 DNSSEC 验证,BIND 9 默认配置基本已经启用dnssec-enable,但要真正验证,还需要配置信任锚以及dnssec-validation auto。内网环境如果没有统一的 DNSSEC 信任链,建议至少保留默认的 auto 模式,并在递归出口防火墙上限制对外访问,降低被攻击面。6.2 配置转发模式:forward only 与 forward first递归模式下,BIND 会自行向根服务器发起迭代查询。但很多内网 DNS 不希望直接暴露到公网,更希望把出网流量集中交给某个上游 DNS(比如运营商 DNS 或公司统一提供的出口 DNS)。这时候就用到forward配置。在 named.conf 的 options 里加:forwarders { 223.5.5.5; 8.8.8.8; }; forward only;forward only表示这台服务器自己不向根服务器查询,只把请求转发给列出的上游。如果上游全部不可达,就返回 SERVFAIL。另一种模式是forward first,表示先转发,如果超时再自己迭代查询。生产环境为了流量可控,我一般用forward only,但上游至少配两个,避免单点。需要注意,转发只影响"非本机权威的域名"查询。本地定义的 example.com 依然由本机直接回答,不会跑到转发器上绕一圈。这个逻辑很容易理解:权威数据优先于转发数据。6.3 测试缓存与转发效果配置完成后执行:dig @192.168.1.10 www.example.org dig @192.168.1.10 www.example.org第二次查询的Query time应该显著小于第一次,并且 ANSWER SECTION 中可以看到来自缓存的记录。再用:rndc stats可以查看缓存命中率,输出中Cache Hits和Cache Misses的比例能直观反映缓存效果。实际运营里我经常开着rndc dumpdb -cache来检查缓存里是否有不该存在的异常记录,这些命令在排查疑难解析问题时很顺手。7. 常见问题与排查技巧实录最后这部分,把我在实操中真正遇到频率最高的坑和排查方法汇总成一张"排障地图"。它既是备忘录,也是团队接手这套 DNS 时的快速上手文档。7.1 解析超时的排查顺序客户端dig @192.168.1.10 example.com没反应或者超时,我建议按下面顺序排查。第一,确认服务进程活着。systemctl status named或ps -ef | grep named,如果进程不在,看日志找启动失败原因。第二,确认 UDP 53 端口有监听。ss -ulnp | grep 53,没有输出说明监听地址配错了。检查listen-on是否覆盖了你访问的内网 IP。之前有一次就是因为监听写成了127.0.0.1,客户端怎么查都超时。第三,确认防火墙。RHEL 系执行firewall-cmd --list-all,看 UDP 53 和 TCP 53 是否放行。TCP 53 常常被忽略,虽然普通查询走 UDP,但响应包太大时(比如 DNSSEC 记录)会切换到 TCP,TCP 被墙同样会导致部分查询失败。第四,确认客户端到服务器的网络连通性。ping服务器 IP 能通不代表 DNS 一定通,直接用dig加+time=3 +tries=2参数快速试探,能减少等待时间。7.2 主从不同步的经典原因主从不同步,优先看从服务器日志。日志里如果有zone example.com/IN: refresh: retry limit reached,说明从服务器连不上主服务器或序列号没有变化。逐个排查:主服务器的allow-transfer是否包含从服务器 IP;从服务器的masters写的 IP 是否有误。主从之间的防火墙是否放行了 TCP 53。区域传输走 TCP,不是 UDP,这点特别容易忽略。主服务器 zone 文件序列号是否递增。从服务器日志提示got NOTIFY但随后no newer serial时,基本就是序列号问题。从服务器的 zone 文件目录是否可写。/var/named/slaves权限不对时,区域传输会报permission denied。提示:修改主服务器 zone 后,手工执行rndc reload example.com可以加速通知推送,比等 NOTIFY 自动触发更可控。生产环境做变更时,我习惯先 reload 主服务器,再在从服务器上rndc reload强制同步,确保数据落地。7.3 SELinux 与文件权限导致的问题RHEL 系的 SELinux 对 named 的限制也是重灾区。zone 文件如果临时从其他位置拷贝过来,SELinux 上下文不正确,named 进程会拒绝读取。表现是配置检查通过、服务也能启动,但解析时永远报 SERVFAIL。排查方法:ls -lZ /var/named/example.com.zone正常输出里应该包含named_zone_t这样的上下文标识。如果不对,执行:restorecon -v /var/named/example.com.zone还有一个常见现象:服务重启正常,但journalctl里频繁出现denied字样,这时候看 SELinux 的 audit 日志,/var/log/audit/audit.log里能找到具体的违规操作。定位后如果是合法操作,用audit2allow生成对应策略放行,或者调整文件上下文,而不是粗暴关闭 SELinux。7.4 日常运维命令速查整理一份我日常用得最多的命令清单:用途命令检查主配置语法named-checkconf检查 zone 文件语法named-checkzone example.com /var/named/example.com.zone重载配置systemctl reload named重启服务systemctl restart named查看服务状态systemctl status named跟踪日志journalctl -u named -f测试解析dig @127.0.0.1 web1.example.com测试反向解析dig @127.0.0.1 -x 192.168.1.11查看缓存统计rndc stats强制重载某区域rndc reload example.com开启查询日志rndc trace/rndc trace 3最后再分享一个我常用的"保命"技巧:每次准备大规模的 DNS 变更前,先导出当前配置文件的备份,并把 zone 文件单独打包。命令很简单,cp /etc/named.conf /etc/named.conf.bak再加上tar czf /var/named/zones_backup.tgz /var/named/*.zone。这个习惯帮我避免过好几次误操作后的手忙脚乱。DNS 一旦出错,影响的是整网所有依赖域名的服务,宁可慢五分钟,也要保证每一步回退路径清晰。
1. 项目概述与核心思路做运维这些年,DNS 算是打交道最多的基础设施之一。可能有人觉得"这不就是个域名解析吗",但真到排查线上故障时,DNS 配置不规范引来的问题,往往比应用本身的问题更隐蔽、更难查。这个项目的核心目标,是从零搭建一套内网 DNS 实战环境,把正向解析、反向解析、主从同步、缓存转发这些基础能力一项项落到底,最终形成一套可以复现的配置模板,既能给团队内部用,也能作为自己理解 DNS 协议与 BIND 实现的练习场。我选择用 Linux 下的 BIND 9 作为承载服务。理由很简单:它是目前使用范围最广、文档最全、行为最符合标准实现的 DNS 服务器软件,社区踩坑记录足够多,出了问题也容易搜到答案。相比直接使用云厂商提供的 DNS 服务或路由器内置的简单转发,BIND 能让你真正掌握解析链路里的每个环节——从 named.conf 的语法、zone 文件的格式、SOA 记录的序列号机制,到 NOTIFY 通知和增量传输的工作方式。只有亲手配置过一遍,日后在处理生产环境问题时才不至于两眼摸黑。这个方案适合谁?一类是想在纯内网环境做域名管理、不想依赖公网 DNS 的团队;另一类是刚入门的运维或网络方向同学,想搞明白"输入一个网址到拿到 IP 地址"之间到底发生了什么。无论哪类读者,我都建议跟着下文把每个配置亲手敲一遍,而不是只读不练。DNS 的配置文件语法严格、错误反馈也比较"老实",用它入门 Linux 服务配置管理,其实是很合适的练手项目。1.1 为什么需要在内部搭建 DNS 服务内部搭建 DNS 的直接动力,是"机器多了以后,记 IP 记不过来了"。服务器、虚拟机、容器、网络设备、打印机……只要设备数量超过十几台,靠 hosts 文件维护映射关系就会非常痛苦。hosts 文件只能解决本机访问的问题,设备一变、IP 一变,所有机器的 hosts 都得跟着改,维护成本直线上升。而一套内部 DNS 可以做到集中管理。只需要在个别节点把 DNS 指向这台上游,新增设备时在 zone 文件里加一条 A 记录,全内网立刻生效。另一个常见场景是服务间调用:微服务架构里,服务和服务的通信往往基于域名而非 IP 地址,比如应用通过db.internal访问数据库主节点,通过api.registry访问注册中心。这些内部域名如果交给公网 DNS 解析,显然不合理,也没有必要。自建 DNS 后,还能顺手把内部服务的 TTL 调低,方便服务迁移时快速切换解析结果。1.2 这套方案要解决的核心痛点结合我实际维护过几套内网 DNS 的经验,最大的痛点往往不是"配不出来",而是"配出来以后过两周就出问题"。典型的有三类:一是配置语法写错,但服务没报错。BIND 对 zone 文件里的空行、Tab 和注释位置比较敏感,多一个空格少一个括号都可能造成解析异常。好在有named-checkconf和named-checkzone这两个工具,用它们提前校验,绝大多数低级错误都能在重启服务之前被发现。二是主从不同步。很多团队做了主从架构,但改完主服务器上的 zone 后,从服务器迟迟不更新。常见原因就是 SOA 记录里的序列号没递增,从服务器认为数据没变化,自然就不做区域传输。这个坑极其经典,下文专门有一节讲它。三是权限和防火墙问题。BIND 通常在 53 端口上监听 TCP 和 UDP,如果防火墙只放行了 TCP,UDP 查询就会全部超时。还有 SELinux 环境下,zone 文件放错目录会导致 named 进程读不到数据。这些问题排查起来不难,但没遇到过的人往往会绕圈子。这个项目就是把上述问题全部过一遍:给出一套能用的配置,指出哪里容易踩坑,再配合 dig 命令的验证方法,让整个排查链路清晰可见。2. 环境准备与基础安装动手之前,先把实验环境说清楚。这套配置适用于主流的 Linux 发行版,我用的是 RHEL 系和 Debian 系各跑过一遍。RHEL 系(比如 CentOS、Rocky Linux)用yum或dnf安装;Debian 系用apt。底层原理一致,只是包名和配置文件路径稍有差异。下文以 RHEL 系为主路径写,Debian 系的差异点我会在合适的位置单独标注。建议准备两台虚拟机或容器来完整演练主从同步。第一台作为主 DNS,第二台作为从 DNS,操作系统配置干净纯净,不要预装多余服务,避免端口冲突干扰排查。2.1 选型说明:为什么是 BIND可能有人会问,现在有很多轻量级 DNS 服务器,比如 dnsmasq、PowerDNS、CoreDNS,为什么偏偏选 BIND?我的回答是:学习阶段选 BIND,是为了学"完整"的 DNS;生产环境如果只是给开发环境做简单缓存,dnsmasq 当然更轻快,但那样学不到 DNS 的核心机制。BIND 覆盖了 DNS 服务器的完整功能面:权威解析、递归查询、转发、缓存、视图、DNSSEC、ACL、日志分类,全部都有成熟的配置语法。虽然它的配置门槛比 dnsmasq 高,但理解它的核心概念(区域、资源记录、SOA、NS)之后,再看其他 DNS 软件的文档,几乎是降维打击。并且 BIND 的性能和稳定性经过了几十年的生产环境验证,很多企业级网络设备的管理界面背后,跑的就是 BIND 或其商业版本,选它做实验和学习对象,技术投入不会浪费。2.2 安装步骤与启动安装命令很简单。RHEL 系:yum install -y bind bind-utilsDebian 系:apt update apt install -y bind9 bind9utils bind9-docRHEL 系安装后的主配置是/etc/named.conf,配置文件目录在/etc/named/,zone 文件默认放在/var/named/下;Debian 系主配置是/etc/bind/named.conf,zone 文件通常在/etc/bind/下。我习惯在 RHEL 系环境下用/etc/named/存放自定义 zone 文件,用include语句引入,保持主配置干净清晰。安装完成后,先不着急写配置,把服务启动一次确认能跑起来:systemctl start named systemctl status named如果启动失败,直接看日志:journalctl -u named -n 50一个常见情况是服务起不来但配置看着没问题,那十有八九是配置文件权限或 SELinux 上下文不对。检查一下/var/named/下的文件权限,确保 named 进程对 zone 文件有读取权限。RHEL 系建议用restorecon -Rv /var/named恢复 SELinux 上下文,这个问题我下文会再展开。提示:实验环境里如果不需要开机自启,可以保持 disabled;但作为内网基础设施,生产环境建议systemctl enable named。3. 核心配置文件拆解:named.conf 与 zone 机制BIND 的配置文件体系分为两层:第一层是named.conf,负责定义全局选项、访问控制、日志和区域声明;第二层是 zone 文件,负责存放具体域名与 IP 映射的资源记录。理解这两层的关系,Debug DNS 就等于完成了一半。3.1 named.conf 全局选项的含义打开新建的 named.conf,通常是空壳加几个默认配置。我以一个最精简的内网权威 DNS 配置为例:options { listen-on port 53 { 192.168.1.10; }; # 监听内网IP listen-on-v6 port 53 { none; }; # 不监听IPv6 directory "/var/named"; dump-file "/var/named/data/cache_dump.db"; statistics-file "/var/named/data/named_stats.txt"; allow-query { 192.168.1.0/24; }; # 只允许内网网段查询 allow-transfer { 192.168.1.20; }; # 只允许从服务器做区域传输 recursion yes; dnssec-enable yes; };这里面有几个参数要特别注意。listen-on决定了服务绑定的网卡地址。默认配置会监听本机所有 IP,但安全起见,我一般明确写到内网 IP,避免服务意外暴露到公网或测试网段。如果本机有多个网卡,不写这个参数就全部暴露了,这不算大错,但不符合最小暴露面原则。allow-query是查询权限。实验环境可以写成内网网段;如果未来要对接办公室的多个 VLAN,可以用 ACL 集中定义。注意,这里控制的是"谁能问我问题",而不是"我能回答什么问题",后者由 zone 的配置和递归行为决定。allow-transfer很关键。它限制哪些服务器可以对这台权威服务器发起区域传输请求。区域传输会把整个 zone 的数据一次性同步过去,如果这个选项配置不当,外部任何人都能把自己伪装成从服务器,把内网域名记录全部拖走,这是内外网信息泄露的高危点。生产环境务必要把这个配置成具体 IP 或专用 ACL。3.2 zone 声明与 named.rfc1912.zones 的关系在 RHEL 系默认配置里,/etc/named.conf末尾通常会 include 一个/etc/named.rfc1912.zones,里面预置了 localhost 和 loopback 等基础 zone。我的习惯是把自己管理的业务 zone 单独写一个文件(比如/etc/named/rdnz_zones.conf),再在主配置里 include 进来。这样一来,主配置只负责全局选项和 ACL,业务区域和公共配置分离,后续用配置管理工具批量下发时思路也清晰。一个典型的正向 zone 声明是:zone "example.com" IN { type master; file "example.com.zone"; allow-update { none; }; };type master表示这是权威主区域。file指向 zone 文件路径,注意这里用的是相对路径,实际根目录由directory选项指定(即/var/named)。allow-update一般直接none,避免开放动态更新。反向 zone 声明则指向 in-addr.arpa 域:zone "1.168.192.in-addr.arpa" IN { type master; file "192.168.1.zone"; };3.3 zone 文件语法与资源记录详解zone 文件是 DNS 配置里最容易被写错的部分。我先解释核心概念,再给完整示例。zone 文件至少要包含SOA记录和NS记录,然后根据业务需求添加 A、AAAA、CNAME、MX、TXT、PTR 等记录。SOA记录是整个区域的"主控记录",它包含如下字段:serial:序列号。主从同步靠它判断数据新旧,每次修改 zone 文件后必须递增,建议用日期格式2025021501。refresh:从服务器多久检查一次主服务器数据版本。retry:从服务器检查失败后重试的间隔。expire:从服务器一直连不上主服务器时,多久之后丢弃本地数据。negative TTL:无记录应答的缓存时间。把这些参数理解透,主从不同步的问题基本解决了一半。NS记录用于声明区域的权威服务器,也就是哪些服务器有资格回答这个区域的查询。实际写 zone 文件时,行首的域名可以省略不写,用 Tab 或空格开头就表示"继承上一行的域名"。这个特性让新手困惑过一阵——看起来没写域名,但记录其实属于某个明确的所有者。建议刚开始写的时候不要偷懒,每一行都写全域名,避免理解偏差。4. 正向解析与反向解析实战这一节把最常用的正向解析和反向解析完整走一遍,每一个步骤都可以照抄。4.1 正向解析 zone 配置示例业务需求如下:内网网段192.168.1.0/24,主 DNS 服务器 IP 为192.168.1.10,域名为ns1.example.com;从 DNS 服务器为192.168.1.20,域名为ns2.example.com。需要解析的机器有web1(192.168.1.11)、web2(192.168.1.12)、db1(192.168.1.13)等。在/var/named/下创建example.com.zone:$TTL 86400 @ IN SOA ns1.example.com. admin.example.com. ( 2025021501 ; serial 3600 ; refresh 600 ; retry 86400 ; expire 300 ) ; negative cache TTL ; ; Name servers @ IN NS ns1.example.com. @ IN NS ns2.example.com. ; ; DNS server hostnames ns1 IN A 192.168.1.10 ns2 IN A 192.168.1.20 ; ; Web servers web1 IN A 192.168.1.11 web2 IN A 192.168.1.12 ; ; Database server db1 IN A 192.168.1.13 ; ; Alias for load balancer www IN CNAME web1.example.com.几个要点说明。$TTL 86400表示默认 TTL 一天。内部服务建议把 TTL 调短(比如 300 秒),这样服务 IP 有变化时,内网机器能在 5 分钟内拿到新的解析结果,不需要等缓存过期。结尾的域名一定要带点号。ns1.example.com.末尾的点表示这个是完整域名(FQDN),如果不写点号,BIND 会默认把当前 zone 的域名追加上去,结果变成ns1.example.com.example.com.。这个错误非常经典,排查时dig返回的解析结果会多出一段奇怪的域名后缀,我见到过不下五次。CNAME记录指向的域名也必须是完整域名,并且最好指向一个本来就存在的 A 记录。如果指向了一个同样用 CNAME 定义的域名,就会形成 CNAME 链,链太长会影响解析效率,甚至在某些代理环境下解析失败。4.2 反向解析 zone 配置实例反向解析的作用是把 IP 解析成域名,主要用于邮件服务器反垃圾邮件校验和日志审计。内网不一定需要,但作为完整 DNS 服务,建议配置上。创建/var/named/192.168.1.zone:$TTL 86400 @ IN SOA ns1.example.com. admin.example.com. ( 2025021501 ; serial 3600 600 86400 300 ) ; @ IN NS ns1.example.com. @ IN NS ns2.example.com. ; 11 IN PTR web1.example.com. 12 IN PTR web2.example.com. 13 IN PTR db1.example.com.然后声明反向区域,在/etc/named/rdnz_zones.conf中添加:zone "1.168.192.in-addr.arpa" IN { type master; file "192.168.1.zone"; };反向 zone 的文件名格式是反向网段.zone,内容里每行开头的数字是要解析的 IP 最后一段。注意反向域名的书写顺序:网段192.168.1.0/24对应反向域是1.168.192.in-addr.arpa,IP 的最后一段写在前面,网段顺序完全反转。4.3 配置检查与加载验证写完配置后,不要急着 restart 服务,先做检查。named-checkconf /etc/named.conf named-checkzone example.com /var/named/example.com.zone没输出即代表通过。如果有问题,它会直接告诉错误在哪行哪个 token。检查通过后重载:systemctl reload named验证解析结果使用dig:dig @192.168.1.10 web1.example.com dig @192.168.1.10 -x 192.168.1.11第一条命令期望输出里有web1.example.com. 86400 IN A 192.168.1.11;第二条期望看到11.1.168.192.in-addr.arpa. 86400 IN PTR web1.example.com.。如果dig返回SERVFAIL,优先检查 zone 文件语法;返回REFUSED,则检查allow-query权限是否覆盖了来源 IP。5. 主从同步实战:从原理到落地主从架构是内网 DNS 高可用的基础。主服务器负责读写,从服务器通过区域传输获得数据副本。客户端可以随便指向任意一台,挂了另一台还能继续解析。5.1 主从同步原理与前提主从同步的触发机制是 NOTIFY 通知配合 SOA 序列号轮询。主服务器加载新 zone 后,会向 NS 记录中声明的从服务器发送 NOTIFY 消息,告知"我有新数据了";从服务器收到通知后,主动向主服务器查询 SOA 序列号。如果序列号比本地的数字大,则发起区域传输,把整个 zone 或增量数据同步过来。即便没有 NOTIFY,从服务器也会每隔 refresh 时间去主服务器查一次序列号。所以"序列号递增"不是可选项,是硬性要求。改 zone 文件后我一般手动 +1,没有用过自动生成。部分团队会用脚本来检测文件变化并自动递增序列号,生产环境那样做更省心,但手工理解机制是第一步。5.2 主服务器端配置主服务器上,在 zone 声明中添加also-notify或直接在 NS 记录中声明从服务器即可。推荐两个都做:NS 记录用来声明权威性,allow-transfer 用来开放传输权限。主服务器 zone 声明调整:zone "example.com" IN { type master; file "example.com.zone"; allow-transfer { 192.168.1.20; }; also-notify { 192.168.1.20; }; };zone 文件中要确认 NS 记录里包含ns2.example.com.和它的 A 记录。如果从服务器的 NS 声明缺失,一些严谨的客户端会拒绝使用这台从服务器做解析,因为它们认为它不是这个区域的权威。这个点之前容易漏。修改完成后照旧named-checkconf、named-checkzone,然后 reload。5.3 从服务器端配置从服务器的 zone 声明使用type slave,不需要手工创建 zone 文件,区域传输会自己把数据落盘。zone "example.com" IN { type slave; masters { 192.168.1.10; }; file "slaves/example.com.zone"; };注意file指向的位置建议放在从服务器有写权限的目录,RHEL 系默认是/var/named/slaves/,这个目录存在并且 named 有写入权限。如果不放在这个目录,区域传输可能因为权限问题失败。从服务器的named.conf全局选项中,allow-transfer就没必要留了,从服务器不向其他机器再提供区域传输。allow-query依然配置内网网段,保证客户端能查询。重启从服务器的 named,然后观察日志:systemctl restart named journalctl -u named -f如果主从配置正确,日志里会出现类似zone example.com/IN: Transfer started和transfer of 'example.com/IN' from 192.168.1.10#53: Transfer status: success的内容。客户端把 DNS 指向从服务器再 dig 一次,能拿到同样的解析结果,说明主从架构生效了。5.4 序列号更新的实际意义前面反复提到序列号,这里用一个真实场景佐证它的重要性。某次我在 A 环境改了 web1 的 IP,从 11 改成 15,修改完成后忘记递增 SOA 序列号。主管服务器重启之后,从服务器自己也正常,但客户端访问新 IP 一直不稳定,时好时坏。排查到最后才发现,从服务器的序列号还是旧值,它认为主服务器的序列号更小或相同,不触发区域传输。当客户端恰好被负载均衡指到从服务器时,拿到的还是旧的解析结果。修复方式很简单:序列号加一,reload 主服务器,然后立刻在从服务器上执行rndc reload example.com,强制刷新这个区域。几秒钟内数据就同步过来了。这个案例说明,对 SOA 序列号机制的理解,不能停留在"知道要改"。它直接影响主从同步是否触发,是整个分布式 DNS 体系里最基础也最关键的握手协议。6. 缓存与转发配置实践除了承担权威解析,DNS 服务器经常同时充当内网客户端的递归解析入口。客户端把 DNS 指向它,它负责向外部的上游 DNS 查询结果并缓存,下次再有相同查询时直接返回缓存,减少跨公网的查询延迟。6.1 缓存机制与 TTL 的关系BIND 默认自带缓存功能,不需要额外安装。缓存的主要控制参数就是 TTL:zone 文件里设置的 TTL 值决定了缓存最长保留多久。比如公网域名的 TTL 是 600 秒,那么内网 DNS 第一次递归解析后会缓存这个答案 10 分钟,期间再有客户端查询,直接从缓存返回,不产生外网流量。缓存虽好,但也要警惕"缓存污染"。如果上游返回了错误答案或者攻击者注入了一条伪造记录,内网 DNS 会把这个答案原样缓存并分发给所有查询者。基础的防御手段是开启 DNSSEC 验证,BIND 9 默认配置基本已经启用dnssec-enable,但要真正验证,还需要配置信任锚以及dnssec-validation auto。内网环境如果没有统一的 DNSSEC 信任链,建议至少保留默认的 auto 模式,并在递归出口防火墙上限制对外访问,降低被攻击面。6.2 配置转发模式:forward only 与 forward first递归模式下,BIND 会自行向根服务器发起迭代查询。但很多内网 DNS 不希望直接暴露到公网,更希望把出网流量集中交给某个上游 DNS(比如运营商 DNS 或公司统一提供的出口 DNS)。这时候就用到forward配置。在 named.conf 的 options 里加:forwarders { 223.5.5.5; 8.8.8.8; }; forward only;forward only表示这台服务器自己不向根服务器查询,只把请求转发给列出的上游。如果上游全部不可达,就返回 SERVFAIL。另一种模式是forward first,表示先转发,如果超时再自己迭代查询。生产环境为了流量可控,我一般用forward only,但上游至少配两个,避免单点。需要注意,转发只影响"非本机权威的域名"查询。本地定义的 example.com 依然由本机直接回答,不会跑到转发器上绕一圈。这个逻辑很容易理解:权威数据优先于转发数据。6.3 测试缓存与转发效果配置完成后执行:dig @192.168.1.10 www.example.org dig @192.168.1.10 www.example.org第二次查询的Query time应该显著小于第一次,并且 ANSWER SECTION 中可以看到来自缓存的记录。再用:rndc stats可以查看缓存命中率,输出中Cache Hits和Cache Misses的比例能直观反映缓存效果。实际运营里我经常开着rndc dumpdb -cache来检查缓存里是否有不该存在的异常记录,这些命令在排查疑难解析问题时很顺手。7. 常见问题与排查技巧实录最后这部分,把我在实操中真正遇到频率最高的坑和排查方法汇总成一张"排障地图"。它既是备忘录,也是团队接手这套 DNS 时的快速上手文档。7.1 解析超时的排查顺序客户端dig @192.168.1.10 example.com没反应或者超时,我建议按下面顺序排查。第一,确认服务进程活着。systemctl status named或ps -ef | grep named,如果进程不在,看日志找启动失败原因。第二,确认 UDP 53 端口有监听。ss -ulnp | grep 53,没有输出说明监听地址配错了。检查listen-on是否覆盖了你访问的内网 IP。之前有一次就是因为监听写成了127.0.0.1,客户端怎么查都超时。第三,确认防火墙。RHEL 系执行firewall-cmd --list-all,看 UDP 53 和 TCP 53 是否放行。TCP 53 常常被忽略,虽然普通查询走 UDP,但响应包太大时(比如 DNSSEC 记录)会切换到 TCP,TCP 被墙同样会导致部分查询失败。第四,确认客户端到服务器的网络连通性。ping服务器 IP 能通不代表 DNS 一定通,直接用dig加+time=3 +tries=2参数快速试探,能减少等待时间。7.2 主从不同步的经典原因主从不同步,优先看从服务器日志。日志里如果有zone example.com/IN: refresh: retry limit reached,说明从服务器连不上主服务器或序列号没有变化。逐个排查:主服务器的allow-transfer是否包含从服务器 IP;从服务器的masters写的 IP 是否有误。主从之间的防火墙是否放行了 TCP 53。区域传输走 TCP,不是 UDP,这点特别容易忽略。主服务器 zone 文件序列号是否递增。从服务器日志提示got NOTIFY但随后no newer serial时,基本就是序列号问题。从服务器的 zone 文件目录是否可写。/var/named/slaves权限不对时,区域传输会报permission denied。提示:修改主服务器 zone 后,手工执行rndc reload example.com可以加速通知推送,比等 NOTIFY 自动触发更可控。生产环境做变更时,我习惯先 reload 主服务器,再在从服务器上rndc reload强制同步,确保数据落地。7.3 SELinux 与文件权限导致的问题RHEL 系的 SELinux 对 named 的限制也是重灾区。zone 文件如果临时从其他位置拷贝过来,SELinux 上下文不正确,named 进程会拒绝读取。表现是配置检查通过、服务也能启动,但解析时永远报 SERVFAIL。排查方法:ls -lZ /var/named/example.com.zone正常输出里应该包含named_zone_t这样的上下文标识。如果不对,执行:restorecon -v /var/named/example.com.zone还有一个常见现象:服务重启正常,但journalctl里频繁出现denied字样,这时候看 SELinux 的 audit 日志,/var/log/audit/audit.log里能找到具体的违规操作。定位后如果是合法操作,用audit2allow生成对应策略放行,或者调整文件上下文,而不是粗暴关闭 SELinux。7.4 日常运维命令速查整理一份我日常用得最多的命令清单:用途命令检查主配置语法named-checkconf检查 zone 文件语法named-checkzone example.com /var/named/example.com.zone重载配置systemctl reload named重启服务systemctl restart named查看服务状态systemctl status named跟踪日志journalctl -u named -f测试解析dig @127.0.0.1 web1.example.com测试反向解析dig @127.0.0.1 -x 192.168.1.11查看缓存统计rndc stats强制重载某区域rndc reload example.com开启查询日志rndc trace/rndc trace 3最后再分享一个我常用的"保命"技巧:每次准备大规模的 DNS 变更前,先导出当前配置文件的备份,并把 zone 文件单独打包。命令很简单,cp /etc/named.conf /etc/named.conf.bak再加上tar czf /var/named/zones_backup.tgz /var/named/*.zone。这个习惯帮我避免过好几次误操作后的手忙脚乱。DNS 一旦出错,影响的是整网所有依赖域名的服务,宁可慢五分钟,也要保证每一步回退路径清晰。