DNS 这个东西,很多人平时都在用,但真要自己动手搭一套,尤其是用 CentOS 8 这种已经步入维护后期的系统来搭,还是有不少门道。我之前给一个内部测试环境做域名统一管理时,就折腾过正向解析和反向解析,踩了不少坑,也积累了一些经验。这篇就把整个过程拆开来讲,从环境准备、配置文件写法,到验证方法、排障技巧,一步步说清楚,大家可以直接照着操作。不管你是刚接触 Linux 服务运维的新人,还是准备把内网域名规范化的开发者,这篇内容都能帮你省下不少摸索的时间。
1. 方案设计:为什么选择 BIND,以及环境准备
1.1 为什么不用 dnsmasq,而选 BIND
说到内网 DNS,很多人第一反应是 dnsmasq,因为它配置简单、占用资源小。但 dnsmasq 更适合做缓存转发,或者给小型局域网提供基础解析。如果解析记录超过几十条、需要管理多个域、还要做规范的正反向解析,dnsmasq 的配置就会变得很难维护。BIND(Berkeley Internet Name Domain)是互联网上使用最广泛的 DNS 服务器软件,CentOS 8 官方仓库里对应的包名是 bind,配置语法通用性强,排障资料也多,在内网环境里做权威解析是最稳妥的选择。
这里也要说一句题外话:CentOS 8 已经停止维护了,但它的包管理方式、服务管理方式和 RHEL 8 几乎一致,这篇内容里的配置思路,用到 RHEL 8、Rocky Linux 8、AlmaLinux 8 上都可以直接照搬。
1.2 测试环境规划
我用的环境是一台最小化安装的 CentOS 8 虚拟机,IP 地址规划如下:
- 服务器 IP:192.168.56.10
- 主机名:ns1.example.lab
- 需要解析的域名:example.lab
- 客户端 IP:192.168.56.20(用于验证解析结果)
为什么要单独弄一个示例域名 example.lab?因为这是内网测试环境,不涉及公网注册,用 .lab 结尾的域名既不会和真实域名冲突,也能让配置过程更清晰。大家实际使用的时候,可以替换成自己公司内部的域名,比如 internal.company.local 之类的。需要注意的是,如果公司域名将来要暴露到公网,内网 DNS 和公网 DNS 的解析逻辑要提前规划好,别让内网记录和外网记录互相覆盖。
1.3 安装 BIND 及相关组件
安装过程非常简单,CentOS 8 默认的 dnf 源里就有:
dnf install -y bind bind-utilsbind 是 DNS 服务端主程序,bind-utils 里包含了 dig、nslookup、host 这些排查工具,必须一起装。很多人在服务器上配好了 DNS,结果发现手里连个 dig 都没有,那就很尴尬了。
装完之后先确认服务名:
systemctl status named在 CentOS 8 上,BIND 的服务名是 named,不是 dnsmasq,也不是 bind9。这个细节经常有人搞混,启动时报错找不到服务,多半就是名字写错了。
2. 正向解析配置:把域名解析成 IP
2.1 主配置文件 /etc/named.conf
BIND 的主配置文件是 /etc/named.conf,默认内容已经写好了基本框架,包括根域提示文件 named.ca、本地回环地址的解析。我们只需要在文件末尾追加自己的 zone 配置。
先看默认的 options 块,这里有两个关键点必须改,否则客户端根本查不到这台服务器:
options { listen-on port 53 { 127.0.0.1; }; listen-on-v6 port 53 { ::1; }; allow-query { localhost; }; };默认情况下,BIND 只监听本机回环地址,只允许本机查询。如果局域网内其他机器要用这台服务器做解析,就必须改成监听所有网卡,以及允许所有客户端查询:
options { listen-on port 53 { any; }; listen-on-v6 port 53 { any; }; allow-query { any; }; };有的教程会直接写成listen-on port 53 { 192.168.56.10; };,只监听内网 IP,这样更严谨,但会增加一些配置上的限制。如果是在受控的内网环境,直接 any 是最省事的。如果是生产环境,建议在防火墙上做限制,把 DNS 端口只开放给需要的网段。
然后在文件末尾追加正向解析的 zone 定义:
zone "example.lab" IN { type master; file "example.lab.zone"; allow-update { none; }; };- type master:表示这台服务器是主权威服务器,数据以本地文件为准。
- file:指定区域数据文件的文件名。注意这个路径是相对 /var/named 目录的,完整路径是 /var/named/example.lab.zone。
- allow-update { none; };:禁止动态更新,内网环境用静态文件管理足够了。允许动态更新会增加安全风险,没有特殊需求一律不要开。
2.2 创建正向区域数据文件
在 /var/named 目录下创建 example.lab.zone 文件:
vim /var/named/example.lab.zone内容如下:
$TTL 1D @ IN SOA ns1.example.lab. admin.example.lab. ( 2025011001 ; serial 1D ; refresh 1H ; retry 1W ; expire 3H ) ; minimum IN NS ns1.example.lab. ns1 IN A 192.168.56.10 www IN A 192.168.56.10 mail IN A 192.168.56.11这里逐行解释一下,很多人照着抄,但不明白每行的含义,出了问题就不知道怎么改:
$TTL 1D:默认的缓存时间,意思是如果客户端查询到这条记录,在没有特殊指定 TTL 的情况下,可以缓存一天。内网环境改动频繁的话,可以把 TTL 调小,比如 5 分钟,代价是客户端的查询请求会变多。
SOA 记录是区域数据的起点,后面的括号里依次是序列号、刷新时间、重试时间、过期时间、否定缓存时间。序列号是排障的关键:它告诉其他服务器“这个区域的版本号是多少”,如果有从服务器做区域传输,或者客户端缓存到期后重新查询,主服务器要判断数据是否有变化,就靠这个数字。每次修改区域数据,序列号必须递增,否则从服务器不会同步。内网可能没有从服务器,但养成递增的好习惯很重要,否则将来一旦加了从服务器,就会遇到莫名奇妙的“数据不生效”问题。
NS 记录声明这个域名由谁来做权威解析,这里是 ns1.example.lab。
A 记录把主机名指向 IP。注意 ns1 这个主机名必须和 SOA 记录里的主机名一致,否则一些较真的 DNS 检测工具会报 mismatch。
文件保存后,用 named-checkzone 检查语法:
named-checkzone example.lab /var/named/example.lab.zone如果输出显示 OK,说明格式没问题。注意这个命令第一个参数是域名,第二个参数是区域文件完整路径,只写文件名会提示找不到文件。
2.3 域名文件权限问题
这是很多人踩过的一个坑。CentOS 8 上 BIND 服务以 named 用户运行,named 用户对区域文件必须有读取权限。最稳妥的做法是:
chown root:named /var/named/example.lab.zone chmod 640 /var/named/example.lab.zone文件属主是 root,属组是 named,权限是 640,也就是 root 可读写、named 组可读。如果你把文件的属主改成了别的用户,或者权限设成了 600,named 启动时会报 permission denied,服务状态显示 active(running),但日志里全是错误。这种问题最迷惑人,因为服务进程没挂,就是查询不出来。
2.4 关闭 SELinux 还是放行?
CentOS 8 默认是开启 SELinux 的,很多人上来就setenforce 0,这样做确实省事,但我不推荐。BIND 官方其实已经给 SELinux 写好了策略,只需要打开对应的布尔值:
setsebool -P named_write_master_zones 1这个布尔值允许 named 进程读写主区域文件。如果不做这一步,即使文件权限是对的,named 也可能因为 SELinux 拦截而无法读取文件内容,现象同样是服务看起来在运行,但解析不出来。
如果只是在测试环境折腾,临时关闭 SELinux 也不是不行,但每次重启系统都要记得重新关闭。生产环境还是建议按正规流程放行。
3. 反向解析配置:让 IP 能查到域名
3.1 为什么需要反向解析
正向解析解决的是“域名 → IP”的问题,反向解析解决的是“IP → 域名”的问题。很多服务在做认证、审计和日志分析时,会尝试反向解析客户端 IP,比如 SSH 登录日志里如果只有 IP 没有主机名,排查起来就得手动查,很麻烦。另外一些邮件服务器会做反向解析校验,如果找不到对应记录,可能会直接拒绝连接。
所以别看反向解析在 Web 访问场景里用得少,运维系统做得规范一点,它是早晚要补上的功能。
3.2 反向区域的命名规则
反向解析的 zone 名称有一个固定写法,必须牢记:IP 网段倒写,加上 in-addr.arpa 后缀。
比如我们要给 192.168.56.0/24 这个网段做反向解析,就要写成:
zone "56.168.192.in-addr.arpa" IN { type master; file "192.168.56.zone"; };规则看起来简单,但真的有人会写错。有一个小技巧:先写下 IP 地址 192.168.56,然后从后往前倒过来,就是 56.168.192,最后加上 in-addr.arpa。这里只写前三个八位组,因为这个 zone 覆盖的是整个 192.168.56.0/24 网段,一个网段一个反向 zone,这是常规思路。
如果分配的是 192.168.0.0/16 这种大段地址,也可以只倒写前两个八位组,但那样的话管理粒度太粗,不建议。
3.3 创建反向区域数据文件
创建 /var/named/192.168.56.zone:
vim /var/named/192.168.56.zone内容如下:
$TTL 1D @ IN SOA ns1.example.lab. admin.example.lab. ( 2025011001 ; serial 1D ; refresh 1H ; retry 1W ; expire 3H ) ; minimum IN NS ns1.example.lab. 10 IN PTR ns1.example.lab. 20 IN PTR client.example.lab.这里的关键是 PTR 记录。比如第 10 行的含义是:IP 192.168.56.10 的解析结果指向 ns1.example.lab。注意写的是数字 10,而不是完整的 192.168.56.10,因为在反向 zone 里,前面的网段部分已经由 zone 名确定了,只需要写主机位。
还有一个小细节:PTR 记录后面跟的主机名,末尾必须要有一个点,写成 ns1.example.lab.,不能写成 ns1.example.lab。这个点代表域名根,少了它,BIND 会自动在结尾补上 zone 名,结果就会变成 ns1.example.lab.56.168.192.in-addr.arpa,完全不是你想要的东西。正向解析里的 NS、SOA、A 记录也是一样的道理,域名末尾的点千万不能省。
检查反向区域文件:
named-checkzone 56.168.192.in-addr.arpa /var/named/192.168.56.zone这里的第一个参数必须和 named.conf 里的 zone 名称完全一致,否则语法检查会提示 mismatch。
3.4 正向和反向的对应关系
内网环境经常会遇到一种情况:主机有 A 记录,但对应的反向记录没配,或者配了反向记录但没有对应的 A 记录。严格来说,PTR 记录和 A 记录不是一对一绑定的,你完全可以让一个 IP 解析回一个不存在的域名,DNS 服务器不会拦你。
但从统一管理角度看,建议正向反向记录保持一致。比如:
- ns1.example.lab 对应 A 记录 192.168.56.10
- 192.168.56.10 对应 PTR 记录 ns1.example.lab
这样将来审计日志、流量分析时不会出现“域名到底是谁”的困惑。很多内部监控系统做主机名关联时,就是优先通过 IP 反查域名再匹配系统信息,正反向不一致会导致监控列表里出现一堆看不懂的 IP。
4. 启动服务与功能验证
4.1 启动并加入开机自启
配置文件都准备好之后,启动服务:
systemctl start named systemctl enable named启动之前建议再跑一遍配置检查:
named-checkconf这个命令如果不输出任何东西,说明主配置文件语法没问题。如果有错误,它会明确指出第几行出错。
启动之后查看服务状态:
systemctl status named看到 active (running) 基本就算起来了。但服务起来了不代表解析一定正常,接下来要通过实际查询来验证。
4.2 防火墙放行 DNS 端口
CentOS 8 默认的 firewalld 是开启的,DNS 服务使用 53 端口,TCP 和 UDP 都要放行。TCP 用于区域传输和超长响应,UDP 用于常规查询。很多人在本机测试 dig 能通,但其他机器一查就超时,十有八九是防火墙没放。
firewall-cmd --permanent --add-service=dns firewall-cmd --reload为什么用--add-service=dns而不是直接开放端口?因为 firewalld 自带的服务定义里已经包含了 53/tcp 和 53/udp,这样写更语义化,也更安全。确认放行结果:
firewall-cmd --list-services输出里应该包含 dns。
4.3 用 dig 验证正向解析
在 DNS 服务器本机或者内网任意一台客户端上,执行:
dig @192.168.56.10 www.example.lab其中 @ 后面的 IP 指定使用这台服务器做查询,避免走系统默认的 /etc/resolv.conf 配置,这样能确认真正生效的是我们刚搭的服务器。执行结果里重点关注 ANSWER SECTION:
;; ANSWER SECTION: www.example.lab. 86400 IN A 192.168.56.10如果能出现这行,说明正向解析已经通了。顺便看一下 STATUS 字段,应该是 NOERROR。如果是 SERVFAIL 或者 REFUSED,说明配置还有问题,排查思路会在第 6 节详细讲。
再验证一下 NS 记录和 SOA 记录:
dig @192.168.56.10 NS example.lab dig @192.168.56.10 SOA example.lab这两个查询能快速确认区域数据文件是否被正确加载。如果 A 记录能查出来,但 SOA 查询失败,多半是 zone 定义和数据文件里的域名不匹配。
4.4 用 dig -x 验证反向解析
反向解析的验证命令要加 -x 参数:
dig -x @192.168.56.10 192.168.56.10注意这个命令的写法:-x 参数表示反向查询,@192.168.56.10 指定 DNS 服务器,192.168.56.10 是要查询的 IP。输出里的 ANSWER SECTION 应该包含:
;; ANSWER SECTION: 10.56.168.192.in-addr.arpa. 86400 IN PTR ns1.example.lab.如果看到这条 PTR 记录,反向解析就配好了。
4.5 把客户端 DNS 指过来
服务器端配置完成后,客户端还需要把 DNS 修改为这台服务器。修改方式简单粗暴,直接编辑 /etc/resolv.conf:
nameserver 192.168.56.10但这里有个坑:如果客户端使用了 NetworkManager,它可能会自动覆盖 /etc/resolv.conf 的修改。在 CentOS 8 客户端上,可以通过 nmcli 来改:
nmcli con mod eth0 ipv4.dns "192.168.56.10" nmcli con up eth0这里的 eth0 要替换成实际的网卡名称,可以用nmcli con show查看。修改完成后,用cat /etc/resolv.conf确认 nameserver 是否已经变成目标地址。如果还是原来的 DNS,说明 NetworkManager 没有接管写权限,需要检查 /etc/NetworkManager/NetworkManager.conf 里的 dns 配置项。
5. 常见问题排查与避坑记录
5.1 服务启动失败,日志提示 permission denied
很多人配置好之后,systemctl start named 直接报失败,journalctl 日志里能看到类似这样的信息:
could not open file '/var/named/example.lab.zone': permission denied排查步骤:
- 检查文件属主:
ls -l /var/named/example.lab.zone,确认属组是 named。 - 检查权限:
getfacl /var/named/example.lab.zone,权限至少是 640。 - 检查 SELinux 布尔值:
getsebool named_write_master_zones,这是排障顺序,千万别一上来就关 SELinux。
我记得有一次在某套环境中,文件权限全对,SELinux 也放行了,但服务还是起不来。最后发现是 /var/named 目录本身的 SELinux 上下文被改坏了,变成了 default_t。修复命令是:
restorecon -Rv /var/named这个命令会把目录下所有文件的 SELinux 上下文恢复到默认策略。这类问题相当隐蔽,常规排查工具都查不出什么异常,只有 restorecon 能救回来。
5.2 改完配置不生效
正向解析和反向解析配好之后,客户端查询,结果还是旧数据。我来复盘一下当时的情况:一开始配置文件里 serial 是 2025011001,后来增加了一条记录,只改了数据文件,没有改 serial。从服务器和客户端的缓存里还是旧数据,导致“改了半天,查出来还是原来那个”。
解决方案很简单:每次修改区域数据文件之后,先named-checkzone确认无误,然后把 serial 加 1,最后执行:
rndc reload这个命令会让 BIND 重新加载区域文件,不需要重启服务。重启服务会造成短暂的服务中断,内网环境可能不明显,但生产环境这种操作越少越好。
还有一个细节要说:serial 不能用时间戳的位数当整数来随意乱改,只要保证递增即可。我的习惯是写成年月日加序号格式,比如 2025011001,一天内多次修改就递增最后两位。即便是同一天改 20 次,序号也能足够覆盖。
5.3 服务正常但客户端查询超时
服务明明显示 active (running),但 dig @192.168.56.10 却超时,然后报 connection timed out。这类问题要从三个层面排查:
第一,防火墙。检查 firewalld 是否放行了 dns 服务:firewall-cmd --list-services。如果输出里没有 dns,按前面的步骤补上。
第二,监听地址。在服务器上执行:
ss -lunp | grep 53看到 192.168.56.10:53 或者 0.0.0.0:53 才算正常。如果只看到 127.0.0.1:53,说明 /etc/named.conf 里的 listen-on 没改,客户端请求进不来。
第三,网络连通性。从客户端 ping 192.168.56.10,确认基础网络是通的。有时候根本不是 DNS 的问题,是客户端到服务器之间的网络路由有问题,折腾半天 DNS 配置,最后发现是防火墙策略把整个网段都隔离了。
5.4 查询返回 SERVFAIL
SERVFAIL 的意思是服务器内部出错了,这个错误信息比超时更有价值,因为它说明网络是通的,是配置有问题。常见原因是区域数据文件格式错误,或者文件里的域名和 named.conf 里的 zone 名称不匹配。
处理办法:先跑named-checkzone example.lab /var/named/example.lab.zone,根据提示修正格式错误。然后去日志里看具体报错:
journalctl -u named -f重新触发一次查询,日志会直接告诉你哪一行哪个字段有问题。BIND 9 的报错信息相对友好,比如 missing ')' 之类的,定位问题很快。
5.5 客户端 resolv.conf 被覆盖
刚才已经提过 NetworkManager 覆盖 resolv.conf 的问题,这里再展开一下。很多人在客户端手动改了 nameserver,重启网络之后发现被还原了,原因是 NetworkManager 认为它才是网络配置的唯一管理者。
最好的做法就是不直接改 /etc/resolv.conf,而是用 nmcli 修改连接配置:
nmcli con mod eth0 ipv4.ignore-auto-dns yes nmcli con mod eth0 ipv4.dns "192.168.56.10" nmcli con up eth0第一条的意思是不使用 DHCP 下发的 DNS,第二条指定我们自己的 DNS 服务器,第三条让配置生效。这套操作在 CentOS 8 的客户端上实测是稳的,重启网络也不会丢。
5.6 域名字符串末尾的“点”
这个问题单独拿出来强调,因为太常见了。区域数据文件里,凡是完整的域名,比如 ns1.example.lab,末尾必须带点,写成 ns1.example.lab.。如果忘了写点,BIND 会把 zone 名追加在后面,变成 ns1.example.lab.example.lab,DNS 查询自然找不到。
解决方案没有捷径,只能在写配置时养成习惯:正向区域文件里,所有 FQDN(完全限定域名)都带点;反向区域文件里,PTR 记录后面的主机名必须带点。可以通过named-checkzone提前发现部分问题,但它不会检查这种“域名变成了更长域名”的逻辑错误,最终还是得靠测试查询来暴露。
5.7 配置项速查表
我在日常排障中习惯整理一个小表格,方便快速定位问题,分享给大家:
| 现象 | 可能原因 | 排查命令 | 处理方法 |
|---|---|---|---|
| 服务启动失败 | 配置语法错误、文件权限不足 | named-checkconf、journalctl -u named | 按日志提示修正 |
| 查询超时 | 防火墙未放行、listen-on 未改 | firewall-cmd --list-services、ss -lunp | 放行 dns、修改监听地址 |
| 查询 SERVFAIL | 区域文件格式错误 | named-checkzone | 修正数据文件 |
| 查询结果不更新 | serial 未递增、缓存未刷新 | rndc reload | 递增 serial |
| 客户端被还原 | NetworkManager 覆盖 | cat /etc/resolv.conf | 用 nmcli 配置 |
6. 排障工具箱:rndc 与日志分析实战
6.1 rndc 的常用操作
rndc 是 BIND 自带的管理工具,调试时非常实用。除了前面提到的rndc reload,还有几个命令值得掌握:
rndc status显示 named 的当前状态,包括版本号、区域数量、CPU 和内存使用情况。升级或者迁移环境时,这个命令可以快速确认服务是否稳定。
rndc stats把当前的统计信息写到 /var/named/data/named_stats.txt 文件里,包括查询次数、各类记录类型命中次数。当你需要判断某台 DNS 服务器是不是真的在被大量客户端使用时,这个文件是很好的参考。
rndc flush清空服务器缓存。注意这个命令对权威区域的查询结果没有影响,只影响转发缓存。内网环境默认不开启递归转发,所以一般用不到,但在混合环境里,清缓存是排查“修改不生效”的重要手段。
这几个命令列在这里,不是说每次配置都得用上,而是遇到问题的时候要知道有这些工具。DNS 排障很多时候靠的就是快速缩小范围,rndc status、rndc reload、rndc flush 三件套基本能覆盖一多半初期问题。
6.2 用日志定位问题
BIND 的日志默认写到 /var/log/messages,也可以通过 journalctl 查看:
journalctl -u named -f-f 参数是持续跟踪日志输出。重新触发一次查询,比如在客户端执行dig @192.168.56.10 www.example.lab,然后观察日志变化。如果查询成功,日志里可能不会有太多内容;如果查询失败,日志会明确告诉你哪个 zone 加载失败、哪个文件解析出错。
有一种情况值得注意:有时候日志里会反复出现 network unreachable 之类的信息,但服务本身明明能正常解析。这通常是 IPv6 环境问题,BIND 尝试使用 IPv6 地址进行某些操作没有成功,但不会影响 IPv4 的解析。如果觉得日志太吵,可以在 named.conf 的 options 块里把 IPv6 监听关掉:
listen-on-v6 { none; };只需要 IPv4 环境的话,这样配置会省掉很多环境噪音,排查问题时更容易聚焦。
7. 我的几点实操体会
最后再聊几句个人经验。搭建正反向解析本身并不复杂,但真正考验人的是理解记录类型之间的关系:SOA 决定区域权威性,NS 决定解析流向,A 记录决定域名最终指向,PTR 记录负责 IP 地址的反查。这几类记录在内网环境中各司其职,缺一条或者错一条,都会引发难缠的连锁问题。我的建议是,不管环境多小、多临时,都把这四种必用记录规规矩矩配好,千万别图省事只写一条 A 记录然后万事大吉。另外,配置完成后不要只在本机 dig,一定要找一台真正的客户端机器实际查询一遍,这样防火墙、网络连通性之类的问题才能提前暴露出来。现在每次搭完 DNS,我习惯再做一轮正反向对应检查,确认所有 A 记录都能反查回正确的 PTR,数据文件序列号也做好备注,再进入下一步。这套流程虽然在前期多花十几分钟,但后面省下的排查时间远超投入。