CrashLoopBackOff 不是一种错误,而是一种状态:容器启动后异常退出,kubelet 按退避策略(10s、20s、40s…最长 5 分钟)反复重启。所以看到它不要慌,先拿到容器退出的 Exit Code,原因基本就定了一半。

第一步:describe 看 Events 和 Last State

kubectl describe pod <pod> -n <ns>

重点看结尾的 Events 和容器状态里的 Last State:

Last State:     Terminated
  Reason:       OOMKilled
  Exit Code:    137

Events 里如果出现 Back-off restarting failed container,说明确实是崩溃循环;如果是 FailedScheduling,那压根没调度成功,属于另一类问题,先查资源与亲和性。

第二步:看懂 Exit Code

Exit Code 含义 排查方向
0 正常退出 容器主进程不是常驻服务,检查 Dockerfile 的 CMD/ENTRYPOINT
1 应用自己报错退出 看日志:配置缺失、启动时依赖不可达
2 用法错误 / shell 报错 启动脚本有语法或参数问题
137 被 SIGKILL 杀死(常见是 OOMKilled) resources.limits.memory 太小,或内存泄漏
143 被 SIGTERM 优雅终止 存活探针太激进,或被人为删除

137 是最常见的:容器内存超过 limits.memory 被内核 OOM Killer 干掉,k8s 记录为 OOMKilled,退出码 137。注意区分「容器真实使用量超限」和「探针触发重启」——后者通常是 143。

第三步:看崩溃前的日志

容器正在 CrashLoop 时,当前实例可能还没起来就退了,logs 打不出内容,必须加 --previous:

kubectl logs <pod> -n <ns> --previous --tail=200

这一步能看到真正的堆栈。常见三类:

  • 连接被拒:Connection refused 指向的 DB / Redis / 消息队列没就绪 —— 用 initContainer 或重试退避解决,别硬上 restartPolicy: Always 掩盖问题。
  • 配置缺失:ConfigMap / Secret 没挂上,或 key 名拼错,报 no such file or directory / missing env。
  • 端口占用:容器内主进程和探针脚本争抢同一端口。

第四步:怀疑探针就把探针摘掉验证

存活探针配得太激进,应用还在初始化就被判定失败,形成「启动 → 被杀 → 再启动」的死循环。这类问题日志里看不到应用错误,只有 Liveness probe failed 事件。

# 临时放宽验证:把初始延迟拉长
livenessProbe:
  initialDelaySeconds: 60
  periodSeconds: 10
  failureThreshold: 3

如果放宽后能稳定运行,说明之前是探针杀死了还在启动的应用,正确做法是加 startupProbe,而不是无限拉大 initialDelaySeconds。

常见坑

  • liveness 和 readiness 混用:liveness 失败会重启容器,readiness 失败只摘流量。依赖外部服务才可用的场景,应该用 readiness,用 liveness 会导致依赖抖动时全量重启。
  • limits.memory 设得比 requests 还小:调度按 requests,运行按 limits,配错会稳定 OOMKilled。
  • JVM 容器内不识别 limit:老版本 JDK 用容器内存上限前需要 -XX:MaxRAMPercentage,否则堆按宿主机内存算,必然超限被杀。
  • 镜像无 shell 进不去:exec 报 unable to upgrade connection,改用 kubectl debug 挂临时容器。

相关速查