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

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

zhuermu··20 分钟
DNSKubernetesCoreDNSNetworkingEKSDocker

一切的起点:那个问题

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

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 层级结构

从上到下的各个层级:

层级说明示例
根区(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 调试中最重要的工具。让我们用一次真实查询,逐段拆解它的输出:

$ 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)问题极其有用:

$ 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 解析流程

递归查询(图中第 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 标志:

docker run --dns 8.8.8.8 --dns 8.8.4.4 nginx

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

对于 Docker Compose,你可以按服务逐个设置:

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 架构

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

  1. kubelet 配置每个 Pod 的 /etc/resolv.conf,使其指向集群 DNS 服务(通常是 10.96.0.10)。
  2. kube-dns Servicekube-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 使用的是宿主机网络。

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

None

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

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:

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

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

CoreDNS 配置深度剖析

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

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 服务器。这正是本文开头那个修复方案。下面是一个完整的、可用于生产环境的示例:

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 秒的时间。

你可以用以下命令验证:

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:

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

在 Pod 内部:

# 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:

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 问题,就从它内部进行测试:

# 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 健康状况

# 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

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

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

启用 CoreDNS 调试日志

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

.: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 日志。 留意 SERVFAILi/o timeoutconnection 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)坏了

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

参考资料

参考资料

  1. DNS for Services and Pods — Kubernetes Documentation
  2. CoreDNS manual — CoreDNS
  3. RFC 1035: Domain Names — Implementation and Specification — IETF