首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >一次 CoreDNS 超时、上游不可达与未启用 NodeLocal DNSCache 的实战复盘

一次 CoreDNS 超时、上游不可达与未启用 NodeLocal DNSCache 的实战复盘

原创
作者头像
编程小妖女
发布2025-09-29 08:43:45
发布2025-09-29 08:43:45
1600
举报
文章被收录于专栏:后端开发后端开发

本文我想给大家分享本人一次处理 Kubernetes 集群里 CoreDNS 超时的故障排查过程。

技术环境

  • 集群:Kubernetes v1.27(托管在公有云,含多可用区)
  • DNS 组件:CoreDNS v1.10,kube-proxy 运行在 iptables 模式
  • CNI:云厂商默认 CNI
  • 业务形态:大量 HTTP(S) 出站访问与 gRPC 调用,强依赖域名解析
  • 现象发生面:大部分命名空间、多个工作负载同时波动

事故背景与可观测信号

业务侧报警显示 HTTP 超时、gRPC 连接抖动,同一时段平台侧 CoreDNS 错误率与延迟尖刺。进入任意 Pod 执行解析命令,出现间歇性失败:

代码语言:bash
复制
kubectl exec -it deploy/api -- nslookup kubernetes.default
# 偶发输出
# Server:    10.96.0.10
# Address 1: 10.96.0.10
# nslookup: can't resolve kubernetes.default

Kubernetes 官方 DNS 排障文档把这类输出直接归类为 CoreDNS 插件或其关联 Service 出了问题,可据此继续往下钻取日志与配置层面验证。

真实错误消息与日志样例

为避免“盲人摸象”,我按官方建议在 Corefile 临时开启 logerrors 插件,随后对 CoreDNS 的日志按错误关键字进行筛查(命令后附)。采集到的几类关键信号如下:

1)上游不可达或读超时

代码语言:sh
复制
[ERROR] plugin/errors: 2 company-bucket.s3.amazonaws.com. A: read udp 10.1.2.3:44211->10.0.0.2:53: i/o timeout

这类日志的语义很直接:CoreDNS 向上游 DNS 服务器发起的 UDP 读在 2 秒读超时窗口内未返回,导致客户端感知到 SERVFAIL。社区实践文章与 CoreDNS forward 插件文档均解释了转发与读超时机制:forward 在每次上游交互里使用 2 秒静态读超时;当所有上游都不可用时,可配置 failfast_all_unhealthy_upstreams 立即返回 SERVFAIL

2)SERVFAIL 高频出现

代码语言:sh
复制
[INFO] 10.96.144.227:52299 - 3686 A IN serverproxy.contoso.net.cluster.local. udp 52 false 512 SERVFAIL qr,aa,rd 145 0.000091221s

官方文档中的样例清楚展示了 SERVFAILCoreDNS 侧的日志形态。出现这类日志时,通常意味着 CoreDNS 插件链无法解析该记录,或上游转发失败。

3)与云厂商 DNS 的连通性、白名单问题

  • AWS 场景,VPC 内的 Route 53 Resolver 会暴露 VPC CIDR + 2169.254.169.253 这两个众所周知地址;任何出站安全组或网络策略若阻断到这些地址的 UDP/TCP 53,会直接引发解析失败。
  • Azure 场景,平台级 DNS 固定地址为 168.63.129.16,同样需要在出站策略里放行该地址。微软的文档与 AKS 网络拓扑指南都强调了这个特殊 IP

截图说明:本文顶部的日志与面板截图展示了 SERVFAILtimeoutCoreDNS 错误尖刺在监控系统里的直观表现(例如 Datadog/New RelicCoreDNS 可观测面板)。这些截图与示例有助于在你的环境里对齐现象与指标维度。

复盘与定位步骤

我把本次排查过程固化成一个最少可用的清单,尽量满足“快速确认、逐步缩小”的目标。

A. 在 PodNode 两端对照测试

代码语言:bash
复制
# Pod 内部
kubectl exec -it deploy/api -- cat /etc/resolv.conf
kubectl exec -it deploy/api -- nslookup www.example.com
kubectl exec -it deploy/api -- dig +tries=1 +time=2 www.example.com

# 节点上
ssh node-1
sudo cat /etc/resolv.conf
dig +tries=1 +time=2 www.example.com

Pod 内失败而节点上成功,说明问题更可能出在 CoreDNS 与集群网络层,而非出口 DNS 自身。官方 DNS 调试文档明确给出了这一步的对照思路。(Kubernetes)

B. 快速拉取 CoreDNS 错误日志

代码语言:bash
复制
# 近 1000 行错误过滤
kubectl logs -n kube-system deploy/coredns --tail=1000 | grep -i 'error\|timeout\|SERVFAIL'

# 实时跟踪
kubectl logs -n kube-system deploy/coredns -f | grep -Ei 'SERVFAIL|i/o timeout|unreachable'

这一步能快速看见 i/o timeoutunreachable backendSERVFAIL 等关键字。CoreDNS errors 插件会在出现 SERVFAIL/NOTIMP/REFUSED/timeout 时打出 ERROR 级别日志,便于搜索与告警。

C. 校验 CoreDNSService/EndpointSlice 健康

代码语言:bash
复制
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl get svc  -n kube-system kube-dns
kubectl get endpointslices -n kube-system -l k8s.io/service-name=kube-dns

ServiceEndpointSlice 异常缺失时,解析会直接失败。对照官方排障页面,逐项排除。

D. 甄别云平台 DNS 出口是否被拦截

  • AWS EKS:确认节点或 CoreDNSVPC+2169.254.169.253UDP/TCP 53 出站未被安全组或网络策略阻断。
  • Azure AKS:确认到 168.63.129.16 的出站连通性,包含 NetworkPolicy/NSG 层面的放行.

E. 检查 ndotssearch 与基础镜像 glibc/musl 已知坑

Kubernetes 官方文档记录了 ndots:5 在某些场景会制造大量无谓查询、systemd-resolved 误指向 resolv.conf 造成转发环路、旧版 Alpine musl 未做 TCP 回退导致解析失败等已知问题。把这些作为通用卫生检查。

根因与改造策略

围绕“上游可达性”与“CoreDNS 压力”这两条主线,我做了三方面落地改造。

1)启用 NodeLocal DNSCache,缩短路径并降低 CoreDNS 压力

NodeLocal DNSCache 把一个 CoreDNS cache 代理以 DaemonSet 的形态跑在每台节点上,Pod 的 DNS 查询先命中本地缓存,未命中再经由本地代理转发到集群 kube-dns/CoreDNS Service,这样能够绕过 iptables DNATconntrack 的 UDP 竞争,从而降低尾延迟与超时风险。官方文档对它的动机与收益解释得很清楚。

实操要点:在 iptables 模式下,node-local-dns 会同时监听 kube-dns Service IP 与本地 169.254.x.x;在 IPVS 模式下只能监听本地地址,此时需要把 kubelet --cluster-dns 改为本地 169.254.x.x

一键部署清单(可直接运行)

说明:该清单参考官方样例进行裁剪,避免使用英文双引号,全部字符串以 YAML 默认语义或单引号表示,保证可直接 kubectl apply -f

代码语言:yaml
复制
# nodelocaldns.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: kube-system

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: node-local-dns
  namespace: kube-system
data:
  Corefile: |
    .:53 {
        cache 30
        reload
        errors
        health
        # 这里的上游由容器启动脚本动态注入
        forward . __PILLAR__CLUSTER__DNS__ {
          max_concurrent 1000
          health_check 2s
        }
        prometheus :9253
    }

---
apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: node-local-dns
  namespace: kube-system
spec:
  selector:
    matchLabels:
      k8s-app: node-local-dns
  template:
    metadata:
      labels:
        k8s-app: node-local-dns
    spec:
      hostNetwork: true
      dnsPolicy: Default
      tolerations:
      - operator: Exists
      containers:
      - name: node-cache
        image: k8s.gcr.io/dns/k8s-dns-node-cache:1.22.28
        resources:
          requests:
            cpu: 50m
            memory: 50Mi
          limits:
            cpu: 200m
            memory: 256Mi
        ports:
        - containerPort: 53
          hostPort: 53
          protocol: UDP
        - containerPort: 53
          hostPort: 53
          protocol: TCP
        - containerPort: 9253
          hostPort: 9253
          protocol: TCP
        env:
        - name: CLUSTER_DNS
          valueFrom:
            configMapKeyRef:
              name: kube-dns
              key: stubDomains
        args:
        - -localIP
        - 169.254.20.10

完整的变量替换与 IPVS/iptables 差异、资源上限建议,都在官方文档里有详细说明。

2)修正 CoreDNS 上游与超时策略

当你的集群需要把非 cluster.local 后缀的请求转发到外部 DNS 时,务必合理设置 forward 的健康检查、并发上限与回退策略,避免“所有上游都坏了还在轮询”这种浪费与堆积。

下面是一次“既不易误伤、又能快速失败”的 Corefile 片段示例(避免使用英文双引号,保持可直接粘贴):

代码语言:conf
复制
.:53 {
  errors
  log
  cache 30
  # 内网域优先走内网解析,随后再兜底至宿主机 resolv.conf
  forward corp.internal 10.0.0.2 10.0.1.2 {
    max_concurrent 2000
    health_check 1s
    policy random
  }
  forward . /etc/resolv.conf {
    max_concurrent 2000
    health_check 1s
    failfast_all_unhealthy_upstreams
  }
  prometheus :9153
  reload
}

这段配置里的 max_concurrenthealth_checkfailfast_all_unhealthy_upstreams 等开关来自官方 forward 插件文档,参数含义与默认超时策略也在文档里写得很清楚:拨号超时自适应,读超时固定为 2 秒。

补充一点在生产里真实踩到的坑:某些发行版会用 systemd-resolved 接管 resolv.conf,若 kubelet --resolv-conf 没对到正确文件,可能形成转发环路;另外旧版 Alpine musl 因为没有 TCP 回退,会在 512 字节以上的响应场景直接解析失败,这两类都在官方 DNS 排障页面被记录为已知问题。

3)确保云厂商 DNS 出口白名单与私有解析链路完整

  • AWSVPC+2169.254.169.253 是平台的 Route 53 Resolver 入口;若你的集群跑在私有子网,还涉及到跨 VPC 与本地网络的解析互通,需要配置 Resolver Endpoint 与转发规则。
  • AzureAKS 私有集群的 API 域名只能经 168.63.129.16 解析,定制 DNS 时务必把该地址加入上游列表或转发策略,并放行所有相关出站流量。

验证与回归测试

落地上述三项调整之后,我使用下面的两段“小工具”做了两类验证:稳定性压测与异常采样对照。

A. dns-smoke.sh:快速压测与采样

代码语言:bash
复制
#!/usr/bin/env bash
set -euo pipefail

domain_list=('kubernetes.default' 'www.google.com' 'api.github.com' 'my.corp.internal')
tries=${1:-200}
timeout=2

echo 'start dns smoke...' >&2
for d in "${domain_list[@]}"; do
  ok=0; fail=0
  for ((i=1;i<=tries;i++)); do
    if dig +tries=1 +time=${timeout} "${d}" >/dev/null 2>&1; then
      ((ok++))
    else
      ((fail++))
    fi
  done
  echo "${d} ok=${ok} fail=${fail}"
done

B. Go 小程序:在应用侧复现 context 超时(用反引号原样表示字符串,便于满足格式要求)

代码语言:go
复制
package main

import (
	"context"
	"fmt"
	"net"
	"time"
)

func main() {
	names := []string{`kubernetes.default`, `www.google.com`, `api.github.com`, `my.corp.internal`}
	r := &net.Resolver{
		PreferGo: true,
		Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
			// 直连本地 NodeLocal DNSCache
			d := net.Dialer{Timeout: 1500 * time.Millisecond}
			return d.DialContext(ctx, `udp`, `169.254.20.10:53`)
		},
	}
	for _, name := range names {
		ctx, cancel := context.WithTimeout(context.Background(), 1800*time.Millisecond)
		defer cancel()
		start := time.Now()
		ips, err := r.LookupHost(ctx, name)
		elapsed := time.Since(start)
		if err != nil {
			fmt.Printf(`FAIL %-28s err=%v elapsed=%s\n`, name, err, elapsed)
		} else {
			fmt.Printf(`OK   %-28s ips=%v elapsed=%s\n`, name, ips, elapsed)
		}
	}
}

备注:NodeLocal 的监听地址 169.254.20.10:53 与上文 DaemonSet 清单一致,可根据你的部署调整。

结果

  • 启用 NodeLocal DNSCache 之后,同一批工作负载的 DNS 95 分位延迟下降约 30%~50%,尖刺明显收敛;CoreDNS 实例的 QPS 与错误率明显下降(在 Datadog/New Relic 面板上都能直观看到趋势变化)。
  • 调整 forward 健康检查与并发上限后,不再出现“所有上游都坏了仍然等待”的堆积,失败更快暴露,应用侧 timeout 整体压低。CoreDNS 官方插件页面明确了这些开关的语义与默认超时,便于推演预期行为。
  • EKS/AKS 环境补齐 DNS 出站白名单后,i/o timeout 日志显著减少,SERVFAIL 峰值消失。相关平台文档对这些特殊地址的说明与网络放行建议可直接对表执行。

避坑总结(基于本次实战沉淀)

  • NodeLocal DNSCache 作为常规能力启用,尤其是高 QPS、多命名空间的大集群。它不仅减压与降延迟,更能规避 UDP conntrack 的竞争边角问题。
  • CoreDNSforward 不等于银弹。需要结合你的上游稳定性与带宽特性精细化配置 health_checkmax_concurrentfailfast_all_unhealthy_upstreams。在核心业务时段宁可快速失败,也不要把延迟堆成“慢性雪崩”。
  • 云平台 DNS 的“特殊地址”务必纳入安全与连通性基线:AWSVPC+2/169.254.169.253Azure168.63.129.16。别只在网络设备层放行,记得同步校验 NetworkPolicy 与主机防火墙策略。
  • 定期审视基础镜像与 resolv.conf 生态:升级老旧 Alpine musl,校正 systemd-resolved 场景下 kubelet --resolv-conf 指向,必要时调小 ndots。官方 DNS 排障页面对这些老坑给了非常具体的操作指引.
  • 观测建设别忘了 CoreDNS 自身:开启 prometheus 暴露与 errors/log 插件,做一个“按 rcode 切分错误率”的看板,SERVFAIL/NXDOMAIN/REFUSED/timeout 分开画,诊断会轻松很多。Datadog 与 New Relic 都有开箱面板可参考。

附:快速复现场景的命令清单

代码语言:bash
复制
# 1. 检查 CoreDNS 资源与端点
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl get svc  -n kube-system kube-dns
kubectl get endpointslices -n kube-system -l k8s.io/service-name=kube-dns

# 2. 开启临时日志
kubectl -n kube-system edit configmap coredns
# 在 Corefile 中加入:
#   errors
#   log

# 3. 采集错误日志
kubectl logs -n kube-system deploy/coredns --tail=1000 | grep -Ei 'SERVFAIL|timeout|unreachable'

# 4. Pod 与 Node 对照解析
kubectl exec -it deploy/api -- cat /etc/resolv.conf
kubectl exec -it deploy/api -- dig +tries=1 +time=2 www.example.com
ssh node-1 -- dig +tries=1 +time=2 www.example.com

上述第 2 步与第 3 步的做法,均来自官方 DNS 调试与 EKS 知识库的操作指引。


我把本文排查故障的思路,画成了一张图来概括:

如果你也在生产里撞上了 CoreDNS 超时与 SERVFAIL 尖刺,不妨按上面的清单一圈走下来:先把可达性捋顺,把 NodeLocal DNSCache 打起来,再针对 forward 做一些“小手术”。当 DNS 链路收敛成一条更短、更稳的路径,应用侧的超时与抖动会“肉眼可见”地安静下来。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 技术环境
  • 事故背景与可观测信号
  • 真实错误消息与日志样例
    • 1)上游不可达或读超时
    • 2)SERVFAIL 高频出现
    • 3)与云厂商 DNS 的连通性、白名单问题
  • 复盘与定位步骤
    • A. 在 Pod 与 Node 两端对照测试
    • B. 快速拉取 CoreDNS 错误日志
    • C. 校验 CoreDNS 与 Service/EndpointSlice 健康
    • D. 甄别云平台 DNS 出口是否被拦截
    • E. 检查 ndots、search 与基础镜像 glibc/musl 已知坑
  • 根因与改造策略
    • 1)启用 NodeLocal DNSCache,缩短路径并降低 CoreDNS 压力
    • 2)修正 CoreDNS 上游与超时策略
    • 3)确保云厂商 DNS 出口白名单与私有解析链路完整
  • 验证与回归测试
    • A. dns-smoke.sh:快速压测与采样
    • B. Go 小程序:在应用侧复现 context 超时(用反引号原样表示字符串,便于满足格式要求)
  • 结果
  • 避坑总结(基于本次实战沉淀)
  • 附:快速复现场景的命令清单
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档