# DNS 深度剖析：从第一性原理到 Kubernetes

> 从 dig 追踪到 Kubernetes 中的 CoreDNS，彻底搞懂 DNS。一份面向实战的容器 DNS 调试指南。

- 作者: zhuermu
- 发布: 2024-01-10
- 网页版: https://zhuermu.com/blog/dns-deep-dive-kubernetes/
- 首发于: https://blog.csdn.net/qq258513813/article/details/135481585

---
## 一切的起点：那个问题

我们当时要构建的一个功能需要从 Kubernetes Pod 内部调用一个内部 API。这个 API 可以通过一个私有域名访问，但 Pod 内部的 DNS 解析却悄无声息地失败了——请求只是一直超时。追查问题之后，修复方案原来只是在 CoreDNS ConfigMap 里加上了一小段配置：

```yaml
internal.example.com:53 {
    errors
    cache 30
    forward . 10.0.0.2
}
```

这段配置告诉 CoreDNS：将针对 `internal.example.com` 的查询转发到我们位于 `10.0.0.2` 的内部 DNS 服务器，而不是使用默认的上游解析器。修复很简单，但要理解它*为什么*有效，需要你扎实掌握 DNS 从第一性原理到 Kubernetes 实现的整条工作链路。

本文将完整走一遍这条链路。

## 第一部分：DNS 基础

### 什么是 DNS？

DNS（域名系统，Domain Name System）只做一件事：**将域名映射为 IP 地址**。它维护着一个分布式的名称到地址映射数据库，因此当你输入 `example.com` 时，浏览器就知道该去连接 `93.184.216.34`。

关键词是*分布式*。没有任何一台服务器持有全世界所有的 DNS 记录。相反，DNS 被组织成一个分层的、逐级委派的系统——理解这个层级结构，是在任何环境中调试 DNS 问题的基础。

### DNS 层级结构

DNS 是一个树状结构。每个域名都从右往左读，从最具体的标签一直读到根：

![DNS 层级结构](/images/blog/dns-deep-dive-kubernetes/dns-hierarchy.svg)

从上到下的各个层级：

| 层级 | 说明 | 示例 |
|-------|-------------|---------|
| **根区（Root Zone）** | 所有 DNS 查询的起点。写作一个点（`.`），通常被省略。技术上每个域名都以它结尾：`example.com.` | `.` |
| **顶级域名（TLD）** | 由注册管理机构管理。分为通用顶级域名（`.com`、`.org`、`.net`）和国家代码顶级域名（`.uk`、`.cn`、`.de`）。 | `.com` |
| **二级域名** | 你向注册商注册的那个域名。 | `example.com` |
| **子域名** | 由域名所有者自行创建，无需注册。 | `api.example.com` |

这个层级结构之所以重要，是因为**只有上一级才知道下一级的域名服务器**。根服务器知道 TLD 服务器。`.com` 的 TLD 服务器知道 `example.com` 的域名服务器。而 `example.com` 的域名服务器知道 `api.example.com` 的地址。解析过程就是沿着这棵树逐级向下走。

### 用 `dig` 剖析一次 DNS 查询

`dig`（Domain Information Groper）命令是 DNS 调试中最重要的工具。让我们用一次真实查询，逐段拆解它的输出：

```bash
$ dig example.com
```

#### 头部（Header）部分

```
; <<>> DiG 9.18.24 <<>> example.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 42781
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
```

这些标志告诉你发生了什么：
- **qr** —— 这是一个查询响应（而非查询本身）
- **rd** —— 期望递归（客户端请求解析器完成整个查询过程）
- **ra** —— 递归可用（服务器支持递归查询）
- **aa** —— （此处不存在）如果出现，表示这是权威应答，即响应直接来自该域名的域名服务器，而非缓存

各类计数（`QUERY: 1, ANSWER: 1`）告诉你每个部分中有多少条记录。

#### 问题（Question）部分

```
;; QUESTION SECTION:
;example.com.                   IN      A
```

这里确认了所查询的内容：`example.com` 的一条 `A` 记录（IPv4 地址）。`IN` 代表 Internet 类别——几乎所有 DNS 查询都使用这个类别。

#### 应答（Answer）部分

```
;; ANSWER SECTION:
example.com.            86400   IN      A       93.184.216.34
```

应答结果：`example.com` 解析为 `93.184.216.34`，TTL（Time to Live，生存时间）为 86400 秒（24 小时）。任何缓存解析器都可以将这个应答存储 24 小时，之后才需要重新查询。

对于位于 CDN 或负载均衡器之后的域名，你常常会看到 CNAME 链：

```
;; ANSWER SECTION:
app.example.com.     300   IN   CNAME   d1234abcdef.cloudfront.net.
d1234abcdef.cloudfront.net. 60 IN A    13.224.67.101
d1234abcdef.cloudfront.net. 60 IN A    13.224.67.42
```

CNAME（Canonical Name，规范名称）记录是一个别名——它表示“要找到 `app.example.com` 的地址，请改为查询 `d1234abcdef.cloudfront.net`”。解析器随后沿着这条链继续，直到得到最终的 A 记录。

#### 权威（Authority）与附加（Additional）部分

```
;; AUTHORITY SECTION:
example.com.            86400   IN      NS      a.iana-servers.net.

;; ADDITIONAL SECTION:
a.iana-servers.net.     86400   IN      A       199.43.135.53
```

**权威**部分列出该域名的权威域名服务器。**附加**部分提供这些域名服务器的 IP 地址（这是一种性能优化，称为“粘合记录 / glue records”，使解析器无需再单独查询一次）。

#### 统计（Statistics）

```
;; Query time: 12 msec
;; SERVER: 127.0.0.53#53(127.0.0.53) (UDP)
;; WHEN: Wed Jan 10 09:15:22 UTC 2024
;; MSG SIZE  rcvd: 56
```

这里告诉你哪台 DNS 服务器处理了本次查询（此处是位于 `127.0.0.53` 的本地 systemd-resolved 桩解析器）、耗时多久，以及响应的大小。

### 用 `dig +trace` 沿层级逐级追踪

`+trace` 标志会让 `dig` 从根开始执行迭代解析，展示每一个步骤。这对于诊断委派（delegation）问题极其有用：

```bash
$ dig +trace example.com
```

**第 1 步 —— 根服务器：**

```
.                   518400  IN  NS  a.root-servers.net.
.                   518400  IN  NS  b.root-servers.net.
.                   518400  IN  NS  c.root-servers.net.
;; ... (13 root server clusters total)
;; Received 239 bytes from 127.0.0.53#53(127.0.0.53) in 4 ms
```

你的本地解析器提供了全部 13 个根服务器集群的列表。它们是硬编码的（即“根提示 / root hints”文件），几乎从不改变。

**第 2 步 —— 查询根，获取 .com TLD 服务器：**

```
com.                172800  IN  NS  a.gtld-servers.net.
com.                172800  IN  NS  b.gtld-servers.net.
;; ... (13 .com TLD servers)
;; Received 1175 bytes from 170.247.170.2#53(b.root-servers.net) in 24 ms
```

根服务器并不知道 `example.com` 的答案，但它知道谁管理 `.com`——也就是 gTLD 服务器。于是它返回一个引荐（referral）。

**第 3 步 —— 查询 .com TLD，获取 example.com 的域名服务器：**

```
example.com.        172800  IN  NS  a.iana-servers.net.
example.com.        172800  IN  NS  b.iana-servers.net.
;; Received 170 bytes from 192.12.94.30#53(e.gtld-servers.net) in 18 ms
```

`.com` 的 TLD 服务器将我们引荐至 `example.com` 的权威域名服务器。

**第 4 步 —— 查询权威服务器，获取最终答案：**

```
example.com.        86400   IN  A   93.184.216.34
;; Received 56 bytes from 199.43.135.53#53(a.iana-servers.net) in 32 ms
```

权威服务器返回真正的 IP 地址。整条链到此结束。

### 递归查询 vs 迭代查询

你可能已经注意到，`dig +trace` 比普通的 `dig` 慢得多。这恰好体现了两种查询模式的区别：

![DNS 解析流程](/images/blog/dns-deep-dive-kubernetes/dns-resolution.svg)

**递归查询**（图中第 1 步和第 8 步）：你的应用向本地解析器询问“`example.com` 的 IP 是什么？”，并期望得到一个最终答案。解析器完成所有工作后返回结果。这就是正常运行时发生的情况——你的应用发起一次调用，得到一个答案。

**迭代查询**（第 2 至 7 步）：解析器依次联系层级结构中的每一级，不断收到引荐（“我不知道，但你可以去问这台服务器”），直到抵达权威应答。使用 `dig +trace` 时，是你的机器直接执行了这些迭代查询。

在实际运行中，递归解析更快，因为解析器很可能已经缓存了根服务器和 TLD 服务器的响应，甚至可能缓存了域名本身。而 `+trace` 方式会绕过所有缓存，这正是它更慢的原因——但它能精确揭示问题可能出现在链路中的哪个环节。

## 第二部分：Docker 容器中的 DNS

在进入 Kubernetes 之前，先了解一下 Docker 是如何处理 DNS 的会很有帮助。

### 容器如何获得它们的 DNS 配置

默认情况下，Docker 在容器启动时会将宿主机的 `/etc/resolv.conf` 复制进每个容器。这意味着容器会继承宿主机所使用的 DNS 服务器。有两种方式可以覆盖这一行为：

1. **修改宿主机的 `/etc/resolv.conf`** —— 影响所有新建的容器（对已运行的容器无效——它们需要重启才能获取变更）。

2. **在容器启动时使用 `--dns` 标志：**

```bash
docker run --dns 8.8.8.8 --dns 8.8.4.4 nginx
```

这会将指定的域名服务器写入容器的 `/etc/resolv.conf`，覆盖宿主机的默认设置。

对于 Docker Compose，你可以按服务逐个设置：

```yaml
services:
  app:
    image: myapp:latest
    dns:
      - 8.8.8.8
      - 1.1.1.1
```

**重要提醒：** 如果宿主机的 `/etc/resolv.conf` 指向 `127.0.0.53`（在使用 systemd-resolved 的 Ubuntu 上很常见），Docker 会检测到这一点并回退到 `8.8.8.8`，因为在拥有独立网络命名空间的容器内，环回地址是无法工作的。

## 第三部分：Kubernetes DNS —— CoreDNS

Kubernetes 将 DNS 配置提升到了另一个层次。每个 Pod 都会自动注入 DNS 设置，集群还运行着自己的 DNS 服务，同时处理内部服务发现和外部解析。

### 架构

![Kubernetes DNS 架构](/images/blog/dns-deep-dive-kubernetes/k8s-dns.svg)

各个部件是这样组合在一起的：

1. **kubelet** 配置每个 Pod 的 `/etc/resolv.conf`，使其指向集群 DNS 服务（通常是 `10.96.0.10`）。
2. **kube-dns Service**（`kube-system` 中的一个 `ClusterIP` 服务）在多个 CoreDNS Pod 之间做负载均衡。
3. **CoreDNS Pod**（通常由一个 Deployment 管理的 2 个副本）处理所有 DNS 查询。它们知道如何通过与 Kubernetes API 通信来解析 `cluster.local` 名称，并将其他所有查询转发到上游。
4. **上游 DNS** 就是节点的 `/etc/resolv.conf` 所指定的那个。在 EKS 上，这是位于 `169.254.169.253` 的 Amazon VPC DNS 解析器。

从 Kubernetes 1.29+ 开始，CoreDNS 是唯一的 DNS 提供者。旧版的 kube-dns（基于 dnsmasq）在更早的版本中已被移除。为了向后兼容，该服务仍然名为 `kube-dns`，但它路由到的是 CoreDNS Pod。

### Pod DNS 策略

每个 Pod 都有一个 `dnsPolicy` 字段，用于控制其 DNS 的配置方式。共有四个选项：

#### `ClusterFirst`（默认）

DNS 查询首先发送到 CoreDNS。如果查询匹配 `cluster.local`（或所配置的集群域），CoreDNS 会使用 Kubernetes API 进行解析。其他所有查询都被转发到上游。这是大多数工作负载所需要的策略。

Pod 的 `/etc/resolv.conf` 看起来像这样：

```
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
```

#### `Default`

Pod 继承其所运行节点的 DNS 配置。CoreDNS 被完全绕过。这意味着该 Pod 无法解析像 `my-service.my-namespace.svc.cluster.local` 这样的 Kubernetes 服务名。

仅在 Pod 不需要与其他 Kubernetes 服务通信时才使用它。

#### `ClusterFirstWithHostNet`

当运行设置了 `hostNetwork: true` 的 Pod 时必须使用。如果没有这个策略，一个使用 `ClusterFirst` 的 hostNetwork Pod 会悄悄地回退到 `Default` 行为，因为该 Pod 共享了节点的网络命名空间。显式设置 `ClusterFirstWithHostNet` 会强制让 DNS 走 CoreDNS，即便 Pod 使用的是宿主机网络。

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: host-network-pod
spec:
  hostNetwork: true
  dnsPolicy: "ClusterFirstWithHostNet"
  containers:
    - name: app
      image: myapp:latest
```

#### `None`

Kubernetes 不注入任何 DNS 设置。你必须通过 `dnsConfig` 自行提供。这让你拥有完全的控制权：

```yaml
apiVersion: v1
kind: Pod
metadata:
  name: custom-dns-pod
spec:
  dnsPolicy: "None"
  dnsConfig:
    nameservers:
      - 10.96.0.10
      - 8.8.8.8
    searches:
      - my-namespace.svc.cluster.local
      - svc.cluster.local
    options:
      - name: ndots
        value: "2"
      - name: edns0
  containers:
    - name: app
      image: myapp:latest
```

在 Pod 内部生成的 `/etc/resolv.conf` 如下：

```
nameserver 10.96.0.10
nameserver 8.8.8.8
search my-namespace.svc.cluster.local svc.cluster.local
options ndots:2 edns0
```

### `ndots:5` 性能问题

默认的搜索配置值得特别关注。在 `ndots:5` 的设置下，任何点数少于 5 个的域名都会被当作“相对”名称处理，解析器会先逐个追加每个搜索域，之后才尝试按原样查询该名称。

当一个 Pod 查询 `api.example.com`（含 2 个点，少于 5 个）时：

1. `api.example.com.default.svc.cluster.local` —— **未命中**
2. `api.example.com.svc.cluster.local` —— **未命中**
3. `api.example.com.cluster.local` —— **未命中**
4. `api.example.com` —— **命中**

一次外部域名查询就发起了 4 次 DNS 查询。对于需要大量外部调用的高流量服务而言，这会显著放大 DNS 负载。两种常见的修复方式：

**方式一：为外部 FQDN 追加一个末尾的点**，在你的应用配置中：

```
api.example.com.    # <-- trailing dot means "this is absolute, don't search"
```

**方式二：在 Pod spec 中降低 ndots：**

```yaml
dnsConfig:
  options:
    - name: ndots
      value: "2"
```

设置 `ndots:2` 意味着只有点数少于 2 个的名称才会走搜索列表。`api.example.com`（2 个点）会立即被当作绝对名称处理。代价是：像 `my-service`（0 个点）这样的短 Kubernetes 服务名仍会走搜索列表，并被正确解析。

### CoreDNS 配置深度剖析

CoreDNS 通过 `kube-system` 命名空间中的一个 ConfigMap 进行配置。下面是带注释的默认配置：

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        errors                          # Log errors to stdout
        health {                        # Health check endpoint on :8080/health
            lameduck 5s                 # Wait 5s before shutting down (graceful)
        }
        ready                           # Readiness probe on :8181/ready
        kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecure               # Resolve pod A records (IP-based names)
            fallthrough in-addr.arpa ip6.arpa  # Pass reverse lookups to next plugin
            ttl 30                      # Cache Kubernetes records for 30s
        }
        prometheus :9153                # Expose Prometheus metrics
        forward . /etc/resolv.conf      # Forward non-cluster queries upstream
        cache 30                        # Cache all responses for 30s
        loop                            # Detect and break forwarding loops
        reload                          # Auto-reload Corefile on ConfigMap changes
        loadbalance                     # Round-robin A/AAAA records
    }
```

我们来详细看看几个关键插件。

#### `kubernetes` 插件

它是 Kubernetes DNS 的核心。它监视 Kubernetes API，并应答以下查询：

- **Service：** `my-service.my-namespace.svc.cluster.local` 解析为 Service 的 ClusterIP
- **Headless service：** 返回各个 Pod IP，而非一个 ClusterIP
- **Pod：** `10-244-1-5.my-namespace.pod.cluster.local`（当设置了 `pods insecure` 时）
- **SRV 记录：** `_http._tcp.my-service.my-namespace.svc.cluster.local` 返回端口信息

#### `forward` 插件

将查询转发到上游 DNS 服务器。`.`（点）表示“匹配所有未被前面插件处理的查询”。你可以指定多个上游：

```
forward . 8.8.8.8 8.8.4.4 {
    max_concurrent 1000
    policy round_robin
}
```

在 EKS 上，节点的 `/etc/resolv.conf` 通常指向位于 `169.254.169.253` 的 VPC DNS 解析器，它负责处理 Route 53 私有托管区域以及标准的公共 DNS 解析。

#### `hosts` 插件

允许内联的主机到 IP 映射，在无需外部 DNS 服务器的情况下覆盖特定名称时非常有用：

```
hosts {
    10.0.0.50 legacy-db.internal
    10.0.0.51 legacy-api.internal
    fallthrough
}
```

`fallthrough` 指令至关重要——如果没有它，任何未匹配到 hosts 条目的查询都会返回 NXDOMAIN，而不会被传递给后续插件。

#### `rewrite` 插件

在处理之前重写查询。适用于域名别名或迁移场景：

```
rewrite name old-service.default.svc.cluster.local new-service.default.svc.cluster.local
```

#### `file` 插件

从区域文件（zone file）提供 DNS 区域服务。当你需要直接在 CoreDNS 中托管内部 DNS 区域时很有用：

```
file /etc/coredns/db.internal.example.com internal.example.com
```

### 添加自定义 DNS 转发

最常见的 CoreDNS 定制就是将特定域名转发到特定的 DNS 服务器。这正是本文开头那个修复方案。下面是一个完整的、可用于生产环境的示例：

```yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: coredns
  namespace: kube-system
data:
  Corefile: |
    internal.example.com:53 {
        errors
        cache 30
        forward . 10.0.0.2
    }
    corp.mycompany.net:53 {
        errors
        cache 60
        forward . 10.1.0.53 10.1.0.54 {
            policy sequential
        }
    }
    .: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
        cache 30
        loop
        reload
        loadbalance
    }
```

编辑完 ConfigMap 后，CoreDNS 会自动获取变更（得益于 `reload` 插件）。无需重启——但请给它最多 30 秒的时间。

你可以用以下命令验证：

```bash
kubectl rollout status deployment/coredns -n kube-system
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=20
```

## 第四部分：EKS 特有的 DNS 注意事项

Amazon EKS 有一些值得了解的 DNS 特性。

### VPC DNS 解析器

每个 VPC 都在“VPC 基址 + 2”的地址上有一个 DNS 解析器（例如，如果你的 VPC CIDR 是 `10.0.0.0/16`，那么解析器就在 `10.0.0.2`）。EKS 节点还能在 `169.254.169.253` 这个链路本地地址上访问到它，该地址无论 VPC CIDR 是什么都始终可用。

这个解析器负责处理：

- 挂载到该 VPC 的 **Route 53 私有托管区域**
- **VPC 接口端点**（例如 `com.amazonaws.us-east-1.s3`）
- **标准公共 DNS** 解析
- 用于转发到本地（on-premises）DNS 的 **Route 53 Resolver 规则**

当 CoreDNS 将查询转发到上游时，它们会发往这个 VPC 解析器，这意味着你的 Pod 无需任何 CoreDNS 配置就能自动获得 Route 53 私有区域解析。

### Amazon VPC CNI 与 DNS

Amazon VPC CNI 插件直接为 Pod 分配 VPC IP 地址。这意味着 Pod 的 DNS 流量像其他任何流量一样走 VPC 网络——不存在覆盖网络（overlay）的转换。从 Pod 发往 `169.254.169.253` VPC 解析器的 DNS 数据包，与从 EC2 实例发出的 DNS 数据包走的是同一条路径。

### DNS 解析限制

VPC DNS 解析器强制施加了**每个网络接口每秒 1024 个数据包**的限制。在拥有大量 Pod 且 DNS 查询繁重的集群中，这可能成为瓶颈。解决方案包括：

- **NodeLocal DNSCache** —— 在每个节点上运行一个 DNS 缓存，减少上游查询
- **CoreDNS 自动扩缩** —— 按集群规模成比例增加副本数
- 在**你的应用中启用 DNS 缓存**（许多 HTTP 客户端和连接池已经这么做了）

## 第五部分：调试 Kubernetes 中的 DNS

当集群中的 DNS 出问题时，你需要一套系统化的方法。下面就是你的工具箱。

### 从 Pod 快速检查 DNS

启动一个带有 DNS 工具的调试 Pod：

```bash
kubectl run dns-debug --image=busybox:1.36 --rm -it --restart=Never -- sh
```

在 Pod 内部：

```bash
# Check what DNS server the pod is using
cat /etc/resolv.conf

# Test Kubernetes service resolution
nslookup kubernetes.default.svc.cluster.local

# Test external resolution
nslookup example.com

# Check a specific DNS server
nslookup example.com 10.96.0.10
```

若要进行更详细的查询，使用一个带 `dig` 的 Pod：

```bash
kubectl run dns-debug --image=alpine/bind-tools --rm -it --restart=Never -- sh

# Inside the pod:
dig kubernetes.default.svc.cluster.local
dig +trace example.com
dig @10.96.0.10 my-service.my-namespace.svc.cluster.local
```

### 对已有 Pod 使用 `kubectl exec`

如果某个特定 Pod 出现了 DNS 问题，就从它内部进行测试：

```bash
# Check the pod's DNS configuration
kubectl exec -it <pod-name> -- cat /etc/resolv.conf

# Test resolution (if the pod has nslookup/dig)
kubectl exec -it <pod-name> -- nslookup my-service.my-namespace.svc.cluster.local

# If no DNS tools available, use wget as a proxy test
kubectl exec -it <pod-name> -- wget -q -O- http://my-service.my-namespace:8080/health
```

### 检查 CoreDNS 健康状况

```bash
# Are CoreDNS pods running?
kubectl get pods -n kube-system -l k8s-app=kube-dns

# Check CoreDNS logs for errors
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50

# Is the kube-dns service healthy?
kubectl get svc kube-dns -n kube-system

# Check endpoints (should list CoreDNS pod IPs)
kubectl get endpoints kube-dns -n kube-system

# View the current CoreDNS configuration
kubectl get configmap coredns -n kube-system -o yaml
```

### CoreDNS 指标

CoreDNS 在端口 `9153` 上暴露 Prometheus 指标。需要关注的关键指标：

- `coredns_dns_requests_total` —— 按类型和区域统计的查询总数
- `coredns_dns_responses_total` —— 按响应码（NOERROR、NXDOMAIN、SERVFAIL）统计的响应数
- `coredns_forward_requests_total` —— 转发到上游的查询数
- `coredns_cache_hits_total` / `coredns_cache_misses_total` —— 缓存有效性
- `coredns_panics_total` —— 应始终为 0

你可以直接查询这些指标：

```bash
kubectl exec -n kube-system <coredns-pod> -- wget -q -O- http://localhost:9153/metrics | grep coredns_dns_requests_total
```

### 启用 CoreDNS 调试日志

若要进行深度调试，可在 CoreDNS ConfigMap 中临时启用 `log` 插件：

```yaml
.:53 {
    log        # <-- add this line; logs every query
    errors
    # ... rest of config
}
```

**警告：** 在生产环境中这会产生海量日志数据。请短暂启用，捕获你需要的内容后立即移除。

### DNS 缓存问题

如果你更新了一条 DNS 记录，但 Pod 仍然看到旧值：

1. **CoreDNS 缓存** —— 默认 30 秒。等待，或在 ConfigMap 中临时设置 `cache 0`。
2. **应用层缓存** —— Java 的 `InetAddress` 默认会缓存 DNS（在较新的 JVM 中，成功查询缓存 30 秒，失败缓存 10 秒）。Go 的标准库遵循 TTL。Node.js 自 v20 起也默认缓存。
3. **VPC DNS 缓存** —— VPC 解析器根据 TTL 进行缓存。除了等待之外，你无能为力。
4. **conntrack 条目** —— 过期的 UDP conntrack 条目会导致 DNS 超时。用 `conntrack -L -p udp --dport 53` 检查。

## 排查清单

当你在 Kubernetes 中遇到 DNS 问题时，按这份清单逐项排查：

- [ ] **确认症状。** 是超时、NXDOMAIN、SERVFAIL，还是 IP 错误？
- [ ] **检查 Pod 的 `/etc/resolv.conf`。** nameserver 是否正确（`10.96.0.10` 或你的集群 DNS IP）？搜索列表是否存在？
- [ ] **检查 Pod 的 `dnsPolicy`。** 如果是 `Default`，Pod 无法解析 Kubernetes 服务。如果是 `hostNetwork: true`，你需要 `ClusterFirstWithHostNet`。
- [ ] **确认 CoreDNS 正在运行。** `kubectl get pods -n kube-system -l k8s-app=kube-dns` —— 所有 Pod 是否都处于 `Running` 状态？
- [ ] **检查 CoreDNS 日志。** 留意 `SERVFAIL`、`i/o timeout` 或 `connection refused` 之类的消息。
- [ ] **从调试 Pod 进行测试。** 这能隔离出问题究竟出在应用上，还是出在 DNS 本身。
- [ ] **测试不同的查询类型。** Pod 能解析 Kubernetes 服务名吗？外部名称呢？两者都能吗？
- [ ] **检查 CoreDNS ConfigMap。** `forward` 指令是否指向了一个可达的上游？如果你有自定义域名区块，语法是否正确？
- [ ] **检查网络策略。** 如果你使用 Calico、Cilium 或其他带有网络策略的 CNI，确保 Pod 能通过 UDP/TCP 53 端口访问 CoreDNS。
- [ ] **检查是否是 ndots 问题。** 如果外部域名解析失败，但追加一个末尾的点（`.`）就能修复，那说明搜索列表在干扰解析。
- [ ] **检查 DNS 速率限制。** 在 EKS 上，VPC 解析器将查询限制为每个 ENI 每秒 1024 个。在高负载下检查是否有 `SERVFAIL` 响应。
- [ ] **验证上游 DNS 的健康状况。** CoreDNS Pod 自身能否解析外部名称？`kubectl exec -n kube-system <coredns-pod> -- nslookup example.com`

## 小结

Kubernetes 中的 DNS 是一个多层系统。最底层是 DNS 协议本身——一个分层的、逐级委派的数据库，通过从根到叶遍历一棵树来解析名称。Docker 增加了一层，将宿主机的 DNS 配置复制进容器。Kubernetes 又增加了一层，运行 CoreDNS 作为集群的 DNS 提供者，同时处理内部服务发现和外部解析。

Kubernetes 中的大多数 DNS 问题都可归入以下几类：

1. **`dnsPolicy` 错误** —— Pod 根本无法访问到 CoreDNS
2. **缺少自定义转发规则** —— 从 CoreDNS 无法访问内部/私有域名
3. **`ndots:5` 开销** —— 外部查询产生了不必要的查询
4. **网络策略阻断了 DNS** —— Pod 无法通过 53 端口访问 CoreDNS
5. **上游 DNS 问题** —— CoreDNS 本身没问题，但它的上游（VPC 解析器、企业 DNS）坏了

有了本文介绍的 `dig` 和 `nslookup` 技巧，再加上这份系统化的排查清单，你应该能够将任何 DNS 问题从 Pod 一路追踪到根服务器，再追踪回来。

## 参考资料

- [Kubernetes DNS for Services and Pods](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/) —— DNS 策略与 CoreDNS 的官方文档
- [CoreDNS Manual](https://coredns.io/manual/toc/) —— 完整的插件参考
- [Amazon EKS Best Practices: DNS](https://aws.github.io/aws-eks-best-practices/networking/dns/) —— EKS 特有的指南
- [DNS Query Walkthrough (RFC 1035)](https://www.ietf.org/rfc/rfc1035.txt) —— 最初的 DNS 规范
- [EDNS(0) Extension Mechanisms](https://www.rfc-editor.org/rfc/rfc6891) —— OPT 伪区段详解

---

## 常见问题

### DNS 在 Kubernetes Pod 内部是如何工作的？

Pod 以 CoreDNS 作为其 DNS 服务器（由 kubelet 设置）。查询会遵循 /etc/resolv.conf 中的搜索域，在回退到上游解析器之前，先依次追加 .svc.cluster.local 之类的后缀。

### 什么是 CoreDNS，Kubernetes 为什么使用它？

CoreDNS 是一个灵活的、基于插件的 DNS 服务器，它在 Kubernetes 中取代 kube-dns 成为默认组件。它通过将服务名解析为集群 IP 来处理服务发现。

### 如何调试 Kubernetes 中的 DNS 解析失败？

从一个调试 Pod 中运行 nslookup 或 dig，检查 CoreDNS 日志和 Pod 状态，核对故障 Pod 中的 /etc/resolv.conf，并检查 CoreDNS ConfigMap 是否存在配置错误。

### Kubernetes DNS 中的 ndots:5 是什么意思？

ndots:5 表示任何点数少于 5 个的查询都会被当作相对名称处理，会先追加搜索域。这会带来额外的 DNS 查询，但能确保集群内的短服务名被正确解析。


---

## 参考资料

- [DNS for Services and Pods](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/) — Kubernetes Documentation
- [CoreDNS manual](https://coredns.io/manual/toc/) — CoreDNS
- [RFC 1035: Domain Names — Implementation and Specification](https://datatracker.ietf.org/doc/html/rfc1035) — IETF
