域名解析时好时坏:一次 Linux DNS 与 resolv.conf 排查复盘

同一域名同一台机器,解析一会儿成功一会儿失败。排查梳理:nsswitch 决定的解析顺序与 /etc/hosts、resolv.conf 的 nameserver 是故障转移不是负载均衡、用 dig 逐个体检 DNS 服务器、nscd 与 JVM 的 DNS 缓存坑、解析慢的优化,以及一套 DNS 排查纪律。

2024 年的一段时间,一个服务开始出现一种让我极度困惑的故障:它调用下游接口,有时候好好的,有时候却直接报 Temporary failure in name resolution——连域名都解析不出来。同一个域名,同一台机器,同一份配置,前一秒还能解析,后一秒就解析失败,过一会儿又自己好了。我第一反应当然是下游服务挂了,可下游同事拍胸脯说服务一直稳得很,而且监控也证明确实没挂。一个域名,在同一台机器上,怎么会"一会儿能解析、一会儿不能"?这件事逼着我把 Linux 的域名解析、resolv.conf、DNS 这一整套彻底理清了。本文复盘这次实战。

问题背景

环境:CentOS 7,服务调用 api.internal.example.com
事故现象:
- 偶发 Temporary failure in name resolution
- 同一域名,时好时坏,自己又会恢复
- 下游服务本身一直正常

现场排查:
# 1. 看本机配的 DNS 服务器
$ cat /etc/resolv.conf
nameserver 10.0.0.2
nameserver 10.0.0.3
options timeout:5 attempts:2

# 2. 反复 dig 这个域名,复现问题
$ for i in $(seq 20); do dig +short api.internal.example.com; sleep 1; done
10.0.5.20
10.0.5.20
;; connection timed out; no servers could be reached   # ★ 偶发失败
10.0.5.20
;; connection timed out; no servers could be reached
10.0.5.20

# 3. 分别指定两个 DNS 服务器单独测
$ dig @10.0.0.2 api.internal.example.com +short
10.0.5.20                       # 10.0.0.2 正常
$ dig @10.0.0.3 api.internal.example.com
;; connection timed out         # ★ 10.0.0.3 是坏的!

根因(后来想清楚的):
1. /etc/resolv.conf 里配了两个 nameserver:
   10.0.0.2(好的)和 10.0.0.3(已经坏了的)。
2. ★ resolv.conf 里多个 nameserver,默认【不是负载均衡】,
   而是【按顺序、故障转移】:先问第一个,
   第一个【超时无响应】了,才问第二个。
3. 正常时,第一个 10.0.0.2 直接答了,一切正常。
4. 但 options 里没配 rotate,且某些 glibc 行为下,
   解析器会触发对第二个坏服务器的查询;
   一旦轮到问 10.0.0.3,它不响应,要【干等 timeout】,
   attempts 次都耗尽 -> 整个解析超时失败。
5. ★ 真正的坑:坏掉的 10.0.0.3 没人发现,它静静躺在
   resolv.conf 里,像个随机触发的地雷。
解析时好时坏,是因为备用 DNS 服务器早就坏了。

修复 1:Linux 的域名解析顺序——先查谁后查谁

# === ★ 一个域名,Linux 到底是怎么一步步解析的 ===
# 程序调 getaddrinfo() 解析域名时,glibc 并不是直接发 DNS,
# 它先看一个配置文件,决定【按什么顺序、问哪些来源】。

# === 这个顺序由 /etc/nsswitch.conf 的 hosts 行决定 ===
$ grep '^hosts' /etc/nsswitch.conf
hosts:      files dns
# ★ files dns 的含义:解析一个域名时 ——
#   1. 先查 files  —— 也就是 /etc/hosts 这个本地文件
#   2. /etc/hosts 里没有,才查 dns —— 走 DNS 服务器
# 顺序是写死的,从左到右。

# === /etc/hosts:本地的"域名-IP"静态映射表 ===
$ cat /etc/hosts
127.0.0.1   localhost
10.0.5.20   api.internal.example.com   # 在这写一行,就不走 DNS 了
# ★ 这是一个常被忽略的【排查 + 应急】手段:
#   - 排查:怀疑 DNS 有问题,在 hosts 里写死,绕开 DNS 验证
#   - 应急:DNS 整体挂了,关键域名先在 hosts 里写死,先恢复服务

# === ★ /etc/hosts 的优先级高于 DNS,这是双刃剑 ===
# 好处:能强制指定某域名解析到哪
# 坑:如果有人很久以前在 hosts 里写死过某域名,
#     后来这个域名的真实 IP 变了,机器却一直用着 hosts 里的老 IP——
#     而且你查 DNS 怎么查都是对的,百思不得其解。
# ★ 排查域名解析,第一件事就是看一眼 /etc/hosts 里有没有它。

# === 验证"实际"解析走了哪条路 ===
$ getent hosts api.internal.example.com
# ★ getent 会完全按 nsswitch.conf 的规则来解析 ——
#   它的结果,才是程序真正会拿到的结果。
# 对比:dig 只查 DNS,getent 走完整链路(含 hosts)。

修复 2:/etc/resolv.conf——DNS 解析的核心配置

# === 走到 DNS 这一步,一切由 /etc/resolv.conf 决定 ===
$ cat /etc/resolv.conf
nameserver 10.0.0.2
nameserver 10.0.0.3
search internal.example.com example.com
options timeout:2 attempts:2 rotate

# === nameserver:DNS 服务器地址 ===
# ★ 可以配多个,但要理解它们的关系:
#   - 默认行为是【故障转移】,不是负载均衡:
#     永远先问第 1 个;第 1 个超时没响应,才问第 2 个。
#   - 最多生效 3 个,多写无效。
# ★ 这次的坑就在这:第 2 个坏了,平时被第 1 个挡着没人知道,
#   一旦轮到它,就是一次解析失败。

# === search:域名搜索后缀 ===
# 当你解析一个【不带点】的短名字,比如 "api",
# 解析器会自动把 search 里的后缀拼上去依次尝试:
#   api.internal.example.com -> api.example.com
# ★ 副作用:解析一个本就完整的域名时,如果它最后没加点,
#   解析器有时也会先拼 search 后缀试一遍(失败)再试原名——
#   平白多出几次无用查询,拖慢解析。

# === options:解析行为的微调 ===
# timeout:N    问一个 DNS 服务器,等多少秒算超时(默认 5)
# attempts:N   每个服务器重试几次(默认 2)
# rotate       ★ 多个 nameserver 之间【轮流】发起查询(负载均衡)
# ndots:N      域名里点数 < N 时,先拼 search 后缀再查
# single-request  某些环境下,把 A/AAAA 查询【分开发】,避坑

# === ★ timeout × attempts × nameserver 数 = 最坏解析耗时 ===
# 默认 timeout 5、attempts 2、两个 nameserver:
#   最坏情况一次解析能卡 5×2×2 = 20 秒!
# ★ 教训:timeout 调小(如 1~2),解析失败能【快速失败】,
#   而不是让整个请求链路被一次 DNS 卡死 20 秒。

# === resolv.conf 常被自动覆盖,要小心 ===
# NetworkManager、dhclient、systemd-resolved 都可能重写它。
$ ls -l /etc/resolv.conf       # 看它是不是个软链接(指向别处)

修复 3:用 dig 排查 DNS——这次的根因

# === dig:排查 DNS 最强的工具 ===
$ dig api.internal.example.com
;; ANSWER SECTION:
api.internal.example.com. 300 IN A 10.0.5.20
;; Query time: 3 msec
;; SERVER: 10.0.0.2#53(10.0.0.2)
# ★ 重点看三处:
#   ANSWER SECTION  —— 解析出的 IP,以及 TTL(这里 300 秒)
#   Query time      —— 这次查询耗时
#   SERVER          —— ★ 实际是哪个 DNS 服务器答的

# === +short:只要结果,适合脚本 ===
$ dig +short api.internal.example.com
10.0.5.20

# === ★ 本次破案的关键:逐个指定 DNS 服务器单独测 ===
$ dig @10.0.0.2 api.internal.example.com +short
10.0.5.20                       # 第一个 DNS:正常
$ dig @10.0.0.3 api.internal.example.com
;; connection timed out; no servers could be reached
# ★ 第二个 DNS:超时无响应 —— 它坏了!
# 把 resolv.conf 里每个 nameserver 都 @ 出来单独 dig 一遍,
# 立刻就能发现"哪个 DNS 服务器是坏的"。

# === 看解析的完整过程(为什么慢、卡在哪)===
$ dig +trace api.internal.example.com
# 从根域名服务器开始,一级级往下追,能看到是哪一级慢/失败。

# === 查不同类型的记录 ===
$ dig api.example.com A         # IPv4 地址
$ dig api.example.com AAAA      # IPv6 地址
$ dig example.com MX            # 邮件服务器
$ dig example.com NS            # 该域名的权威 DNS
$ dig -x 10.0.5.20              # 反向解析:IP 查域名

# === nslookup / host:更轻量的查询 ===
$ nslookup api.internal.example.com
$ host api.internal.example.com
# 快速看一眼用它们;要细节、要排查,用 dig。

# === ★ dig 和程序解析的区别,务必记住 ===
# dig 【只查 DNS】,它【不看 /etc/hosts】、不走 nsswitch。
# 程序解析走的是完整链路。所以:
#   dig 能解析、程序却解析失败 -> 问题可能在 hosts 或 nsswitch
#   要复现"程序看到的结果",用 getent hosts,别用 dig。

修复 4:DNS 缓存——nscd 与 systemd-resolved

# === DNS 解析结果会被缓存,缓存也可能是坑 ===
# 缓存的好处:同一域名反复解析,不必每次都发 DNS 查询。
# 缓存的坑:域名 IP 变了,但本机还拿着【过期的缓存】。

# === 缓存可能在好几个地方 ===
# 1. DNS 记录自带的 TTL —— 解析结果"保鲜"多少秒(dig 里能看到)
# 2. 本机的缓存服务:nscd、systemd-resolved、dnsmasq
# 3. ★ 应用程序自己进程内的缓存(尤其 JVM —— 见下)

# === nscd:传统的名称服务缓存守护进程 ===
$ systemctl status nscd        # 看它有没有跑
$ nscd -g                      # 看缓存命中统计
$ nscd -i hosts                # ★ 清空 hosts 的缓存
$ systemctl restart nscd       # 或直接重启,缓存全清

# === systemd-resolved:较新系统的解析服务 ===
$ systemd-resolve --status     # 看它的 DNS 配置和状态(新版:resolvectl status)
$ systemd-resolve --flush-caches    # ★ 清空它的缓存
$ resolvectl query api.example.com  # 用它来解析测试

# === ★ JVM 的 DNS 缓存 —— 一个极隐蔽的坑 ===
# Java 程序默认会把 DNS 解析结果【缓存在进程内】,
# 默认策略下,成功的解析甚至可能【永久缓存】。
# 后果:域名 IP 变了,操作系统层面早更新了,
#       但这个 Java 进程还死守着启动时解析到的老 IP。
# 解法:在 java.security 里配 networkaddress.cache.ttl=60
#       让 JVM 的 DNS 缓存也有个合理的过期时间。
# ★ 排查"OS 层 dig 正常、唯独某 Java 服务连错 IP",查这个。

# === 验证缓存有没有在作怪 ===
$ dig +short api.example.com   # OS 层解析到的(可能也有缓存)
# 和"程序实际连的 IP"对比 —— 不一致,就是某层缓存过期了。

修复 5:解析慢、解析超时的优化

# === ★ DNS 慢,会拖慢整个请求 —— 它是请求的第一步 ===
# 每次建连前都要先解析域名,DNS 慢 1 秒,请求就慢 1 秒。

# === 优化 1:timeout 调小,让失败【快速失败】 ===
$ vim /etc/resolv.conf
options timeout:1 attempts:2
# 默认 timeout 5 太长 —— 一个坏 DNS 能让你干等 5 秒。
# 调成 1~2 秒,坏 DNS 能被快速跳过。

# === 优化 2:nameserver 把最快、最稳的放第一个 ===
# 默认是顺序故障转移,第一个最关键 ——
# 第一个稳,绝大多数查询一次就成,根本轮不到后面的。
# 加 options rotate 则变成轮流查询,分摊压力。

# === 优化 3:警惕 search 后缀放大查询次数 ===
# search 后缀多 + ndots 设置不当时,
# 解析一个域名可能在底层变成查 4~5 次。
# ★ 对策:代码里、配置里用域名,【末尾加一个点】,
#   如 api.example.com.  —— 末尾的点表示"这是完整域名,
#   别再拼 search 后缀了",一步到位。

# === 优化 4:稳定不变的关键域名,直接写进 /etc/hosts ===
# 内网那些 IP 几乎不变的核心服务,写进 hosts ——
# 解析瞬间完成,且完全不受 DNS 服务器故障影响。
# ★ 代价:IP 真变了得手动改,所以只用于"极少变"的域名。

# === 优化 5:本机上一个缓存,减少对外查询 ===
# 部署 dnsmasq 或 systemd-resolved 做本地 DNS 缓存,
# 热点域名的解析直接命中本机缓存,不必每次都出网查询。

# === 量化 DNS 到底花了多少时间 ===
$ dig api.example.com | grep 'Query time'
# 或者用 curl 看完整耗时拆解里的 DNS 部分:
$ curl -o /dev/null -s -w 'dns:%{time_namelookup} total:%{time_total}\n' \
    https://api.example.com/
# ★ time_namelookup 就是 DNS 解析耗时 —— 它若占了大头,就是 DNS 慢。

修复 6:DNS 排查纪律

# === 这次事故暴露的 DNS 认知盲区,定几条纪律 ===

# === 1. ★ 解析时好时坏,逐个 dig @每个 nameserver ===
$ dig @10.0.0.2 域名
$ dig @10.0.0.3 域名
# 把 resolv.conf 里每个 DNS 服务器单独测 ——
# "时好时坏"几乎总是因为其中某个备用 DNS 坏了。

# === 2. resolv.conf 多个 nameserver 是故障转移,不是负载均衡 ===
# 默认先问第一个;它超时,才问第二个。
# 所以备用 DNS 坏了平时看不出来,要主动逐个体检。

# === 3. ★ 排查解析,先看 /etc/hosts ===
$ grep 域名 /etc/hosts
# hosts 优先级高于 DNS。有人写死过的老记录,是头号隐形坑。

# === 4. 复现"程序看到的解析",用 getent,不用 dig ===
$ getent hosts 域名            # 走完整链路(hosts + DNS)
$ dig 域名                     # 只查 DNS
# 两者结果不一致 -> 问题在 hosts 或 nsswitch。

# === 5. timeout 一定要调小 ===
# 默认 timeout 5 会让一次解析失败卡很久。
# options timeout:1~2,让坏 DNS 被快速跳过。

# === 6. 别忘了缓存层 ===
# nscd、systemd-resolved、尤其 JVM 进程内缓存 ——
# OS 层 dig 正常但程序连错 IP,十有八九是某层缓存过期。

# === 7. 排查 DNS 的命令链 ===
$ cat /etc/resolv.conf         # ① 配了哪些 DNS、什么 options
$ grep 域名 /etc/hosts         # ② hosts 里有没有写死它
$ getent hosts 域名            # ③ 程序实际会解析成什么
$ dig @每个nameserver 域名     # ④ 逐个体检 DNS 服务器
$ dig +trace 域名              # ⑤ 慢/失败时追完整解析过程
# 按这个顺序,DNS 问题基本能定位。

命令速查

需求                        命令
=============================================================
看本机 DNS 配置              cat /etc/resolv.conf
看解析顺序(files/dns)      grep hosts /etc/nsswitch.conf
看 hosts 里写死的映射        cat /etc/hosts
程序视角的完整解析           getent hosts 域名
查 DNS(详细)              dig 域名
指定某个 DNS 服务器查        dig @DNS服务器IP 域名
只要解析结果                dig +short 域名
追完整解析过程              dig +trace 域名
反向解析 IP 查域名           dig -x IP
清 DNS 缓存                  nscd -i hosts / systemd-resolve --flush-caches

口诀:解析时好时坏先看 hosts -> 再逐个 dig @每个 nameserver
      多个 DNS 是故障转移不是负载均衡 -> timeout 务必调小

避坑清单

  1. resolv.conf 多个 nameserver 默认是故障转移,先问第一个,坏了才问下一个
  2. 备用 DNS 服务器坏了平时被主 DNS 挡着,要主动逐个 dig @ 体检
  3. 排查解析第一件事看 /etc/hosts,它优先级高于 DNS,老记录是隐形坑
  4. dig 只查 DNS 不看 hosts,复现程序视角的解析要用 getent hosts
  5. resolv.conf 的 timeout 默认 5 秒太长,要调到 1~2 秒让坏 DNS 快速跳过
  6. 最坏解析耗时 = timeout × attempts × nameserver 数,可能高达几十秒
  7. search 后缀会放大查询次数,完整域名末尾加点可跳过 search 拼接
  8. nscd、systemd-resolved 会缓存解析结果,IP 变了要清缓存
  9. JVM 默认进程内缓存 DNS 甚至永久缓存,要配 networkaddress.cache.ttl
  10. resolv.conf 常被 NetworkManager/dhclient 自动覆盖,改动可能不持久

总结

这次"域名解析时好时坏"的事故,纠正了我对 /etc/resolv.conf 里那几行 nameserver 一个想当然的理解。在这次之前,我一直以为,在 resolv.conf 里配两个 DNS 服务器,是为了"负载均衡"和"高可用"——两个服务器一起分担解析请求,坏了一个还有另一个顶上,稳如磐石。正是这个错误的想象,让我在面对那个时好时坏的故障时,完全没往 DNS 配置本身去想:我心想都配了两个 DNS 了,就算坏一个,另一个也能扛住,解析怎么可能失败呢?复盘到根上,我才搞清楚 resolv.conf 里多个 nameserver 真实的工作方式。它默认的行为,根本不是"负载均衡",而是"故障转移",而且是一种相当朴素、甚至有点笨拙的故障转移:解析器永远、无条件地先去问列表里的第一个 DNS 服务器;只有当第一个服务器在 timeout 规定的时间内【完全没有响应】时,解析器才会退而求其次,去问第二个。这个机制有一个极其隐蔽的副作用——只要第一个 DNS 服务器是好的,它每次都能正常应答,那么第二个 DNS 服务器到底是死是活,平时根本【没有任何人、任何监控会知道】。它就那样静静地躺在 resolv.conf 的第二行里,像一颗被遗忘的地雷。我那台服务器的情况,正是如此:第二个 DNS 服务器 10.0.0.3,不知在多久之前就已经坏掉了,但因为第一个 10.0.0.2 一直兢兢业业地工作着,把所有查询都挡在了前面,所以这颗地雷的存在,从来没被察觉。而一旦在某些情况下,解析流程触发了对第二个服务器的查询,请求就会落到那台早已死掉的 10.0.0.3 上,然后就是漫长的、徒劳的等待——等到 timeout 耗尽、attempts 次重试也全部耗尽,这一次域名解析,就以一个 Temporary failure in name resolution 宣告失败。这,就是"时好时坏"背后那个一点都不玄学的真相:它不是 DNS 在随机抽风,而是一颗早就埋下的、会被随机踩中的地雷。找到根因之后,破案的方法回头看简单得可笑:我只要把 resolv.conf 里的每一个 nameserver,都用 dig @ 单独地、点对点地查询一遍,第二个服务器的超时无响应,立刻就会暴露在眼前。这成了我从这次事故里带走的第一条铁律——当域名解析表现出"时好时坏"这种症状时,不要去怀疑域名、怀疑下游服务,第一件事,就是把本机配的每一个 DNS 服务器拎出来挨个体检。这次排查也顺带逼着我把整条域名解析链路彻底捋了一遍,补上了不少认知缺口:我这才真正重视起 /etc/hosts 那个文件——它的优先级是高于 DNS 的,任何人在很久以前往里写死的一条过期记录,都可能让你对着完全正确的 DNS 配置百思不得其解;我也才弄清 dig 和程序实际解析的区别——dig 只查 DNS,而程序走的是 nsswitch.conf 定义的完整链路,要复现程序真正看到的解析结果,该用的工具是 getent hosts 而不是 dig;我还重新认识了 timeout 这个参数的分量——它默认的 5 秒实在太长,一个坏掉的 DNS 就能让一次解析、乃至上层整个请求,硬生生卡住好几秒,把它调小到 1 到 2 秒,才能让坏 DNS 被快速地跳过去。这次从一个看似玄学的故障出发,我最大的收获,是不再把"配了多个 DNS"当成一种可以高枕无忧的冗余:冗余的备份如果从不被检验,它就不是冗余,而是一颗你以为不存在、却随时可能被踩响的地雷。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
Linux教程

一边说发了一边说没收到:一次 Linux tcpdump 抓包排查复盘

2026-5-20 18:45:32

Linux教程

一个文件权限 777 还是访问不了:一次 Linux 权限与 SELinux 排查复盘

2026-5-20 18:53:39

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索