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挂临时容器。
相关速查
- kubectl 排查速查:Pod 状态、容器调试、服务连通命令
- Kubernetes 命令速查:通用集群管理
- Docker 清理速查:镜像与构建缓存占用排查