别让 systemd-resolved 毁掉你的内网 DNS
说实话,在银河麒麟 V10(以下简称麒麟 V10)上折腾内网 DNS,我一开始是真没把 systemd-resolved 当回事。直到我在一台干净的系统上装好 Bind9,配好 zone 文件,满心欢喜地启动服务,然后 dig 一下内网域名——还是去了公网 DNS 那边,而且 /etc/resolv.conf 死活不听我指挥。那一刻我才反应过来,这又是 systemd-resolved 在背后抢地盘。
如果你也在麒麟 V10 上做过类似的事,大概率能对上号:服务装好了、端口也监听了,但系统解析请求根本不走你的 Bind9,或者你一重启电脑配置就“回到解放前”。这篇文章就是把我在麒麟 V10 下用 Bind9 搭内网 DNS 的全过程、踩过的坑和最终的解决办法,一次性写清楚。
适合谁来参考?两类人:一是刚接触麒麟 V10、想在公司内网或者实验室里搭一套私有 DNS 的运维/开发;二是已经被 systemd-resolved 折磨过、想搞清楚它到底怎么关、怎么绕的人。内容覆盖从安装、配置、zone 文件编写,到最关键的 systemd-resolved 冲突处理、防火墙/SELinux 放行,再到验证和排错,每一步我都会解释“为什么要这么做”,而不是只丢给你一串命令。
1. 环境准备与整体思路拆解
1.1 为什么选择 Bind9 而不是 Dnsmasq
我知道可能有人会问:内网 DNS 用 Dnsmasq 不是更轻量吗?确实,Dnsmasq 在几十台机器的小型局域网里完全够用,配置也简单,甚至还能顺带做 DHCP。但我选择 Bind9 的原因很直接:麒麟 V10 这类服务器场景通常应用在政企、军工或专业研究环境,这类环境对 DNS 的规范性要求更高,Bind9 支持的 zone 管理、view 分区、ACL 控制、日志审计这些能力是 Dnsmasq 给不了的。
还有一点很现实,内网 DNS 往往不只是“解析几个机器名”那么简单,后续你可能要加反向解析、要按来源 IP 返回不同结果、要做子域委托,Bind9 的扩展性明显更好。如果你只是临时用一下、机器不超过 20 台,那 Dnsmasq 确实更省事;但我这篇文章按 Bind9 来写,因为标题就是要用 Bind9,而且它在生产环境里的表现更稳。
1.2 麒麟 V10 的系统特点对 DNS 的影响
麒麟 V10 有桌面版和服务器版之分,无论哪个版本,底子都是 Linux。大部分基于 RPM 体系的发行版,网络管理用的是 NetworkManager,DNS 解析栈用的是 glibc + nsswitch,而 systemd-resolved 则是中间的一个“代理层”。
关键点在这:麒麟 V10 默认开启了 systemd-resolved,它会去监听 127.0.0.53:53。你 Bind9 装好再监听 53 端口,要么起不来,要么起来了但系统请求根本不会发到你的 Bind9 上,因为本机解析流量都被 systemd-resolved“截胡”了。这套机制本来是解决多网络切换时 DNS 自动更新的问题,但在固定内网环境里,它就是添乱的那个角色,这也是这篇文章标题里“别再被 systemd-resolved 坑了”的由来。
在开始动手之前,我先把整体思路理一遍,你心里有个底:
- 装好 Bind9 相关包。
- 写主配置文件 named.conf,定义 access control、监听地址、zone 声明。
- 编写正反向 zone 数据文件。
- 用 named-checkconf、named-checkzone 做语法检查。
- 处理 systemd-resolved 冲突,让本机解析走自己的 Bind9。
- 放行防火墙、调整 SELinux,确保远程机器也能查询。
- 用 dig、host、systemd-resolve 等工具做全面验证。
每一步都有坑,我一个个说。
2. Bind9 安装与核心配置详解
2.1 安装 Bind9 及配套工具
麒麟 V10 的软件仓库里有现成的 Bind 包,直接用 yum 装就行:
yum install -y bind bind-utilsbind 是主程序,bind-utils 提供 dig、host、nslookup 这些查询工具,强烈建议一起装,后面验证全靠它们。
装完之后确认一下版本:
named -v正常情况下会输出类似BIND 9.11.x之类的信息。麒麟 V10 的仓库版本不算特别新,但稳定,够用。
这里有个小细节:麒麟 V10 上 named 服务默认以named用户运行,但如果你装了 bind-chroot 包(有些定制镜像里会带上这个),那 named 的根目录会被锁到/var/named/chroot下面,配置文件路径、zone 文件路径全都要跟着变。我这次在标准麒麟 V10 上没遇到 chroot,但如果你发现自己改了/etc/named.conf却完全不生效、服务还起不来,先检查是不是装了 chroot 版。这是很多人在麒麟环境里遇到的第一个“隐形坑”。
2.2 主配置文件 /etc/named.conf 的实战写法
先说明一下,麒麟 V10 自带的/etc/named.conf是一个最小默认配置,它会在本机回环接口上监听 DNS 查询,并且只允许本机访问,完全没法当内网 DNS 用。我们的目标很简单:监听内网 IP,允许内网网段查询,配置我们的私网 zone。
我直接给出一个我在内网环境常用的模板,你按自己环境改一下 IP 和域名就行:
options { listen-on port 53 { 127.0.0.1; 192.168.10.10; }; listen-on-v6 port 53 { ::1; }; 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"; recursion yes; allow-query { localhost; 192.168.10.0/24; }; allow-recursion { localhost; 192.168.10.0/24; }; allow-transfer { none; }; forwarders { 114.114.114.114; 223.5.5.5; }; dnssec-enable yes; dnssec-validation yes; managed-keys-directory "/var/named/dynamic"; pid-file "/run/named/named.pid"; session-keyfile "/run/named/session.key"; }; zone "." IN { type hint; file "named.ca"; }; zone "internal.example" IN { type master; file "internal.example.zone"; allow-update { none; }; }; zone "10.168.192.in-addr.arpa" IN { type master; file "192.168.10.zone"; allow-update { none; }; }; include "/etc/named.rfc1912.zones"; include "/etc/named.root.key";挑几个关键配置单独解释,免得你照抄之后不知道动了哪根筋:
第一,listen-on里除了回环地址,必须加上服务器本身的内网 IP。如果不加,Bind9 默认只监听本机回环,局域网其他机器根本连不上。很多人装完发现“只有自己能解析,别人解析不了”,九成是这里没配。
第二,allow-query和allow-recursion建议明确限制网段,别图省事直接写any。内网 DNS 如果对全网开放递归,很容易被当成开放解析器,轻则被外部扫描,重则被用来做 DNS 放大攻击。这一点在政企环境里尤其敏感。我的习惯是:查询和递归都严格限制到内网子网。
第三,forwarders配的是上游公网 DNS,比如 114.114.114.114、223.5.5.5。内网解析不了的外部域名,Bind9 会转发给它们。如果你所在内网有安全要求、不允许出公网解析,那这一段可以直接删掉,Bind9 就只做纯内网解析。
第四,dnssec-enable和dnssec-validation这两个参数,如果你的内网 DNS 纯粹解析私网域名,其实可以关掉(设为 no),能省掉不少 DNSSEC 验证上的麻烦。但如果你还要转发解析公网域名,建议保持 yes,不然有些启用了 DNSSEC 的域名会解析失败。
2.3 zone 文件里最容易写错的地方
配置好主文件之后,正反向 zone 文件是另一大“事故高发区”。我写一个正向 zone 示例:
$TTL 1D @ IN SOA ns1.internal.example. admin.internal.example. ( 2024011801 ; serial 3H ; refresh 15M ; retry 1W ; expiry 1D ) ; minimum IN NS ns1.internal.example. ns1 IN A 192.168.10.10 gw IN A 192.168.10.1 web01 IN A 192.168.10.21 db01 IN A 192.168.10.22反向 zone 文件:
$TTL 1D @ IN SOA ns1.internal.example. admin.internal.example. ( 2024011801 ; serial 3H ; refresh 15M ; retry 1W ; expiry 1D ) ; minimum IN NS ns1.internal.example. 10 IN PTR ns1.internal.example. 1 IN PTR gw.internal.example. 21 IN PTR web01.internal.example. 22 IN PTR db01.internal.example.这里必须提醒几件事,都是我在实际配置中踩过的或者帮别人排查时见过的高频问题:
一是 SOA 记录里的serial。它相当于 zone 文件的版本号,每次你修改这个 zone 文件,必须把 serial 数字往上加,否则从服务器或者缓存不会重新加载。很多新手改完 zone 文件重启 named,dig 发现还是旧数据,就是 serial 没改。
二是反向 zone 文件名和网络段的对应关系。192.168.10.0/24网段的反向 zone 名是10.168.192.in-addr.arpa,也就是把 IP 反过来写、去掉最后一段,然后把in-addr.arpa附加在后面。反过来,10.168.192里面对应的反向记录是 IP 最后一段,不是完整 IP。
三是文件结尾必须有换行。这个听起来很蠢,但 Bind9 对 zone 文件结尾缺失换行的情况会直接报 parse error,服务起不来。Vim 默认会自动加换行,但如果是用 echo 或者某些编辑器写文件,很容易踩到这个坑。
四是文件所有者和权限。麒麟 V10 上 zone 文件放在/var/named/下面,建议属主设为named:named,权限给640。如果权限不对,named 进程可能读不到 zone 文件,日志里会报 permission denied,服务看起来是起来了,但 zone 加载失败,解析照样不通。
3. 核心实操:让系统解析真正走内网 DNS
3.1 关键冲突点:systemd-resolved 的 127.0.0.53
前面说了一堆理论,现在到真正决定成败的环节了。麒麟 V10 上默认跑着 systemd-resolved,它干了一件大事:把127.0.0.53:53这个地址占了。所有本机应用程序查 DNS(比如你直接在服务器上 dig、ping 域名),都会先发给 127.0.0.53,再由 systemd-resolved 转发给它在/etc/resolv.conf里记录的上游 DNS。
也就是说,就算你把/etc/named.conf配得再对、把 Bind9 服务起来并且绑定了 53 端口,你也监听不了 127.0.0.53(被 systemd-resolved 占着),而且本机程序发出去的解析请求也只会去 127.0.0.53,根本轮不到你的 Bind9 处理。这就是“服务起来了、配置也没错,但解析不生效”的终极原因。
解决思路分两种:
- 方案 A:停掉 systemd-resolved,释放 127.0.0.53,把
/etc/resolv.conf改成指向 127.0.0.1 或服务器内网 IP。这是最干净、最直接的做法,内网固定环境比较推荐。 - 方案 B:保留 systemd-resolved,但关闭它的 stub listener,然后让它把你指定的 DNS 作为上游转发下去。适合不太想动系统组件、又希望统一管理解析的场景。
我在实际部署中更倾向于方案 A,理由很简单:既然我们自己在跑 Bind9,就没必要再留一层转发代理,减少一层就少一个故障点。
3.2 动手关闭 systemd-resolved(方案 A 实操)
先编辑 systemd-resolved 的配置文件:
vim /etc/systemd/resolved.conf找到DNSStubListener这一行,修改成:
DNSStubListener=no这一行的意思是:让 systemd-resolved 不再监听 127.0.0.53:53,把端口还给系统。如果这行被注释了,去掉注释后改。
然后依次执行:
systemctl stop systemd-resolved systemctl disable systemd-resolvedstop是立刻停掉,disable是防止开机重启后又起来。两个都要做,只做stop的话,机器一重启 systemd-resolved 又回来了,DNS 又乱套。
接下来处理/etc/resolv.conf。这个文件在麒麟 V10 上默认是指向/run/systemd/resolve/stub-resolv.conf的软链接,内容里写着nameserver 127.0.0.53。现在我们把它替换成真实的配置文件:
rm -f /etc/resolv.conf然后新建一个,写入我们自己的配置:
vi /etc/resolv.conf内容就两行核心配置:
nameserver 127.0.0.1 search internal.examplenameserver 127.0.0.1表示本机所有解析请求都交给本机的 Bind9,也就是我们刚刚搭好的内网 DNS。search internal.example是可选配置,加上之后,你 ping web01 的时候系统会自动解析成web01.internal.example,内网体验会舒服很多。
这个步骤要注意一个问题:如果你这台机器用的是 NetworkManager 管理网络连接,它可能在网卡重连、重启网络服务的时候自动覆盖/etc/resolv.conf。到时候你会发现又变成了老样子。解决办法有两个:一是把 NetworkManager 的 DNS 接管功能禁用掉,在/etc/NetworkManager/NetworkManager.conf的[main]段下加一行:
[main] dns=none改完重启 NetworkManager:
systemctl restart NetworkManager二是干脆不用 NetworkManager 管理这个网卡,改用静态配置,这个看你们内网的管理习惯。我个人更推荐静态 IP + 手动管理的/etc/resolv.conf,在服务器场景下最可控。
3.3 保留 systemd-resolved 的退让方案(方案 B 实操)
如果你所在环境的运维规范里强制要求 systemd-resolved 必须保持运行(有些安全基线扫描会限制系统组件状态),那方案 B 可以让你少动系统组件:
同样打开/etc/systemd/resolved.conf:
vim /etc/systemd/resolved.conf先关掉 stub listener:
DNSStubListener=no再增加两条:
DNS=127.0.0.1 Domains=internal.exampleDNS=127.0.0.1的意思是让 systemd-resolved 把解析请求转发给本机 Bind9;Domains=internal.example是在私网域上用这个 DNS。保存后:
systemctl restart systemd-resolved然后在/etc/resolv.conf里保持nameserver 127.0.0.53不动,因为 systemd-resolved 还能正常转发。
但这种做法的缺点很明显:你没法彻底卸载 systemd-resolved,多了一层进程,排查问题时要多考虑一层转发链路。我自己的习惯是内网环境直接方案 A,只有安全扫描特别严格的环境才用方案 B。
4. 防火墙、SELinux 与远程查询放行
4.1 防火墙必须放行 TCP/UDP 53
Bind9 服务起来了,本机也能解析了,但局域网其他机器还是查不了——那问题多半出在防火墙。
麒麟 V10 默认可能开着 firewalld 或者直接用 iptables,取决于你的桌面/服务器定制包。我用 firewalld 的情况比较多,放行 DNS 服务的命令:
firewall-cmd --permanent --add-service=dns firewall-cmd --reload如果你用的是 iptables 管理,那就得自己加规则:
iptables -A INPUT -p udp --dport 53 -j ACCEPT iptables -A INPUT -p tcp --dport 53 -j ACCEPT service iptables save这里要特别提醒一下:DNS 查询默认走 UDP 53,但区域传输(AXFR/IXFR)走 TCP 53。如果你只放行了 UDP,dig 查询正常但dig axfr可能超时。日常使用建议 TCP 和 UDP 都放行,免得到时候排查半天。
还有个小概率坑:如果 Bind9 是绑定在特定内网 IP 上,而防火墙开启了zone管理,你得确保允许流量进入的 zone 里包含了对应网卡。否则就算你加了--add-service=dns,它可能只对默认 zone 生效。
4.2 SELinux 对 named 的限制
很多人在麒麟 V10 上安装完 Bind9,服务起不来,查日志发现Permission denied或者open /etc/named.conf: Permission denied,然后百思不得其解:我明明用 root 运行的,怎么会没权限?
原因多半是 SELinux。麒麟 V10 默认开启 SELinux,而 named 在 SELinux 中被限制在一个特定上下文里,它只能读特定目录下的配置文件。如果你把 zone 文件放在了别的自定义目录,又没更新 SELinux 上下文,就会触发拒绝。
查看 SELinux 状态:
getenforce如果是Enforcing,那就要给 named 相关的文件打上正确上下文。常用命令:
semanage fcontext -a -t named_zone_t "/var/named(/.*)?" restorecon -Rv /var/named如果 zone 文件放在/var/named下,默认上下文正常是没问题的。但如果文件是新创建的、来源是上传压缩包解压出来的,SELinux 上下文可能没继承,就是历史安全上下文丢失,需要执行restorecon -Rv /var/named恢复。
如果你确实不想处理 SELinux 上下文,但也有别急着直接setenforce 0——这在政企环境里可能过不了安全基线。建议先用ausearch -m avc查一下具体是哪条策略被拒绝了,再对症处理,能不动 SELinux 就不动。
5. 启动服务与解析验证的完整记录
5.1 启动 Bind9 并设为开机自启
配置文件、zone 文件都准备完之后,别急着直接systemctl start named,先做语法检查。
检查主配置:
named-checkconf /etc/named.conf没有任何输出说明语法没问题。
检查正向 zone:
named-checkzone internal.example /var/named/internal.example.zone检查反向 zone:
named-checkzone 10.168.192.in-addr.arpa /var/named/192.168.10.zone这两条命令会输出OK或者直接报错。我特别建议大家养成这个习惯:先检查再启动。省得启动失败之后去翻日志,冤枉得很。
语法检查通过后,正式启动:
systemctl start named systemctl enable named然后确认一下状态:
systemctl status named如果服务状态是 active (running),再看端口监听:
netstat -lnptu | grep 53你会看到named监听在127.0.0.1:53和192.168.10.10:53上面。如果你之前已经关闭了 systemd-resolved,这里不会出现127.0.0.53:53占用的情况,这也是验证系统组件是否处理干净的一个标志。
5.2 用 dig 验证正反向解析与转发能力
服务起来之后,第一轮验证本机解析:
dig @127.0.0.1 web01.internal.example重点看 Answer 段有没有返回192.168.10.21,以及SERVER字段显示的是不是127.0.0.1#53。如果 Answer 段为空或者REFUSED,说明 zone 加载没生效或者查询权限没配上。
再测反向:
dig @127.0.0.1 -x 192.168.10.21正常会返回:
21.10.168.192.in-addr.arpa. 86400 IN PTR web01.internal.example.然后验证公网域名转发是否正常:
dig @127.0.0.1 www.baidu.com如果之前配了 forwarders,这里会返回真实 IP,说明你的内网 DNS 能同时做私网和公网解析,可以当内网统一的解析入口。
最后,从局域网内另一台机器上验证远程查询。把客户机的 DNS 临时改成 192.168.10.10(你的 Bind9 内网 IP),然后:
dig @192.168.10.10 web01.internal.example如果通了,说明防火墙、allow-query 都配置正确。这一步一定要做,很多人在服务器本机测的没问题,但远端客户机一查就失败,基本都是防火墙或者 allow-query 没放行。
6. 常见问题与排查技巧实录
6.1 高频故障速查表
我在麒麟 V10 上折腾 Bind9 的时候记录过不少问题,这里整理成一个速查表,方便你对照排查:
| 现象 | 可能原因 | 排查方向与解决 |
|---|---|---|
| 服务启动失败,日志报 permission denied | SELinux 上下文不对或 zone 权限问题 | 检查/var/named下文件的属主和权限,restorecon -Rv /var/named |
| dig 查询返回 SERVFAIL | zone 文件语法错误或 serial 未更新 | 运行named-checkzone检查,确认 serial 修改过 |
| 本机能解析,远端机器不行 | 防火墙未放行,或 allow-query 没配网段 | firewall-cmd --add-service=dns,检查 named.conf 的 allow-query |
| 开机后 DNS 配置被重置 | NetworkManager 覆盖 resolv.conf | 在/etc/NetworkManager/NetworkManager.conf里设置dns=none |
| 本机 dig 还是走 127.0.0.53 | systemd-resolved 没关干净 | 检查systemctl status systemd-resolved,确认 stop 和 disable 都执行 |
| 重启后 systemd-resolved 复活 | 只 stop 了,没 disable | 执行systemctl disable systemd-resolved |
| 日志报 resolver priming query 超时 | 内网无法访问上游公网 DNS | 关闭 forwarders 或改为内网可访问的 DNS 网关 |
6.2 一个我印象深刻的排查案例
有一次我在一台麒麟 V10 服务器上搭好 DNS,本机测试一切正常,但第二天发现内网其他机器全都不听指挥,还是用各自原来的 DNS。我上服务器一查,/etc/resolv.conf又变回了127.0.0.53,systemd-resolved 也自动回来了。
后来才发现,这台机器上有一个定时任务在每晚同步网络配置,直接调用了 NetworkManager 的接口,NetworkManager 不仅把网卡配置刷新了,还把/etc/resolv.conf的软链接又建回来了。所以说,只关 systemd-resolved 不够,还得把 NetworkManager 的 DNS 接管功能关掉,双管齐下才能真正“锁死”解析配置。
这种问题在纯手动配置的 Linux 上很少出现,但在麒麟 V10 这种带桌面环境和管理工具的发行版上特别容易碰到。遇到这种“配置总是被改回去”的情况,最后一个办法是可以把/etc/resolv.conf设为不可变属性:
chattr +i /etc/resolv.conf这个命令会让文件无法被修改,就算是 root 也不行(除非先chattr -i)。这是强制手段,慎用,但它确实能在一堆“管家工具”争抢 DNS 配置的时候保你一条命。
6.3 日志是排错的第一信源
排查 Bind9 问题,日志永远是最靠谱的。麒麟 V10 上 named 的日志一般在/var/named/data/named.run,或者用 journalctl 查:
journalctl -u named -f-f是实时跟踪输出,适合在启动服务或者做查询的同时观察日志。常见的关键日志有:
client 192.168.x.x#xxxxx: query:说明收到了某个客户端的查询请求,如果远端机器查不了,先看这里有没有到达 Bind9。zone internal.example/IN: loaded serial 2024011801:说明 zone 加载成功。error (network unreachable) resolving:说明 Bind9 在向上游 DNS 发请求时网络不通,检查 forwarders 配置和网络路由。dns_master_load: file not found:说明 zone 文件路径写错了,或者文件不存在。
很多人一上来就抓瞎不知道该看什么,我建议遇到问题先按这个顺序来:查服务状态systemctl status named→ 查端口监听netstat -lnptu | grep 53→ 查日志journalctl -u named -f→ 在本机 dig 测试 → 在远端 dig 测试。从内到外逐层排查,基本能定位八成问题。
7. 一些额外的加固建议
DNS 服务在内网里往往扮演着“基础设施”的角色,出问题影响面大,暴露面也大。我额外补充几个建议,方便你部署到生产环境前想清楚:
第一,zone 传输要限制。如果你需要部署多台 DNS 做冗余,主从同步时一定要注意 allow-transfer 只写从服务器 IP,别写 any。我之前见过一个内网环境因为 allow-transfer 没限制,被别人一条dig axfr internal.example就拿到了全部内网主机名和 IP 映射,整个内网拓扑直接暴露了。
第二,日志可以做单独配置。Bind9 默认的日志比较简单,如果需要审计内网所有解析记录,可以单独配置 channel 把 query log 落盘到独立的日志文件,配合 logrotate 做轮转。内网安全审计时,解析记录往往比流量记录更有价值。
第三,版本升级和补丁要及时。Bind9 是开源 BIND 的发行版,历史上出现过不少 CVE,虽然内网环境相对安全,但如果你的内网 DNS 同时也承担公网域名的递归解析,那就处于公网可达状态,该打的补丁必须打。
第四,DNS 和 DHCP 要联动规划。很多内网环境用 DHCP 自动分配 IP,如果 DHCP 分配给客户机的 DNS 还是旧地址,那你这边 DNS 配得再完美也白搭。记得检查 DHCP 服务器上的 DNS 选项,统一发一个新的 DNS 地址。
写在最后
麒麟 V10 下用 Bind9 搭内网 DNS,技术上不复杂,真正让人头疼的往往不是 Bind9 本身,而是 systemd-resolved 和 NetworkManager 这些系统组件在背后“偷偷捣乱”。把这几个关系理清楚、配置文件落到位、防火墙/SELinux 处理好,一套能服务整个内网的 DNS 环境就算真正立住了。
我个人在实际部署中还有一个体会:DNS 这种基础服务,最重要的是“改动可回滚”。每次修改完配置、升级 serial、重启服务前,最好把旧的配置文件和 zone 文件备份一份,出了问题能快速回到上一个正常状态。这个习惯帮我省过不少半夜被叫起来救火的场景。最后再分享一个小技巧:把named-checkconf和named-checkzone两条检查命令写进每次部署的固定流程里,哪怕只是改一行配置,也先检查再重启,这个习惯能帮你避开至少一半的坑。