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

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

zhuermu··20 分钟
DNSKubernetesCoreDNSNetworkingEKSDocker

本文首发于 blog.csdn.net

一切的起点:那个问题#

我们当时要构建的一个功能需要从 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

常见问题

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 查询,但能确保集群内的短服务名被正确解析。
分享这篇文章 微博 X LinkedIn
微信扫码

用微信扫一扫,把文章带到聊天或朋友圈。

讨论

用 GitHub 账号评论 —— 评论存放在本仓库的 Discussions 里。

继续阅读