1. 从一个"ping 得通 IP 却打不开网页"的经典故障说起
如果你在 Linux 上折腾过服务器或者桌面环境,大概率遇到过这种让人抓狂的场景:ping 8.8.8.8完全正常,延迟稳定,但ping www.example.com直接报unknown host,浏览器也打不开任何网站。这时候有经验的人第一反应就是——DNS 出问题了。
域名解析服务器(DNS Server)在 Linux 系统里扮演的角色,说白了就是一本"电话簿"。人记不住142.250.72.14这种数字串,但能记住www.example.com,DNS 的工作就是把这串人类友好的名字翻译成机器能识别的 IP 地址。听起来简单,但真正落到 Linux 系统上,从/etc/hosts到/etc/resolv.conf,从systemd-resolved到nscd,从dig到resolvectl,这一整套机制层层叠叠,稍有不慎就会踩坑。
这篇内容面向的是需要在 Linux 环境下配置、调试、自建域名解析服务的运维人员、后端开发者和网络爱好者。我会把 Linux 域名解析的完整链路拆开讲清楚:解析请求到底走了哪条路、配置文件之间谁覆盖谁、怎么自建一个可用的解析服务器、以及那些文档里不会写但实际会遇到的坑。不管你是刚接触 Linux 网络的新手,还是想系统梳理一遍解析链路的老手,都能从里面找到能直接用的东西。
2. Linux 域名解析的完整链路:请求到底走了哪条路
很多人以为 DNS 解析就是"系统发个请求给 DNS 服务器,拿回 IP",但 Linux 上的实际情况要复杂得多。一个域名解析请求从应用程序发起,到最终拿到 IP,中间可能经过五六个环节,任何一个环节配置不对,结果就是解析失败或者解析到了错误的结果。
2.1 应用程序到系统解析器的第一跳
当你在终端敲下curl www.example.com时,curl 并不会自己去查 DNS,它调用的是系统提供的解析接口,通常是getaddrinfo()这个函数。这个函数属于 glibc(GNU C 库),是绝大多数 Linux 程序做域名解析的入口。
getaddrinfo()拿到域名后,会按照一个固定的顺序去查找:
- 先查
/etc/hosts文件。这是本地静态映射表,优先级最高。如果你在里面写了127.0.0.1 www.example.com,那么不管外部 DNS 怎么配,这个域名永远解析到本地。 - 再查
/etc/nsswitch.conf里hosts:这一行的配置。这一行决定了查找顺序和使用的服务,典型配置是hosts: files dns,意思是先查文件(files 对应/etc/hosts),再查 DNS。 - 最后才走 DNS 查询,根据
/etc/resolv.conf里配置的 nameserver 地址去请求。
这个顺序非常关键。我见过不少人调试了半天 DNS 服务器,结果发现是/etc/hosts里有一条陈旧的记录在作怪。所以排查解析问题的第一步,永远是cat /etc/hosts和cat /etc/nsswitch.conf。
2.2 /etc/resolv.conf 的真实面目
/etc/resolv.conf是 Linux 上最"名不副实"的文件之一。很多人以为它是手动配置的静态文件,但在现代 Linux 发行版上,它经常是被其他程序动态生成的。
这个文件里最核心的几行:
nameserver 8.8.8.8 nameserver 114.114.114.114 search example.local options timeout:2 attempts:3 rotatenameserver:指定 DNS 服务器地址,最多可以写三个(glibc 的限制),按顺序尝试。search:搜索域,当你查询一个不带点的短名字(比如webserver)时,系统会自动拼接成webserver.example.local去查。options:控制解析行为,timeout是单次查询超时秒数,attempts是重试次数,rotate表示轮询使用多个 nameserver。
问题在于,这个文件可能被NetworkManager、systemd-resolved、dhclient、netplan等多个程序争抢着改写。你手动改完,重启网络服务后又被覆盖了,这是新手最常踩的坑。
2.3 systemd-resolved 带来的新格局
从 Ubuntu 18.04、CentOS 8 开始,systemd-resolved成了默认的解析管理服务。它会在127.0.0.53:53上启动一个本地 DNS 缓存和转发器,然后/etc/resolv.conf通常被改成指向127.0.0.53的软链接。
这时候你cat /etc/resolv.conf看到的是:
nameserver 127.0.0.53 options edns0 trust-ad真正的上游 DNS 配置藏在/run/systemd/resolve/resolv.conf或者通过resolvectl status查看。这种设计的好处是支持 per-interface 的 DNS 配置、内置缓存、DNSSEC 验证等,坏处是排查问题时多了一层,很多人搞不清楚请求到底发给了谁。
判断当前系统用的是哪种模式,可以看两个地方:
| 检查项 | 传统模式 | systemd-resolved 模式 |
|---|---|---|
/etc/resolv.conf类型 | 普通文件 | 软链接到/run/systemd/resolve/stub-resolv.conf |
| nameserver 地址 | 真实 DNS 服务器 IP | 127.0.0.53 |
| 查看上游 DNS | 直接看 resolv.conf | resolvectl status |
| 缓存 | 无(除非装 nscd) | 内置缓存 |
2.4 解析请求的完整路径还原
把上面这些串起来,一个完整的解析路径是这样的:
应用程序调用getaddrinfo()→ glibc 读/etc/nsswitch.conf确定顺序 → 查/etc/hosts→ 未命中则读/etc/resolv.conf拿到 nameserver → 如果 nameserver 是127.0.0.53,请求发给 systemd-resolved → systemd-resolved 查自己的缓存 → 未命中则转发给上游 DNS → 上游返回结果 → 逐层返回给应用程序。
理解这条链路的意义在于:当解析出问题时,你可以沿着这条路径逐段排查,而不是盲目地重启网络或者乱改配置。我个人的习惯是先用dig直接指定 DNS 服务器测试,确认是上游问题还是本地问题,再决定往哪个方向查。
3. 自建域名解析服务器:选型、安装与最小可用配置
搞清楚了客户端链路,接下来聊服务端。自建 DNS 服务器的需求场景很多:内网域名解析、本地开发环境、广告过滤、缓存加速、学习研究等。选哪个软件、怎么配、配完怎么验证,这里面有不少门道。
3.1 主流 DNS 服务软件怎么选
Linux 上能当 DNS 服务器用的软件不少,但常用的就那么几个,各有各的定位:
- BIND:老牌选手,功能最全,支持权威解析、递归解析、DNSSEC、视图(view)等,配置相对复杂。适合需要完整权威 DNS 功能的场景。
- dnsmasq:轻量级,配置极简,同时提供 DNS 和 DHCP 功能,自带缓存。适合内网小规模环境、本地开发、路由器等场景。
- Unbound:专注递归解析和缓存,安全性好,支持 DNSSEC 验证,配置比 BIND 简单。适合做本地递归解析器。
- CoreDNS:Go 写的,插件化架构,配置用 Corefile,在容器和云原生环境里很流行。适合 K8s 集群内 DNS。
选型建议很直接:如果你只是想让内网几台机器能互相用域名访问,或者本地开发想自定义域名,dnsmasq 是最省事的选择;如果你要搭建一个对外提供解析的权威服务器,BIND 或 CoreDNS 更合适;如果你只是想要一个带缓存的本地递归解析器,Unbound 最省心。
下面我以 dnsmasq 为例,走一遍完整的搭建流程,因为它的配置最直观,也最容易验证效果。
3.2 dnsmasq 的安装与核心配置逐行拆解
安装很简单,主流发行版都有包:
# Debian/Ubuntu 系 sudo apt update sudo apt install dnsmasq # RHEL/CentOS 系 sudo yum install dnsmasq安装完先别急着启动,因为很多系统上 53 端口可能被systemd-resolved占着。检查一下:
sudo ss -tulnp | grep :53如果看到systemd-resolve占用了 53 端口,需要先处理冲突。最简单的办法是禁用 systemd-resolved 的 stub listener,编辑/etc/systemd/resolved.conf,把DNSStubListener=yes改成DNSStubListener=no,然后重启服务。
接下来是 dnsmasq 的主配置文件/etc/dnsmasq.conf,我挑几个最关键的参数讲:
# 监听地址,只监听本地和内网网卡 listen-address=127.0.0.1,192.168.1.10 # 不读取 /etc/resolv.conf 作为上游,避免循环 no-resolv # 手动指定上游 DNS server=223.5.5.5 server=119.29.29.29 # 缓存大小,150 条足够小规模使用 cache-size=150 # 自定义域名解析,把内网域名指向指定 IP address=/dev.local/192.168.1.100 address=/test.internal/192.168.1.101 # 记录查询日志,调试时打开 log-queries log-facility=/var/log/dnsmasq.log这里有几个点值得展开说。no-resolv这个参数很多人会忽略,如果不加,dnsmasq 会去读/etc/resolv.conf拿上游服务器,而如果/etc/resolv.conf又指向了 dnsmasq 自己(127.0.0.1),就会形成解析循环,所有查询都超时。这是自建 DNS 最经典的坑之一。
address=/dev.local/192.168.1.100这种写法是 dnsmasq 的杀手锏,它会把dev.local及其所有子域名(比如api.dev.local、db.dev.local)全部解析到192.168.1.100。做本地开发时特别方便,不用每加一个子域名就改一次配置。
3.3 启动、验证与常见启动失败排查
配置写好后启动服务:
sudo systemctl restart dnsmasq sudo systemctl status dnsmasq验证解析是否生效,用dig指定服务器地址测试:
dig @127.0.0.1 api.dev.local如果返回的 ANSWER SECTION 里显示192.168.1.100,说明配置生效了。再测一个外部域名:
dig @127.0.0.1 www.example.com能正常返回结果,说明上游转发也通了。
启动失败最常见的原因有三个:一是 53 端口被占用,ss -tulnp | grep :53一看便知;二是配置文件语法错误,用dnsmasq --test可以检查配置;三是权限问题,dnsmasq 默认以dnsmasq用户运行,如果日志目录或缓存目录权限不对会启动失败,看journalctl -u dnsmasq的报错信息基本能定位。
3.4 让客户端用上自建 DNS 的几种方式
服务器搭好了,怎么让其他机器用上?有几种做法:
- 手动改客户端
/etc/resolv.conf:最直接,但重启网络后可能被覆盖。 - 在 DHCP 服务器里下发 DNS 地址:如果内网有 DHCP,把下发的 DNS 改成自建服务器地址,所有通过 DHCP 获取 IP 的机器自动生效。
- 在 NetworkManager 里配置:桌面环境常用,通过
nmcli或图形界面设置。 - 在路由器上配置:把路由器的 DNS 转发指向自建服务器,全内网生效。
我个人推荐在 DHCP 层面统一下发,这样管理最集中,也不会因为某台机器手动改配置而出现解析不一致的问题。
4. 解析排错实战:从现象到根因的完整排查链路
DNS 问题的难点不在于配置,而在于排查。因为解析失败的表现形式很多,可能是超时、可能是返回错误 IP、可能是时好时坏,每种现象背后的原因都不一样。这一节我把常见的几类问题拆开,给出完整的排查思路。
4.1 工具选择:dig、nslookup、host 该用哪个
Linux 上排查 DNS 的工具主要有三个:
- dig:功能最强,输出信息最全,支持指定服务器、查询类型、追踪解析路径。排查首选。
- nslookup:交互式,简单查询够用,但输出信息少,逐渐被淘汰。
- host:最简洁,适合快速查一条记录。
我基本只用 dig,因为它的输出包含了查询时间、返回状态码、flags 等关键信息。几个常用姿势:
# 查 A 记录 dig www.example.com # 指定 DNS 服务器 dig @8.8.8.8 www.example.com # 查特定记录类型 dig MX example.com dig TXT example.com # 追踪完整解析路径 dig +trace www.example.com # 只看简短结果 dig +short www.example.comdig +trace特别有用,它会从根域名服务器开始,一步步追踪到最终的权威服务器,能清楚看到解析在哪一环断掉了。
4.2 超时与 SERVFAIL:上游不可达还是配置错误
dig返回connection timed out; no servers could be reached,说明请求根本没发出去或者没收到响应。排查顺序:
- 确认 DNS 服务器地址可达:
ping 8.8.8.8看网络通不通。 - 确认 53 端口没被防火墙拦:
sudo iptables -L -n | grep 53或者sudo firewall-cmd --list-all。 - 确认本地 DNS 服务在监听:
ss -tulnp | grep :53。 - 确认没有解析循环:检查
/etc/resolv.conf和 dnsmasq 配置里有没有互相指向。
返回SERVFAIL则是服务器收到了请求但处理失败,常见原因是上游 DNS 返回了错误、DNSSEC 验证失败、或者域名本身配置有问题。这时候用dig @上游DNS 域名直接测上游,能快速判断是本地问题还是上游问题。
4.3 解析结果不对:缓存、hosts 与 search 域的干扰
有时候解析不报错,但拿到的 IP 是错的。这种情况排查起来更绕,因为"错"的定义不明确。常见原因:
- 本地缓存:systemd-resolved 或 nscd 缓存了旧记录。用
resolvectl flush-caches或systemctl restart nscd清缓存。 - /etc/hosts 干扰:前面提过,优先级最高,一定要检查。
- search 域拼接:查询短名字时被自动拼接了 search 域,导致查到了意料之外的域名。用
dig查完整域名(末尾带点,如www.example.com.)可以避免拼接。 - 多个 nameserver 轮询:
options rotate会导致请求在不同服务器间轮询,如果其中一台返回了不同结果,就会出现时好时坏。去掉 rotate 或者统一各服务器配置。
我遇到过一个典型案例:某台机器解析内网域名时好时坏,最后发现是/etc/resolv.conf里配了两个 nameserver,一个是正确的内网 DNS,另一个是运营商 DNS,rotate 模式下请求轮询到运营商 DNS 时,内网域名自然解析不了。去掉 rotate 并把内网 DNS 放第一位就解决了。
4.4 用 tcpdump 抓包定位解析请求的真实去向
当所有配置看起来都对,但解析就是不对时,抓包是终极手段。在 DNS 服务器上执行:
sudo tcpdump -i any -n port 53然后在客户端发起一次解析,观察抓包结果:
- 如果看不到任何包,说明请求根本没到这台服务器,问题在客户端配置或网络路径。
- 如果看到请求但没看到响应,说明服务器收到了但没回,可能是服务没正常运行或处理出错。
- 如果看到请求和响应,但客户端说解析失败,说明响应内容有问题,可能是格式错误或被中间设备篡改。
抓包能直接告诉你"请求到底去了哪",避免在配置层面瞎猜。这个技能在排查跨网段、跨防火墙的 DNS 问题时尤其管用。
5. 缓存、性能与安全:让解析服务真正好用
一个能用的 DNS 服务器和一个好用的 DNS 服务器之间,差的是缓存策略、性能调优和安全防护。这部分内容很多教程不会讲,但实际生产环境里非常关键。
5.1 缓存命中率决定了你的解析有多快
DNS 查询是有网络延迟的,一次递归查询可能要几百毫秒。缓存的作用就是把重复查询的结果存在本地,下次直接返回,延迟降到微秒级。
dnsmasq 的cache-size参数控制缓存条目数,默认 150,对于几十台机器的内网够用,但如果是几百台机器的环境,建议调到 1000 以上。查看缓存命中情况:
# 向 dnsmasq 发送 SIGUSR1 信号,会把统计信息写入日志 sudo kill -USR1 $(pidof dnsmasq) sudo tail -f /var/log/dnsmasq.log日志里会显示cache size、cache insertions、cache evictions、cache hits、cache misses等统计。如果 evictions 很高,说明缓存太小,需要调大;如果 hits 很低,说明查询的域名太分散,缓存帮助有限。
TTL(Time To Live)也影响缓存效果。每条 DNS 记录都有自己的 TTL,dnsmasq 会遵守这个值。如果某个域名的 TTL 只有 60 秒,那缓存 60 秒后就失效了,需要重新查询。对于内网自定义域名,可以通过配置强制指定 TTL,减少重复查询。
5.2 上游服务器的选择与容灾
上游 DNS 的选择直接影响解析速度和可靠性。几个原则:
- 就近选择:物理距离近的 DNS 服务器延迟低。国内常用的有 223.5.5.5、119.29.29.29 等。
- 多配几个:至少配两个上游,一个挂了另一个顶上。
- 区分场景:内网域名走内网 DNS,外网域名走公共 DNS,可以通过 dnsmasq 的
server=/domain/ip语法实现分流。
# 所有 example.com 的查询走指定服务器 server=/example.com/192.168.1.1 # 其他查询走默认上游 server=223.5.5.5 server=119.29.29.29这种分流配置在企业内网很常见,能避免内网域名泄露到公共 DNS,也能保证外网解析走最优路径。
5.3 防止 DNS 被滥用:访问控制与限速
自建 DNS 服务器如果暴露在公网上,很容易被利用来做 DNS 放大攻击。基本的防护措施:
- 限制监听地址:只监听内网网卡,不要监听
0.0.0.0。 - 配置访问控制:dnsmasq 用
interface和except-interface控制,BIND 用 ACL。 - 开启查询日志:便于发现异常查询模式。
- 限制递归查询:只对信任的网段开放递归,其他网段只能查权威记录。
dnsmasq 的一个简单访问控制配置:
# 只允许内网网段查询 interface=eth0 except-interface=eth1 bind-interfacesbind-interfaces让 dnsmasq 真正绑定到指定接口,而不是监听所有接口再过滤,安全性更好。
5.4 日志与监控:出问题时能快速定位
DNS 服务的日志平时看着烦,出问题时是救命稻草。dnsmasq 开启log-queries后,每次查询都会记录:
query[A] www.example.com from 192.168.1.50 forwarded www.example.com to 223.5.5.5 reply www.example.com is 93.184.216.34从日志里能看出谁在查、查了什么、转发给了谁、返回了什么。排查"某台机器解析异常"这类问题时,直接 grep 那台机器的 IP 就能看到它的所有查询记录。
对于规模稍大的环境,建议把 DNS 日志接入集中日志系统,配合告警规则,比如"某域名查询量突增"、"SERVFAIL 比例超过阈值"等,能提前发现异常。
6. 那些文档里不写、但实际会踩的坑
配置 DNS 这件事,理论看一遍就懂,但实际操作中总有一些让人摸不着头脑的问题。这一节我把自己和身边同行踩过的坑整理出来,都是真实遇到过的场景。
6.1 resolv.conf 被覆盖的三种典型场景
手动改完/etc/resolv.conf,重启后失效,这是最高频的问题。覆盖它的"元凶"主要有三个:
- NetworkManager:桌面版和部分服务器版默认启用,会根据自己的连接配置重写 resolv.conf。解决办法是在连接配置里设置
ipv4.ignore-auto-dns yes并手动指定 DNS,或者直接改 NetworkManager 的 dns 配置模式。 - dhclient:DHCP 客户端拿到 IP 后会根据 DHCP 下发的 DNS 重写 resolv.conf。可以在
/etc/dhcp/dhclient.conf里加supersede domain-name-servers 127.0.0.1;来覆盖。 - systemd-resolved:前面讲过,它会把 resolv.conf 变成软链接。要手动控制的话,得先禁用它或者改它的配置。
判断是谁在覆盖,可以看/etc/resolv.conf的文件类型和修改时间,结合systemctl status看相关服务状态。最彻底的办法是明确指定一个管理方,其他都禁用,避免多个程序抢着改。
6.2 容器环境里的 DNS 特殊性
在 Docker 和 Kubernetes 环境里,DNS 的行为和宿主机不一样,这是很多人容易忽略的。
Docker 默认会给容器配置一个内置的 DNS 解析器(通常是 127.0.0.11),它负责解析容器名和转发外部查询。如果你在容器里手动改了/etc/resolv.conf,重启容器后会被 Docker 覆盖。要自定义,得在docker run时用--dns参数,或者在daemon.json里配置全局 DNS。
Kubernetes 里每个 Pod 的/etc/resolv.conf由 kubelet 根据 Pod 的 dnsPolicy 生成,默认是ClusterFirst,先查集群内的 CoreDNS,查不到再转发给上游。如果集群内 DNS 出问题,Pod 之间的服务发现就会失败,表现是服务名解析不了。排查时先看 CoreDNS 的 Pod 是否正常,再看 CoreDNS 的 ConfigMap 配置。
6.3 IPv6 与双栈环境下的解析顺序
在同时有 IPv4 和 IPv6 的环境里,解析行为会更复杂。getaddrinfo()默认会同时查询 A 记录(IPv4)和 AAAA 记录(IPv6),然后根据系统的地址选择策略决定用哪个。
如果 IPv6 网络不通但系统优先返回了 IPv6 地址,就会出现"解析成功但连接失败"的现象。这种情况可以通过修改/etc/gai.conf调整地址选择优先级,或者在应用层面强制使用 IPv4。
排查这类问题时,dig A 域名和dig AAAA 域名分别查一下,看看两种记录是否都正常返回,再结合ping6测试 IPv6 连通性,基本能定位问题。
6.4 域名末尾那个点:容易被忽略的细节
DNS 里域名末尾的点表示"绝对域名"(FQDN),不带点则表示相对域名,会拼接 search 域。这个细节在配置和排查时经常被忽略。
比如/etc/resolv.conf里有search corp.local,你查询db,系统实际查的是db.corp.local。如果你本意是查一个叫db的绝对域名,就得写成db.(末尾带点)。
在 dnsmasq 配置里,address=/dev.local/192.168.1.100这种写法匹配的是dev.local及其子域,但不会匹配dev.local.这种带末尾点的形式(实际上 dnsmasq 会做规范化处理,但理解这个概念对排查问题有帮助)。写配置时养成明确使用 FQDN 的习惯,能避免很多歧义。
7. 我个人的几条实操心得
折腾 DNS 这些年,有几个习惯是踩坑踩出来的,分享出来供参考。
第一,改任何 DNS 配置前先备份。cp /etc/resolv.conf /etc/resolv.conf.bak这种操作花不了几秒钟,但出问题时能救命。尤其是远程服务器上,改错 DNS 配置可能导致 SSH 都连不上(如果 SSH 登录用了域名),有备份至少能通过其他方式恢复。
第二,用 dig 而不是 ping 来验证解析。ping 只能告诉你通不通,dig 能告诉你解析到了什么、走了哪条路径、耗时多少。排查问题时信息量完全不是一个级别。
第三,自建 DNS 一定要配好上游容灾。我见过只配一个上游 DNS 的服务器,上游一挂,整个内网解析全瘫。至少配两个,最好来自不同的服务商,避免同时故障。
第四,日志该开就开,但别一直开着。log-queries在排查时非常有用,但长期开启会产生大量日志,占满磁盘。建议平时关着,需要排查时临时打开,查完再关。
第五,理解优先级顺序比记住配置命令更重要。/etc/hosts>nsswitch.conf指定的顺序 >/etc/resolv.conf的 nameserver,这个顺序是排查一切解析问题的基石。记住它,遇到问题先按这个顺序过一遍,大部分问题都能快速定位。
DNS 这东西,配置起来不难,难的是理解它背后的整套机制和排查思路。把链路搞清楚,把工具用熟练,剩下的就是经验积累了。希望这篇内容能帮你在下次遇到解析问题时,少走一点弯路。