找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4812

积分

0

好友

620

主题
发表于 1 小时前 | 查看: 3| 回复: 0

一、问题背景

DNS 解析是最容易被忽略、也最能一波带走整片服务的“隐形炸弹”。它不是你的应用业务,却直接决定应用发出的域名请求能不能打得出去。这类问题在云栈社区的运维板块也经常有人求助讨论。

一线运维大概率遇到过这些:

  • 应用偶尔“卡住十几秒”然后失败,重试就好了,网络 layer 全通,ping 网关正常——最后追到是 DNS 查询超时。
  • 换了上游 DNS 或改了 resolv.conf 之后,原来没问题的一批域名开始飘,有的通有的不通。
  • 内网服务偶尔解析出奇怪结果,排查发现是 search 域把短名拼成了别的全限定域名,打到了错误的主机。
  • 容器里跑的应用明明配好了 DNS,却迟迟解析不来,一看是 ndots:5 让每个查询之前先做一堆不必要的尝试,累积延迟翻倍。
  • 一次应用升级后,解析从“几毫秒”变成“几百毫秒”,还没法解释,直到看到查询路径里多了几跳。

DNS 的坑非常多,而最常翻车的三个就是标题里点名的:resolv.conf 的配置与顺序、ndots 对查询尝试次数的影响、search 域对短名解析的改写。这三者加上与系统缓存、nscd/systemd-resolved、NTP 时钟、超时重试的组合,构成了 DNS 慢/失败排查的核心地图。

这篇文章给你一条从“现象”到“根因”的完整排查路径,每一层都配命令、预期输出、判断逻辑和修复/回滚。目标不是堆 DNS 概念,而是让你能在生产上十分钟定位“是 DNS 的问题,是配置的问题,还是上游的问题”,以及怎么安全地改、怎么验证、怎么回滚。

二、适用场景

适合以下情况:

  • 应用域名解析慢、超时、时通时不通、偶发失败。
  • 修改过 resolv.conf 或 /etc/nsswitch.conf、/etc/hosts、DNS 相关配置后行为改变。
  • 内网服务用短名(db、redis)访问,解析到错误主机或延迟高。
  • 容器/K8s Pod 内解析行为与物理机不一致。
  • 需要判断是“域名解析问题”还是“网络/TLS/连接问题”,把故障定位到正确层。
  • 需要统一多台机器 DNS 配置并做验证与回滚。

慎用场景:如果你连 ping 目标 IP 都不同,那不是 DNS 问题,先解决连通性。DNS 排查的前提是“网络层可达、问题范围集中在名称解析”。

三、核心知识点

3.1 解析流程

一次域名解析大致经过:

  1. 应用调用 getaddrinfo() 等解析函数。
  2. 查询顺序由 /etc/nsswitch.conf 的 hosts: 行决定,常见顺序 files dns(先查 /etc/hosts,查不到再查 DNS)。
  3. DNS 查询按 /etc/resolv.conf 的 nameserver 逐个尝试。
  4. 查询名要依据 ndots 和 search 决定是“直接作为全限定域名查询”还是“依次拼 search 域尝试”。
  5. 结果可能被 nscd / systemd-resolved / 各进程缓存。
  6. 每个 nameserver 有 timeout 和 attempts,超时后重试。

理解这条链路,才知道故障可能卡在哪一环。

3.2 resolv.conf 的关键字段

/etc/resolv.conf 是几乎所有解析栈都读的主配置文件(systemd 发行版可能是由 systemd-resolved 生成的软链或由它接管)。常用字段:

nameserver 10.0.0.53      # 最多可配多个,按顺序尝试
search example.com sub.example.com   # 多个 search 域,用空格分隔
domain example.com        # 旧式单域配置,某些场景与 search 互斥
options timeout:1 attempts:2 ndots:1 rotate
  • nameserver:最多 3 个,查询时按顺序(或 options rotate 轮转)逐个尝试。
  • search / domain:用于给“非全限定名”拼后缀。
  • ndots:决定“名字里含几个点以上的,先当全限定名直接查;少于这个就先用 search 域拼”。默认 1。
  • timeout:单个 nameserver 的单次查询超时(秒),默认 5。
  • attempts:每个 nameserver 的重试次数,默认 2。
  • rotate:在多个 nameserver 间轮转,缓解总是打第一个。

3.3 ndots 的语义,恐怕是最被低估的

ndots 表示“名字中至少含 N 个点,才把整个名字当作 FQDN 直接查”。

默认 ndots:1。假设 search 域是 example.com:

  • host.example.com:含 1 个点,满足 ndots:1,直接查 host.example.com。
  • host:0 个点,少于 ndots:1,先尝试 host.example.com,不行再试原样 host。

在把 ndots 调到 5(很多编排/K8s 配置常用 ndots:5)时,host(0 点)和 host.example.com(1 点)这种名字都会先去试一堆 search 拼接的变体,每个变体都增加一次尝试和可能的超时延迟。如果 search 域还配了好几个,且某些拼接的后缀无解析,逐个尝试带来的累积延迟会非常可观。这是容器内“解析慢”的头号嫌疑。

3.4 search 域的“坑”从哪里来

search 拼接逻辑 + 机器上找不到的外部域名名撞车,容易解析到错误目标。比如内网 db 拼成 db.prod.example.com 与公网同名冲突,或某个短名被 search 拼到了不对的域,造成“解析成功但打得是错地方”。

同时 search 域里域名解析不了的,每次查询都要多试失败路径,浪费一次超时/延迟。

3.5 /etc/hosts 与 nsswitch.conf

  • /etc/hosts:静态映射,最高优先级(若 hosts: 行把 files 放最前)。
  • /etc/nsswitch.conf 的 hosts: 决定顺序。例如 hosts: files dns 表示先查 hosts 再查 DNS。
  • 排查“解析结果和预期不符”时,先看 /etc/hosts 有没有被污染。

3.6 缓存层

  • nscd(传统)/ systemd-resolved(现代发行版):会缓存解析结果。
  • 应用进程自己的 DNS 缓存(如 Java、Go、Node 各有缓存策略)。
  • 改配置后看不到变化,先怀疑缓存。

3.7 上游与递归

机器配置的 nameserver 通常是递归解析器(如 /etc/resolv.conf 指向的区 DNS、云 VPC DNS、或公共 8.8.8.8)。解析慢可能在上游,也可能在到上游的路径。用 dig +trace 能把递归路径摊开看每跳耗时。

四、整体排查思路

DNS 排查按“从近到远、从本地到上游”推进:

第一步:先确认是不是 DNS 问题(对比 IP 直连)
   ↓
第二步:看本地解析栈配置(resolv.conf / nsswitch.conf / hosts)
   ↓
第三步:用 dig 复现并拆分“配置本身”与“上游响应”
   ↓
第四步:核对 ndots / search 对查询路径的改写
   ↓
第五步:检查超时/重试/cache 层(timeout、attempts、nscd/resolved)
   ↓
第六步:核对时间(NTP)与多 nameserver 顺序/故障
   ↓
第七步:修复 + 验证
   ↓
第八步:回滚预案 + 防复发(监控与缓存策略)

每步都有明确的可执行检查。下面逐层展开。

五、实战排查步骤

5.1 第一步:确认确实是 DNS,而不是连通性

目标:把“域名解析慢/失败”和“到目标的网络不通”区分开。

# 直接用 IP 试连通性
ping -c 3 <目标IP>
curl -v --connect-timeout 3 http://<目标IP>/   # 如果你的目标有 HTTP

# 先解析出 IP 本身花多久
time getent hosts <域名>
time nslookup <域名>

预期输出与判断:

  • getent hosts 慢/失败 → 确实是“名称解析”的问题,继续往下。
  • getent hosts 很快、IP curl 也通 → 那问题可能不在普通解析,而在应用用的具体解析函数/缓存/代理,另查。
  • 连 IP 都不通 → 不是 DNS,是连通性,先修网络。

注意:getent hosts 走的是 glibc 解析栈(含 hosts/dns),与 dig(直接查 DNS,绕过 /etc/hosts 的 files 部分)行为不完全一样。两者都试,能看出“是本机配置截断,还是上游/栈本身慢”。

5.2 第二步:检查本地解析配置

cat /etc/resolv.conf
cat /etc/nsswitch.conf | grep hosts
cat /etc/hosts

判断逻辑:

  • nameserver 指向的 IP 是否可达、是否合理(内网机指到了公网 114/8.8 且不通,就是它)。
  • hosts: 顺序是否被改成先 DNS 意外跳过 files。
  • /etc/hosts 里有没有干扰项。
  • 若 /etc/resolv.conf 由 DHCP/systemd-resolved 自动生成,手动改会被覆盖,要改源头。

5.3 第三步:用 dig 分离本地栈与上游

dig 直接查询,能绕过部分本地配置,用于定位“上游能否解析”:

# 指定服务器直接查,绕开搜索拼接
dig @10.0.0.53 <FQDN>

# 看用了多久
dig @10.0.0.53 <FQDN> +time=2 +tries=1

# 看 search 拼接下实际会发哪些查询
dig <短名> +search +time=2

# 摊开递归路径看每跳耗时
dig +trace <FQDN>

预期输出与判断:

  • dig @server FQDN 快且返回正确 → 上游 OK,问题在本地配置(ndots/search/顺序/缓存)。
  • dig @server FQDN 慢/超时 → 上游或到上游路径有问题。
  • dig 短名 +search 能看到实际拼接出的多个查询,逐个看哪个慢/失败。
  • +trace 看每跳耗时,定位是根域/权威延迟,还是递归器本地。

注意:@server 指定 IP 时 +search 通常不生效于拼接,要理解你要隔离的是哪层。

5.4 第四步:核对 ndots 与 search 的改写

# 看当前 options
cat /etc/resolv.conf | grep -E 'ndots|search|options'

# 用带 search 的解析看短名会拼成什么
getent hosts <短名> || true
dig <短名> +search +short

判断逻辑:

  • ndots:5 + search a b c:短名会依次尝试 name.a、name.b、name.c、name 多个,每个都可能加延迟。
  • 想让“短名只当短名查”、“FQDN 直查”,要调整 ndots 或去掉多余 search 域,或调用方用带结尾点 /绝对域名 的超限定写法。

调优方向:

# 对“几乎全用 FQDN 的应用”,把 ndots 降到 1 或 0,减少无谓拼接尝试
options ndots:1

# 或去掉多余 search 域
search example.com

5.5 第五步:超时、重试与缓存

# 查看默认超时/尝试
grep -E 'timeout|attempts|rotate' /etc/resolv.conf

# 查看是否有 nscd / systemd-resolved 接管
systemctl status nscd systemd-resolved 2>/dev/null
ls -l /etc/resolv.conf       # 看是否软链到 systemd-resolved

# 查看 resolved 是否拦截
readlink -f /etc/resolv.conf
resolvectl status 2>/dev/null | head

判断逻辑:

  • 默认 timeout:5 attempts:2 意味最坏情况一次解析可能拖到几十秒,多个 search 变体叠加更夸张。
  • 改配置后“没效果”,先清缓存:systemctl restart nscd / systemctl restart systemd-resolved / 应用复位。
  • systemd-resolved 接管时,/etc/resolv.conf 是指向 /run/systemd/resolve/... 的软链,直接改链文件会失效,要改 resolved 配置。

5.6 第六步:NTP 与多 nameserver 顺序

DNS(尤其 DNSSEC、TLS、部分递归器)对时间敏感:

timedatectl
timedatectl status | grep -E 'synchronized|NTP'

# 多 nameserver 行为
grep nameserver /etc/resolv.conf

判断:

  • 时间大幅偏差 → 影响安全扩展与若干递归器策略,先校时。
  • 多个 nameserver 中第一个不可达/极慢,会拖累所有查询(除非 rotate)。用 options rotate 轮转,或把最可靠的放最前。

5.7 第七步:修复与验证

按根因分别修:

  • 修 resolv.conf 后:
# 用 dig 复测
dig @10.0.0.53 <FQDN> +time=2 +tries=1
getent hosts <FQDN>
  • 若 systemd-resolved 接管,改 /etc/systemd/resolved.conf 的 DNS=、Domains=,再 systemctl restart systemd-resolved。
  • 短名场景调 ndots / 精简 search。

验证要成体系:getent hosts(glibc 栈)、dig(上游直查)、应用实际请求三者都变快/正常,才算修复。

六、常用命令速查

# 解析与测速
dig @server name
dig name +short
dig name +trace
getent hosts name
nslookup name
host name

# 配置
cat /etc/resolv.conf
cat /etc/nsswitch.conf | grep hosts
cat /etc/hosts
ls -l /etc/resolv.conf

# 缓存与服务
systemctl status nscd systemd-resolved
systemctl restart nscd systemd-resolved
resolvectl status

# 时间
timedatectl status

# 抓包确认是否真的发了 DNS 查询
tcpdump -i eth0 -n port 53 -c 50

七、每个根因的深度排查:命令、输出、判断

下面把高频根因逐一展开,给足命令和判断点。

7.1 nameserver 不可达 / 顺序不当

确认:

# 挨个测 nameserver 可达性
cat /etc/resolv.conf | grep nameserver
for ns in $(awk '/nameserver/{print $2}' /etc/resolv.conf); do
echo "== $ns =="; timeout 2 nc -vz "$ns" 53 2>&1 || echo "$ns:53 不可达"
done

判断:任一不可达且排前面的,就会把查询拖到 timeout 才轮到下一个。修复:把可达且快的放前面,或用 rotate,或把不可达的删掉。

7.2 ndots 过大导致的延迟

确认:

cat /etc/resolv.conf | grep ndots
# 或
resolvectl status

time dig 短名 +search +time=2 | grep -E 'time|status'

判断:search 变体多次尝试导致时间长。修复:调 ndots 或精简 search;对确定 FQDN 用结尾点(name.example.com.)。

验证:

time getent hosts name.example.com.
time getent hosts name.example.com

7.3 search 域拼错/撞域

确认:

getent hosts <短名>
dig <短名> +search

判断:解析到错误的 IP,多半是 search 拼接把短名带到了不该带的域。修复:清掉多余 search 域,或短名改 FQDN,或 /etc/hosts 静态钉住。

风险:/etc/hosts 静态映射是绕开 DNS 的强手段,但也带来“迁移/变更不生效”的新坑,用了要记录。

7.4 缓存造成的“改了没效果”

确认:

cat /etc/resolv.conf
nscd 状态 / resolved 状态

修复并验证:

systemctl restart nscd    # 如果启用
systemctl restart systemd-resolved
getent hosts <FQDN>       # 复测

7.5 时间偏差影响解析

确认:

timedatectl

修复:timedatectl set-ntp on 或跑 chrony/ntpdate,让时间同步。

7.6 应用层独立解析/缓存(Java、Go、Node)

Java:依赖 JVM 的 networkaddress.cache.ttl,默认带缓存,改主机后老结果要等 TTL。Go:net.Dialer 与 LOOKUP 控制。Node:DNS 结果有进程内 cache。

# Java 示例:把 TTL 调小是 JVM 启动参数层面的,属应用配置,不在 resolv.conf
# 这是“应用层”,与系统 /etc/hosts 无关,需在应用侧处理

判断:系统 dig 正常、应用却不正常 → 查应用自己的缓存/代理/DNS 库策略。

7.7 DNSSEC 校验导致的失败/慢

部分递归器启用 DNSSEC,权威侧记录有问题会 SERVFAIL。确认:

dig @server FQDN
# SERVFAIL 提示校验问题

判断:SERVFAIL 而不是 NXDOMAIN → 可能是 DNSSEC。验证关 DANE 单测或连公共递归复查,属协作才处理的上游问题。

八、配置示例

8.1 一个面向“内网全 FQDN 应用”的 resolv.conf 示例

# /etc/resolv.conf
# 注释说明由谁管理
nameserver 10.0.0.53
nameserver 10.0.1.53
search example.com
options timeout:1 attempts:2 ndots:1

其中 ndots:1 让“带一个点的”直接当 FQDN,短名才用 search,减少拼接延迟。

8.2 面向“短名多、内网服务多”的示例

nameserver 10.0.0.53
search prod.example.com staging.example.com
options timeout:2 attempts:2 ndots:1 rotate

rotate 让两个 nameserver 轮转,避免总打第一个。

8.3 K8s/容器场景提醒

Pod 的 dnsConfig 里可能覆盖 ndots。比如 ndots:5 是常见默认,短名查询会先试一堆 search 变体。如需,可在 spec.dnsConfig.options 里设 ndots:1,但要评估对同一域名空间其它解析的影响,属应用/编排层配置,与 /etc/resolv.conf 分开管理。

# 该示例是 Kubernetes Pod 的 DNS 配置片段(示意)
dnsPolicy: ClusterFirst
dnsConfig:
  options:
  - name: ndots
    value: "1"
  - name: single-request-reopen

说明:single-request-reopen 这类选项在不同 resolver(glibc / musl / 应用自实现)语义不同,是否开启以实际运行栈为准,别照抄。

九、日志与指标观察方法

  • DNS 本身常无独立日志文件,靠主动查询命令和抓包观察。抓包最可信:
tcpdump -i any -n 'udp port 53' -c 100
tcpdump -i any -n 'tcp port 53' -c 100
  • 观察点:查询发往哪个源/目标、单次查询耗时、重传、失败([.] 域标记)、SERVFAIL。

  • 如果被 systemd-resolved 接管,resolvectl statistics 能看命中率与时长:

resolvectl statistics
  • 正常范围:局域网解析通常毫秒级;跨网/公网递归可能几十毫秒到数百毫秒,需结合你网络基线判断。异常表现:稳定几百毫秒以上、timeout、SERVFAIL 突增、查询重复。

十、排查路径(决策树)

域名解析慢/失败?

A. IP 直连通吗?
   不通 → 不是 DNS,先查网络
   通 → 往下

B. dig @server FQDN 正常吗?
   不正常 → 上游/到上游路径问题
   正常 → 往下

C. getent hosts FQDN 正常吗?
   不正常 → 本地 config/栈/缓存/时间
   正常 → 往下

D. 短名 vs FQDN 表现一样吗?
   不同 → 重点查 ndots/search
   一样 → 查 cache/超时/多 nameserver

E. 抓到包的耗时/失败在哪?
   本地前几跳 → 配置/拼接
   上游无回应 → 上游/防火墙 UDP53

沿树走到叶子就是根因。

十一、风险提醒

  • 改 /etc/resolv.conf 属于系统配置变更,改错可能整机解析失效,先备份再改。
  • 手动改的 resolv.conf 会被 DHCP / systemd-resolved 覆盖,生产要用正规入口。
  • 乱删 search 域可能让确实依赖短名的服务失联。
  • ndots 调低可能让本应被 search 处理的短名变成裸查询失败。
  • rotate 会改变查询行为,验证后再落生产。
  • restart nscd/systemd-resolved 会清一次全局缓存,是一闪而过的清理动作,属高危清单里的“清理日志/重启服务”,执行窗口内可能有短暂抖动,需低峰做。
  • 在 /etc/hosts 钉记录会绕开 DNS,容易造成“迁移后仍解析到旧 IP”的新坑,务必登记。
  • 删除/覆盖配置前先 cp 备份,给出回滚内容。
  • 生产变更要备份、灰度、回滚、记录。

十二、验证方式

  • 备份存在:ls -l /etc/resolv.conf.bak*。
  • dig @server FQDN 短时间返回正确 RCODE。
  • getent hosts FQDN 快速返回。
  • 应用实连正常。
  • 抓包确认查询次数/耗时符合预期(短名不产生多余变体)。
  • 多 nameserver 顺序/rotate 行为按预期。
  • 恢复一个周期无告警、无新增错误。

十三、回滚方案

# 备份
cp /etc/resolv.conf /etc/resolv.conf.bak.$(date +%F)

# 回滚(恢复修改前的文件)
cp /etc/resolv.conf.bak.$(date +%F) /etc/resolv.conf

# 若 systemd-resolved 模式,另处理系统配置后再重启
# 记录原 resolve 配置并回滚后 systemctl restart systemd-resolved
  • 若因 restart nscd 抖动:等待自愈,或按序重放服务。
  • 回滚后验证 getent hosts 与应用恢复。

十四、生产环境注意事项

  • 变更窗口选低峰,通知受影响业务。
  • 灰度单台验证再批量。
  • 统一配置用配置管理下发,避免漂移。
  • 多租户/集群注意与应用层缓存、Pod 覆盖的交互。
  • 把“解析成功率 + 耗时”纳入监控,异常自动反馈。
  • 文档化:谁负责、上游 DNS 是谁、moment 依赖、回滚方式。
  • 时间同步(NTP)本身也纳入监控,时间漂移会放大 DNS 类问题。

进阶排查、实例和交接清单

防复发:把 DNS 纳入常态化运维

  • 建基线:记录正常时解析耗时与成功率,作为告警参照,阈值结合业务基线调整。
  • 监控项:DNS 查询成功率、平均耗时、SERVFAIL 计数(以实际 exporter/采集器指标为准),到达阈值告警。
  • 定期体检:季度核对 resolv.conf 一致性、搜索多余 search 域、检查过期 /etc/hosts 记录。
  • 变更记录:每次 DNS/nameserver/ndots 变更都留 change log。
  • 演练:重要域名做一次“断 nameserver + 回滚”演练,确保流程可走。

完整案例复盘

案例:容器内服务偶发“连接超时”,追到 ndots。

现象:某微服务出去调第三方偶发卡十几秒重试成功。

排查:IP 直连正常,dig @vpc-dns FQDN +time=2 快。但 getent hosts 对服务内短名明显偏慢。看 /etc/resolv.conf 里 options ndots:5 search prod.example.com staging.example.com,短名每次要先拼多个 search 变体,每个失败路径占一次等待。

修复:对这类“几乎全用 FQDN”的服务,把 ndots 调回 1,或应用用 FQDN + 结尾点。灰度单台验证后批量。

验证:time getent hosts FQDN 与抓包确认查询次数下降到 1 次、耗时回到毫秒级。回滚预案:记录原 resolv/覆盖配置,异常时恢复。

复盘:根因在 ndots:5 带来的额外拼接尝试 × 若干 search 域 × timeout 叠加延迟。教训是“改解析配置前先看 ndots/search 对查询路径的改写,而不是只盯 nameserver”。

dig 子命令深入:把“每一层”都看明白

dig 是最趁手的 DNS 探针。这里集中讲几个现场最常用的子用例,每条的输出怎么读。

17.1 常规查询与 RCODE

dig @10.0.0.53 example.com A

看 status(NOERROR/NXDOMAIN/SERVFAIL)、ANSWER 的 ttl、Query time。Query time 是最直观的“这一趟解析花了多久”。

dig +norecurse example.com     # 不递归,看权威角度
dig example.com +short         # 只要结果
dig example.com +noall +answer

判断:+norecurse 在递归器上会得到 status: NOERROR 但 flags 里无 ra,只给出非递归答案,用于判断权威侧有没有问题。

17.3 追递归路径

dig example.com +trace

+trace 从根到权威逐层查,能看到每跳 Query time。慢的那一跳就是延迟来源。是权威偏慢还是你到权威的链路偏慢,肉眼可见。

17.4 指定查询类型与 TCP

dig @server example.com CNAME
dig @server +tcp example.com

DNS over UDP 默认;某些环境 UDP 被过滤或数据太大需 TCP。+tcp 确认 TCP 路径是否也有问题,能帮忙判断“是 UDP 规则挡了 53”这种原因。

17.5 带超时与重试的控制

dig @server example.com +time=2 +tries=1

+time 设单次超时秒数,+tries 设重试次数。这能模拟“快失败”场景,帮你看在网络不佳时是否拖很久。

17.6 一次查好几个名字/批量

for n in db redis cache; do echo "== $n =="; dig $n +short; done

批量看短名各自解析,方便快速扫描哪个慢、哪个失败。

常见 FAQ

19.1 为什么 dig 快而 getent hosts 慢

因为 dig 默认直接查指定的 nameserver,可能跳过 search 拼接和部分缓存层;getent hosts 走系统栈,会读 /etc/hosts、nsswitch、ndots/search、/etc/resolv.conf。两者不一致时,慢的那侧才是问题所在。

19.2 ndots 具体怎么影响速度

ndots 决定“名字有几个点以上才算 FQDN 直查”。点少时按 search 逐个拼,每个失败变体都吃一次尝试/等待。ndots:5 会让一个短名先试很多带点的变体,累积延迟明显。想让短名不拼,就让调用方用 name.(结尾点)或用 ndots:1。

19.3 改 resolv.conf 没生效

多半被 systemd-resolved 接管或 DHCP 覆盖。readlink -f /etc/resolv.conf 看是否软链,是则改 resolved 配置源头,别改链目标。

19.4 为什么同一域名有时通有时不通

多 nameserver 中某台不可达、或 timeout 太小导致偶发超时、或负载分散到慢分支。用 for 循环多次 dig 看失败率,配 rotate 或调整 nameserver 顺序。

19.5 我该不该在 /etc/hosts 钉一堆内网域名

可以但慎重。/etc/hosts 优先级最高、绕开 DNS,能解决“解析不到内网服务”却也会在“IP 变更”时留下旧记录,形成迁移后仍解析到旧 IP 的新坑。若用,登记、限范围、配合监控回收。

19.6 时间不准会影响解析吗

影响。时间偏差大会干扰 DNSSEC 校验、部分递归器策略。timedatectl 核对,跑 NTP/chrony 同步。

19.7 应用自己缓存了 DNS

Java/Go/Node 各自有 DNS 缓存或解析策略,/etc/hosts 与 resolv.conf 改了应用不认是正常的进程内缓存。这类要改应用侧 TTL 或重启应用,不在系统配置。

DNS 变更的合规化操作模板

真正动 DNS 配置前,用这个模板把变更写明白,走正规变更流程。

变更标题:app-01 调整 DNS search 域与 ndots
变更原因:容器内短名解析因 ndots:5 + 多 search 产生多余耗时
变更机器:app 集群(灰度 app-01 → 分两批)
变更前检查:dig/getent 基线耗时;确认 /etc/resolv.conf 归属(是否 resolved)
变更操作:备份原文件 → verbs;灰度一台 → 验证 getent/dig/应用 → 批量
涉及文件:/etc/resolv.conf(或被 systemd-resolved 管理的配置源头)
验证方式:getent 耗时回落 + dig 查询次数下降 + 应用无报错
回滚方式:cp 备份还原 + systemctl restart nscd/resolved;异常恢复
影响范围:该机器所有名称解析行为,声明相关业务
审批人/执行人:____

把解析链路纳入监控的建议

  • 采集“解析成功/失败/耗时”,用 dig/getent 定时探针或系统指标,字段以实际采集器为准。
  • 告警:成功率为 0 持续 N 分钟 → 告警;耗时超基线倍数 → 告警。阈值结合业务周期调整,不必用绝对数字。
# 探针示例(示意脚本,按你的环境路径调整)
for n in <关键域名>; do
  start=$(date +%s%3N)
  getent hosts "$n" >/dev/null 2>&1
  rc=$?
  end=$(date +%s%3N)
  echo "$n rc=$rc ms=$((end-start)) $(date +%F\ %T)"
done
  • 配合 resolvectl statistics(resolved 接管时)看命中率与时长趋势。

dig 输出逐字段怎么读

很多排查卡住是因为不会读 dig 的输出。这里以一次普通查询为例,把字段拆给你。

dig @10.0.0.53 www.example.com A

输出示意:

; <<>> DiG 9.x <<>> @10.0.0.53 www.example.com A
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 12345
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1232
;; QUESTION SECTION:
;www.example.com.  IN  A
;; ANSWER SECTION:
www.example.com.  300  IN  A  93.184.216.34
;; Query time: 23 msec
;; SERVER: 10.0.0.53#53

逐项判断:

  • status: NOERROR:查询成功;NXDOMAIN(域名不存在)、SERVFAIL(解析器处理失败)、REFUSED(被拒绝)。
  • flags:qr(响应)、rd(递归请求)、ra(递归可用)。ra 缺失说明该服务器没在递归。
  • ANSWER:返回的 A 记录与 ttl(这里是 300)。
  • Query time:单次往返耗时,最直观的“快慢”指标。
  • SERVER:实际应答的服务器与端口。

再带 +short 只要结果:

dig @10.0.0.53 www.example.com A +short
# 输出一行 IP,最简单

判断逻辑:dig 正常(NOERROR 且快)而应用仍慢 → 问题在系统栈/配置/缓存/拼接,不是上游。

resolv.conf 被自动管理时怎么正确改

很多发行版 /etc/resolv.conf 是 link,手动 vi 改完一刷新就复原。改之前先确认归属:

ls -l /etc/resolv.conf
readlink -f /etc/resolv.conf
systemctl status systemd-resolved 2>/dev/null

分支处理:

  • 若 link 到 /run/systemd/resolve/stub-resolv.conf → 被 systemd-resolved 接管。
  • 由 DHCP 管理 → /etc/sysconfig/network-scripts / NetworkManager / netplan 里配 DNS,别硬改 link 文件。

改 systemd-resolved 的示例(/etc/systemd/resolved.conf):

[Resolve]
DNS=10.0.0.53 10.0.1.53
Domains=~. example.com
FallbackDNS=119.29.29.29

改完生效:

systemctl restart systemd-resolved
resolvectl status
resolvectl query example.com

判断:resolvectl query 能看出当前 resolved 用的哪些 DNS、命中缓存与否。

各语言运行时的 DNS 行为差异表

栈 是否读 /etc/hosts 进程内缓存 主要影响
glibc getent 是 无(按 TTL) 系统栈基准
dig(BIND) 否 有(小) 测上游最干净
Java 显式 InetAddress 策略 默认有(TTL) 改 hosts 后老结果
Go 是 每跳无/受 Resolver 控制 可用纯 Go 查询
Node 是 有(tls/dns 缓存配置) 改后需重启或 TTL
musl 简化 视实现 Alpine 容器常见坑

判断:同一 FQDN dig 通、Java/Node 应用不通,先假设应用层缓存/解析库,复查 TTL 与应用重启,而不是又改系统配置。

三个更深的案例:如何把“慢/失败”落到字

26.1 案例一:公网域名突然全部超时,但内网正常

现象:出公网域名解析超时,内网域名秒回。

排查:

dig @119.29.29.29 www.baidu.com +time=2 +tries=1
dig @10.0.0.53 www.baidu.com +time=2 +tries=1
getent hosts www.baidu.com

发现:公网解析器不可达,而 resolv.conf 里 nameserver 只指向内网且内网对外递归被限制。修复:给对外流量配可达的递归(云 VPC DNS 的对外接口或防火墙放行 UDP53),并加 FallbackDNS 兜底。

26.2 案例二:改了 /etc/hosts 却仍解析到旧 IP

现象:迁移内网服务 IP 后,应用仍打旧 IP。

排查:

getent hosts svc.internal
cat /etc/hosts
# Java 应用还有进程内缓存

修复:清相关进程缓存 / 重启应用;并给 hosts 记录做登记。这是“从 resolv.conf 之外的角度”定位。

26.3 案例三:Pod 解析特别慢,host 却正常

现象:容器内部分域名解析比宿主机慢一个数量级。

排查:查 Pod 的 resolv.conf(kubelet 注入),看 ndots 与 search;对比宿主机。修复:在 dnsConfig 里按需调 ndots 精简 search,灰度验证后批量。

结论:三层案例共同点是“先在正确层把 dig/getent 分开,再谈改哪一层”,避免跨层乱改。

把几个关键指标做成只读探针脚本

/usr/local/bin/dns-health.sh:只读采集一个关键 FQDN 的解析耗时与状态,可脚本化、可入 cron。

#!/usr/bin/env bash
# DNS 健康探针(只读)
NAME="${1:-www.example.com}"
SERVERS=("${@:2}")
[ ${#SERVERS[@]} -eq 0 ] && SERVERS=("10.0.0.53" "119.29.29.29")

for ns in "${SERVERS[@]}"; do
  out=$(dig @"$ns" "$NAME" +time=2 +tries=1 2>/dev/null)
  st=$(printf '%s\n' "$out" | awk '/status:/{print $4}')
  qt=$(printf '%s\n' "$out" | awk '/Query time/{print $4}')
  echo "$(date +%F\ %T) $ns $NAME status=$st qtime=${qt}ms"
done

# getent 系统栈视角
start=$(date +%s%3N)
getent hosts "$NAME" >/dev/null 2>&1; rc=$?
end=$(date +%s%3N)
echo "system-stack name=$NAME rc=$rc ms=$((end-start))"

用法:

sudo bash /usr/local/bin/dns-health.sh www.example.com 10.0.0.53

脚本只读无副作用,可挂入 cron 生成日志,也能供监控采集。判断:status 非 NOERROR 或 qtime 超过你的基线 → 升级处理。

回滚的细节:哪些变更可能“复发”

  • 手动改 /etc/resolv.conf 后又被 DHCP/resolved 覆盖 → 回滚要与源头一致,否则改回又没了。
  • systemd-resolved 改法回滚 = 记录原 resolved.conf 并还原后再 restart。
  • 应用层 TTL 改了要回滚到原配置。
  • /etc/hosts 钉的记录回滚 = 删除对应行。

回滚不是“把值改回去”三秒钟,而是要确认“源头不再次覆盖”。这一层容易被忽略,导致反复。

监控与告警设计的最小闭环

  • 采集:定时跑 dns-health.sh,把 qtime/status/rc 写日志或 push 指标。
  • 指标:解析成功率、平均耗时、SERVFAIL/NXDOMAIN 计数(字段以实际采集器为准)。
  • 告警阈值:成功率持续 0 或耗时超基线 N 倍 → 告警;结合业务周期调整,不做绝对硬标准。
  • 联动:告警触发即按“先 dig 后 getent 再 tcpdump”的主线自动/人工复现。

IPv6 与 DNS:别只在 IPv4 上折腾

很多“解析慢/奇怪”其实混了 IPv6 的干扰。先看应用/系统在 v6 上的解析路径:

# 确认本机 v6 与路由
ip -6 addr
cat /etc/gai.conf | grep -v '^#' | head

# AAAA 查询
dig @10.0.0.53 www.example.com AAAA +time=2
getent ahosts www.example.com | head

典型坑:

  • getent ahosts 会给出 A/AAAA 混合,应用可能卡在等待不存在的 AAAA 响应上(“AAAA 卡”)。
  • /etc/gai.conf 的地址选择策略(RFC 6724 可重排)会改变实际先连 IPv4 还是 IPv6。
  • IPv6 规则(ip6tables/nft 的 ip6 表)未配时,v6 域名访问可能被拦或回环。

判断逻辑:

  • 应用配置了“优先 AAAA”而网络没有可用 v6 → 出现额外等待。可在 gai.conf 调顺序,或应用侧只走 v4。
  • v6 与 v4 的连通性要分别验证,ping6/curl -6/-4 分开测。

修复与回滚:

  • 调 gai.conf 前备份 cp /etc/gai.conf{,.bak},回滚即还原。改动点对应用可见,灰度验证。

DPRIVS:关于 DNS over HTTPS/TLS 与转发器的一点提醒

现代解析栈有 dnscrypt/DoH/DoT、sslh、unbound、dnsmasq 等中间层。若机器上有这些组件,resolv.conf 里的 nameserver 可能只是“本地 127.0.0.53 的 stub”,真正的上游在别的组件配置里。

# 看是否 stub
grep nameserver /etc/resolv.conf
# unresolved 转发配置
cat /etc/systemd/resolved.conf 2>/dev/null
cat /etc/unbound/unbound.conf 2>/dev/null | grep -iE 'forward|tcp|tls'

判断:nameserver 是 127.0.0.53/127.0.0.1 时,真正决策在 resolved/unbound/dnsmasq,改 resolv.conf 的 nameserver 可能没用,要改转发组件。这解释了“我明明改了 resolv.conf 怎么没变”的又一类根因。

定量:把 qtime、命中数、失败率一起看

单次慢不等于故障,要量化。给一段采集伪代码思路(示意,按你的采集器改):

# 连续 20 次查询统计(示意,用你环境的命令)
for i in $(seq 1 20); do
  dig @10.0.0.53 www.example.com +time=1 +tries=1 2>/dev/null
done | grep -c 'NOERROR'  # 成功次数

判断逻辑:

  • 成功率明显低于 100%、或耗时开始超出基线 → 进入正式排查。
  • 瞬时 1 次慢不构成事故,看趋势与多个指标(qtime、SERVFAIL 计数、到达率)联合判断。

跨机器 DNS 配置一致性

机器多时 DNS 不一致会制造“这台通那台不通”。用配置管理统一:

# Ansible 示例(示意):统一下发 resolv.conf
- name: 下发 resolv.conf
  ansible.builtin.copy:
    content: |
      nameserver 10.0.0.53
      search example.com
      options timeout:2 attempts:2 ndots:1
    dest: /etc/resolv.conf
    notify: restart resolv-manager

判断:所有机器 resolv.conf 一致后仍有差异 → 回到网络/上游/区域差异,别只怪 DNS。回滚=恢复旧 resolv.conf 与旧配置。

一张“DNS 排查速查清单”

现场遇到 DNS 问题时,把这张清单从上到下一勾到底,不漏步骤:

□ 1. IP 直连正常吗(ping/curl 目标 IP)
□ 2. dig @server FQDN 快且 NOERROR 吗
□ 3. getent hosts FQDN 快吗
□ 4. resolv.conf 的 nameserver 可达且在首位吗
□ 5. 有被 systemd-resolved/DHCP 接管吗(readlink)
□ 6. ndots 是否过大、search 是否多余
□ 7. 短名是否被拼到错误域
□ 8. 改配置后 nscd/resolved 缓存清了没
□ 9. 应用层是否有独立缓存/TTL
□ 10. 时间同步(NTP)正常吗
□ 11. IPv6 是否有干扰
□ 12. 有没有转发层(unbound/dnsmasq)在拦截
□ 13. 抓包(tcpdump port 53)确认查询走了哪
□ 14. 基线/回滚已就位

勾完还定位不到,说明问题在更上游的递归/权威,或需协调对应方。这份清单与前面每一节一一对应,是整篇文章的浓缩执行版。

现场案例一:已经解析出 IP,访问仍卡在 Trying

情境。 容器内执行 curl -v https://www.example.com/,日志先显示 Host ... was resolved,随后显示 Trying 203.0.113.20:443...,等待数秒后超时。先别继续调整 ndots 或 CoreDNS;这个输出已经说明本次调用成功拿到 IP。要弄清客户端向目标发出的 SYN 有没有出容器、经 Pod 网卡到节点、经防火墙与 NAT 出口、是否获得 SYN-ACK。

getent ahostsv4 www.example.com
curl -4v --connect-timeout 5 https://www.example.com/
ip route get 203.0.113.20
# 在有权限的节点抓包,对目标地址限制范围,避免采集其他业务
sudo tcpdump -ni any 'host 203.0.113.20 and tcp port 443'

证据和处理。 只见 SYN 重传,优先排查 NetworkPolicy、节点出口策略、SNAT、上游 ACL 与目标服务;握手完成后 TLS 卡住,则核对 SNI、证书、代理及中间盒。IP 直连验证 HTTPS 时推荐 curl --resolve 域名:443:目标IP https://域名/,保留 Host 与 SNI。修改后从同一个 Pod 同一域名复测,记录 DNS、TCP、TLS 三段耗时。curl -w '%{time_namelookup} %{time_connect} %{time_appconnect}\n' 可以观察三个累计时间戳;差分后才是各阶段耗时。

现场案例二:Pod 外域解析延迟与 search 域放大

情境。 Pod 的 /etc/resolv.conf 有三个 Kubernetes search 后缀和 ndots:5,应用大量访问 api.example.com。在 glibc resolver 路径下,api.example.com 的点数低于 5,先尝试拼接搜索域,可能产生多次 A/AAAA 查询。若每次候选查询都超时,外域首次解析延迟会放大;如果很快得到 NXDOMAIN,影响可能很小。是否真的受影响必须看实际查询与耗时。

cat /etc/resolv.conf
getent ahosts api.example.com
dig api.example.com. A +time=2 +tries=1
# 观察 Pod 的传统 DNS 请求;若使用 DoH/DoT,要改用相应代理日志
sudo tcpdump -ni any -vv 'port 53'

验证。 在测试 Pod 上调整 dnsConfig.options.ndots 或对特定 FQDN 使用末尾点,然后对照真实应用(glibc、Go、Java 行为不同)的 P95 解析耗时、DNS QPS 与集群短服务名调用。不要把 ndots 一律降为 0:service.namespace 等内部短名的解析顺序会改变。生产 Pod 的配置应通过 workload 声明修改并灰度,不要在容器里手改生成文件。

现场案例三:dig 正常,应用仍报无法解析

情境。 dig @10.43.0.10 api.example.com 返回 A 记录,但 Python/Java 服务提示域名不存在。dig 的直接查询不能代表 NSS、/etc/hosts、systemd-resolved、本地缓存,也不能证明应用进程的网络命名空间与测试 shell 相同。

getent hosts api.example.com
sed -n '/^hosts:/p' /etc/nsswitch.conf
cat /etc/hosts
readlink -f /etc/resolv.conf
# 如果进程在另一网络命名空间,需要先确认 PID 与权限
sudo nsenter -t <pid> -n getent hosts api.example.com
resolvectl query api.example.com

分流。 getent 错、dig 对:查 NSS 顺序、hosts 覆盖、本地 stub 与搜索域。系统调用正确但应用仍错:查进程 DNS 缓存、代理配置、运行时的独立解析器与容器内文件视图。清缓存前记录当前解析结果和缓存 TTL;单纯重启应用可能暂时掩盖上游问题。

现场案例四:A 记录正常,IPv6 路径超时

情境。 同一域名有 A 和 AAAA,应用在 IPv6 地址建立连接慢,最后回退 IPv4。DNS 没有失败;问题在地址选择与 IPv6 路由。只执行 dig A 会遗漏这个分支。

dig api.example.com A +short
dig api.example.com AAAA +short
getent ahosts api.example.com
curl -4v --connect-timeout 3 https://api.example.com/
curl -6v --connect-timeout 3 https://api.example.com/

处理。 对照 ip -6 route、容器 IPv6 支持、出口防火墙、代理的 AAAA 处理和目标服务监听。临时强制 IPv4 只能作为定位与有记录的降级,不应在根因未明时删除公共 DNS 的 AAAA。复测要分别覆盖 IPv4、IPv6 成功率以及客户端选择路径。

现场案例五:多 nameserver 与缓存导致的“修复未生效”

情境。 第一个 nameserver 间歇丢包,第二个健康;应用却有周期性长尾。不同 resolver 的重试、轮转与缓存策略不同,有时表现为同机不同进程结果不一致。先分别测每个上游,确认超时是真丢包、服务端忙还是 TCP 回退。

for server in 10.43.0.10 10.43.0.11; do
  dig @"$server" api.example.com A +time=2 +tries=1 | tail -8
done
resolvectl statistics          # systemd-resolved 在用时
ss -uapn | head -30
journalctl -u systemd-resolved --since '30 minutes ago'

结论。 先修上游稳定性或网络丢包,再查本机与应用缓存。不能只以一次 dig 的 Query time 判定真实请求分位数;需要多个样本并区分冷/热缓存。修复后对比上游错误率、应用请求错误率与尾延迟,观察时间应覆盖缓存过期周期。

审校后必须保留的边界

  • dig +trace 会沿权威链逐级查询,适合定位授权链问题;它绕过本地递归解析器的一些行为,不能模拟应用普通解析。
  • 传统 DNS 的 UDP/TCP 53 不等于所有域名解析流量;systemd-resolved、DoH、DoT 和应用内置解析器需分开看。
  • /etc/hosts 可覆盖本机查询,但容器有独立文件视图;Kubernetes Service 名依赖集群 DNS,并非外部 DNS 记录。
  • 结论至少同时包含:故障进程/命名空间、查询名称与类型、解析服务器、阶段性耗时、网络路径、修复前后同口径验证。

案例六:Kubernetes 集群域名正常,外部域名间歇 SERVFAIL

集群短名称查询成功只能证明 Pod 到 DNS 服务地址的部分链路可用;外部域名还需要递归转发、上游 DNS 与出口网络。CoreDNS 可能正常响应内部 cluster.local,同时 forward 插件因上游不通、超时或循环转发对外域返回 SERVFAIL。采集时同时选择一个内部 Service FQDN 和一个外部域名,分别测 A/AAAA,保留 DNS 响应码和服务端日志时间线。

kubectl -n kube-system get pods -l k8s-app=kube-dns -o wide
kubectl -n kube-system get svc kube-dns -o wide
kubectl -n kube-system get configmap coredns -o yaml
kubectl -n kube-system logs -l k8s-app=kube-dns --since=15m --max-log-requests=10 | tail -100

先核对 CoreDNS Pod 是否重启、CPU 是否饱和、forward 目标是否指向另一个会回查集群域名的地址。再看节点出口访问上游 DNS 的 UDP/TCP 53;某些环境禁止出口 53,会表现为域内可用、外域失败。若上游服务使用 DoT/DoH,则对应端口和健康检查另需考虑。验证不要用单次 nslookup;至少覆盖故障 Pod 所在节点、另一个节点与直接指定上游 DNS 的对照组。改 Corefile 后先核查语法与监控、分批滚动,确保内部 Service 查询没有被连带影响。

案例七:解析结果正确,却连到错误服务

某业务在迁移时,新旧实例共存。dig 返回的 IP 与记录一致,但应用配置了代理或 /etc/hosts 覆盖,实际连接去了旧服务。还有一种情况是多 A 记录里部分目标过期;单次 dig 看见一个健康 IP 无法排除某些客户端命中坏地址。对于这种问题,必须逐个区分域名解析结果、地址选择、连接目的与服务端日志,而不是笼统称“DNS 污染”。

getent ahosts api.example.com
dig api.example.com A +short
sed -n '/^hosts:/p' /etc/nsswitch.conf
rg -i 'api.example.com' /etc/hosts
curl -v --connect-timeout 5 https://api.example.com/ -o /dev/null
env | rg -i '(^|_)https?_proxy=|no_proxy='

抓取请求 trace ID,在目标服务日志确认是否收到了请求;若 DNS 轮询包含多个 IP,可用 curl --resolve 对每个地址保持原域名与 SNI 独立测试。缓存 TTL、客户端连接复用、代理 DNS 解析位置都会影响切换速度。变更时提前降低 TTL 仍不能保证所有客户端瞬时更新,应该保留旧端服务或网关转发的过渡窗口。

案例八:重试引发 DNS 查询放大

应用遇到短暂解析失败后立刻重试,每个请求可能触发多个 search 后缀 × A/AAAA × 上游重试,故障时 DNS QPS 不降反升。服务端看起来负载高,客户端 P99 也高。如果只扩容 DNS,不处理应用并发重试,新的容量可能再次耗尽。结合应用的实际解析器与缓存策略,设置有上限的指数退避,并用监控分别计数请求数、实际 DNS 查询数、超时与 SERVFAIL。

# 记录传统 DNS 流量的时间线;实际生产抓包需要限制持续时间和文件容量
sudo timeout 20 tcpdump -ni any -c 300 'udp port 53 or tcp port 53'
# 在 systemd-resolved 系统查询缓存统计
resolvectl statistics

应关注故障期间短时间失败率,而不是只看平均解析耗时;成功请求被缓存时平均值可能很好看,失败请求已在应用层超时。验证方案为逐步降低错误注入频率,确认应用重试不再放大 QPS,并且业务端到端错误率随上游恢复同步下降。缓存负结果的 TTL 与实现有关,不要擅自宣称修改 resolv.conf 的 timeout 能解决所有上游问题。

解析路径的四个边界

第一,glibc 的 ndots 规则不能直接推及所有语言运行时:Go 可能选择 cgo 或纯 Go,Java 常有自身缓存,浏览器和代理可能独立解析。第二,getent 经 NSS 更接近多数系统程序,但并不保证完全模拟具体应用。第三,DNS 查询成功不证明 TCP、TLS 及 HTTP 成功,排障记录应分列三个阶段。第四,主机、容器和 Pod 视角可能分别使用不同 resolv.conf 和网络命名空间,采样必须在故障进程同一执行环境完成。

案例九:DNSSEC 验证失败与时间同步

启用 DNSSEC 验证的递归解析器可能因签名链、过期或系统时间异常返回 SERVFAIL,普通非验证查询却成功。只有确认查询路径启用了 DNSSEC,且相关日志显示验证错误,才把时钟纳入 DNS 根因。普通 glibc A/AAAA 查询不是因为“时间不同步就必然失败”。

timedatectl status
resolvectl status
# 查询指定 DNS 是否支持并验证 DNSSEC,输出要结合 AD 位和递归器配置分析
dig @<DNS_SERVER_IP> example.com A +dnssec

同一域名分别经已知健康递归器和故障递归器查询,比较响应码与日志。校时前检查 NTP 服务状态与主机统一策略;大幅拨动生产时间可能影响数据库、证书和调度任务,需要单独控制。验证修复后用原故障递归器反复查询不同记录类型,并确认应用层连接恢复。不能仅凭 dig +dnssec 返回 RRSIG 就断言本地已完成验证。

案例十:系统 resolver 与代理 resolver 的责任边界

应用设置 HTTPS_PROXY 后,访问外部站点的域名可能由代理解析,也可能由本机先解析,取决于代理协议与客户端实现。Pod 里 getent hosts example.com 正常,curl 经代理却报解析失败;这时修 Pod 的 CoreDNS 可能毫无效果。先用 curl -v 看是否连接代理,再到代理进程所在主机核对它的解析环境和出口。

env | rg -i 'http_proxy|https_proxy|all_proxy|no_proxy'
curl -v --connect-timeout 5 https://example.com/ -o /dev/null
# 对照不使用代理的路径,仅用于有权直接访问目标的测试环境
curl --noproxy '*' -v --connect-timeout 5 https://example.com/ -o /dev/null

本地禁用代理后不能连接并不证明 DNS 故障,也可能是出口策略规定必须经代理。需要分别记录客户端到代理的 TCP 建连、代理解析目标域名、代理到目标的连接和代理返回码。NO_PROXY 的域名后缀匹配规则随客户端变化,特别注意大小写和端口。

案例十一:TCP/53 被禁,UDP 大响应无法回退

大 DNS 响应在 UDP 下可能被截断并要求客户端改走 TCP/53。网络策略只放行 UDP 53 时,常见小回答正常,TXT、DNSSEC 或较大的响应却失败。先在同一上游分别用 UDP 与 TCP 查询,比较 TC 位和失败情况,再核查出口与 CoreDNS 的连接日志。

dig @<DNS_SERVER_IP> example.com TXT +time=2 +tries=1
dig @<DNS_SERVER_IP> example.com TXT +tcp +time=2 +tries=1

若使用 EDNS 或响应路径存在分片丢包,症状还可能跟 MTU 有关;不要看到大响应失败就只调 ndots。验证时覆盖 A、AAAA、TXT 和 DNSSEC 相关记录及真实业务请求,确保 TCP 回退路径畅通。网络策略变更应仅开放到目标递归器的 UDP/TCP 53,而不是把整个节点的出站端口放开。

发布前事实核对表

文章中的“DNS 慢”需说明具体统计口径:单次上游 Query time、系统解析耗时,还是 HTTP 请求 time_namelookup。三个数字涉及缓存与调用路径,不能相互替代。发布示例请统一使用保留地址和示例域名,勿公开生产 DNS IP、内部域名和抓包内容。最终变更应报告异常开始时间、涉及节点/Pod、上游健康、请求量与失败率、准确根因、回滚条件、复测样本量及观察时长。

进阶专题:把解析耗时拆成客户端、CoreDNS 与上游三段

当 Pod 内的 getent 变慢,必须区分解析库本地搜索候选、Pod 到 CoreDNS 的网络、CoreDNS 到上游递归器三段。先在 Pod 的网络命名空间记录查询名称、记录类型、响应码和总耗时;再从节点/服务端记录 CoreDNS 的请求量、缓存命中、转发请求、上游错误与超时。若仅某个 Pod 慢而同节点其他 Pod 正常,优先查该 Pod 的 resolv.conf、进程独立缓存、容器资源压力与本地代理;若同一节点所有 Pod 慢而其他节点正常,排查 kube-proxy/eBPF、NodeLocal DNSCache、节点到 DNS Service IP 的链路。

kubectl exec -n <namespace> <pod> -- cat /etc/resolv.conf
kubectl exec -n <namespace> <pod> -- getent ahostsv4 api.example.com
kubectl -n kube-system get pods -o wide | rg 'coredns|node-local-dns'
kubectl -n kube-system get svc kube-dns -o wide

采样要覆盖故障时间。CoreDNS 自身健康不意味着所有上游健康,forward 可能在多个上游间切换;一个错误节点也会造成长尾。若上游延迟高,分别直查每个上游;若 CoreDNS 端显示处理很快而客户端慢,查 Service 转发或客户端重试。结论表应将“客户端观察到的总耗时”和“服务端处理耗时”分列,二者差异才指向网络或排队。

进阶专题:抓包中怎样读 DNS 请求与响应

同一个逻辑查询可能由搜索域、A/AAAA、重试与 TCP 回退产生多包。抓包时以事务 ID、问题名称、源端口和 DNS 服务器配对,不能仅数 port 53 数据包就断定应用查询次数。看到 NXDOMAIN 要先看请求是否是拼接后的候选名;候选失败之后绝对域名可能成功。看到 NOERROR 还要看 answer 是否为空;没有 A 记录但有 AAAA 或 CNAME 是另一种情况。

sudo timeout 30 tcpdump -ni any -vv -s 0 -c 200 'udp port 53 or tcp port 53'
# 以指定 DNS 对照绝对名称;末尾点避免搜索域重写
dig @<DNS_SERVER_IP> api.example.com. A +time=2 +tries=1
dig @<DNS_SERVER_IP> api.example.com. AAAA +time=2 +tries=1

生产抓包需限定时间、过滤目标并谨慎保存,因为内部域名可能含业务信息。若应用走 DoH/DoT,此方法看到的只是到代理或加密服务的流量,问题名称要从对应服务日志获取。故障定位报告中保留脱敏后的 query name、qtype、rcode、服务器、耗时,方便别人复现。

进阶专题:负缓存与记录变更后的生效时间

DNS 记录的 TTL 只是权威/递归链路中一项缓存期限。NSS 缓存、systemd-resolved、nscd、JVM、浏览器、代理和长连接会使切流表象不同。修改记录后,某台机器“仍连旧 IP”不等于上游 DNS 未更新:它可能保留了缓存响应,也可能已解析新 IP 但复用旧 TCP 连接。应先同时记录解析结果、实际连接的 remote address、应用连接池与缓存层。

getent ahosts api.example.com
resolvectl query api.example.com
ss -tnp | rg ':443'
# 直接查权威/上游的视角与本地系统视角对照
dig @<KNOWN_RESOLVER> api.example.com A +noall +answer

缓存清理有业务代价;大量实例同时清空可能把查询压向上游。切换前提前规划旧目标的过渡期、配置新旧并行健康检查。验证时不能只等 TTL 秒数,还需观察应用日志中实际连接目标和错误率;长连接需要业务上可控的更新策略。

进阶专题:内外网同名域名的分视图问题

内网 DNS 可能将 api.example.com 解析为私网地址,公共递归器返回公网地址。手动把 /etc/resolv.conf 的 nameserver 换成公共 DNS,可能“解决”一个外域解析问题,却破坏内部服务访问或走了错误出口。排查时先明确工作负载应走哪个视图;在办公室、VPN、Pod、宿主机分别记录同名的结果,不要以公网 dig 结果判定内网 DNS 错误。

getent ahostsv4 api.example.com
dig @<INTERNAL_RESOLVER> api.example.com A +short
dig @<EXTERNAL_RESOLVER> api.example.com A +short

若分视图原本是设计,应修内网解析器的记录或转发策略。私网地址不能从公网抵达也属正常。验证要确认解析到期望区域的目标,且实际 TCP/TLS 成功;DNS 层“有答案”只是第一关。

值班交接时的最小证据包

一次 DNS 事故结束时至少留存:受影响的主机/Pod/节点、具体查询域名和记录类型、客户端语言运行时及是否用代理、resolv.conf 和 NSS 配置、发生时间、上游解析器、上游与客户端各自响应码和耗时、抓包或日志、修复动作、回滚方式及故障后多轮验证。监控端看 P95/P99、失败率和上游 QPS,不以平均 Query time 取代尾延迟。对于偶发失败,保留一个低负载的持续探针,以分钟级样本看趋势和异常窗口,而非依赖单次人工 dig。




上一篇:CC/CV充电电路:限流+恒压+涓流防过充,TIP122/2N2222原理详解
下一篇:4万次请求实测:GPT-6 Astra开xhigh反而更省Token?
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-9-30 08:13 , Processed in 1.447973 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表