在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配置,灵活应对多网卡多域的需求。