在Debian系统中,systemd-resolved是默认的DNS解析服务,它接管了传统的/etc/resolv.conf管理方式。当你需要让内网域名(比如公司内部的app.local、db.internal等)能够被正确解析时,核心操作就是告诉systemd-resolved去查询指定的内网DNS服务器,而不是只依赖公共DNS。具体做法是:编辑/etc/systemd/resolved.conf配置文件,设置DNS和Domains参数指向内网DNS地址和内网域名后缀,然后重启服务使其生效。整个过程不复杂,但细节决定成败,下面我会把每一步、每一个坑都讲清楚。
一、先搞清楚systemd-resolved到底在干什么
Debian从Stretch(9)版本开始,默认使用systemd-resolved作为本地DNS缓存和解析器。它监听在127.0.0.53这个地址上,然后通过一个符号链接/etc/resolv.conf -> /run/systemd/resolve/stub-resolv.conf把自己伪装成传统的resolv.conf。这意味着你直接改/etc/resolv.conf是没用的,重启就会被覆盖。你必须通过systemd-resolved自己的配置文件来控制它的行为。理解这一点是解决内网域名解析问题的前提。
二、确认当前DNS解析状态
在动手之前,先看看当前系统的DNS解析到底是什么情况。执行以下命令:
resolvectl status
这个命令会列出每个网络接口当前使用的DNS服务器、DNS域名后缀、DNSSEC状态等信息。你会看到类似"DNS Server: 8.8.8.8"这样的输出,说明当前走的是公共DNS,内网域名自然解析不了。再用resolvectl query测试一下内网域名:
resolvectl query app.local
如果返回"app.local: resolve call failed: No such host",那就确认了问题所在——系统不知道去哪里查内网域名。
三、配置内网DNS解析的核心步骤
第一步,编辑systemd-resolved的主配置文件:
sudo nano /etc/systemd/resolved.conf
第二步,找到[Resolve]段落,修改或添加以下关键参数:
[Resolve] DNS=192.168.1.100 192.168.1.101 Domains=~internal ~local DNSStubListener=yes DNSSEC=no FallbackDNS=1.1.1.1
这里逐行解释:DNS后面跟的是你内网DNS服务器的IP地址,可以写多个做冗余;Domains后面的~internal表示以.internal结尾的域名都走内网DNS解析,~local同理;DNSStubListener=yes是让它在127.0.0.53上监听;DNSSEC=no在内网环境一般关掉,避免解析失败;FallbackDNS是当内网DNS都不可用时的兜底方案。
四、为什么要用Domains参数而不是只设DNS
这是很多人踩的坑。如果你只设置了DNS=192.168.1.100,systemd-resolved会把所有DNS查询都发给这个内网服务器。但内网DNS服务器通常只能解析内网域名,解析外部域名(比如debian.org)会超时或失败。Domains参数的作用是告诉systemd-resolved:"只有以.internal或.local结尾的域名才发给内网DNS,其他的走公共DNS"。这个分流机制非常关键,没有它你的服务器可能连外网都上不了。
五、关于DNS服务器的选择和配置
内网DNS服务器一般有两种来源:一是公司自建的DNS(比如BIND、CoreDNS、dnsmasq),二是路由器或DHCP服务器自带的DNS转发。如果你用的是dnsmasq做内网DNS,配置很简单,在/etc/dnsmasq.conf里加一行:
server=/internal/192.168.1.100 server=/local/192.168.1.100
如果你的内网DNS是通过DHCP自动下发的,那情况稍微复杂一点。systemd-resolved默认会接受DHCP下发的DNS配置,但有时候会和手动配置冲突。解决办法是在resolved.conf里加上:
[Resolve] DNS=192.168.1.100 Domains=~internal
然后确保DHCP客户端不覆盖你的设置。如果用的是NetworkManager,需要在连接配置里把"忽略自动DNS"勾上。如果用的是传统的/etc/network/interfaces,一般不会有冲突。
六、重启服务并验证
配置改完后,重启systemd-resolved服务:
sudo systemctl restart systemd-resolved
然后再次检查状态:
resolvectl status
确认DNS Server显示的是你设置的内网IP,Domains显示了~internal和~local。接下来测试内网域名解析:
resolvectl query app.internal resolvectl query db.local
如果返回了正确的IP地址,说明配置成功。再测试一个外网域名确认分流正常:
resolvectl query debian.org
应该走的是FallbackDNS或者公共DNS,返回正常的解析结果。
七、处理多网卡和多DNS域的场景
实际生产环境中,服务器往往有多个网卡,每个网卡可能对应不同的网络区域。比如eth0连外网,eth1连内网管理网络。这时候你需要针对不同接口做不同的DNS配置。systemd-resolved支持per-link配置,可以用resolvectl命令直接设置:
sudo resolvectl dns eth0 1.1.1.1 sudo resolvectl domain eth0 "~." sudo resolvectl dns eth1 192.168.1.100 sudo resolvectl domain eth1 "~internal ~local"
这种方式的好处是不用改全局配置文件,针对每个接口独立设置,互不干扰。而且这些设置是持久化的,重启不会丢失。对于有复杂网络拓扑的运维场景,这种方法比改resolved.conf更灵活、更安全。
八、常见问题排查
问题一:配置后内网域名还是解析不了。先检查resolved.conf的语法有没有错误,用systemd-analyze verify /etc/systemd/resolved.conf验证。再检查内网DNS服务器本身是否正常运行,从服务器上直接用dig或nslookup测试:
dig @192.168.1.100 app.internal
如果dig直接查内网DNS能解析,但通过systemd-resolved不行,那大概率是Domains参数没配对,或者DNSStubListener没开。
问题二:外网也解析不了了。这通常是Domains参数写错了,比如写成了Domains=internal(没有~前缀),这样所有查询都被发给内网DNS了。记住,~前缀表示"匹配该后缀的域名",没有~就是"只用这个DNS"。
问题三:重启后配置被DHCP覆盖。在resolved.conf里加上:
[Resolve] DNS=192.168.1.100 Domains=~internal DHCPDNS=no
DHCPDNS=no明确告诉systemd-resolved忽略DHCP下发的DNS设置。
九、和传统/etc/resolv.conf方式的对比
有些老运维习惯直接改/etc/resolv.conf,在Debian上这招已经不好使了。即使你手动写了nameserver 192.168.1.100,systemd-resolved会在下次网络变化时把它覆盖掉。正确的做法永远是通过resolved.conf或者resolvectl命令来管理。如果你实在需要用传统方式(比如某些老旧应用不兼容),可以在resolved.conf里设置:
[Resolve] DNSStubListener=no
然后手动创建/etc/resolv.conf。但这不推荐,因为你就失去了systemd-resolved的DNS缓存和管理能力。
十、生产环境的最佳实践建议
第一,内网DNS至少配两个IP做冗余,避免单点故障。第二,Domains参数尽量精确,只匹配真正需要走内网DNS的后缀,避免过度分流。第三,关闭DNSSEC在内网环境是合理的,因为内网域名一般没有签名。第四,定期用resolvectl statistics查看缓存命中情况,如果命中率很低说明缓存没起作用,检查一下是否有配置问题。第五,把内网DNS解析的配置纳入版本管理,/etc/systemd/resolved.conf应该和其他系统配置一起做备份和审计。
总结一下,Debian上用systemd-resolved处理内网域名解析,本质就是三件事:指定内网DNS服务器IP、用Domains参数做域名后缀分流、确保服务重启后配置持久生效。掌握这三点,绝大多数内网解析场景都能搞定。遇到复杂网络环境就用per-link配置,灵活应对多网卡多域的需求。
