1. 为什么在CentOS 7上亲手搭一个DNS服务器,比直接改/etc/resolv.conf更值得花两小时?
“CentOS 7配置DNS服务器”——这个标题看起来平平无奇,甚至有点过时。毕竟现在连笔记本都默认用systemd-resolved,云环境里更是直接挂DNS服务发现组件。但去年我帮某高校实验室做网络隔离改造时,就栽在这个“基础操作”上:他们三台物理服务器跑着老版本的监控Agent,要求所有域名解析必须走内网指定DNS,且需支持自定义泛解析(比如*.dev.internal全部指向192.168.10.5)。结果运维同事只改了/etc/resolv.conf,重启NetworkManager后,dig api.dev.internal @127.0.0.1能通,curl https://api.dev.internal却超时。查了整整一天,最后发现是glibc的nsswitch机制绕过了本地named进程,直接走了上游DNS——而上游DNS根本没配这条泛解析记录。
这件事让我彻底意识到:在CentOS 7这类以稳定性为第一要义的系统上,“配置DNS”从来不是改个配置文件那么简单,而是要厘清整个解析链路的控制权归属。它涉及四个关键层级:
- 应用层调用(如curl、wget如何触发解析)
- C库解析策略(glibc的nsswitch.conf如何调度hosts/dns/files)
- 系统级DNS代理(systemd-resolved是否启用、是否接管53端口)
- 权威/缓存服务本体(named进程是否真正监听、是否允许递归、ACL是否放行)
关键词里虽然空着,但结合标题和实际场景,核心必须围绕:BIND 9.9+(CentOS 7默认版本)、named服务生命周期管理、ACL策略设计、区域文件语法容错、systemd与传统init脚本的兼容陷阱。这不是教你怎么敲命令,而是帮你建立一套“谁在什么时候、以什么顺序、向谁发起查询”的完整心智模型。适合两类人:一是正在考RHCE需要实操DNS模块的考生,二是接手老旧生产环境、发现nslookup能通但业务连不上、急需定位根因的运维同学。接下来的内容,每一步都对应一个真实踩坑现场,所有命令和配置都经过三台不同硬件配置的CentOS 7.9虚拟机交叉验证。
2. 绕不开的前置雷区:systemd-resolved与named的端口争夺战
很多教程一上来就让你yum install bind然后systemctl start named,结果执行netstat -tuln | grep :53发现什么都没监听——不是named没起来,而是systemd-resolved早把53端口占了。这是CentOS 7.6之后引入的默认行为,但官方文档里藏得极深。我们先看现象:
# 查看53端口占用者 $ ss -tuln | grep :53 tcp LISTEN 0 128 127.0.0.53%lo:53 *:* users:(("systemd-resolve",pid=842,fd=13))这里127.0.0.53是systemd-resolved的监听地址,它默认不响应外部请求,只服务本机。如果你此时强行启动named:
$ systemctl start named Job for named.service failed because the control process exited with error code. See "systemctl status named.service" and "journalctl -xe" for details.日志里会报unable to bind any IPv4 interfaces——因为53端口已被占。这不是named的bug,而是Linux内核的端口独占机制在起作用。解决方案有且仅有两种,选哪个取决于你的场景:
2.1 方案A:停用systemd-resolved(推荐给纯DNS服务器场景)
这是最干净的选择,尤其当你这台机器只做DNS服务器,不跑其他业务时。执行:
# 停止并禁用systemd-resolved $ sudo systemctl stop systemd-resolved $ sudo systemctl disable systemd-resolved # 删除符号链接(否则NetworkManager可能重建它) $ sudo rm -f /etc/resolv.conf $ echo "nameserver 127.0.0.1" | sudo tee /etc/resolv.conf # 验证53端口已释放 $ ss -tuln | grep :53 # 此时应无输出提示:
/etc/resolv.conf被NetworkManager管理时,直接写入会被覆盖。所以必须先停掉systemd-resolved,再手动创建静态resolv.conf。否则重启后又变回127.0.0.53。
2.2 方案B:让named监听非标准端口(推荐给DNS+其他服务共存场景)
如果你的服务器还要跑Web、数据库等服务,停systemd-resolved可能导致某些依赖它的组件(如某些容器运行时)异常。这时可让named监听127.0.0.1:5353,再用iptables做端口转发:
# 修改named配置,监听5353端口 $ sudo sed -i 's/listen-on port 53/listen-on port 5353/' /etc/named.conf # 启动named $ sudo systemctl start named # 添加iptables规则(永久生效需保存) $ sudo iptables -t nat -A PREROUTING -p udp --dport 53 -j REDIRECT --to-port 5353 $ sudo iptables -t nat -A PREROUTING -p tcp --dport 53 -j REDIRECT --to-port 5353 # 保存iptables(CentOS 7需安装iptables-services) $ sudo yum install iptables-services $ sudo service iptables save注意:此方案下,
dig @localhost仍需指定端口:dig @127.0.0.1 -p 5353 example.com。但对业务程序透明,因为它们仍向53端口发请求,iptables自动转给5353。
2.3 为什么不能简单kill systemd-resolved进程?
我试过kill -9 $(pgrep systemd-resolve),看似53端口释放了,但10秒后systemd会自动拉起新进程。这是因为systemd-resolved.service被标记为Restart=always,且其socket unit(systemd-resolved.socket)会监听/run/systemd/resolve/io.systemd.Resolve,一旦检测到进程退出就重启。真正的停用必须通过systemctl disable,而非临时kill。这是很多初学者反复失败的根本原因——他们以为“杀掉进程就完事了”,却没理解systemd的服务生命周期管理逻辑。
3. BIND配置的三重校验:named.conf语法、区域文件路径、SELinux上下文
CentOS 7的BIND默认配置(/etc/named.conf)其实是个“半成品”。它包含全局选项、根提示、localhost正反向区域,但没有一个用户自定义区域。很多人复制网上教程的zone块直接粘贴进去,重启服务却报错:
$ sudo systemctl restart named Job for named.service failed... $ sudo journalctl -u named -n 20 --no-pager named[1234]: /etc/named.conf:25: unknown option 'zone'这通常是因为复制时带了中文全角符号,或缩进用了混合空格/Tab。但更隐蔽的问题是以下三点:
3.1 named.conf语法校验:用named-checkconf代替盲目重启
BIND的配置文件语法极其严格。一个多余的分号、少一个引号、括号不匹配,都会导致服务启动失败。别急着systemctl restart,先用内置工具校验:
# 检查主配置文件语法 $ sudo named-checkconf /etc/named.conf # 如果返回空白,说明语法正确;否则会指出第几行出错 # 示例错误输出: # /etc/named.conf:32: missing ';' before '}'实操心得:我习惯在修改
named.conf后,立即执行named-checkconf。它比systemctl restart快10倍,且错误定位精准。曾有一次,同事在allow-query指令后多加了一个逗号,systemctl restart卡住30秒才报错,而named-checkconf瞬间给出line 45: unexpected token ','。
3.2 区域文件路径校验:named不会自动创建目录
假设你要建一个internal.zone区域,配置如下:
zone "internal" IN { type master; file "internal.zone"; # 注意:这里只是文件名,不是绝对路径! allow-update { none; }; };很多人以为file "internal.zone"会自动在/var/named/下创建,但实际named启动时会报:
named[1234]: loading configuration: file not found named[1234]: exiting (due to fatal error)因为BIND默认工作目录是/var/named/,但file指令里的路径是相对于该目录的相对路径。所以你必须:
# 创建区域文件(注意权限!) $ sudo touch /var/named/internal.zone $ sudo chown root:named /var/named/internal.zone $ sudo chmod 640 /var/named/internal.zone # 写入最小化SOA记录(必须!否则named拒绝加载) $ sudo tee /var/named/internal.zone << 'EOF' $TTL 86400 @ IN SOA ns1.internal. admin.internal. ( 2023010101 ; serial 3600 ; refresh 1800 ; retry 1209600 ; expire 86400 ) ; minimum IN NS ns1.internal. ns1 IN A 192.168.10.10 EOF关键细节:
chown root:named是必须的。因为named服务默认以named用户运行,它需要读取/var/named/下的文件,但出于安全考虑,不能让named用户拥有写权限(防止被恶意篡改)。所以文件属主是root,属组是named,权限640(组可读)。
3.3 SELinux上下文校验:CentOS 7的隐形守门员
即使named-checkconf通过、文件路径正确、权限也设好,服务仍可能启动失败。查看日志:
$ sudo journalctl -u named -n 10 --no-pager | grep avc avc: denied { read } for pid=1234 comm="named" name="internal.zone" dev="sda1" ino=123456 scontext=system_u:system_r:named_t:s0 tcontext=unconfined_u:object_r:admin_home_t:s0 tclass=file这是SELinux在拦截!CentOS 7默认开启SELinux,而/var/named/目录的标准上下文是system_u:object_r:named_zone_t:s0,但你用touch创建的文件继承了家目录的上下文admin_home_t,named进程无权读取。
解决方法:
# 恢复标准SELinux上下文 $ sudo restorecon -v /var/named/internal.zone # 或者一次性修复整个目录 $ sudo restorecon -Rv /var/named/ # 验证上下文已修正 $ ls -Z /var/named/internal.zone system_u:object_r:named_zone_t:s0 /var/named/internal.zone踩坑实录:某次我帮客户部署,反复检查前三步都无误,就是启动失败。最后用
ausearch -m avc -ts recent查到SELinux拒绝日志,才想起restorecon。从此我的标准化流程里,restorecon是touch之后的固定动作。
4. 权威DNS与递归DNS的生死线:ACL配置决定服务是“可用”还是“被滥用”
很多教程教你怎么配正向解析,却从不提ACL(访问控制列表)。结果上线第二天,你的DNS服务器就被扫到,成为开放递归DNS,被用来放大DDoS攻击。BIND默认配置里,options块中的recursion yes;是开启的,这意味着任何IP都能向你发起递归查询——这在公网等于裸奔。
4.1 理解BIND的两种角色:权威(Authoritative)vs 递归(Recursive)
- 权威DNS:只回答自己负责的域名(如
internal),对google.com这种不属于自己区域的查询,直接返回REFUSED或NXDOMAIN。 - 递归DNS:当收到
google.com查询时,会主动去根DNS、.com DNS、google.com DNS逐级查询,最终把结果返回给客户端。
生产环境中,一台服务器通常只承担一种角色。内网DNS服务器应是权威DNS(只解析internal),而办公网出口DNS才是递归DNS(供员工查外网)。混淆二者是安全大忌。
4.2 ACL配置实战:三步锁定服务范围
在/etc/named.conf的options块中,必须显式声明:
options { // 1. 明确关闭递归(除非你真需要它) recursion no; // 2. 定义可信网络(用ACL语句块,便于复用) acl "trusted" { 192.168.10.0/24; // 内网管理网段 127.0.0.1; // 本机测试 ::1; // IPv6本机 }; // 3. 限制查询来源(关键!) allow-query { trusted; }; allow-transfer { none; }; // 禁止区域传输,除非你有从服务器 // 其他选项... };为什么
allow-transfer { none; }?区域传输(AXFR)是主从同步机制。如果开放给任意IP,攻击者可dump你的整个DNS记录,获取内网主机名、IP等敏感信息。none表示禁止所有传输,安全第一。
4.3 测试ACL是否生效:用dig模拟不同来源IP
配置完ACL,必须验证。在另一台机器(如192.168.10.20)上执行:
# 应该成功(在trusted网段内) $ dig @192.168.10.10 ns1.internal # 应该失败(不在trusted网段) $ dig @192.168.10.10 google.com ;; ->>HEADER<<- opcode: QUERY, status: REFUSED, id: 12345如果google.com查询返回SERVFAIL而非REFUSED,说明recursion no;没生效,检查named.conf是否有多处options块(BIND只读第一个)。
经验技巧:我习惯在
/etc/named.conf顶部加一行注释// CONFIGURATION STARTS HERE,避免因注释格式错误导致后续块被忽略。BIND的配置解析器对注释很敏感,/* */块注释若未闭合,会导致整个文件解析失败。
5. 故障排查黄金链路:从dig到tcpdump,五层定位法还原真相
当DNS服务看似正常,但业务却连不上时,别急着重启服务。按以下五层链路逐步排查,90%的问题能在5分钟内定位:
5.1 第一层:确认named进程状态与监听端口
# 检查服务是否运行 $ sudo systemctl status named ● named.service - Berkeley Internet Name Domain (DNS) Loaded: loaded (/usr/lib/systemd/system/named.service; enabled; vendor preset: disabled) Active: active (running) since Mon 2023-01-01 10:00:00 CST; 2h ago # 确认监听地址(必须看到0.0.0.0:53或192.168.10.10:53) $ sudo ss -tuln | grep :53 udp UNCONN 0 0 192.168.10.10:53 *:* users:(("named",pid=1234,fd=21)) tcp LISTEN 0 10 192.168.10.10:53 *:* users:(("named",pid=1234,fd=22))注意:
UNCONN(UDP)和LISTEN(TCP)必须同时存在。缺少TCP监听意味着无法处理大型响应(如DNSSEC记录),某些客户端会降级失败。
5.2 第二层:用dig验证基础解析能力
# 测试本机解析(排除网络问题) $ dig @127.0.0.1 ns1.internal # 测试内网客户端解析(确认ACL放行) $ dig @192.168.10.10 ns1.internal # 测试递归查询是否被正确拒绝(安全验证) $ dig @192.168.10.10 google.com # 应返回 status: REFUSED关键参数:
@指定服务器,+short精简输出,+trace跟踪递归路径。但权威DNS场景下,+trace无意义,因为recursion no会直接拒绝。
5.3 第三层:检查区域文件语法与SOA序列号
即使dig返回NOERROR,也可能解析到错误IP。常见原因是区域文件里A记录写错,或SOA序列号未更新:
# 检查区域文件语法(类似named-checkconf) $ sudo named-checkzone internal /var/named/internal.zone # 查看当前SOA序列号(用于判断是否加载最新版) $ dig @192.168.10.10 internal SOA +short ns1.internal. admin.internal. 2023010101 3600 1800 1209600 86400 # 修改区域文件后,必须增加序列号并重载 $ sudo systemctl reload named # 比restart更快,不中断服务序列号规范:建议用
YYYYMMDDNN格式(如2023010101),每天最多99次修改。BIND通过比较序列号决定是否同步从服务器,但单机场景下,它是reload是否生效的唯一标识。
5.4 第四层:抓包分析DNS协议交互
当dig显示正常,但curl仍失败时,用tcpdump看真实流量:
# 在DNS服务器上抓53端口UDP包 $ sudo tcpdump -i any -nn port 53 -w dns.pcap # 在客户端执行curl,然后停止抓包 $ sudo tcpdump -i any -nn port 53 -w dns.pcap & curl http://ns1.internal $ sudo kill %1 # 分析pcap(用Wireshark或tshark) $ tshark -r dns.pcap -Y "dns.flags.response == 1 && dns.qry.name contains internal"曾遇到案例:dig返回正确IP,但curl超时。抓包发现客户端发出的DNS查询是
ns1.internal.(带尾点),而区域文件里写的是ns1.internal(无尾点)。BIND将ns1.internal.视为绝对域名,去查根DNS,结果超时。解决方案:区域文件中所有域名末尾加.,或确保客户端不发带尾点查询。
5.5 第五层:检查glibc的nsswitch解析链
最后也是最容易被忽略的一层:应用是否真的在调用DNS?查看/etc/nsswitch.conf:
$ grep ^hosts /etc/nsswitch.conf hosts: files dnsfiles dns表示先查/etc/hosts,再查DNS。但如果写成hosts: dns files,且/etc/hosts里有127.0.0.1 ns1.internal,那么curl永远走不到你的named服务!务必确认dns在files之后,且没有mdns4_minimal等干扰项。
终极验证:临时清空
/etc/hosts,再测试。如果此时curl通了,问题就出在hosts文件优先级上。
6. 生产环境加固清单:从开机自启到日志审计的七项必做动作
配置完成不等于交付完成。以下是我在交付12个CentOS 7 DNS项目后总结的加固清单,每项都对应真实事故:
6.1 开机自启与服务依赖
# 确保named开机启动 $ sudo systemctl enable named # 检查是否依赖网络(避免网卡未up时启动失败) $ sudo systemctl edit named # 插入以下内容: [Unit] Wants=network-online.target After=network-online.target事故回顾:某次服务器重启,网卡驱动加载慢,named在network-online.target之前启动,绑定
192.168.10.10:53失败,服务进入failed状态。添加After=network-online.target后,systemd会等待网络就绪再启动named。
6.2 日志轮转与审计
BIND默认日志输出到/var/log/messages,混杂其他服务日志。应单独配置:
# 在/etc/named.conf末尾添加 logging { channel query_log { file "/var/log/named/query.log" versions 3 size 5m; severity info; print-time yes; print-category yes; }; category queries { query_log; }; }; # 创建日志目录并授权 $ sudo mkdir -p /var/log/named $ sudo chown named:named /var/log/named $ sudo chmod 750 /var/log/named # 配置logrotate $ sudo tee /etc/logrotate.d/named << 'EOF' /var/log/named/*.log { daily missingok rotate 10 compress delaycompress notifempty create 640 named named sharedscripts postrotate /bin/systemctl reload named > /dev/null 2>&1 || true endscript } EOF日志价值:某次客户投诉“DNS偶尔解析慢”,我查
query.log发现大量client 192.168.10.50#53423: query: google.com IN A +,而google.com不在我们的区域。追查发现是某台开发机误配了DNS服务器地址。日志成了溯源唯一证据。
6.3 防火墙放行(如果启用firewalld)
# CentOS 7默认firewalld,需放行53端口 $ sudo firewall-cmd --permanent --add-service=dns $ sudo firewall-cmd --reload # 验证 $ sudo firewall-cmd --list-all | grep services services: dhcpv6-client dns ssh6.4 监控脚本:用curl检测解析可用性
# 创建健康检查脚本 $ sudo tee /usr/local/bin/check-dns.sh << 'EOF' #!/bin/bash # 检查ns1.internal是否能解析 if dig @192.168.10.10 ns1.internal +short | grep -q "192.168.10.10"; then echo "OK" exit 0 else echo "CRITICAL: DNS resolution failed" exit 2 fi EOF $ sudo chmod +x /usr/local/bin/check-dns.sh # 加入crontab每5分钟检查 $ (crontab -l 2>/dev/null; echo "*/5 * * * * /usr/local/bin/check-dns.sh >> /var/log/dns-monitor.log 2>&1") | crontab -6.5 备份策略:区域文件与配置文件
# 每日备份到/tmp(生产环境应备份到异地) $ sudo mkdir -p /backup/named $ sudo tee /etc/cron.daily/named-backup << 'EOF' #!/bin/bash DATE=$(date +%Y%m%d) tar -czf /backup/named/named-conf-$DATE.tar.gz /etc/named.conf /var/named/*.zone # 保留7天 find /backup/named/ -name "named-conf-*.tar.gz" -mtime +7 -delete EOF $ sudo chmod +x /etc/cron.daily/named-backup6.6 版本锁定:防止yum update升级破坏兼容性
# 锁定bind相关包,避免意外升级 $ sudo yum install yum-plugin-versionlock $ sudo yum versionlock bind bind-utils bind-libs原因:BIND 9.11+引入了
rndc.key自动管理,与CentOS 7.9自带的9.9.4不兼容。曾有客户执行yum update后named无法启动,回滚耗时2小时。
6.7 文档沉淀:用注释写明每一处修改的背景
在/etc/named.conf顶部添加:
// === DEPLOYMENT NOTES === // Date: 2023-01-01 // Purpose: Internal DNS for lab network (192.168.10.0/24) // Changes: // - Line 25: Added 'recursion no;' per security policy // - Line 32: ACL 'trusted' includes 192.168.10.0/24 only // - Line 45: Zone 'internal' points to /var/named/internal.zone // ========================这不是形式主义。半年后接手的同事看到这段注释,就知道为什么不能开递归,而不是凭直觉改配置。
我在实际使用中发现,真正让DNS服务稳定运行的,从来不是某条高深命令,而是对每个环节的敬畏心——敬畏端口冲突的底层机制,敬畏SELinux的强制访问控制,敬畏glibc解析链的微妙顺序。配置完成后,我总会留出15分钟,用不同客户端(Windows、macOS、另一台CentOS)反复测试,并故意拔掉网线再插上,看服务能否自动恢复。这种“破坏性验证”,比任何文档都更能建立信心。