📚 建议收藏 | 遇到 Kubernetes Pod 反复重启,看这一篇就够了
凌晨两点,手机狂震。告警群消息一条接一条:"prod-api-gateway 挂了""服务不可用""接口超时"。你迷迷糊糊打开电脑,kubectl get pods 一看:
NAMESPACE NAME READY STATUS RESTARTS AGE
prod api-gateway-7d8f9f6f4-2abcx 0/1 CrashLoopBackOff 43 15m
Restarts 43 次,CrashLoopBackOff------这场景,Kubernetes 运维都懂。
很多人的第一反应是:删 Pod、rollout restart、甚至重启节点。千万别! 你连根因都没看,Pod 被删了,日志也没了。
CrashLoopBackOff 从来不是根因,它只是 Kubernetes 在告诉你:你的容器一直在崩,我已经重启很多次了。真正的问题,一定在容器内部。
这篇文章按生产环境真实排障流程,把 CrashLoopBackOff 的定位思路、经典报错、排查命令和修复方法讲透。适用 Kubernetes 1.28~1.36(当前受支持的主流版本,最新补丁为 v1.36.3,发布于 2026 年 7 月 22 日)。
速查急救包
拿到一个 CrashLoopBackOff 的 Pod,按顺序执行这 7 条命令:
# 1. 确认 Pod 状态和重启次数
kubectl get pod <pod-name> -n <namespace>
# 2. 看事件和 Last State(最重要的一步)
kubectl describe pod <pod-name> -n <namespace>
# 3. 看崩溃前的日志(⚠️ 一定要加 --previous)
kubectl logs <pod-name> -n <namespace> --previous --tail=50
# 4. 看当前日志(容器还在跑的时候)
kubectl logs <pod-name> -n <namespace> --tail=50
# 5. 快速看退出码
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.status.containerStatuses[*].lastState.terminated.exitCode}'
# 6. 多容器 Pod 指定容器
kubectl logs <pod-name> -c <container-name> -n <namespace> --previous
# 7. 进入容器调试(如果还能启动)
kubectl exec -it <pod-name> -n <namespace> -- /bin/sh
核心原则 :describe 看事件和退出码,logs --previous 看崩溃前的日志。两个命令组合,80% 的问题都能定位。
kubectl describe 输出的 Events 部分是最先应该看的内容------它直接告诉你发生了什么(OOMKilled、Liveness probe failed、Mount 失败等),比看退出码更直观。
这 7 条命令是按"先看状态、再看事件、最后看日志"的顺序排列的。下面我们逐条拆解,告诉你每一条命令能挖出什么信息。
CrashLoopBackOff 到底是什么?
Pod 的生命周期是这样的:Pending → Running → Succeeded 或 Failed。CrashLoopBackOff 不是一个独立的阶段,而是容器反复崩溃后 Kubelet 进入的一种退避状态。
用大白话翻译:
- Crash:容器退出码非 0,或者被 kill 了
- Loop:这事反复发生
- BackOff:Kubelet 每次重启之间等待的时间越来越长
退避机制是怎么工作的 :容器崩溃后,Kubelet 会依据 Pod 的 restartPolicy 决定是否重启。默认情况下 restartPolicy 是 Always,任何退出------哪怕是正常退出------都会触发重启。而 Kubelet 的指数退避策略是这样的:10s → 20s → 40s → 80s → 160s → 封顶 300s(5 分钟) 。每次容器崩溃,等待时间翻倍,直到 5 分钟上限。只要容器成功运行一段足够长的时间(Kubelet 内部默认约为 10 分钟内无任何问题),这个计数器才会重置。
关键认知 :CrashLoopBackOff 不等于 ImagePullBackOff。后者是镜像拉不下来,前者是镜像拉下来了,但容器跑不起来 。问题出在运行时,不是拉取阶段。
📌 本文聚焦主容器 的 CrashLoopBackOff。如果你的 Pod 状态是 Init:CrashLoopBackOff,问题出在 Init Container。排查思路类似,但命令不同------需要用 kubectl logs <pod-name> -c <init-container-name> 查看 Init Container 的日志。
退出码速查:看一眼就知道大致方向
kubectl describe pod 里的 Last State 字段会显示 Exit Code。这个数字能帮你快速缩小范围:
|---------|---------|-----------------------------|-------------------------------------------------------------------------|
| 退出码 | 含义 | 常见原因 | 排查方向 |
| 0 | 正常退出 | 容器任务完成但没设置常驻进程 | 检查 ENTRYPOINT/CMD,是不是执行完就退了 |
| 1 | 通用错误 | 应用主动异常退出 | 看应用日志,代码 bug、配置错误、依赖缺失 |
| 126 | 命令不可执行 | 脚本没执行权限 | chmod +x 或检查 shebang |
| 127 | 命令不存在 | ENTRYPOINT/CMD 写错了 | 检查镜像里的路径和命令名 |
| 137 | SIGKILL | OOM 被 kill,或被探针强制终止(容器重启场景) | 检查内存 limit,看是否 OOMKilled;检查探针配置 |
| 139 | SIGSEGV | 段错误 | 内存越界,CGO 程序常见 |
| 143 | SIGTERM | 进程收到终止信号后主动优雅退出 | 检查 terminationGracePeriodSeconds,或是否有外界发起了终止(如 kubectl delete、滚动更新) |
退出码 137 要特别留意 :公式是 137 = 128 + 9,9 是 SIGKILL 的信号值。如果 describe 里 Reason 字段显示 OOMKilled,说明容器实际内存使用超过了 limit,被 cgroup 干掉了。如果 Reason 是 Error 且事件里有 Liveness probe failed,说明是存活探针失败后 Kubelet 主动 kill 了容器。
关于退出码 143:它代表进程收到 SIGTERM 后主动优雅退出 。探针失败时 Kubelet 是直接杀进程(SIGKILL),不会给容器发 SIGTERM 让它优雅退出。所以看到 143,优先考虑是不是有人执行了 kubectl delete、滚动更新、或者进程自己收到了终止信号。
💡 一个小技巧:用 JSONPath 直接取退出码,不用翻 describe 输出:
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.status.containerStatuses[*].lastState.terminated.exitCode}'
五大根因分类排查
1. 应用自身崩溃(退出码 1/139)
现象 :kubectl logs --previous 看到应用报错日志,比如 panic、segmentation fault、connection refused、config file not found。
原因:代码 bug、配置错误、依赖服务没准备好、端口被占用。
我踩过的坑 :有次 Spring Boot 应用启动时要连数据库做 schema 迁移,但数据库还没就绪,应用直接退出。日志里清清楚楚写着 Communications link failure,但因为是英文日志被忽略了,折腾了半小时才发现。看日志要仔细。
解决方案:
- 修复代码 bug
- 检查环境变量和配置文件是否完整
- 确保依赖服务(DB、Redis、MQ)先启动
- 如果是启动时依赖外部服务,考虑用
initContainer做健康检查等待
2. OOMKilled(退出码 137)
现象 :kubectl describe pod 里 Last State 的 Reason 是 OOMKilled。
原因 :容器内存使用超过 limits.memory,被 cgroup 强制杀死。
解决方案:
-
调高
limits.memory -
排查应用是否存在内存泄漏
-
检查 JVM 等运行时是否正确识别容器内存限制(Java 要加
-XX:MaxRAMPercentage)resources:
limits:
memory: "2Gi" # 原来 1Gi,调高
requests:
memory: "1Gi"
3. 探针配置不当(退出码 137,OOM 除外)
现象 :kubectl describe pod 的事件里有 Liveness probe failed,容器被重启。退出码通常是 137(SIGKILL),因为 Kubelet 是直接杀掉容器的。
原因:这是生产环境最常见也最隐蔽的坑。livenessProbe 启动太早,应用还没初始化完成就被判定为不健康,Kubelet 直接杀掉重启。有些团队从测试环境直接复制探针配置到生产,但生产环境启动更慢(要加载更多配置、连更多服务),测试环境能跑的参数到生产就挂了。
注意:探针失败触发的重启是直接 SIGKILL(退出码 137),和 Pod 被正常删除时先 SIGTERM 再 SIGKILL 的优雅终止流程是两回事。
解决方案:
方案一:用 startupProbe 保护慢启动应用(推荐)
如果配置了 startupProbe,liveness 和 readiness 探针会等到 startupProbe 成功后才会开始执行。这给了慢启动应用足够的初始化时间。
startupProbe: # 启动探针,专门解决慢启动问题
httpGet:
path: /healthz
port: 8080
failureThreshold: 30 # 允许失败 30 次
periodSeconds: 5 # 每 5 秒探测一次,总共给 150 秒启动时间
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
方案二:调大 initialDelaySeconds
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 60 # 给足启动时间
failureThreshold: 5
⚠️ 探针配置的致命误区 :livenessProbe 和 readinessProbe 用同一个接口、同一个参数。liveness 失败会重启容器 ,readiness 失败只是切流量 。liveness 探针应该检查应用是否处于不可恢复的故障状态(如死锁),而不应该检查外部依赖(如数据库) 。外部依赖的健康检查应该交给 readiness 探针。
4. 配置/密钥/依赖缺失
现象 :应用日志里出现 config file not found、environment variable is empty、connection failed。
原因:ConfigMap 或 Secret 不存在、键名写错、挂载路径不对。
解决方案:
- 检查 ConfigMap/Secret 是否存在:
kubectl get cm,secret -n <namespace> - 检查 Deployment 里的
envFrom和volumeMounts配置 - 确认挂载路径没有覆盖掉镜像里原有的文件
5. ENTRYPOINT/CMD 错误(退出码 126/127)
现象 :容器启动后立即退出,日志可能为空或报 exec: "xxx": executable file not found in $PATH。
原因:Dockerfile 里的 ENTRYPOINT 或 CMD 写错了路径、命令名拼错了、脚本没有执行权限。
解决方案:
- 本地用
docker run --entrypoint /bin/sh -it <image>进入镜像手动执行命令验证 - 检查脚本是否有
#!/bin/bash和chmod +x - 注意基础镜像的架构(x86 vs ARM)
一个完整排查案例
假设你遇到这个 Pod:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
user-service-6d7c6c7f8d-x2lmf 0/1 CrashLoopBackOff 43 15m
Step 1:看 describe
$ kubectl describe pod user-service-6d7c6c7f8d-x2lmf -n prod
...
Last State: Terminated
Exit Code: 137
Reason: OOMKilled
...
Events:
Warning BackOff Back-off restarting failed container
退出码 137 + OOMKilled,指向内存问题。
Step 2:看日志
$ kubectl logs user-service-6d7c6c7f8d-x2lmf -n prod --previous --tail=50
java.lang.OutOfMemoryError: Java heap space
...
确认是 JVM 内存不够。
Step 3:修复 ------调高 memory limit,或者在 JVM 参数里加 -XX:MaxRAMPercentage=80.0。
Step 4:验证 ------重新部署,Pod 状态变成 Running,重启次数不再增长。
验证方法
修复后,用这几个命令确认问题已解决:
# 1. Pod 状态变为 Running
kubectl get pod <pod-name> -n <namespace>
# 2. 重启次数不再增长(观察 2-3 分钟)
kubectl get pod <pod-name> -n <namespace> -w
# 3. 日志正常,没有报错
kubectl logs <pod-name> -n <namespace> --tail=20
# 4. 应用功能验证(curl 或业务测试)
kubectl exec -it <pod-name> -n <namespace> -- curl localhost:8080/health
⚠️ 致命误区:别一上来就删 Pod
CrashLoopBackOff 排查最大的坑就是不诊断直接删 Pod。
你想想:Pod 被删了,kubectl logs --previous 还能看到崩溃前的日志吗?不能 。kubectl describe 里的 Last State 和 Events 呢?也没了。
正确的顺序:
1. kubectl get pod → 确认状态
2. kubectl describe pod → 看事件和退出码
3. kubectl logs --previous → 看崩溃前的日志
4. 诊断 → 修复 → 验证
💡 极少人知道的小技巧 :如果 Pod 重启太快根本来不及 exec 进去,可以用 kubectl debug 启动一个调试容器。Ephemeral Containers 在 v1.25+ 已正式 GA(稳定) ,无需开启任何 Feature Gate,开箱即用。你的集群是 1.28+,可以直接使用:
这样即使主容器在崩溃循环,你也能在同一个网络和文件系统命名空间里排查。
kubectl debug -it <pod-name> -n <namespace> --image=busybox -- sh
结尾
总结几个核心要点:
- CrashLoopBackOff 是状态,不是根因------根因在容器内部
- 排查三件套 :
describe→logs --previous→ 退出码 - 退出码是路标:137 看内存和探针,143 看优雅终止,1 看应用日志
- 探针是双刃剑 ------配置不当比不配置还可怕。慢启动应用用
startupProbe,它会禁用 liveness/readiness 直到启动成功 - 别删 Pod------保留现场比什么都重要
你遇到过最诡异的 CrashLoopBackOff 是什么原因?欢迎在评论区分享,说不定你的经历能帮到其他正在凌晨三点查日志的兄弟。
觉得有用的话,转发给团队里负责 K8s 的同事,少踩一个坑就是少一个凌晨告警。