☰
CoreDNS CrashLoopBackOff 六大根因与修复指南
2026/10/20 17:47:43 网站建设 项目流程

1. 先说现象:CoreDNS 反复崩溃到底长什么样

kubeadm 把集群拉起来,node 也都是 Ready,但 kube-system 里总有那么一两个 Pod 不对劲,CoreDNS 就是最常见的那个。用 kubectl get pods -n kube-system 一眼扫过去,两个 coredns-7f8b4c5d6c-xxx 的 READY 都是 0/1,STATUS 列顶着 CrashLoopBackOff,RESTARTS 从 5 开始往上跳,隔一会儿再看又多了几次。这状态是什么意思?简单说就是:Pod 每次启动起来几秒钟就崩,kubelet 拉起来再崩,如此反复,而且每次重启的间隔会翻倍增长,从 10 秒、20 秒一路加到 300 秒封顶。

CrashLoopBackOff 和 ImagePullBackOff 不一样,镜像拉不下来是后者,启动即崩溃是前者,两者的排查方向完全不同。CoreDNS 崩了最直接的后果就是集群内部的 Service 域名解析全部失灵。你跑一个测试 Pod 去 nslookup kubernetes.default,大概率返回 server can't find,或者干脆 connection timed out。之后你部署任何工作负载,只要涉及 Service 间调用,全都会被 DNS 卡死。所以这个组件虽然不起眼,却是集群正常工作的命门。

实际排查的时候有个很实用的前提认知:CrashLoopBackOff 只是一个现象级状态,真正的原因藏在事件、日志和描述信息里。与其上来就重启节点、重建 Pod 碰运气,不如先把状态钉死,按日志反推。这一节先让你知道怎么快速拿到第一手信息,后面的章节再逐条拆解根因和修复办法。

2. 直接原因拆解:为什么 CoreDNS 会 CrashLoopBackOff

CrashLoopBackOff 不是单一故障,我梳理一下实战中遇到过的、以及社区里高频出现的根因,大概能归成六类。前两类占了七成以上,但后面几类也别忽略,因为它们往往藏得更深。

2.1 systemd-resolved 造成的解析循环

这是 Ubuntu 18.04 及以上系统,或者启用了 systemd-resolved 的发行版最容易踩的坑。这类系统里 /etc/resolv.conf 通常是一个指向 /run/systemd/resolve/stub-resolv.conf 的软链接,文件内容写着 nameserver 127.0.0.53。kubelet 启动 Pod 时会把这个文件内容拷贝进容器,CoreDNS 启动后 forward 插件默认转发到 /etc/resolv.conf 里的上游,也就是 127.0.0.53。问题来了:容器里的 127.0.0.53 并不是宿主机的 systemd-resolved,而是容器自己的回环地址,CoreDNS 等于把请求转发给了自己,立刻形成解析循环。

这类故障的日志特征非常明显。CoreDNS 日志里会出现类似 plugin/loop: Loop (127.0.0.53:53) detected for zone "." 的报错,这是 CoreDNS 内置的 loop 插件在启动自检时抓到的。只要看到 loop detected,基本可以 90% 确定是 DNS 配置回环,不用再怀疑别的东西。这个插件的存在本身就是用来防这种事故的,但它只能报错,不能自己修复,最终还是得靠我们把上游配置改正。

2.2 内存限制太低导致 OOMKilled

kubeadm 默认部署的 CoreDNS,资源配额是 requests 70Mi、limits 170Mi,这个配置在绝大多数场景下够用。但遇上节点本身内存紧张、或者集群里 DNS 查询量特别大的时候,CoreDNS 的内存占用会迅速涨上去,一旦超过 170Mi 的 limit,就会被 cgroup 的 OOM Killer 直接杀掉。kubelet 检测到容器退出,又开始重启,于是进入 CrashLoopBackOff。

这种原因的判定很简单:kubectl describe 看容器状态,Last State 那一栏 Reason 写的是 OOMKilled,Exit Code 是 137。如果你只看 logs,往往会发现日志非常干净,什么都没有,那就更说明是资源层面被杀,而不是程序本身报错。值得注意的是,有时候节点内存真的不够,CoreDNS 这种系统组件也会被节点的 eviction 机制赶走,现象同样是反复重启,这个我在后面节点压力部分再展开。

2.3 CNI 网络插件未就绪

还有一种常见场景是网络插件本身没装好或没跑起来。比如集群用了 flannel 或 calico,但对应的 daemonset Pod 没起来,节点 NetworkReady 状态是 False。CoreDNS 被调度到这种节点上,Pod 启动时容器网络根本初始化不了,网络插件接口没就绪,kubelet 反复尝试,Pod 也会表现为 CrashLoopBackOff 或 ContainerCreating 卡住。

这类问题有一个典型特征:describe 事件里能看到 NetworkNotReady 或者 failed to setup network for pod。排查时千万别一头扎进 CoreDNS 的配置里,先去看看 flannel 或 calico 的 daemonset 是否全部 Running,再确认 kubectl get nodes 的 STATUS 是不是一直 NotReady。网络层没恢复,CoreDNS 怎么改配置都白搭。实操中我见过不少人把 Corefile 改了好几轮,最后发现是 flannel 的镜像在某个节点上一直拉不下来。

2.4 Corefile 转发目标指向了自己

这段要讲的是自己改出问题的情况。很多人排到 DNS 解析不了,就想着去改 CoreDNS 的 Corefile,把 forward 后面的上游换一换。结果一个不小心,把上游填成了 CoreDNS 自己的 Service IP。比如集群默认的 DNS Service 是 10.96.0.10,你要是写成 forward . 10.96.0.10,那 CoreDNS 把外部解析请求转发出去,转了一圈又回到自己身上,同样是死循环。日志里一样会出现 loop detected。

除此之外,还有一种更隐蔽的配置错误:forward 指向了一个不可达的地址。比如写成了某台内网 DNS 服务器的 IP,但集群到那台服务器之间网络不通。这种情况下 CoreDNS 不会崩得那么频繁,但日志里大量刷 connection refused 或 i/o timeout,整体表现为部分域名解析失败,严重时也会触发健康检查失败被 kubelet 干掉。这提醒我们,配置修改前务必先确认上游 DNS 的可达性,最简单的办法就是在一个测试 Pod 里直接 dig 或 nslookup 那个上游地址。

2.5 镜像与版本错配

这个原因在新老版本混搭的集群里更容易碰到。比如 kubeadm 生成的 coredns 镜像和集群版本不匹配,或者内部镜像仓库同步不完整,拉下来的是一个旧的或损坏的镜像,启动时直接段错误或者找不到入口程序,容器秒退。日志会出现 exec format error 或者 /coredns: not found 之类的信息。

遇到这种迹象,先确认 Pod 镜像是不是 kubeadm 对应版本的那一个:用 kubectl get deployment coredns -n kube-system -o yaml 查看 image 字段,再和当前 kubeadm 版本支持的镜像清单对照。如果你是自己从公共仓库拉镜像,注意架构匹配问题,x86 节点不能跑 arm64 的镜像,反之亦然。仓库拉不到合适镜像时,优先换官方 registry 或改一个已知兼容的 tag,而不是盲目升版本。

2.6 节点压力与驱逐

最后这类容易被忽略:节点本身处于 MemoryPressure 或 DiskPressure 状态,kubelet 的驱逐机制会把低优先级的 Pod 一个个清走,CoreDNS 这种系统组件虽然优先级高,但在资源极度紧张时也扛不住。伴随的现象往往是同一节点上多个 Pod 同时崩溃,不光是 CoreDNS。查看 kubectl describe node 的 Conditions,能看到 MemoryPressure 或 DiskPressure 标记为 True,同时节点上有大量 evicted 状态的 Pod。

这种场景光改 CoreDNS 配置没用,得先解决节点资源问题。比如清理磁盘上的镜像和日志、扩容内存、或者减少该节点上的工作负载。节点恢复正常后,被驱逐的 Pod 不一定会自动回来,这时 kubectl delete pod 强制删除旧的,让 Deployment 控制器重建即可。

3. 实操排查:从零定位到彻底修复

上面分析了六种根因,接下来是完整可复现的排查与修复流程。我推荐你严格按这个顺序走,每一步都做验证,千万别跳步,否则很容易被表面现象带偏。

3.1 第一步:看状态、看事件、看日志

先用一条命令拿到 Pod 全貌,然后逐步深入。第一步永远是状态,第二步 describe,第三步 logs,这个顺序能覆盖绝大多数场景。

kubectl get pods -n kube-system | grep coredns kubectl describe pod -n kube-system <coredns-pod-name>

describe 输出的信息量很大,我一般优先看两个区块。第一个是 Events,如果里面有 FailedScheduling、NetworkNotReady、FailedMount 等关键词,方向瞬间就清晰了。第二个是 Containers 下面的 Last State,它记录的是容器上次退出的状态,关键看 Reason 是 Error 还是 OOMKilled,以及 Exit Code 是多少。假如 Last State 只有 Started: False,说明容器可能卡在启动阶段,这时候就要去看日志。

kubectl logs -n kube-system <coredns-pod-name> --previous

--previous 很关键,因为 CrashLoopBackOff 状态下当前容器可能根本没起来,直接 logs 只能看到空的输出,而 --previous 能看到上一次崩溃进程留下的完整日志。拿不到日志的时候,还可以用 kubectl get pod -o yaml 直接看 containerStatuses 里的字段,里面会有 lastState 的详细记录,这条命令在 describe 信息不全时非常有用。

3.2 第二步:对症修复 DNS 解析配置

先检查宿主的 resolv.conf 是不是 stub 软链接。

ls -l /etc/resolv.conf cat /etc/resolv.conf

如果看到 /etc/resolv.conf -> /run/systemd/resolve/stub-resolv.conf,并且内容只有 127.0.0.53,那基本坐实了回环。修复方式有三种,按推荐程度排序。

第一种,给 kubelet 指定一个真正包含上游 DNS 的解析文件,而不去动系统默认配置。Ubuntu 上 /run/systemd/resolve/resolv.conf 这个文件是 systemd-resolved 生成的非 stub 版本,里面是实际的上游 DNS。kubeadm 生成的 kubelet 服务通常已经预留了 KUBELET_EXTRA_ARGS 这个环境变量,我们只需要在 drop-in 目录里追加配置即可:

mkdir -p /etc/systemd/system/kubelet.service.d cat > /etc/systemd/system/kubelet.service.d/99-dns.conf <<'EOF' [Service] Environment="KUBELET_EXTRA_ARGS=--resolv-conf=/run/systemd/resolve/resolv.conf" EOF systemctl daemon-reload systemctl restart kubelet

第二种,直接修改宿主的 /etc/resolv.conf,把它指向一个可靠的上游 DNS。注意先备份,修改后 systemd-resolved 可能在某个时机重新生成软链接,把改动覆盖掉。这种方式适合临时验证,生产环境不建议长期用:

cp /etc/resolv.conf /etc/resolv.conf.bak echo "nameserver 8.8.8.8" > /etc/resolv.conf systemctl restart kubelet

第三种,不动 kubelet,而是改 CoreDNS 的 Corefile,让 forward 直接指向公共 DNS 或内网 DNS,不再读取 /etc/resolv.conf:

kubectl edit configmap coredns -n kube-system

找到 forward 那一行,把 forward . /etc/resolv.conf 改成你确认可达的地址,比如 forward . 8.8.8.8 8.8.4.4,保存后重启 CoreDNS Deployment。这种改法有个好处是快,但它只救了 CoreDNS,其他 Pod 从 kubelet 继承的 resolv.conf 依然是坏样板,后续新起的 Pod 解析还会有隐患。所以我的建议是:先按第一种把 kubelet 的解析源修正,再考虑 Corefile 内是否还要自定义上游。如果刚准备用 kubeadm 部署新集群,也可以直接在 kubeadm 配置文件里写死 resolvConf 字段,把该值设为 /run/systemd/resolve/resolv.conf,从源头规避这个坑。

3.3 第三步:给 CoreDNS 调一个合理的内存配额

如果是 OOMKilled,第二步的 DNS 配置就不用动了,直接把 Deployment 的资源限制调上去:

kubectl edit deployment coredns -n kube-system

在 containers 下面的 resources 里改成如下配置:

resources: requests: cpu: 100m memory: 128Mi limits: cpu: 1000m memory: 256Mi

为什么原来 170Mi 不够?CoreDNS 在查询量大、缓存命中率低的时候,内存会长得很快,而且 Go 运行时本身也有基础内存开销。把 limit 提到 256Mi,requests 保持 128Mi,既避免被 OOM,也防止 requests 定太高把节点资源占死。如果你的集群规模大,还要考虑把副本数从 2 提到 3 甚至更多,配合反亲和性让副本分散到不同节点,这样单节点故障时 DNS 不至于整体不可用。

调整完资源,记得顺手看一眼节点本身的剩余内存。如果 free -h 显示节点内存快满了,先解决节点压力,否则 CoreDNS 就算调大 limit,也可能被节点驱逐机制再次弄死。实际排障中,我遇到过调完配额还是反复重启的情况,最后发现是节点磁盘被日志占满,kubelet 一直处于 DiskPressure,跟 CoreDNS 本身一点关系都没有。

3.4 第四步:重建 CoreDNS 并验证集群解析

配置改完,统一做一次滚动重启,把旧 Pod 全部替换掉:

kubectl -n kube-system rollout restart deployment coredns kubectl -n kube-system rollout status deployment coredns

第一条命令触发滚动更新,第二条命令查看滚动状态。等 Pod 状态变成 Running 后,用标准方式验证集群 DNS 是否恢复:

kubectl run -it --rm test-dns --image=busybox:1.28 --restart=Never -- nslookup kubernetes.default.svc.cluster.local

正常的输出会显示 Server: 10.96.0.10 和 Address 10.96.0.10:53,下面给出 kubernetes.default.svc.cluster.local 对应的 ClusterIP。注意这里的 10.96.0.10 是 kubeadm 默认的 service cidr 下的 DNS 地址,如果你安装时自定义了 service-cidr,需要换成自己的 DNS Service IP。如果报 server can't find,说明 DNS 服务虽然起来了但解析链路还有问题;如果报 connection timed out,则要检查 CoreDNS 到上游 DNS 的连通性,以及 kube-proxy 的转发规则是否正常。

还可以顺手验证一下 Service 的端点是否就绪:

kubectl get endpoints coredns -n kube-system

ENDPOINTS 列必须有 Pod IP,不能是 。为空说明 Service 的 selector 和 CoreDNS 的 Pod 标签对不上,域名解析自然全挂,这种情况 CoreDNS 本身不崩,但效果和崩了一样。这个检查点很多人会漏,值得记下来。

4. 常见问题速查与避坑提醒

4.1 问题速查表

我把上面所有根因整理成一张表,排查时可以先对号入座:

日志关键字 / 现象大概率原因快速定位命令处理建议
plugin/loop: Loop detectedDNS 配置回环(systemd-resolved 或 forward 指向自身)cat /etc/resolv.conf修正 kubelet resolv-conf 或 Corefile 上游
OOMKilled / Exit Code 137内存 limit 过低或节点内存紧张kubectl describe pod调大 limit,清理节点内存压力
NetworkNotReady / failed to setup networkCNI 插件未就绪kubectl get pods -n kube-system安装或重建 flannel/calico
exec format error / /coredns: not found镜像架构或版本不匹配kubectl get deployment -o yaml更换匹配版本的镜像
connection refused 大量刷屏forward 上游不可达kubectl logs coredns确认上游 DNS 可达,换地址
FailedScheduling节点污点或资源不足kubectl describe node去除污点或调整资源请求

这张表不是万能答案,但覆盖了绝大多数场景。如果你看完表还是没头绪,就把 describe 输出的 Events 和容器 Last State 原样拿去检索,比直接搜 CrashLoopBackOff 更有效,因为真正有用的线索往往是那些具体报错片段。

4.2 实战中容易忽略的五个细节

第一,改 /etc/resolv.conf 之前务必备份,并且理解 systemd-resolved 会重新生成软链接。很多人改完看着没问题,过一会儿 kubelet 一重启,文件又被恢复成 stub 版本,折腾半天白费功夫。第二,排查顺序别搞反。先确认节点 Ready、CNI daemonset 全部 Running,再去看 CoreDNS 配置。网络层的问题不解决,CoreDNS 的日志会给你错误的方向,让你以为是自身配置问题。

第三,修改 configmap 后一定要 rollout restart。configmap 更新后 CoreDNS 不会自动热加载,只有删除 Pod 或滚动重启才让新配置生效。很多人改完 Corefile 发现没变化,就是对这步忽略了。第四,用 kubectl logs --previous 读崩溃前的日志,而不是只读当前日志。CrashLoopBackOff 状态下当前容器经常没有输出,容易误判为没日志,实际上上一次崩溃的日志里线索全在。第五,资源配额不是越大越好。CoreDNS 的 requests 如果设置得太高,可能让某些小节点直接放不下这些 Pod,反而触发新的排不上去。合理值要结合节点规格和副本数综合判断,别一刀切。

5. 写在最后:多次踩坑后的一些体会

回头看我处理过的这些集群,CoreDNS 崩溃的原因真的不复杂,大部分时候就是一个系统组件配置回环的问题,或者是资源配额给得太紧。但为什么这么小的事难倒了很多人?因为 CrashLoopBackOff 这个状态信息量太低,它不告诉你哪里错了,只告诉你反复挂了。如果不按日志和 describe 逐层下钻,很容易在错误的方向上浪费时间,甚至把节点重启了好几遍也没用。

我个人实际操作中的一个习惯是:排查这类系统组件问题时,永远先在脑子里过一遍自己的网络栈。检查节点条件、检查 CNI、检查 resolv.conf、检查资源配额,这个顺序能避开八成弯路。另一个习惯是,任何修改前都先用一条命令把现状留档,改完以后再对照验证,这也是我能快速回滚、不至于修出第二个故障的原因。尤其是 CoreDNS 这种全局组件,改动前留备份,改完做解析测试,这两步做到位,排障效率会高很多。

最后分享一个小技巧:集群稳定运行之后,给 CoreDNS 加上资源监控和告警,别等它崩了才去摸排。我在实际项目里见过太多次 DNS 挂掉导致全集群看起来都死机的事故,而根源只是一台节点磁盘被打满。把 CoreDNS 的内存、QPS、重启次数都纳入监控,很多问题在变成 CrashLoopBackOff 之前就被发现了。等你踩过几次这个坑再回头看,会发现 CoreDNS 其实是个非常稳定的组件,绝大多数故障都是环境配置的锅,而不是它本身的错。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询