Kubernetes 基础体系 · 第 28/83 篇。示例基于 Kubernetes 当前稳定 API;弃用、版本偏差、云厂商差异和生产风险会明确说明。
Kubernetes DNS:CoreDNS、Service Discovery、Search、缓存和故障
Kubernetes 中的 DNS 不是一个独立于 Service 的“主机名系统”。它是 Service、EndpointSlice、Pod 网络、容器内解析器和 CoreDNS 共同组成的服务发现路径:
应用程序
│ getaddrinfo()/DNS 查询
▼
容器内 /etc/resolv.conf
│ nameserver <集群 DNS Service IP>
▼
CoreDNS
├─ 根据 Kubernetes API 中的 Service、EndpointSlice、Pod 状态生成集群域名记录
├─ 根据 Corefile 决定是否转发集群外域名
└─ 使用缓存减少重复查询
▼
Service ClusterIP 或 Pod IP
│
├─ ClusterIP:kube-proxy 或其他实现转发到后端 Endpoint
└─ Headless Service:DNS 直接返回 Endpoint 地址
这条链路中,每层解决的问题不同:
- Service Discovery 定义“服务使用什么稳定名称,以及名称对应哪些地址”。
- CoreDNS 将 Kubernetes 对象状态转换为 DNS 响应。
- Search 让应用可以使用短名称,例如
redis,但也可能导致多个额外查询。 - 缓存 缩短响应时间、降低 API 和上游 DNS 压力,但会延迟状态变化的可见性。
- 故障 可能发生在解析器、Pod 到 DNS Service 的网络、CoreDNS、Kubernetes API、上游 DNS 或 Service 后端,而不一定是“DNS 挂了”。
下文使用默认集群域名 cluster.local 举例。集群域名由 kubelet 的 --cluster-domain 配置决定,并不保证所有集群都是这个值。
1. Kubernetes DNS 的边界:DNS 只负责得到地址,不负责完成连接
先区分两个动作:
- DNS 查询:例如把
orders.default.svc.cluster.local解析为10.96.42.17。 - 连接建立:应用再向
10.96.42.17:8080发起 TCP、TLS 或 HTTP 请求。
DNS 成功不代表应用一定能连接:
- Service 可能没有可用 Endpoint;
- NetworkPolicy 可能阻止流量;
- kube-proxy、eBPF Service 实现或 CNI 可能故障;
- 后端 Pod 可能监听了错误端口;
- 应用可能把 HTTP、TLS、协议版本配置错了。
反过来,DNS 失败也不代表后端地址完全不可达。若已经知道某个 Pod IP,直接连接它可能成功,但这绕过了 Service 的稳定地址和负载分发,不应作为正常服务发现方案。
因此,排障时应把问题拆成:
名字是否能解析?
↓
解析出的地址是否正确?
↓
到该地址的网络是否可达?
↓
Service 是否有后端?
↓
后端端口和应用协议是否正确?
2. CoreDNS 如何成为 Kubernetes 的 DNS 服务器
2.1 Pod 为什么知道 DNS 服务器地址
Pod 创建时,kubelet 会根据 Pod 的 dnsPolicy 生成容器内的 /etc/resolv.conf。一个常见结果类似:
nameserver 10.96.0.10
search default.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
这里的 10.96.0.10 通常是名为 kube-dns 的 Service 的 ClusterIP。尽管 Service 名字仍然叫 kube-dns,实际后端通常是 CoreDNS Pod;这是历史兼容命名,不表示必须运行名为 kube-dns 的组件。
nameserver 是 DNS 服务入口,不是 CoreDNS Pod 的固定 IP。CoreDNS Pod 可以重建、扩缩容或迁移节点,Service 仍提供稳定的虚拟地址。
查看本集群实际值:
kubectl -n kube-system get svc kube-dns \
-o wide
kubectl -n kube-system get pods \
-l k8s-app=kube-dns \
-o wide
预期可以看到:
kube-dnsService 的 ClusterIP;- 一个或多个 CoreDNS Pod;
- CoreDNS Pod 的 Pod IP 不一定等于
kube-dns的 ClusterIP。
如果某个业务 Pod 的 nameserver 指向一个不存在的地址,或者该地址对应的 Service 没有可用 Endpoint,那么所有集群内名称解析都可能超时。
2.2 CoreDNS 的两个关键输入
CoreDNS 处理 Kubernetes 域名时,主要依赖两类输入:
-
Kubernetes API 对象
- Service;
- EndpointSlice;
- Pod;
- Namespace 等。
-
Corefile 配置
- 哪个域名由
kubernetes插件处理; - 集群外域名是否转发给上游 DNS;
- 是否启用缓存、健康检查、指标等插件。
- 哪个域名由
典型 Corefile 结构如下,实际内容由发行版和安装方式决定:
.:53 {
errors
health
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
}
这里需要区分规范和实现:
- Kubernetes 规定了集群 DNS 名称和 Service Discovery 的语义;
- CoreDNS 的插件链、Corefile 写法、默认 TTL 和默认转发方式属于 CoreDNS 实现及发行版配置;
- 云厂商托管集群可能修改 CoreDNS 镜像、配置、NodeLocal DNSCache 部署方式或监控端口。
查看实际配置,不要凭默认值猜测:
kubectl -n kube-system get configmap coredns \
-o yaml
kubectl -n kube-system get deployment coredns \
-o yaml
CoreDNS 通常通过 informer 监听 Kubernetes API 的对象变化,在本地维护 Service、EndpointSlice 等状态。查询到达时,它优先从这份本地状态生成响应,而不是每次 DNS 查询都访问 Kubernetes API。
这带来两个重要结果:
- 正常查询延迟不应等于一次 Kubernetes API 请求的延迟;
- API、RBAC、网络或 CoreDNS informer 出现问题时,DNS 状态可能停止更新,即使旧记录仍能从缓存或本地状态中返回。
3. Service Discovery:不同 Service 类型产生不同 DNS 语义
3.1 普通 Service:名称解析到 ClusterIP
假设创建一个 Service:
apiVersion: v1
kind: Service
metadata:
name: orders
namespace: default
spec:
selector:
app: orders
ports:
- name: http
port: 80
targetPort: 8080
通常可以使用以下完整名称访问:
orders.default.svc.cluster.local
其 A 记录通常返回 Service 的 ClusterIP:
orders.default.svc.cluster.local. A 10.96.42.17
如果集群启用了 IPv6 或双栈配置,也可能返回 AAAA 记录。DNS 返回的是 Service 地址,而不是某个后端 Pod 地址。
数据流是:
应用查询 orders.default.svc.cluster.local
↓
CoreDNS 返回 ClusterIP 10.96.42.17
↓
应用连接 10.96.42.17:80
↓
Service 转发到某个 Endpoint 的 8080 端口
这里的 port: 80 是 Service 对外提供的端口,targetPort: 8080 是后端 Pod 端口。DNS 只返回地址,不编码端口;应用仍必须使用 Service 的端口 80。
检查 Service 和 EndpointSlice:
kubectl -n default get svc orders -o wide
kubectl -n default get endpointslice \
-l kubernetes.io/service-name=orders \
-o yaml
EndpointSlice 是当前 Kubernetes 推荐的后端地址表示方式。旧的 Endpoints API 在大规模场景中扩展性较差,并且已被弃用;排查新集群时应优先查看 EndpointSlice。
3.2 没有后端时,Service 名称仍可能存在
Service 对象存在与否,和它是否有可用后端是两个状态。
如果 orders Service 存在,但没有匹配 app=orders 的 Pod:
- CoreDNS 仍可能把
orders.default.svc.cluster.local解析为 ClusterIP; - 连接 ClusterIP 时可能没有可转发的后端;
- 具体表现可能是连接超时、连接拒绝或由 Service 实现返回其他网络结果,取决于 kube-proxy、CNI 和规则状态。
因此:
kubectl -n default get svc orders
kubectl -n default get endpointslice \
-l kubernetes.io/service-name=orders
必须一起看。仅执行 nslookup orders 不能证明业务服务可用。
3.3 Headless Service:DNS 返回 Endpoint 地址
当 Service 设置:
spec:
clusterIP: None
它就是 Headless Service。它没有虚拟 ClusterIP,DNS 通常直接返回关联 Endpoint 的地址。
例如:
apiVersion: v1
kind: Service
metadata:
name: db
namespace: default
spec:
clusterIP: None
selector:
app: db
ports:
- name: postgres
port: 5432
targetPort: 5432
查询:
db.default.svc.cluster.local
可能返回多个 A 记录:
db.default.svc.cluster.local. A 10.244.1.21
db.default.svc.cluster.local. A 10.244.2.18
db.default.svc.cluster.local. A 10.244.3.09
此时 DNS 本身不提供普通 ClusterIP Service 那种统一虚拟地址。客户端如何选择地址、是否轮询、是否重试,取决于客户端解析器和应用实现。
这解释了一个常见误解:
“DNS 返回了多个 Pod IP,所以 Kubernetes 一定会自动均匀负载均衡。”
不一定。DNS 只返回地址集合;客户端可能只使用第一个地址,也可能缓存整个集合,也可能自行实现轮询。连接失败后的重试策略同样由客户端决定。
3.4 EndpointSlice 中的 ready、serving 和 terminating
EndpointSlice 不只是一个 IP 列表。Endpoint 条目可能包含:
addresses:地址;conditions.ready:是否就绪;conditions.serving:是否仍能提供服务;conditions.terminating:是否正在终止。
CoreDNS 和 Service 流量实现会结合这些状态决定普通服务发现和流量转发行为。具体细节会随 Kubernetes 版本和实现演进,但生产排障时不能只看 Pod 是否处于 Running:
Running不等于Ready;- Pod 通过 readiness probe 后,才通常会成为普通 Service 的可用后端;
- Pod 删除过程中可能仍有连接排空相关状态;
publishNotReadyAddresses: true会改变未就绪 Endpoint 的发布语义,常用于某些需要先发现彼此、再完成选主或初始化的系统。
如果一个新 Pod 已经 Running,但 DNS 的 Headless Service 结果中暂时没有它,可能是:
- readiness 尚未通过;
- EndpointSlice 尚未更新;
- CoreDNS informer 尚未观察到变化;
- DNS 客户端或 CoreDNS 缓存仍保存旧结果。
这不是单纯的“DNS 随机丢记录”。
3.5 Service 的 DNS 记录形式
常见记录形式如下。
普通 Service
<service>.<namespace>.svc.<cluster-domain>
通常返回 ClusterIP 的 A 或 AAAA 记录。
Headless Service
同样的名称通常返回一个或多个 Endpoint 地址。
按端口命名的 SRV 记录
若端口有名称,例如:
ports:
- name: http
port: 80
targetPort: 8080
可能存在类似:
_http._tcp.orders.default.svc.cluster.local
SRV 记录包含端口和目标名称,适合支持 SRV 的客户端。它不是所有应用都会自动使用的机制;应用若只调用普通的 getaddrinfo(),通常不会因为存在 SRV 记录而自动获得端口。
对于 Headless Service,SRV 结果可指向具体 Pod 的 DNS 名称;实际结果受 Endpoint、端口名和 CoreDNS 行为影响,必须用 dig 验证,不应根据名称形式臆测。
ExternalName Service
例如:
apiVersion: v1
kind: Service
metadata:
name: payments
namespace: default
spec:
type: ExternalName
externalName: payments.example.com
查询:
payments.default.svc.cluster.local
通常得到指向 payments.example.com 的 CNAME,而不是 ClusterIP。
ExternalName 不创建代理、Endpoint 或负载均衡规则。它只是 DNS 层的别名,因此存在边界:
- 客户端必须正确处理 CNAME;
- TLS 证书名、HTTP Host 和外部服务身份仍需正确配置;
- 它不提供 Kubernetes NetworkPolicy 意义上的出口控制;
- 外部域名变化会受 DNS 缓存影响。
3.6 Pod DNS 名称不是普通 Service 的替代品
Kubernetes 可以为 Pod 提供若干形式的 DNS 名称,但它们有前置条件和版本边界,不能把所有 Pod IP 都想当然地映射成稳定域名。
使用 StatefulSet 时,常见组合是:
- 一个 Headless Service;
StatefulSet.spec.serviceName指向该 Service;- Pod 设置稳定的 ordinal 身份。
例如 Pod 可能使用:
db-0.db.default.svc.cluster.local
这里的 db-0 是 StatefulSet 的稳定序号身份,db 是 Headless Service。Pod 重建后 IP 可以变化,但该逻辑身份仍可保持。
较早的 Kubernetes 文档还描述过基于 Pod IP 的特殊 DNS 名称,例如把 IPv4 地址中的点替换为连字符。这类行为与 pods 插件配置、版本和发行版有关,当前生产设计不应依赖未验证的 Pod-IP 反向编码名称。稳定服务访问应优先使用 Service;稳定实例身份应使用 StatefulSet 加 Headless Service。
4. Search:短名称为什么能工作,又为什么会产生额外查询
4.1 名称的逐级缩短
在 default Namespace 中,Pod 常见的 search 列表是:
default.svc.cluster.local
svc.cluster.local
cluster.local
因此应用查询短名称:
orders
典型解析器会尝试把它扩展为:
orders.default.svc.cluster.local
orders.svc.cluster.local
orders.cluster.local
orders
最终 orders.default.svc.cluster.local 成功后,应用就能访问同 Namespace 的 Service。
跨 Namespace 访问时,推荐至少使用:
orders.production
它会基于当前 Pod 的 search 列表尝试:
orders.production.default.svc.cluster.local
orders.production.svc.cluster.local
orders.production.cluster.local
orders.production
其中真正有效的通常是:
orders.production.svc.cluster.local
如果直接使用完整限定名:
orders.production.svc.cluster.local.
末尾的点表示 DNS 根,名称已经是绝对名称,通常不会再套用 search 列表。
4.2 ndots:5 如何影响查询顺序
ndots 表示名称中至少包含多少个点时,解析器倾向于先把它当作绝对名称处理。Kubernetes 常见配置是:
options ndots:5
以典型 glibc resolver 为例:
查询 orders
点数为 0,小于 5:
- 尝试
orders.default.svc.cluster.local - 尝试其他 search 后缀
- 最后尝试绝对名称
orders.
查询 orders.default.svc.cluster.local
点数为 3,小于 5:
- 可能先尝试
orders.default.svc.cluster.local.default.svc.cluster.local - 再尝试其他 search 拼接结果
- 最后尝试真正的
orders.default.svc.cluster.local
这些前置查询大多会得到 NXDOMAIN 或其他失败响应,然后才到达正确名称。
查询 orders.default.svc.cluster.local.
末尾点表示绝对名称:
- 直接查询
orders.default.svc.cluster.local - 不拼接 search 后缀
因此,在高频、跨 Namespace 或集群外域名访问场景中,合理使用完整名称和末尾点可以减少无效查询。但不能假定所有语言运行时、操作系统和 libc 完全遵循同一顺序;Go 的纯 Go resolver、glibc、musl 和应用自带 DNS 客户端可能存在差异。
4.3 Search 的代价不只是“多几毫秒”
设 search 列表有 个后缀,应用查询的名称未达到 ndots 阈值,并且正确答案位于第 个候选名称。若每个候选需要一次请求,则最多可能产生:
次请求;若所有候选都失败,还可能继续查询绝对名称,使请求数接近:
实际请求数还会受到以下因素影响:
- A 和 AAAA 查询;
- 应用是否并行发起 IPv4/IPv6 查询;
- resolver 是否重试;
- UDP 截断后是否改用 TCP;
- CoreDNS 是否命中缓存;
- 上游 DNS 是否返回 NXDOMAIN、SERVFAIL 或超时。
例如一个应用每秒发起 1000 次对 api.external.example.com 的请求,而该名称有 4 个点、低于 ndots:5。典型解析器可能先查询多个带集群 search 后缀的名称,再查询外部真实名称。结果是:
- CoreDNS 收到更多无效请求;
- 上游企业 DNS 或云 DNS 承受额外压力;
- 延迟和超时概率增加;
- 负缓存可能保存这些无效名称。
可验证实际行为:
kubectl run -it --rm dns-debug \
--image=registry.k8s.io/e2e-test-images/dnsutils:1.3 \
--restart=Never -- sh
进入容器后:
cat /etc/resolv.conf
dig orders.default.svc.cluster.local
dig orders.default.svc.cluster.local.
dig +search orders
dig +short api.example.com
dig +search orders 显式使用 search 列表;带末尾点的名称则不会因 search 列表扩展。抓包时可进一步确认解析器真实发出的查询顺序:
kubectl sniff <pod-name> -n <namespace> -f "udp port 53 or tcp port 53"
kubectl sniff 需要额外插件和抓包权限,生产环境不应默认安装到所有节点;也可以在节点、DNS Pod 或 NodeLocal DNSCache 路径上使用受控的 tcpdump。
4.4 Pod 的 DNS 策略会改变 search 行为
常见 dnsPolicy 有:
ClusterFirst:默认策略,集群内名称优先交给集群 DNS;ClusterFirstWithHostNet:适用于hostNetwork: true的 Pod;Default:继承运行该 Pod 的节点 DNS 配置;None:不使用默认策略,必须通过dnsConfig明确提供配置。
例如:
spec:
dnsPolicy: None
dnsConfig:
nameservers:
- 10.96.0.10
searches:
- default.svc.cluster.local
- svc.cluster.local
- cluster.local
options:
- name: ndots
value: "2"
dnsPolicy: None 的风险是配置错误会直接导致集群服务解析失败;nameservers、search 列表长度和单项长度还受操作系统及 DNS 配置限制。不要只修改容器镜像里的 /etc/resolv.conf,因为它通常由 kubelet 在 Pod 启动时注入,重建 Pod 后会恢复。
hostNetwork: true 的 Pod 若仍使用默认 ClusterFirst,可能无法按预期使用集群 DNS,通常需要显式选择 ClusterFirstWithHostNet。实际配置应检查 Pod 的 dnsPolicy 和最终文件,而不是只看 Deployment 模板。
5. 缓存:哪些状态会被记住,为什么修改后不会立即生效
5.1 Kubernetes DNS 中可能存在多层缓存
一次解析可能经过多层缓存:
应用自身缓存
↓
语言运行时或 libc 缓存
↓
NodeLocal DNSCache(可选)
↓
CoreDNS cache 插件
↓
上游 DNS 缓存
Kubernetes 不规定每个应用和发行版必须使用哪些缓存。CoreDNS 的 cache 插件是常见实现,但它不是 Kubernetes API 的一部分。
缓存的基本规则是:
- 正缓存:记录存在,例如 Service A 记录;
- 负缓存:记录不存在,例如 NXDOMAIN;
- 记录带有 TTL,缓存通常在 TTL 到期前直接返回;
- 过期后才重新查询权威来源或上游。
CoreDNS 的 kubernetes 插件负责生成集群域名响应,cache 插件负责缓存响应。Corefile 中的:
cache 30
表示一个常见的缓存配置示例,但不能把这个数字当成所有集群的保证值。应以实际 Corefile、CoreDNS 版本和插件行为为准。
kubernetes 插件还可能通过 ttl 配置控制其生成记录的 TTL,例如:
kubernetes cluster.local {
ttl 30
}
TTL 为 0 可以禁止这类记录被缓存,但会增加查询和 CoreDNS 负载;较长 TTL 则降低负载,却延长变更传播时间。生产调优必须同时考虑 CoreDNS 缓存、NodeLocal DNSCache、应用缓存和上游缓存,单独修改一个 TTL 不一定改变最终效果。
5.2 一个 Service 变更的完整时间线
假设应用查询:
api.default.svc.cluster.local
初始结果是 ClusterIP 10.96.1.10。
现在删除 Service 并重新创建同名 Service,新的 ClusterIP 为 10.96.2.20。可能发生以下过程:
- Kubernetes API 写入新对象;
- CoreDNS informer 收到 Service 变化;
- CoreDNS 本地状态更新;
- CoreDNS 新查询开始生成
10.96.2.20; - 但 NodeLocal DNSCache、CoreDNS cache 或应用缓存仍可能保存旧的
10.96.1.10; - 缓存 TTL 到期后,调用方才逐步看到新值。
因此,kubectl get svc 看到新 ClusterIP,并不能证明每个客户端立刻都拿到新地址。
验证时要绕开不同层级:
# 直接询问指定 DNS 服务器
dig @10.96.0.10 api.default.svc.cluster.local
# 在业务 Pod 内询问,观察该 Pod 实际使用的 nameserver
kubectl exec -n default deploy/client -- \
getent ahostsv4 api.default.svc.cluster.local
dig @10.96.0.10 直接指定 DNS 服务器,便于确认 CoreDNS 或其 Service 的结果;getent 更接近应用通过系统 resolver 的实际行为,但可能受 libc 和本地缓存影响。
5.3 负缓存会让“刚创建的 Service”暂时解析失败
如果应用在 Service 创建前查询:
new-api.default.svc.cluster.local
CoreDNS 可能返回 NXDOMAIN,并由缓存保存该负结果。随后即使 Service 已创建,短时间内仍可能看到旧的 NXDOMAIN。
这类故障的特征是:
kubectl get svc new-api已经成功;- 某些新启动的 Pod 能解析,旧进程仍失败;
- 清理应用缓存或等待 TTL 后恢复;
- 反复重试时结果从 NXDOMAIN 变成成功。
这不是 Kubernetes API 回滚,也不是 DNS “随机”。它是不同观察者处于不同缓存状态。
5.4 缓存不是一致性协议
DNS TTL 只提供“在这段时间内可以复用该结果”的约束,不提供强一致性。尤其不应把短 TTL 当作服务发布完成的事务边界:
- Service 对象更新、EndpointSlice 更新和 CoreDNS informer 更新有传播路径;
- 客户端可能缓存结果超过预期;
- 某些应用忽略 TTL;
- 某些连接池长期复用已解析的地址;
- 已建立的 TCP 连接不会因为 DNS 记录改变而自动迁移。
如果应用需要无损切换后端,必须设计连接排空、重试、健康检查和生命周期协作,而不是只依赖 DNS TTL。
6. CoreDNS 到上游 DNS:集群域名和外部域名的分流
CoreDNS 通常负责:
cluster.local
svc.cluster.local
Pod/Service 相关反向解析区域
对其他名称,例如:
www.example.com
database.corp.example
则根据 Corefile 使用 forward 或其他插件转发。
常见配置:
.:53 {
errors
health
ready
kubernetes cluster.local in-addr.arpa ip6.arpa
forward . /etc/resolv.conf
cache 30
loop
reload
}
/etc/resolv.conf 在这里是 CoreDNS 容器自身看到的上游配置,不一定等于业务 Pod 的 /etc/resolv.conf。这一区别很重要。
6.1 常见的转发错误
如果节点的 /etc/resolv.conf 指向集群 DNS Service,而 CoreDNS 又把所有非集群域名转发到 /etc/resolv.conf,就可能形成环路:
CoreDNS
→ 节点 resolv.conf
→ 集群 DNS Service
→ CoreDNS
→ ...
CoreDNS 的 loop 插件通常用于检测这类配置问题,但不能替代人工检查。发生环路时,外部域名可能大量超时或返回 SERVFAIL,而集群内域名仍然看似正常。
检查:
kubectl -n kube-system exec deploy/coredns -- \
cat /etc/resolv.conf
kubectl -n kube-system logs deploy/coredns
上游企业 DNS、云 VPC DNS 和节点 resolv.conf 的来源具有明显环境差异。不要直接把生产 Corefile 从一个集群复制到另一个集群,必须验证:
- 上游 DNS 地址是否可达;
- 是否需要搜索域或特殊转发区域;
- 是否存在 UDP 53 被阻止、DNS over TLS/HTTPS 等企业策略;
- 节点 resolv.conf 是否由 systemd-resolved 生成了 stub 地址;
- CoreDNS Pod 是否能访问该 stub 地址。
7. 用一个最小实验验证 Service Discovery
下面创建一个后端和一个客户端。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: registry.k8s.io/e2e-test-images/agnhost:2.39
args: ["netexec"]
ports:
- name: http
containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: web
namespace: default
spec:
selector:
app: web
ports:
- name: http
port: 80
targetPort: 8080
---
apiVersion: v1
kind: Pod
metadata:
name: dns-client
namespace: default
spec:
restartPolicy: Never
containers:
- name: client
image: registry.k8s.io/e2e-test-images/dnsutils:1.3
command: ["sleep", "3600"]
应用配置:
kubectl apply -f example.yaml
kubectl -n default wait \
--for=condition=available deployment/web \
--timeout=120s
kubectl -n default wait \
--for=condition=Ready pod/dns-client \
--timeout=120s
查看 Service 和 EndpointSlice:
kubectl -n default get svc web -o wide
kubectl -n default get endpointslice \
-l kubernetes.io/service-name=web \
-o wide
执行解析:
kubectl -n default exec dns-client -- \
nslookup web
kubectl -n default exec dns-client -- \
nslookup web.default.svc.cluster.local
kubectl -n default exec dns-client -- \
dig +short web.default.svc.cluster.local.
kubectl -n default exec dns-client -- \
getent hosts web
预期结果:
web可以通过 search 列表解析;- 完整名称解析到 Service ClusterIP;
getent返回的地址应与kubectl get svc web中的 ClusterIP 对应;- EndpointSlice 中应出现两个后端地址。
测试连接:
kubectl -n default exec dns-client -- \
wget -qO- http://web/
这个请求验证的不只是 DNS,还验证了:
web能解析;- ClusterIP 可以访问;
- Service 端口 80 到
targetPort8080 的映射有效; - 至少一个后端能处理 HTTP 请求。
如果 nslookup 成功而 wget 失败,问题已经越过 DNS 层,应继续检查 Service、EndpointSlice、kube-proxy/CNI、NetworkPolicy 和应用监听端口。
8. 故障诊断:按响应类型和路径定位
8.1 先看 Pod 实际 DNS 配置
kubectl exec -n <namespace> <pod> -- cat /etc/resolv.conf
重点检查:
nameserver是否为预期的集群 DNS 地址;- search 列表是否包含正确的 Namespace 和集群域;
options ndots是否造成过多查询;- Pod 是否使用
dnsPolicy: Default或None; hostNetwork是否与 DNS 策略匹配。
若只有某一个 Pod 失败,优先怀疑该 Pod 的策略、网络命名空间、镜像 resolver 或本地配置;若所有 Namespace 都失败,优先检查集群 DNS Service、CoreDNS 和 CNI。
8.2 检查 DNS Service 和 CoreDNS Endpoint
kubectl -n kube-system get svc kube-dns -o wide
kubectl -n kube-system get endpointslice \
-l kubernetes.io/service-name=kube-dns \
-o wide
kubectl -n kube-system get pods \
-l k8s-app=kube-dns \
-o wide
kubectl -n kube-system get deployment coredns
判断关系:
kube-dnsService 不存在或 ClusterIP 不符合 Pod 配置:Pod 可能使用了旧配置或集群安装异常;- Service 存在但 EndpointSlice 为空:CoreDNS Pod 没有被选中、未 Ready 或标签不匹配;
- CoreDNS Pod CrashLoopBackOff:先看日志和 Corefile;
- CoreDNS Ready,但 Pod 到 DNS Service 不通:继续查 Service 转发、CNI、NetworkPolicy 和节点路径。
8.3 直接从 Pod 查询集群域和外部域
kubectl exec -n <namespace> <pod> -- \
nslookup kubernetes.default.svc.cluster.local
kubectl exec -n <namespace> <pod> -- \
nslookup example.com
这两个查询用于区分:
| 集群域名 | 外部域名 | 更可能的范围 |
|---|---|---|
| 失败 | 失败 | Pod 到 DNS Service、CoreDNS 本身、DNS 网络路径 |
| 成功 | 失败 | CoreDNS 上游转发、节点 resolv.conf、外部 DNS 或出口网络 |
| 失败 | 成功 | CoreDNS 的 kubernetes 插件、API 同步、集群域配置 |
| 成功 | 成功 | DNS 基本路径可用,继续排查具体名称、缓存、Service 或应用 |
注意不要把 nslookup 的“Server”字段当成权威证据;它只表示客户端使用的 nameserver,不证明该服务器正确处理了目标区域。
8.4 区分 NXDOMAIN、SERVFAIL 和 timeout
NXDOMAIN
含义通常是“该名称不存在”。常见原因:
- Service 名称拼写错误;
- Namespace 错误;
- 查询了不存在的记录类型;
- Service 尚未创建;
- 负缓存尚未过期;
- CoreDNS 的集群域配置与 kubelet 配置不一致。
先验证对象:
kubectl get svc -A | grep '<service-name>'
kubectl get namespace
SERVFAIL
含义是服务器无法完成查询,通常不是“名称确定不存在”。可能原因:
- CoreDNS 插件配置错误;
- Kubernetes API 不可用或同步失败;
- 上游 DNS 失败;
- DNSSEC、转发或循环配置问题;
- CoreDNS 内部资源耗尽或异常。
应查看 CoreDNS 日志、指标和 Corefile,而不是直接重启所有业务 Pod。
timeout
含义是请求没有在客户端等待时间内得到响应。常见原因:
- Pod 到 DNS Service 的 UDP/TCP 53 不通;
- NetworkPolicy 阻止 DNS;
- Service 转发规则或 CNI 异常;
- CoreDNS 进程无响应;
- 上游 DNS 超时;
- UDP 分片、MTU 或 conntrack 问题;
- 高负载导致请求排队或丢弃。
使用 dig 观察响应和传输:
kubectl exec -n <namespace> <pod> -- \
dig +time=2 +tries=1 kubernetes.default.svc.cluster.local
kubectl exec -n <namespace> <pod> -- \
dig +tcp +time=2 +tries=1 kubernetes.default.svc.cluster.local
如果 UDP 失败而 TCP 成功,不能简单得出“DNS 正常”:这提示 UDP 路径、MTU、分片或防火墙可能有问题。DNS 查询并不只使用 UDP,响应较大时也可能因截断改用 TCP。
8.5 检查 NetworkPolicy 和 CNI
NetworkPolicy 经常只允许应用到应用的流量,却忘记允许 Pod 到 DNS 的 UDP/TCP 53:
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
这段规则只是说明方向,不是对所有 CNI 的完整生产策略。某些环境中 DNS 通过 NodeLocal DNSCache 提供,目标地址可能是节点本地 IP,而不是 kube-dns Service IP;策略必须根据实际 nameserver 和 CNI 实现验证。
如果 DNS timeout 与节点、可用区、特定 CNI 路径相关,应进一步检查:
- Pod 到 DNS Service IP 的路由;
- Service 转换规则;
- CNI dataplane;
- MTU;
- conntrack 表和丢包;
- 节点防火墙;
- NodeLocal DNSCache 的监听地址和日志。
这也是为什么 DNS 故障排查不能停在 kubectl get pods。
9. CoreDNS 本身的容量与高并发问题
DNS 请求通常是短请求,但会产生突发流量。以下模式容易放大请求数量:
- 应用频繁调用解析函数而不复用连接;
- 使用短名称配合较高
ndots; - 外部域名大量触发无效 search 查询;
- 缓存被频繁失效或 TTL 过短;
- CoreDNS 副本数不足;
- 上游 DNS 响应慢;
- DNS 查询和业务请求共用受限的网络路径。
CoreDNS 的常见指标和日志可以帮助判断:
kubectl -n kube-system logs deploy/coredns
kubectl -n kube-system port-forward \
deploy/coredns 9153:9153
是否能访问指标端口取决于部署配置。常见 Prometheus 指标包括请求总数、响应码、请求延迟、缓存命中等,但指标名称和插件是否启用必须以实际 CoreDNS 版本为准。
在大型集群中,NodeLocal DNSCache 是常见的可选架构:每个节点运行本地 DNS 缓存,Pod 先访问节点本地地址,再由它访问 CoreDNS。它可以减少跨节点 UDP 路径和 conntrack 压力,但会增加一层组件和故障面:
Pod → 节点本地 DNSCache → CoreDNS Service/Pod → 上游 DNS
启用后,排障必须同时查看:
- Pod
/etc/resolv.conf指向谁; - NodeLocal DNSCache 是否运行;
- 本地监听地址是否存在;
- 本地缓存到 CoreDNS 的路径;
- CoreDNS 到上游的路径。
NodeLocal DNSCache 的部署方式不是所有 Kubernetes 安装都默认提供,不能只根据“集群使用 CoreDNS”判断它是否存在。
10. 修改 CoreDNS 配置时的状态和风险
CoreDNS 配置通常保存在:
kubectl -n kube-system get configmap coredns -o yaml
修改 ConfigMap 可能触发 CoreDNS 的 reload 插件重新加载,也可能需要由集群安装方式执行滚动更新。不要直接编辑正在运行的 CoreDNS Pod,因为 Pod 重建后修改会丢失。
修改后至少验证:
kubectl -n kube-system rollout status deployment/coredns
kubectl run -it --rm dns-check \
--image=registry.k8s.io/e2e-test-images/dnsutils:1.3 \
--restart=Never -- \
nslookup kubernetes.default.svc.cluster.local
kubectl run -it --rm external-dns-check \
--image=registry.k8s.io/e2e-test-images/dnsutils:1.3 \
--restart=Never -- \
nslookup example.com
生产风险包括:
- 删除
kubernetes插件导致集群内 Service 全部无法解析; - 修改
forward目标导致外部域名全部超时; - 引入转发环路;
- 使用不兼容插件语法导致 CoreDNS 启动失败;
- TTL 过短造成查询量暴增;
fallthrough范围错误,让本应返回 NXDOMAIN 的名称继续被错误转发;- 仅更新 ConfigMap,却没有确认 CoreDNS Pod 已加载新配置。
因此配置变更必须同时验证集群域、反向区域和外部域名,而不是只测一个 example.com。
11. 常见误解与反例
误解一:Service 名称解析成功就代表服务正常
反例:
orders.default.svc.cluster.local → 10.96.42.17
但 EndpointSlice 为空。此时 DNS 完全可以成功,业务连接仍然失败。
正确检查:
kubectl -n default get svc orders
kubectl -n default get endpointslice \
-l kubernetes.io/service-name=orders
误解二:Headless Service 会自动提供稳定的单一入口
Headless Service 返回的是 Endpoint 地址集合,不是虚拟 ClusterIP。Pod 重启后地址可能变化,客户端必须能处理多个地址、缓存和重试。
需要稳定实例身份时,应使用 StatefulSet 和 Headless Service,而不是把某个 Pod IP 写死。
误解三:改了 Service selector,DNS 应该立刻变化
selector 变化首先影响 EndpointSlice,随后 CoreDNS 需要观察到变化,最后客户端缓存才会过期。变化传播是多阶段过程:
Service selector
→ EndpointSlice controller
→ EndpointSlice
→ CoreDNS informer
→ CoreDNS cache
→ NodeLocal/app cache
→ 应用下一次查询
任何阶段延迟或故障,都可能造成旧结果仍被使用。
误解四:把 ndots 改小就一定更快
较小的 ndots 可能减少外部域名的 search 查询,但也会改变短名称和相对名称的解析顺序。应用、基础设施组件和外部 DNS 可能依赖现有行为。应先用 dig 或抓包确认查询数量,再针对特定工作负载调整,而不是全局盲改。
误解五:重启 CoreDNS 可以修复所有 DNS 故障
如果根因是:
- Pod 到 DNS Service 的网络不通;
- NetworkPolicy 阻断;
- CoreDNS 上游不可达;
- EndpointSlice 没有后端;
- 应用缓存旧地址;
重启 CoreDNS 只能暂时清除部分内存状态,不能修复根因,甚至可能在重启期间扩大故障范围。
12. 一条可复用的排障路径
当业务报告“域名解析失败”时,可以按以下顺序缩小范围:
第一步:确认名称和 Namespace
kubectl get svc <name> -n <namespace>
确认应用使用的是:
<name>.<namespace>.svc.<cluster-domain>
而不是误把跨 Namespace Service 当成当前 Namespace 的短名称。
第二步:确认 Service 和后端状态
kubectl get svc <name> -n <namespace> -o yaml
kubectl get endpointslice \
-n <namespace> \
-l kubernetes.io/service-name=<name> \
-o yaml
确认 ClusterIP、端口、selector、Endpoint 地址和 ready 状态。
第三步:确认 Pod resolver 配置
kubectl exec -n <namespace> <pod> -- cat /etc/resolv.conf
确认 nameserver、search、ndots 和 dnsPolicy。
第四步:从同一个网络命名空间查询
kubectl exec -n <namespace> <pod> -- \
dig +short <name>.<namespace>.svc.<cluster-domain>.
使用末尾点避免 search 干扰,再测试短名称:
kubectl exec -n <namespace> <pod> -- \
getent hosts <name>
第五步:对比集群域和外部域
kubectl exec -n <namespace> <pod> -- \
nslookup kubernetes.default.svc.cluster.local
kubectl exec -n <namespace> <pod> -- \
nslookup example.com
由响应组合判断问题在内部 DNS、上游 DNS 还是共同网络路径。
第六步:检查 CoreDNS 和 DNS Service
kubectl -n kube-system get svc kube-dns -o wide
kubectl -n kube-system get endpointslice \
-l kubernetes.io/service-name=kube-dns -o wide
kubectl -n kube-system get pods -l k8s-app=kube-dns
kubectl -n kube-system logs deploy/coredns
kubectl -n kube-system get configmap coredns -o yaml
第七步:若为 timeout,再查网络路径
重点查看 UDP/TCP 53、Service 转发、CNI、NetworkPolicy、MTU、conntrack 和节点防火墙。若为 NXDOMAIN 或 SERVFAIL,则优先检查名称、Corefile、API 同步和缓存,而不是先抓包。
13. 生产设计中的取舍
Kubernetes DNS 的稳定性来自多个层次的组合,而不是某个单独参数:
- 使用 Service 名称而非 Pod IP;
- 需要实例身份时使用 StatefulSet 加 Headless Service;
- 跨 Namespace 时使用明确名称;
- 高频或外部域名查询关注
ndots和 search 放大; - 设计连接池和重试时考虑 DNS 变化不会迁移已有连接;
- 关注正缓存和负缓存造成的传播延迟;
- 对 CoreDNS 上游转发设置清晰的失败边界;
- 用 EndpointSlice 判断后端,而不是只看 Service 对象;
- 将 CoreDNS、NodeLocal DNSCache、CNI 和 NetworkPolicy 纳入同一条故障路径;
- 对 Corefile 修改进行集群内和集群外双向验证。
从规范角度看,Service 的 DNS 名称和相关服务发现语义是 Kubernetes 的核心能力;从实现角度看,CoreDNS 插件链、缓存默认值、NodeLocal DNSCache 和云厂商网络路径都可能不同。真正可靠的判断必须基于三份现场证据:
Pod 的 /etc/resolv.conf
CoreDNS 的实际 Corefile 与日志
Service/EndpointSlice 的实际状态
只有把这三者与实际 DNS 响应、Service 连接结果结合起来,才能区分“名称不存在”“解析结果过期”“DNS 路径不通”和“DNS 正常但服务后端故障”。
系列导航与关联阅读
- 系列入口:Kubernetes 完整学习路线:从 Pod 与控制面到安全、运维和 Operator
- 上一篇:kube-proxy 与服务转发:iptables、IPVS、nftables、Session 和诊断
- 下一篇:Ingress 与 Gateway API:路由、TLS、Controller、策略和迁移
- 延伸:Kubernetes Service:ClusterIP、NodePort、LoadBalancer、EndpointSlice 和流量
- 延伸:Kubernetes 网络排障:DNS、Service、CNI、MTU、Conntrack 和抓包
官方资料
本文依据 Kubernetes、CNCF 与相关项目官方文档重新梳理;正文和生产清单由 WR BLOG 编写。

评论
0 条讨论