☰
Linux BIND DNS服务器搭建、递归访问控制与故障排查
2026/10/27 13:22:27 网站建设 项目流程

1. 自建 DNS 之前,先把这几个问题想明白

在 Linux 上搭一台 DNS 域名解析服务器,说难不难,yum install bind加两个区域文件,半小时就能跑起来;说简单也不简单,我见过太多人把服务启动成功当成大功告成,结果客户端一接入就到处不通,最后连问题出在哪一层都分不清。之所以会这样,根源往往不在配置写错,而在动手之前就没想清楚这台机器要承担什么角色:它是只给内网几十台机器做内部域名解析,还是要给整个办公网当出口 DNS,还是要在测试环境里模拟一套完整的公网解析链路。这三种定位对应的配置思路区别很大,尤其是访问控制和递归策略,一旦选错,轻则解析变慢,重则把服务器变成一个对全网敞开的解析节点。

这里说的 DNS 域名解析服务器,本质就是一台把"名字"翻译成"IP 地址"的机器,顺带还能反过来把 IP 翻译回名字,也就是常说的正向解析和反向解析。Linux 环境下最主流、资料最全的实现是 BIND,几乎所有的发行版仓库里都有现成的包。这篇内容面向的读者范围比较宽:刚接触 Linux 运维的新手可以照着把整套流程走通,有一定基础的同行可以重点看配置项的取舍逻辑、主从同步和排查顺序那几节,那部分是我实际动手时踩坑最密集的地方。文中所有域名统一用文档专用的示例域名和私有网段,你可以直接替换成自己的规划,不用改结构。

1.1 什么情况下公共 DNS 够用,什么情况下必须自己搭

很多人第一反应是:外面那么多公共解析服务,又快又稳,为什么还要自己折腾一台?这个疑问很合理,先把边界划清楚。如果你的机器全部在云上,服务之间的调用都靠公网域名,那确实没必要自建,直接指向现成的解析服务就行,运维成本为零。但只要出现下面这几种情况,自建就几乎是唯一解。

第一种是内网里有大量自己编的主机名。比如一批测试机、数据库节点、中间件实例,它们的名字在公网根本不存在,你又不愿意让大家靠记 IP 来访问。把这些名字集中写在一个区域文件里,全公司的人只要把 DNS 指向这台服务器,就能用db01.test.internal这样的名字直连,改机器 IP 时只需要改一行记录,而不是挨个通知。

第二种是需要对解析结果有完全控制权。比如把某个域名临时指到一台灰度机器上做验证,或者按网段返回不同的地址做分流,这些都是公共解析服务给不了的。自己搭的服务器,区域文件改完重载一下,几十秒内全网生效,改错了回滚也快。

第三种是做故障隔离和本地缓存。内网出口带宽紧张的时候,递归查询带来的额外延迟其实挺可观。本地跑一台带缓存的解析服务器,热点域名的查询直接命中缓存,响应时间从几十毫秒降到一两毫秒,顺带还能在出口链路抖动时靠缓存撑一段时间。这三点加起来,就是绝大多数自建场景的真实动机。

注意:自建 DNS 只解决"名字到地址"的映射,不要指望它顺带解决网络连通性。很多新手把解析问题和路由问题混在一起排查,白白浪费几个小时。

1.2 四款常见 DNS 软件的横向对比与选型逻辑

Linux 上能当 DNS 服务器用的软件不止一款,选错了后期会很别扭。我按实际使用频率和适用场景列个对照,方便你对号入座。

软件配置形态功能完整度上手难度典型适用场景
BIND文本配置文件 + 区域文件最完整,支持主从、DNSSEC、视图中等,概念多企业内网、教学实验、需要完整解析能力
dnsmasq单个配置文件够用,主打缓存和简单映射很低小型网络、开发环境、路由器侧
PowerDNS数据库后端 + 配置完整,适合大规模较高有平台化需求、记录量大的场景
CoreDNS插件化配置完整,云原生友好中等容器集群内部服务发现

如果你的目标是把 DNS 的整套机制搞明白,包括区域、授权、递归、主从这些概念,BIND 是最好的教材,它把这些东西全部显式暴露在配置文件里,你看得见每一个决策点。dnsmasq 更像是"够用就好"的选择,一个文件里写几行address=/xxx/1.2.3.4就完事,但要做反向解析、主从同步就力不从心了。

PowerDNS 的优势在于记录存在数据库里,改记录不用改文件也不用重载,适合记录条目上千、需要程序化管理的情况,代价是要多维护一个数据库。CoreDNS 主要活在容器世界里,配置文件是一串插件链,逻辑清晰但和传统 DNS 运维习惯差别较大。

我的建议很直接:第一次搭、想把原理吃透,用 BIND;只是想给几台开发机做个名字映射,dnsmasq 十分钟搞定,别上 BIND 给自己找麻烦。下面所有内容都以 BIND 为准展开,因为它覆盖的概念最全,学会了再去看其他实现,基本是降维。

2. 基础环境与 BIND 安装:把地基打牢

动手装软件之前,有几个看起来和 DNS 无关、实际上会直接影响成败的基础项。我见过不止一次这种情况:配置文件反复检查没毛病,named进程也在跑,就是客户端解析不通,最后发现是主机名解析顺序或者时间偏差导致的。这些准备动作花不了十分钟,但能省掉后面大半的排查时间。

2.1 主机名、静态 IP、时间同步三件套

第一件事是把主机名定下来,并写进/etc/hosts。BIND 启动时会去解析本机主机名,如果这一步卡住或者解析结果不对,会出现启动缓慢甚至直接失败。做法很简单:

hostnamectl set-hostname ns01.test.internal

然后编辑/etc/hosts,确保主机名对应到本机的内网地址,而不是只挂在一行127.0.0.1后面:

127.0.0.1 localhost localhost.localdomain 192.168.10.10 ns01.test.internal ns01

这里有个细节值得说清楚。很多发行版默认把主机名放在127.0.0.1那一行,本机自己解析没问题,但一旦这台机器对外提供服务,某些查询会返回回环地址,客户端拿到127.0.0.1当然连不上。把它单独绑到真实的内网 IP 上,能避开一整类莫名其妙的故障。

第二件事是静态 IP。DNS 服务器的地址必须是固定的,不能靠 DHCP 分配,否则客户端配置里的地址随时失效。在服务器上把网卡配置改成静态地址,同时把网关和 DNS 一起写清楚。注意给这台机器自己配 DNS 时,先临时指向一个可用的外部解析服务,等自己的服务跑起来再改回来,不然装包阶段可能因为解析失败而卡住。

第三件事是时间同步。这条最容易被忽略,但影响很实在:主从架构下,SOA 记录里的序列号比较、日志时间线对齐、证书校验,全都依赖时间的准确性。用chronyd或systemd-timesyncd把时间同步开起来,检查一下偏移量:

timedatectl status chronyc tracking

偏移控制在几百毫秒以内就够了,不需要追求纳秒级。我之所以把这条单列出来,是因为曾经遇到过主从不同步、日志对不上时间导致排查方向完全跑偏的情况,那种时候你会特别后悔没在一开始花两分钟把时间同步打开。

2.2 BIND 的安装与版本差异

安装本身一条命令的事,但不同发行版的包名和默认路径有差别,先确认清楚再动手,免得后面找配置文件找半天。

# 基于 RPM 的发行版 dnf install bind bind-utils -y # 基于 DEB 的发行版 apt update && apt install bind9 bind9-utils dnsutils -y

装完之后先别急着改配置,把版本和服务状态确认一下:

named -v systemctl status named # RPM 系服务名是 named systemctl status bind9 # DEB 系服务名是 bind9

路径差异是新手最容易懵的地方。RPM 系的主配置在/etc/named.conf,区域文件默认放在/var/named/;DEB 系的主配置在/etc/bind/named.conf,区域文件放在/etc/bind/或者/var/lib/bind/,而且它把配置拆成了named.conf.options、named.conf.local、named.conf.default-zones几个文件。功能上完全一样,只是组织方式不同,看到别人的教程路径对不上时,先确认自己用的是哪一套,别硬套。

我这里推荐一个习惯:不管哪个发行版,区域文件统一放到一个自己建的目录,比如/var/named/zones/,配置里用绝对路径引用。好处是以后备份、迁移、做版本管理时目标很明确,不会被系统自带的默认区域文件混在一起。

2.3 防火墙与 SELinux:90% 的"启动正常但解析不通"出在这里

服务装好、配置写完、systemctl start也返回成功,客户端一查就是超时。这个时候别急着怀疑配置,先把这两道门检查一遍。

防火墙放行 53 端口,UDP 和 TCP 都要放。DNS 查询默认走 UDP,但当响应超过 512 字节(比如返回记录很多,或者带上了 DNSSEC 相关数据)时会自动切到 TCP 重试,只放 UDP 会出现"小查询正常、大查询失败"的诡异现象。

# firewalld firewall-cmd --permanent --add-service=dns firewall-cmd --reload # 或直接开端口 firewall-cmd --permanent --add-port=53/tcp --add-port=53/udp firewall-cmd --reload

SELinux 是另一个高频坑点。它默认会限制named进程只能读特定目录下的文件,你把区域文件放到自己建的目录里,进程就没有读权限,日志里会出现permission denied之类的报错,但服务本身还是"运行中"状态。

getenforce # 临时观察用,别在生产上长期关 setenforce 0 # 更规范的做法:给目录打上正确的标签 semanage fcontext -a -t named_zone_t "/var/named/zones(/.*)?" restorecon -Rv /var/named/zones

提示:不要图省事直接把 SELinux 永久关掉。先setenforce 0确认问题确实是它引起的,再回头用标签或者布尔值的方式正正经经解决,这个习惯能让你少背很多锅。

还有一种情况是端口被占用。53 端口有时候会被系统自带的解析服务或者容器占着,named起不来但报错不明显。装之前先看一眼:

ss -lntup | grep :53

如果看到别的进程占着,先把它停掉或者改端口,不然后面所有配置都是白写。

3. 配置文件逐行拆解:从 named.conf 到区域文件

配置这块是整件事的核心,也是最容易照抄教程抄出问题的地方。我下面按"主配置 → 正向区域 → 记录类型 → 反向区域"的顺序讲,每一处都说明白为什么这么写,而不是只给一段能跑的代码。你把这几个文件的结构吃透了,后面换域名、加记录、做迁移都是机械操作。

3.1 named.conf 的结构与每一个必改项

主配置分成两大块:全局选项options和区域声明zone。先看一个可以直接用的骨架:

options { listen-on port 53 { 127.0.0.1; 192.168.10.10; }; listen-on-v6 { none; }; directory "/var/named"; dump-file "/var/named/data/cache_dump.db"; statistics-file "/var/named/data/named_stats.txt"; memstatistics-file "/var/named/data/named_mem_stats.txt"; allow-query { 192.168.10.0/24; 127.0.0.1; }; recursion yes; allow-recursion { 192.168.10.0/24; 127.0.0.1; }; forwarders { 192.168.10.1; }; forward only; dnssec-validation no; version "not currently available"; }; logging { channel default_log { file "/var/log/named/named.log" versions 5 size 20m; severity info; print-time yes; print-category yes; }; category default { default_log; }; category queries { default_log; }; }; zone "test.internal" IN { type master; file "/var/named/zones/test.internal.zone"; allow-update { none; }; allow-transfer { 192.168.10.11; }; }; zone "10.168.192.in-addr.arpa" IN { type master; file "/var/named/zones/10.168.192.rev"; allow-update { none; }; allow-transfer { 192.168.10.11; }; };

逐项说几个关键点。listen-on决定服务监听在哪个地址上,默认可能只监听127.0.0.1,那样外部客户端永远连不上。把它显式写成回环加内网地址,既保证本机能查,也保证网内能查。

directory是工作目录,相对路径的区域文件都基于它解析。我习惯在区域声明里写绝对路径,省得来回算相对路径算错。

allow-query是最容易被忽略的一条。不写的话,默认行为在不同版本里不一致,有的版本是允许所有来源查询。这意味着你的服务器一旦暴露在有公网入口的网络上,就等于给全世界提供免费解析。显式限定成内网网段,是基本的安全底线。

allow-recursion比allow-query更要紧。递归查询意味着服务器会替客户端去问上游,这个能力如果对外开放,会被利用来做放大攻击。把它限定在内网,是自建 DNS 必须做的一件事。

dnssec-validation在纯内网环境里建议先关掉,否则上游不支持 DNSSEC 时会解析失败,排查起来很绕。等整体跑通、确认上游支持之后再打开。

version那行是隐藏版本号,减少信息暴露。allow-transfer限定只允许从服务器来拉区域数据,防止有人把你的完整记录表拖走。

配置写完别直接重启,先用工具检查语法:

named-checkconf /etc/named.conf

它会把行号和错误类型报出来,比等服务启动失败再翻日志高效得多。

3.2 正向区域文件:SOA 里每个字段都是有含义的

正向区域负责"名字 → IP"。文件开头必须是$TTL和 SOA 记录,这两样写错,整个区域都加载不了。

$TTL 600 @ IN SOA ns01.test.internal. admin.test.internal. ( 2024051501 ; Serial 3600 ; Refresh 900 ; Retry 604800 ; Expire 600 ) ; Negative Cache TTL @ IN NS ns01.test.internal. @ IN A 192.168.10.10 ns01 IN A 192.168.10.10 db01 IN A 192.168.10.21 db02 IN A 192.168.10.22 web IN A 192.168.10.30 www IN CNAME web.test.internal. mail IN A 192.168.10.40 @ IN MX 10 mail.test.internal.

SOA 里那几个数字不是随便填的,每个都有实际作用。Serial是序列号,主从同步靠它判断有没有更新,规则是只增不减,常见做法是用日期加两位序号,比如2024051501表示 5 月 15 日的第一版,改一次加一。Refresh是从服务器多久来问一次主服务器有没有更新,3600 秒即一小时,内网环境够用,追求同步速度可以调到 600。Retry是同步失败后的重试间隔,别设得比 Refresh 还长。Expire是主服务器失联多久之后从服务器放弃提供解析,604800 秒是一周,太短会在主服务器维护时导致整片解析中断。Negative Cache TTL是"查不到"这个结果被缓存多久,设得过大,新加记录后客户端要等很久才能查到,设得过小,不存在的域名会频繁打到你头上,600 秒是比较折中的值。

末尾那个点特别重要。test.internal.后面的点是"绝对域名"的意思,不写点会被当成相对域名自动拼上区域名,变成ns01.test.internal.test.internal,这种错误不会报语法错,但解析结果一定是错的。

自检一下区域文件:

named-checkzone test.internal /var/named/zones/test.internal.zone

输出OK才算过。这一步花三秒钟,能避免掉大量"服务起来了但查不到"的问题。

3.3 A、CNAME、MX、TXT 记录的写法与优先级

记录类型不多,但用错场景会很别扭。A 记录是最基本的,一个名字对一个 IPv4 地址,同名多条 A 记录就构成了最简单的轮询负载,客户端按顺序取用。AAAA 记录对应 IPv6,如果内网没有 IPv6,不写就行。

CNAME 是别名,指向另一个名字而不是 IP。上面例子里的www指向web,好处是以后web换地址,只改一条 A 记录,所有别名自动跟着变。但 CNAME 有两条硬规矩要记住:一是不能和同名的其他记录共存,www有了 CNAME 就不能再给它写 A 记录或者 MX 记录,否则区域加载会报错;二是根域名(@)不要设 CNAME,那会导致整个区域的 NS 记录冲突。

MX 记录管邮件路由,前面的数字是优先级,数值越小越优先。写 MX 的时候必须以名字结尾带点,并且这个目标名本身要有 A 记录,否则邮件投递会失败。TXT 记录现在最常见的用途是各种验证,比如域名归属校验、邮件策略声明,值是一串带引号的字符串,注意转义和长度限制。

实际规划记录时,我建议按功能分组,加注释说明每条的用途,比如; 数据库节点、; 对外服务。区域文件是给人看的,半年后回来改的人可能就是你,注释能救命。

3.4 反向区域与 PTR 记录:为什么 ping 会慢半拍

反向解析是把 IP 翻回名字,用ping或者看日志的时候特别有用,日志里显示主机名比显示一串数字好读太多。反向区域的命名规则是把网段倒过来加上in-addr.arpa:192.168.10.0/24对应的区域名就是10.168.192.in-addr.arpa。

$TTL 600 @ IN SOA ns01.test.internal. admin.test.internal. ( 2024051501 3600 900 604800 600 ) @ IN NS ns01.test.internal. 10 IN PTR ns01.test.internal. 21 IN PTR db01.test.internal. 22 IN PTR db02.test.internal. 30 IN PTR web.test.internal. 40 IN PTR mail.test.internal.

注意主机位只写最后一段,192.168.10.21在区域里就写21。PTR 记录的目标也必须是带点的绝对域名。

很多新手会遇到"ping 一个内网地址,前面几秒没反应,过一会儿才出结果"的情况。这通常是反向解析超时导致的:系统先尝试 PTR 查询,你的服务器没配反向区域,它就只能一路问到上游,等超时才放弃。把反向区域补齐,这个问题立刻消失。虽然不影响功能,但体验差别很大,值得花十分钟配好。

提示:反向区域不是必须的,但如果你经常看日志、做监控、排查连接来源,配一份会让所有相关工作都舒服很多。

4. 递归、转发与访问控制:DNS 服务器最容易出安全事故的地方

到这一步,解析已经能用了,接下来这部分决定的是"能不能长期稳定地用"。递归策略和访问控制看起来是进阶内容,实际上对一个稍微正式一点的环境来说,它们是必修课。我见过最典型的事故是:一台配置不严的解析服务器被外部扫描到,开放递归被利用,机器带宽被打满,业务跟着遭殃。

4.1 递归查询和转发器,到底该开哪个

先把这个概念说清楚。递归是服务器替客户端跑完全程:客户端只问一次,服务器自己去根、顶级域、权威服务器一层层问下来,把最终答案拿回来。转发是服务器把请求整包丢给上游,让上游去跑腿,自己只负责传话和缓存。

内网环境里,这两种方案各有取舍。开递归的话,你的服务器需要能直接访问外部 53 端口,很多办公网出口是不放这个的,那就只能走转发。开转发的话,所有请求都压在上级服务器上,上级挂了你就全挂,而且缓存命中率取决于上级的策略。

我的实际做法是:内网里同时开递归和转发,但用forward only明确表示"只转发、不自己递归",这样行为是可预期的,不会出现一部分请求走转发、一部分自己跑的情况,排查时好定位。如果出口环境允许直接对外查询,那就把forwarders去掉,让服务器自己递归,缓存命中率更高,对上游依赖也更小。

forwarders { 192.168.10.1; 192.168.10.2; }; forward only;

配置两个上游地址是为了容错。注意上游不要填自己,也不要把公共解析服务和内网出口混着填,否则查询路径会乱,出问题时很难判断是哪一条链路的问题。

4.2 allow-query、allow-recursion、ACL 的组合写法

这三者的关系可以这么理解:allow-query管"谁能向我提问",allow-recursion管"我替谁去跑腿",allow-transfer管"谁能搬走我的记录"。三个都开了,才能既可用又安全。

网段多了以后,直接在每条里写一堆 CIDR 会很乱,这时候用 ACL 把地址分组,可读性会好很多:

acl "internal" { 127.0.0.1; 192.168.10.0/24; 192.168.20.0/24; }; acl "slaves" { 192.168.10.11; }; options { allow-query { internal; }; allow-recursion { internal; }; allow-transfer { slaves; }; allow-query-cache { internal; }; };

一个常见的误区是只写了allow-query就以为安全了。实际上递归权限是独立控制的,某些版本在没有显式配置allow-recursion时会退化成跟随allow-query,看起来一样,但不同版本行为可能有差异,都写清楚最稳妥。

allow-query-cache控制的是"谁能查缓存里的内容",把它和递归一起限定住,基本就堵死了被当作开放解析器的可能。配置完可以自测一下:从一台不在允许列表里的机器上发起查询,应该被拒绝(返回REFUSED),而不是正常返回结果。

4.3 rndc 与配置热重载

改完区域文件要不要重启服务?不需要,而且不推荐。重启会清空缓存,短时间内所有查询都回源,对上游和你自己都是压力。正确的做法是用rndc做重载和状态查询。

先配置好rndc,它需要一个 key 文件和对应的控制端口:

rndc-confgen -a systemctl restart named

之后常用命令就这么几个:

rndc status # 看服务整体状态、当前查询数 rndc reload # 重载全部配置和区域 rndc reload test.internal # 只重载某个区域 rndc flush # 清空缓存 rndc querylog # 开/关查询日志,排查时很有用

改区域文件后的标准动作是:先named-checkzone校验,再rndc reload 区域名,然后立刻用dig验证一遍新记录。这套动作可以在几十秒内完成,用户完全无感。

一个必须记住的点:只改区域文件内容、不改序列号,重载是不生效的。BIND 会认为序列号没变,区域没有更新,直接跳过。这个坑我踩过,当时改了记录怎么查都是旧值,反复看文件确认没错,最后发现就是序列号忘了改。养成习惯:动区域文件的第一件事就是先把序列号加一。

4.4 主从架构:第二台服务器怎么接进来

单台服务器意味着单点故障,尤其是当全公司机器的 DNS 都指向它的时候,它一挂,所有人上不了网。主从架构解决的就是这个问题,配置量不大,收益很直接。

在主服务器上,已经通过allow-transfer { slaves; }授权了从服务器地址。在从服务器上,区域声明把type改成slave(新版本也支持写secondary),并用masters指向主服务器:

zone "test.internal" IN { type slave; masters { 192.168.10.10; }; file "/var/named/zones/slave/test.internal.zone"; };

注意从服务器的file路径指向一个可写目录,因为区域数据是它自己从主服务器拉下来保存的,不会去读主服务器上的源文件。这个目录的权限和 SELinux 标签要单独确认,不然会同步失败。

主服务器上也可以加also-notify,在区域更新时主动推送通知给从服务器,比等Refresh周期到了再来拉要快得多:

zone "test.internal" IN { type master; file "/var/named/zones/test.internal.zone"; allow-transfer { slaves; }; also-notify { 192.168.10.11; }; };

验证同步是否成功,最直接的办法是在从服务器上dig查询,看返回的flags里有没有aa(权威应答),以及ANSWER段内容是否和主服务器一致。也可以看从服务器上的区域文件有没有被实际写入,文件有内容且时间戳在更新,说明同步链路是通的。

客户端配置层面,把主从两个地址都写进resolv.conf就够了,系统会先问第一个,超时再问第二个。别指望它做健康检查,第一个地址如果不响应,每次查询都要等一次超时,体验会很差。所以主服务器故障时要尽快把地址顺序调过来,或者干脆把主从地址在网关上做统一分发。

5. 客户端接入与验证:别只看能 ping 通

服务端配置完,最后一公里是客户端怎么接进来、怎么确认真的生效了。这一节看起来简单,实际上问题不少,尤其是现在很多发行版默认跑了本地解析守护进程,你改了resolv.conf可能几分钟后就被覆盖回去。

5.1 改 resolv.conf 的正确姿势与持久化

最直接的方式是编辑/etc/resolv.conf:

nameserver 192.168.10.10 nameserver 192.168.10.11 search test.internal

search这一行很实用,配上之后用户在访问db01时会自动补全成db01.test.internal,不用每次打全名。但要注意search列表不要写太长,超过两三个会影响解析性能,因为每次失败都要依次尝试。

问题在于现代发行版里/etc/resolv.conf经常是个软链接,指向systemd-resolved或者网络管理工具生成的运行时文件,你手改的内容在服务重启或者网络重连后会被冲掉。判断办法:

ls -l /etc/resolv.conf

如果它指向/run/systemd/resolve/stub-resolv.conf之类的位置,就得改上层配置而不是直接改这个文件。用systemd-resolved的机器可以这样设:

resolvectl dns eth0 192.168.10.10 192.168.10.11 resolvectl domain eth0 test.internal

走 NetworkManager 的机器,改连接配置里的 DNS 项,然后重新激活连接,改动才会持久。这块不搞清楚,你会陷入"配好了、能用、过一会儿又不行"的循环,浪费大量时间。

5.2 用 dig 读懂一次完整解析

dig是验证 DNS 的瑞士军刀,比nslookup信息全,建议养成习惯用它。

dig @192.168.10.10 db01.test.internal A ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 43215 ;; flags: qr aa rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 1, ADDITIONAL: 1 ;; ANSWER SECTION: db01.test.internal. 600 IN A 192.168.10.21

几处关键信息值得记住。status: NOERROR表示查询成功;REFUSED是访问控制拒绝了你的来源;SERVFAIL通常是区域加载有问题或者上游不可达;NXDOMAIN是名字确实不存在。flags里的aa表示这是权威应答,来自本地区域文件而不是缓存;ra表示服务器支持递归。ANSWER段里的600就是 TTL,客户端会缓存这么多秒。

想看完整的解析链路,就用+trace,它会从根开始一步步打印每一跳的查询过程,理解 DNS 分层结构最直观的方式就是跑一次这个。查反向解析用-x:

dig @192.168.10.10 -x 192.168.10.21

如果这里返回正常而正向也正常,说明正反两套区域都配对了。查不到的时候,先用+norecurse排除递归的影响,确认权威数据本身是否存在,能快速缩小范围。

5.3 批量验证与简单压测

单条dig只能验证个别记录,实际交付前应该做一轮批量检查,把所有关键名字跑一遍,别等用户报错才发现漏配。

for h in ns01 db01 db02 web www mail; do printf "%-10s " "$h" dig +short @192.168.10.10 "$h.test.internal" A done

+short只输出结果,适合脚本处理。跑完一眼就能看出哪条没解析出来。想批量验证正反一致,可以导出所有 A 记录,逐个做反向查询比对。

简单压测可以用dnsperf之类的工具,也可以用最朴素的循环。对小型内网来说,重点不是扛多少 QPS,而是确认在几十个并发下响应时间没有明显劣化。我一般会观察两个指标:rndc status里的缓存命中情况,和dig的Query time平均值。缓存命中率上来了,正常查询应该稳定在毫秒级。

6. 故障排查实录:六个常见坑与定位顺序

配置和验证都过了,剩下的时间大概率会花在排查上。这一节我把实际遇到过的问题按类型整理出来,附上定位思路。有个总原则值得先记住:从下往上排查,先确认链路,再确认服务,最后才看配置。反过来做,你会在一堆本来没问题的配置里绕圈子。

6.1 服务起不来:从日志和 checkconf 入手

systemctl start失败时,第一件事看状态和日志,别猜:

systemctl status named -l journalctl -u named -n 50 --no-pager tail -n 100 /var/log/named/named.log

报错信息通常很明确。loading configuration: permission denied基本就是 SELinux 或者文件权限;zone xxx/IN: loading from master file failed: file not found是路径写错,注意相对路径是相对于directory选项的;not at top of zone通常是区域文件里漏了$TTL或者 SOA 位置不对;bad zone name是区域名写错了。

改完之后先named-checkconf校验主配置,再named-checkzone 区域名 文件路径校验区域文件,两个都通过再重启。这套流程走下来,能过滤掉九成的启动类问题。

还有一个容易被忽视的:端口被占用。ss -lntup | grep :53看一眼,如果被systemd-resolved占着,就把它停掉或者让named监听其他地址,别在两台服务之间抢同一个端口。

6.2 服务起来了但解析不了:六层排查顺序

服务在跑,客户端查不通,按这个顺序走,能最快定位到问题在哪一层:

层序检查项命令异常表现与对策
1进程是否在跑systemctl status named未运行先解决启动问题
2端口是否监听ss -lntup | grep :53只监听 127.0.0.1 说明 listen-on 要改
3本机能否自查dig @127.0.0.1 区域名本机不通多半是区域文件或语法问题
4内网能否访问dig @服务器IP 区域名不通查防火墙、SELinux、listen-on
5是否被拒绝看返回 statusREFUSED 说明 allow-query 没放行来源
6记录是否正确named-checkzone区域加载正常但值不对,检查序列号是否更新

第 3 层和第 4 层的区别特别有用。本机@127.0.0.1查得到,说明服务和区域文件都没问题,问题在链路和访问控制;本机都查不到,那就是配置本身的毛病,别往网络方向查。我自己排查时基本都是一步到位先做第 3 层,能用它砍掉一半的可能性。

另一个高频问题是"改了记录不生效"。前面提过序列号,这里再强调一次:先看 TTL 有没有过期,用dig +nocache绕开缓存直接问服务器,如果服务器返回的已经是新值,那问题在客户端缓存,等 TTL 到期或者手动清一下就行;如果服务器返回还是旧值,那就是没重载或者序列号没改。

6.3 常见问题速查表

把实际遇到过的问题整理成表,出问题时先扫一遍,比自己从零想快得多。

现象可能原因处理方式
服务启动失败,报 permission deniedSELinux 标签不正确restorecon -Rv或调整策略
客户端查询超时防火墙未放行 UDP 53放行 TCP/UDP 53
返回 REFUSED来源不在 allow-query 名单添加对应网段
返回 SERVFAIL区域加载异常或上游不可达查日志、named-checkzone、测上游
大响应失败、小查询正常只放行了 UDP补放 TCP 53
修改记录后查不到新值序列号未变或客户端缓存序列号加一 +rndc reload
ping 主机名慢几秒反向区域缺失补齐 PTR 记录
主从数据不一致序列号相同或 transfer 未授权检查 allow-transfer 和序列号
解析偶发失败上游不稳定或双上游配置混乱精简上游地址,看日志确认

这张表不是万能的,但它覆盖了日常九成以上的情况。遇到表里没有的,回到"六层排查"的思路按顺序走,绝大多数问题都能收敛到某一层。

6.4 几个只有踩过才知道的细节

第一个细节是关于search域和后缀补全的。客户端查db01失败时,系统会依次尝试db01.test.internal,如果配了多个搜索域,还会逐个试一遍。搜索域配多了,一次失败的查询会变成五六个实际请求,全部超时之后才返回结果,用户就感觉"特别慢"。如果内网里经常有查不存在名字的情况(比如某些软件的探测行为),把搜索域精简到一个,或者在客户端侧做统一规范,效果立竿见影。

第二个细节是日志的量。category queries打开之后,每次查询都写一行,QPS 稍微高一点磁盘就被刷得很快。建议日常关掉,只在排查窗口里用rndc querylog临时打开,定位完立刻关掉。日志文件本身也要配versions和size做轮转,上面的配置里写了保留 5 个、每个 20M,这是我觉得比较合理的量。

第三个细节是allow-transfer一定要显式写。默认放开的区域传输会把你的完整记录表暴露出去,虽然内网风险没那么大,但这是习惯问题。反过来,从服务器那边如果不通,先确认这条有没有放行从服务器地址,这是主从同步失败最常见的原因之一。

第四个是备份。区域文件是纯文本,丢起来也干脆。改之前先cp一份带日期的副本,或者干脆放进版本管理里,改动记录一目了然。这算不上什么高深技巧,但在改错记录要回滚的时候,它比任何排查技巧都管用。

我个人在几十套环境里反复搭这套东西,最后留下来的习惯其实就三条:改区域文件先改序列号、每次改完先named-checkzone再rndc reload、新环境上线前一定用不在白名单里的地址测一次拒绝行为。前两条省下了大量的反复折腾,第三条让我确认安全策略真的生效了,而不是写在配置里看着像生效了。至于选 BIND 还是更轻量的方案,看你到底想把 DNS 当黑盒用,还是想把它弄明白——想弄明白,BIND 的配置文件本身就是最好的教材。

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

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

立即咨询