AI Infra 训练营
总览
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
HiHuo 主站
GitHub
总览
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
HiHuo 主站
GitHub
  • 网络专题

    • 网络心智模型:从一个包的"一生"讲起
    • 看懂网络命令输出 —— 把每一个数字讲清楚
    • 容器网络底层:netns / veth / bridge / VXLAN
    • K8s 网络深入:Service / NodePort / Ingress 流量端到端
    • DNS 深入:CoreDNS / resolv.conf / ndots / 解析坑
    • 生产网络故障排查方法论

DNS 深入:CoreDNS / resolv.conf / ndots / 解析坑

K8s 集群内一半的"网络问题"其实是 DNS 问题——而 90% 的 DNS 问题都源于:客户端不正确解读 /etc/resolv.conf、ndots:5 的隐藏代价、CoreDNS forward 链断了。

这一篇把 K8s DNS 从客户端 resolver 一路追到 CoreDNS、再到上游、再到生产坑。

这篇要回答什么

  1. 为什么 kubectl exec pod -- dig my-svc 比 dig my-svc.default.svc.cluster.local 慢?
  2. ndots:5 是什么神奇配置?为什么是生产事故的常客?
  3. CoreDNS 把"我不认识"的域名给谁?
  4. 短域名 / 长域名 / FQDN 哪个最快?为什么?
  5. Pod 解析 kubernetes 这种短词时,到底真发了几次 DNS 查询?
  6. "DNS 偶发性超时"通常是哪里坏了?

1. 一次完整 DNS 解析的"实际"路径

Pod 跑 curl http://my-svc:8080。看似简单,实际背后:

sequenceDiagram
    autonumber
    participant App
    participant Resolver as glibc resolver
    participant ResolvConf as /etc/resolv.conf
    participant CoreDNS
    participant K8sAPI as K8s API store
    participant Upstream as 上游 DNS<br>(节点 /etc/resolv.conf)

    App->>Resolver: getaddrinfo("my-svc")
    Resolver->>ResolvConf: 读配置<br>nameserver / search / options
    Note over Resolver: search: default.svc.cluster.local<br>          svc.cluster.local<br>          cluster.local<br>options ndots:5
    Note over Resolver: "my-svc" 点数 = 0 < 5<br>→ 先拼 search domain
    Resolver->>CoreDNS: my-svc.default.svc.cluster.local. A?
    CoreDNS->>K8sAPI: 查 Service "my-svc" in "default"
    K8sAPI-->>CoreDNS: ClusterIP 10.96.1.5
    CoreDNS-->>Resolver: NOERROR, A=10.96.1.5
    Resolver-->>App: 10.96.1.5
    App->>App: connect(10.96.1.5:8080)

这是顺利的情况。my-svc 拼 default ns 的 search domain 就找到了。

如果是访问外部域名 github.com 呢?

sequenceDiagram
    autonumber
    participant App
    participant Resolver as glibc resolver
    participant CoreDNS
    participant K8sAPI
    participant Upstream as 上游 DNS

    App->>Resolver: getaddrinfo("github.com")
    Note over Resolver: 点数 = 1 < 5<br>→ 先拼 search domain
    Resolver->>CoreDNS: github.com.default.svc.cluster.local. A?
    CoreDNS->>K8sAPI: ❌ 不是 K8s service
    Note over CoreDNS: K8s plugin 不命中<br>→ 转给 forward plugin
    CoreDNS->>Upstream: github.com.default.svc.cluster.local. A?
    Note over Upstream: 上游也找不到这个奇怪域名
    Upstream-->>CoreDNS: NXDOMAIN
    CoreDNS-->>Resolver: NXDOMAIN
    Note over Resolver: 第 1 个 search 失败、试第 2 个
    Resolver->>CoreDNS: github.com.svc.cluster.local. A?
    CoreDNS->>Upstream: github.com.svc.cluster.local. A?
    Upstream-->>CoreDNS: NXDOMAIN
    CoreDNS-->>Resolver: NXDOMAIN
    Note over Resolver: 试第 3 个
    Resolver->>CoreDNS: github.com.cluster.local. A?
    CoreDNS-->>Resolver: NXDOMAIN
    Note over Resolver: 终于试不带 search 的原名
    Resolver->>CoreDNS: github.com. A?
    CoreDNS->>Upstream: github.com. A?
    Upstream-->>CoreDNS: A=140.82.114.4
    CoreDNS-->>Resolver: A=140.82.114.4
    Resolver-->>App: 140.82.114.4

这就是 ndots:5 的代价

解析一个外部域名 github.com 需要 4 次 DNS 查询——前 3 次都是 NXDOMAIN。

每次查询 IPv4 + IPv6(A + AAAA)共 8 次。集群内域名前缀短 + DNS 上游慢 = 你的应用启动慢、外网慢、各种偶发性超时。

K8s 生产事故 #1 来源就是这里。


2. /etc/resolv.conf 配置详解

看 pod 的 resolv.conf

$ kubectl exec my-pod -- cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5

每行的含义:

nameserver
nameserver 10.96.0.10

去问哪个 DNS server。多个 nameserver = 故障转移:

nameserver 10.96.0.10
nameserver 10.96.0.11

默认行为:先问 10.96.0.10、超时(默认 5 秒)后问 10.96.0.11。不是负载均衡。

search
search default.svc.cluster.local svc.cluster.local cluster.local

短名补全的后缀列表。按顺序尝试:

  • my-svc → my-svc.default.svc.cluster.local → 如果失败 → my-svc.svc.cluster.local → 失败 → my-svc.cluster.local → 都失败 → 用原名 my-svc
options ndots
options ndots:5

关键参数。规则:

如果查询的域名 含点数 < ndots:
    先拼 search domain 试
    都失败再用原名
否则 (含点数 >= ndots):
    直接用原名

K8s 默认 ndots:5 = "含点数 < 5 就先拼 search"。

github.com(1 个点)< 5 → 先拼 search → 4 次查询。

a.b.c.d.e.f(5 个点)≥ 5 → 直接查 → 1 次查询。

google.com.(末尾点 = FQDN)→ 直接查、跳过 search → 1 次。

其它 options
options timeout:2 attempts:3 single-request-reopen
选项含义
timeout:N等 N 秒(默认 5)
attempts:N重试次数(默认 2)
rotate多 nameserver 时轮询(不再"先用第一个")
single-requestA 和 AAAA 串行查(默认并行)
single-request-reopenA/AAAA 各自用独立 socket(glibc bug workaround)
use-vc强制 TCP(默认 UDP)

3. ndots:5 的真相 + 优化

为什么 K8s 默认 ndots:5

K8s 团队想让 pod 里 curl http://my-svc 直接通——配 search 让它自动补成 my-svc.default.svc.cluster.local。

但 ndots:1 就够(my-svc 0 个点 < 1 也会触发 search)。为什么是 5?

历史原因:早期 K8s 用 KubeDNS、不同 plugin 行为有些差异。ndots:5 是个兼容性"保险"值——保证各种短名都走 search。

副作用:每个外部域名 4-8 次查询

绝大多数生产事故来源:

# 应用大量调用外部 API
curl https://api.example.com    # 1 个点 < 5 → 4 次 NXDOMAIN
curl https://github.com          # 1 个点 < 5 → 4 次 NXDOMAIN
curl https://prom.example.com   # 2 个点 < 5 → 4 次 NXDOMAIN

每秒成千次 API 调用 → CoreDNS 被淹 → 应用启动慢 / 偶发性 timeout / NXDOMAIN 日志爆炸。

解决方案对比

方案 1 - FQDN 加末尾点
$ curl https://github.com.    # ← 末尾点表示 FQDN,跳过 search

应用层修改、但很多 HTTP 库不支持 / 配置麻烦。

方案 2 - pod 设 ndots-2
spec:
  dnsConfig:
    options:
      - name: ndots
        value: "2"

ndots:2 = 含点数 < 2 才拼 search。github.com(1 个点)< 2 还是会拼 search、但只少了一些场景。

推荐 ndots:1:

dnsConfig:
  options:
    - name: ndots
      value: "1"

my-svc(0 个点)< 1 仍走 search(OK)、github.com(1 个点)≥ 1 不走 search → 1 次查询。

方案 3 - NodeLocal DNSCache

每个节点起一个 DNS 缓存 daemon、pod 查 169.254.20.10(节点本地 IP)。

# 装 NodeLocal DNSCache
kubectl apply -f nodelocaldns.yaml

好处:

  • 查询不走 kube-proxy → 减少 iptables 开销
  • 节点本地 cache → 重复查询直接命中
  • CoreDNS 压力降 80%+

详见 K8s 官方文档 nodelocaldns。

方案 4 - 单 dnsPolicy
spec:
  dnsPolicy: None
  dnsConfig:
    nameservers:
      - 10.96.0.10
    searches:
      - default.svc.cluster.local
    options:
      - name: ndots
        value: "2"

完全自定义 resolv.conf。适合知道自己在做什么的情况。


4. CoreDNS 工作机制

看 CoreDNS 配置

$ kubectl get cm -n kube-system coredns -o yaml
data:
  Corefile: |
    .:53 {
        errors
        health {
           lameduck 5s
        }
        ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
           pods insecure
           fallthrough in-addr.arpa ip6.arpa
           ttl 30
        }
        prometheus :9153
        forward . /etc/resolv.conf {
           max_concurrent 1000
        }
        cache 30
        loop
        reload
        loadbalance
    }

逐 plugin 解读:

Plugin作用
errors错误打日志
health/health 健康检查端点
ready/ready 就绪检查
kubernetes cluster.localK8s 集成:解析 *.cluster.local 走 K8s API
prometheus :9153暴露 metrics
forward . /etc/resolv.conf不认识的域名转发给节点上游 DNS
cache 30缓存 30 秒(减少重复查询)
loop检测 DNS 转发循环
reload配置改了自动 reload
loadbalance多 A 记录时随机顺序(伪负载均衡)

Plugin 链:先 kubernetes、再 forward

                 ┌─────────────────┐
                 │   Corefile      │
                 │   .:53          │
                 └────────┬────────┘
                          │
              查询到达 CoreDNS
                          │
                          ▼
            ┌─────────────────────────┐
            │ kubernetes plugin       │  ← 先问这里
            │ "是 *.cluster.local?"   │
            └──────┬──────────────┬───┘
                   ▼              ▼
                  YES            NO (fallthrough)
                   │              │
                   ▼              ▼
              从 K8s API     ┌────────────┐
              返回 ClusterIP │ forward    │
                            │ /etc/resolv.conf
                            └─────┬──────┘
                                  ▼
                            节点上游 DNS
                            (运营商 / 8.8.8.8 / 公司 DNS)

CoreDNS forward 的"上游"是什么

forward . /etc/resolv.conf {
   max_concurrent 1000
}

. 表示"匹配所有"。/etc/resolv.conf 是 CoreDNS pod 看到的 resolv.conf —— 但是!

CoreDNS pod 跑在 K8s 节点上,但它的 dnsPolicy: Default(不是 ClusterFirst)。所以它看到的是节点的 resolv.conf(通过 hostNetwork 或 mount)。

# CoreDNS pod 里看的 resolv.conf
$ kubectl exec -n kube-system coredns-xxx -- cat /etc/resolv.conf
nameserver 8.8.8.8
nameserver 1.1.1.1

节点的 DNS 配置 → CoreDNS 用这个上游。

显式指定上游(生产推荐)

不要靠 /etc/resolv.conf 上游、显式配:

forward . 1.1.1.1 8.8.8.8 {
    max_concurrent 1000
}

或者强烈推荐用阿里 / 腾讯 / 公司内部 DNS:

forward . 223.5.5.5 119.29.29.29 {
    max_concurrent 1000
    prefer_udp
}

为啥重要:

  • 节点 /etc/resolv.conf 可能配错 / 改动
  • 公网 DNS 速度参差不齐
  • 显式配置更可控、可监控

5. K8s DNS 域名规范

Service / Pod DNS 命名

Service
⟨service⟩.⟨namespace⟩.svc.⟨cluster-domain⟩

例子:

域名含义
kubernetes.default.svc.cluster.localdefault ns 的 kubernetes service
prom.monitoring.svc.cluster.localmonitoring ns 的 prom service

短形式(依赖 search domain):

my-svc                    # 在 default ns 的 pod 里
my-svc.other-ns            # 在 default ns 里访问 other-ns
my-svc.other-ns.svc        # 同上
Pod (普通 pod)
⟨ip-with-dashes⟩.⟨namespace⟩.pod.⟨cluster-domain⟩

例子:

10.244.0.5 → 10-244-0-5.default.pod.cluster.local

但这是默认不工作的——要 CoreDNS 配 pods 才行(默认 pods insecure 开了)。

StatefulSet Pod
⟨pod-name⟩.⟨headless-service⟩.⟨namespace⟩.svc.⟨cluster-domain⟩

StatefulSet + Headless Service:

$ kubectl get statefulset
NAME      READY   AGE
mysql     3/3     1h
$ kubectl get pod -l app=mysql
mysql-0   1/1
mysql-1   1/1
mysql-2   1/1
$ kubectl get svc mysql -o jsonpath='{.spec.clusterIP}'
None                        # Headless

# DNS 都通:
mysql-0.mysql.default.svc.cluster.local
mysql-1.mysql.default.svc.cluster.local
mysql-2.mysql.default.svc.cluster.local

这是为什么 StatefulSet 需要 Headless Service——稳定的 pod DNS 名。

Headless Service 的 SRV 记录

$ kubectl exec test-pod -- dig SRV _http._tcp.my-headless.default.svc.cluster.local
;; ANSWER SECTION:
_http._tcp.my-headless.default.svc.cluster.local. 30 IN SRV 0 33 8080 10-244-0-5.my-headless.default.svc.cluster.local.
_http._tcp.my-headless.default.svc.cluster.local. 30 IN SRV 0 33 8080 10-244-1-6.my-headless.default.svc.cluster.local.
_http._tcp.my-headless.default.svc.cluster.local. 30 IN SRV 0 33 8080 10-244-2-7.my-headless.default.svc.cluster.local.

SRV 记录返回端口 + 主机名——SDK / 客户端可以解析它自己做 LB。Kafka / Cassandra / Zookeeper 客户端依赖这种 SRV 自动发现。


6. 排查 DNS 解析问题

第 0 步:CoreDNS 是不是健康

$ kubectl get pod -n kube-system -l k8s-app=kube-dns
NAME                       READY   STATUS    AGE
coredns-7b6c8df-abc123     1/1     Running   1d
coredns-7b6c8df-def456     1/1     Running   1d

$ kubectl logs -n kube-system coredns-7b6c8df-abc123 --tail=30

异常信号(要查):

[ERROR] plugin/forward: ... no responding upstream
[ERROR] plugin/errors: ... NXDOMAIN ...
[INFO] Reloading

第 1 步:直接问 CoreDNS

$ kubectl exec test-pod -- nslookup kubernetes.default
Server:    10.96.0.10
Address:   10.96.0.10:53
Name:      kubernetes.default.svc.cluster.local
Address:   10.96.0.1

# 或者用 dig(更详细)
$ kubectl exec test-pod -- dig kubernetes.default.svc.cluster.local @10.96.0.10

响应 = CoreDNS 工作正常。

第 2 步:测某 Service

$ kubectl exec test-pod -- dig my-svc.my-ns.svc.cluster.local @10.96.0.10
;; QUESTION SECTION:
;my-svc.my-ns.svc.cluster.local. IN A

;; ANSWER SECTION:
my-svc.my-ns.svc.cluster.local. 30 IN A 10.96.1.5

;; status: NOERROR

NOERROR + ANSWER SECTION 非空 = 解析成功。

如果 NXDOMAIN:

  • Service 名拼错 / namespace 错
  • Service 真的不存在(kubectl get svc -n my-ns my-svc)

第 3 步:测搜索域是不是按预期工作

# 在 pod 里
$ kubectl exec test-pod -- cat /etc/resolv.conf
search default.svc.cluster.local svc.cluster.local cluster.local
nameserver 10.96.0.10
options ndots:5

# 模拟 ndots:5 + search 的拼接
$ kubectl exec test-pod -- nslookup my-svc
Server:    10.96.0.10
Address:   10.96.0.10:53
Name:      my-svc.default.svc.cluster.local
Address:   10.96.1.5

显示真实解析到的是 my-svc.default.svc.cluster.local —— search 加上去的。

第 4 步:测上游

# 在 pod 里测外部域名
$ kubectl exec test-pod -- dig github.com @10.96.0.10
;; ANSWER SECTION:
github.com. 60 IN A 140.82.114.4

# 也可以直接问节点上游
$ kubectl exec test-pod -- dig github.com @8.8.8.8

如果 CoreDNS 解析外部域名失败、但 @8.8.8.8 直接解析成功 → CoreDNS forward 配置 / 上游 DNS 节点连不通。

# 看 CoreDNS pod 自己能不能解析外部
$ kubectl exec -n kube-system coredns-xxx -- dig github.com

第 5 步:抓 DNS 包

# 节点上抓 pod 的 DNS 流量
$ PID=$(crictl inspect $(crictl ps -q --name test-pod) | jq '.info.pid')
$ nsenter -t $PID -n tcpdump -i any -nn 'port 53' -A

# 触发解析
$ kubectl exec test-pod -- dig my-svc

# 看抓到的包

输出会显示完整的 DNS 查询和响应内容。能看清楚:

  • 哪些 search domain 被尝试了
  • CoreDNS 回应是什么
  • 是否有重传

7. 反面教材合集

反面 1:以为 service 名加 ns 一定通

$ kubectl exec pod-in-default -- curl http://my-svc.other-ns
# Could not resolve host

my-svc.other-ns 拼 search 后变成 my-svc.other-ns.default.svc.cluster.local——default ns 里没这个 svc。

正确写法:

my-svc.other-ns.svc.cluster.local        # FQDN
my-svc.other-ns.svc                       # 拼 search 也通

养成习惯:跨 ns 一律 .svc.cluster.local 全写。

反面 2:以为 dnsPolicy: Default 跟节点一致就好

spec:
  dnsPolicy: Default       # ← 用节点 resolv.conf

pod 用节点 /etc/resolv.conf,不走 CoreDNS。结果:

  • 集群内 Service 解析全断(my-svc 找不到)
  • 只能访问公网

适用:CoreDNS / 其它系统 pod、不需要集群内服务。业务 pod 永远不要 dnsPolicy: Default。

反面 3:CoreDNS 缓存 stale 数据

$ kubectl get svc my-svc -o jsonpath='{.spec.clusterIP}'
10.96.1.5            # 当前 IP

$ kubectl delete svc my-svc
$ kubectl apply -f my-svc.yaml      # 重建、IP 变了
$ kubectl get svc my-svc -o jsonpath='{.spec.clusterIP}'
10.96.2.5            # 新 IP

# 但 pod 还用老 IP
$ kubectl exec test-pod -- dig my-svc
# 10.96.1.5            ← cache stale

CoreDNS cache 30 = 缓存 30 秒。改了 Service 之后等 30 秒或调小:

cache 5

或者关 cache(不推荐生产)。

反面 4:用 NodeLocal DNSCache 不当 nameserver 还指 CoreDNS

装了 NodeLocal DNSCache 之后、pod 的 resolv.conf 没改:

$ kubectl exec my-pod -- cat /etc/resolv.conf
nameserver 10.96.0.10        # ← 还指 CoreDNS、不是 NodeLocal

NodeLocal 装了但没生效——pod 仍走 CoreDNS、白装。

修:

# kubelet 配置(每个节点)
clusterDNS:
  - 169.254.20.10            # NodeLocal DNS IP

或者用 pod-level 的 dnsConfig。重启 kubelet 生效。

反面 5:misconfigured search 引发 NXDOMAIN 雪崩

$ cat /etc/resolv.conf
search   default.svc.cluster.local svc.cluster.local cluster.local  example.com  internal.example.com  prod.example.com
options ndots:5

search 加了一堆"为了方便短名"——但每个外部域名查询要先试 6 个 search!

  • github.com →
    • github.com.default.svc.cluster.local (NXDOMAIN)
    • github.com.svc.cluster.local (NXDOMAIN)
    • github.com.cluster.local (NXDOMAIN)
    • github.com.example.com (NXDOMAIN)
    • github.com.internal.example.com (NXDOMAIN)
    • github.com.prod.example.com (NXDOMAIN)
    • github.com. (NOERROR)
  • 共 7 次查询 × A + AAAA = 14 个包

生产事故:CoreDNS 被 NXDOMAIN 雪崩淹没、所有 pod DNS 解析慢。

修:

  • 删 search 里非 K8s 域名
  • 或者每个 pod dnsConfig.options.ndots=1
  • 或者 NodeLocal DNSCache 拦住雪崩

反面 6:AAAA 查询 timeout 拖累

应用同时查 A + AAAA(IPv4 + IPv6)。IPv6 没启用 / 上游不支持 AAAA → AAAA 查询超时(5 秒)→ 应用启动慢。

$ kubectl exec test-pod -- dig AAAA github.com
;; connection timed out; no servers could be reached      # ← timeout

修:

  • 应用层禁 IPv6 解析(Go: GODEBUG=netdns=cgo+1 / Node.js: --dns-result-order=ipv4first)
  • pod resolv.conf 加 options single-request-reopen
  • CoreDNS forward 上游用支持 AAAA 的 server

反面 7:CoreDNS forward 配错让 *.cluster.local 也走外面

forward . 8.8.8.8 1.1.1.1     # ← 直接配上游、缺 K8s plugin 处理

kubernetes cluster.local in-addr.arpa ip6.arpa {
   pods insecure
   fallthrough in-addr.arpa ip6.arpa
   ttl 30
}

如果 kubernetes plugin 没在 forward 前面 → cluster.local 也会被 forward 出去。

plugin 顺序很重要——kubernetes 必须在 forward 之前。


8. 排查 cheatsheet

"DNS 解析慢"5 步排查

flowchart TD
    Start[应用 DNS 慢] --> S1{1. pod 直接<br>dig 集群内 svc 慢?}
    S1 -->|否, 内部快| S2{2. dig 外部域名慢?}
    S2 -->|外部慢| C1[CoreDNS forward 上游问题]
    S1 -->|内部慢| S3{3. dig @CoreDNS IP 直连?}
    S3 -->|慢| C2[CoreDNS pod 本身慢/挂]
    S3 -->|快| C3[pod 到 CoreDNS<br>网络问题]
    S2 -->|外部快| S4{4. 看 ndots / search 雪崩?}
    S4 -->|是| C4[NXDOMAIN 雪崩<br>降 ndots 或加 NodeLocal]
    S4 -->|否| S5[5. 看应用 DNS<br>实现细节<br>cgo vs go vs glibc]

几个有用的诊断命令

测时延
# 直接测
kubectl exec test-pod -- time dig kubernetes.default

# 总解析时间
kubectl exec test-pod -- dig kubernetes.default | grep "Query time"
看实际查询路径
# 节点上抓 pod 的 DNS 包
PID=$(crictl inspect $(crictl ps -q --name test-pod) | jq '.info.pid')
nsenter -t $PID -n tcpdump -i any -nn 'port 53' -A &

# 触发
kubectl exec test-pod -- nslookup github.com

# 看抓包结果:发了几次 DNS 查询、search 试了几个、上游 forward 了哪些
CoreDNS 日志
# 临时开 debug 日志
kubectl edit cm -n kube-system coredns
# 在 Corefile 加: log

kubectl rollout restart deploy -n kube-system coredns

# 看日志
kubectl logs -n kube-system coredns-xxx -f
看 metrics
# 查询率
sum by (server, type) (rate(coredns_dns_requests_total[1m]))

# NXDOMAIN 率(**雪崩信号**)
sum by (server) (rate(coredns_dns_responses_total{rcode="NXDOMAIN"}[1m]))

# 上游 forward 延迟
histogram_quantile(0.99, sum by (le, to) (rate(coredns_forward_request_duration_seconds_bucket[5m])))

9. 生产实战 case study

Case 1:pod 启动慢

现象:业务 pod 启动到 ready 需要 60 秒,本地测试 5 秒搞定。

排查:

$ kubectl exec my-pod -- time curl https://api.example.com
# real    8m20.xxxs                  ← 8 分钟!

$ kubectl exec my-pod -- time dig api.example.com
# Query time: 8200 ms                ← DNS 慢

$ kubectl exec my-pod -- cat /etc/resolv.conf
options ndots:5
search default.svc.cluster.local ... example.com ... prod.example.com   ← 6 个 search domain

根因:search 里有 6 个 domain × ndots:5 → 解析 api.example.com 要发 7 次查询 + IPv6 timeout × 6 → 8 分钟。

修法:

spec:
  dnsConfig:
    options:
      - name: ndots
        value: "1"

启动时间从 60 秒 → 5 秒。

Case 2:CoreDNS 偶发性挂

现象:每隔 1-2 小时业务 5xx 飙、几秒后恢复。

排查:

$ kubectl get events -n kube-system | grep coredns
... Killing coredns-xxx (OOMKilled)         ← OOM 了!

$ kubectl top pods -n kube-system | grep coredns
coredns-xxx    100m   170Mi/170Mi          ← 内存满

根因:CoreDNS 默认 memory limit 170Mi 太小,集群规模大 + cache 多了打满。

修法:

$ kubectl edit deploy -n kube-system coredns
# resources.limits.memory: 512Mi

或者:

cache 10           # 缩短缓存(少占内存)

Case 3:外网偶尔不通

现象:访问外部 API 间歇性 timeout。

排查:

$ kubectl exec my-pod -- for i in 1 2 3 4 5; do dig +timeout=2 github.com; done
# 第 1、4 次失败、其它通

# 看 CoreDNS forward 上游
$ kubectl exec -n kube-system coredns-xxx -- dig github.com
# 也间歇性失败

# 节点上看
$ ssh m1 'cat /etc/resolv.conf'
nameserver 192.168.1.1                # ← 老路由器、不稳

$ ssh m1 'dig github.com @192.168.1.1'
# 间歇性失败

根因:节点 DNS(运营商提供)不稳。

修法:CoreDNS 显式配上游:

forward . 223.5.5.5 119.29.29.29 8.8.8.8 {
    max_concurrent 1000
    policy random          # 随机选上游
    health_check 5s        # 健康检查
}

Case 4:StatefulSet pod 互相找不到

现象:mysql-0 找不到 mysql-1。

排查:

$ kubectl exec mysql-0 -- dig mysql-1.mysql.default.svc.cluster.local
;; ANSWER SECTION:
mysql-1.mysql.default.svc.cluster.local. 30 IN A 10.244.1.6   ← 解析对了

$ kubectl exec mysql-0 -- ping 10.244.1.6
# 通

# 但应用配置文件用的是 mysql-1(短名)
$ kubectl exec mysql-0 -- nslookup mysql-1
# Could not resolve            ← 短名解析失败

根因:短名 mysql-1 被 search 拼成 mysql-1.default.svc.cluster.local → 这是 mysql ns 的 service 短名错。

修法:应用配置用完整名 mysql-1.mysql.default.svc.cluster.local 或 mysql-1.mysql。

Case 5:CoreDNS 内存暴涨

现象:CoreDNS pod 内存持续涨、最终 OOM。

排查:

sum by (server) (rate(coredns_dns_requests_total[1m]))
# 突然涨到几万 qps

根因:某个业务 pod bug 死循环查询 / 误装了 dnsConfig 加了一堆 nameserver、导致 CoreDNS 被打挂。

修法:

  • 找出 noisy pod、限流 / 修复
  • CoreDNS 加 cache 60 增缓存
  • 装 NodeLocal DNSCache 隔离

10. 下一步

文档内容
00-mental-model.md网络心智模型
01-output-reading.md看懂命令输出(含 dig 详解)
02-namespaces.md容器网络底层
03-k8s-network-deep.mdK8s Service / Ingress
本篇DNS / CoreDNS / ndots 深入
05-troubleshooting.md生产网络故障排查

命令文档

DNS 相关命令:

  • dig —— DNS 查询命令详解
  • tcpdump —— 抓 53 端口 DNS 流量
  • nsenter —— 进 pod 网络看真实 DNS 行为
  • helm —— 装 NodeLocal DNSCache
在 GitHub 上编辑此页
Prev
K8s 网络深入:Service / NodePort / Ingress 流量端到端
Next
生产网络故障排查方法论