一、问题背景
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 解析流程
一次域名解析大致经过:
- 应用调用
getaddrinfo() 等解析函数。
- 查询顺序由
/etc/nsswitch.conf 的 hosts: 行决定,常见顺序 files dns(先查 /etc/hosts,查不到再查 DNS)。
- DNS 查询按
/etc/resolv.conf 的 nameserver 逐个尝试。
- 查询名要依据
ndots 和 search 决定是“直接作为全限定域名查询”还是“依次拼 search 域尝试”。
- 结果可能被 nscd / systemd-resolved / 各进程缓存。
- 每个 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 第七步:修复与验证
按根因分别修:
# 用 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
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 是最直观的“这一趟解析花了多久”。
17.2 强制不缓存、不带 search
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。