我不会起名字322 · 后端 / 算法 / 数据库
文章目录
- [K8s Pod 反复重启 CrashLoopBackOff:从 exit code 到根因的 5 步排查](#K8s Pod 反复重启 CrashLoopBackOff:从 exit code 到根因的 5 步排查)
-
- [一、先搞清 CrashLoopBackOff 到底是什么](#一、先搞清 CrashLoopBackOff 到底是什么)
- [二、第 1 步:读三样东西,别急着改 yaml](#二、第 1 步:读三样东西,别急着改 yaml)
- [三、第 2 步:把 exit code 对上号](#三、第 2 步:把 exit code 对上号)
- [四、第 3 步:按 137 / 143 分两条路](#四、第 3 步:按 137 / 143 分两条路)
- [五、第 4 步:对齐另外三类高频根因](#五、第 4 步:对齐另外三类高频根因)
- [六、第 5 步:一个"先看哪个"的决策顺序](#六、第 5 步:一个"先看哪个"的决策顺序)
- 七、总结
K8s Pod 反复重启 CrashLoopBackOff:从 exit code 到根因的 5 步排查
周三晚上发版,服务部署上去 37 秒重启一次。kubectl get pod 一排 CrashLoopBackOff,我去看容器日志------只有一行,还是上一轮的。那一刻我的第一反应是"日志呢",第二反应是"我该看哪个日志"。
这篇把这次排查完整复盘一遍。核心结论先给:CrashLoopBackOff 不是一种故障,是五种故障的表现 。你不需要重新部署、也不需要瞎改探针,只要按 exit code → 根因 的顺序走 5 步,八成能定位。
一、先搞清 CrashLoopBackOff 到底是什么
它字面意思是"崩溃循环 + 退避重启"。拆开是两件事:
- CrashLoop :容器启动后进程退出了,kubelet 按
restartPolicy重启,又退出,又重启。 - BackOff:kubelet 不会无限快地重启,它按指数退避拉长间隔(10s → 20s → 40s → ...→ 300s 封顶)。
所以你在 kubectl get pod 里看到的 RESTARTS 计数在涨、间隔越来越长,是正常行为,不是集群坏了。真正的问题是"为什么进程会退出"。
关键判据:Pod 是 Running 过又挂,还是一直没到 Running? 前者看应用日志,后者多半是镜像/配置/权限问题。这一步决定了后面看哪里。
二、第 1 步:读三样东西,别急着改 yaml
绝大多数人第一反应是 kubectl logs <pod>,然后什么都没有。因为当前容器已经挂了,日志随着容器一起没了。正确顺序是三样:
bash
# ① 看事件和容器状态:退出码、重启次数、上次结束原因
kubectl describe pod <pod-name> -n <ns> | sed -n '/Containers:/,/Conditions:/p'
# ② 看上一次挂掉那个容器的日志(关键!)
kubectl logs <pod-name> -n <ns> --previous
# ③ 看该 Pod 从创建到现在的所有事件(镜像拉取、探针失败都会在这)
kubectl describe pod <pod-name> -n <ns> | sed -n '/Events:/,$p'
--previous(简写 -p)是这一步的灵魂。它读的是上一个已经终止的容器实例的日志,也就是"死之前说了什么"。
三、第 2 步:把 exit code 对上号
kubectl describe pod 的 Last State 里会写 Exit Code。这个数字是分诊台,先看它再谈别的。
| Exit Code | 含义 | 常见根因 | 先看哪里 |
|---|---|---|---|
| 0 | 进程正常退出 | 主进程跑完就结束了(把一次性任务当常驻服务)、entrypoint 只有 echo | 镜像的 CMD/ENTRYPOINT |
| 1 | 通用错误 | 应用启动时校验失败:配置缺失、DB 连不上、端口被占 | --previous 的应用日志 |
| 2 | shell/参数错误 | 命令拼错、脚本参数不对、sh -c 里的语法错 |
容器 command/args |
| 126 | 命令不可执行 | 二进制没有 +x、架构不匹配(arm 镜像跑在 amd 节点) | 镜像构建 |
| 127 | 找不到命令 | entrypoint 路径写错、PATH 不对 | Dockerfile ENTRYPOINT |
| 137 | 被 SIGKILL(128+9) | OOMKilled 或被 kubectl delete --force |
describe 里的 OOMKilled |
| 143 | 被 SIGTERM(128+15) | 探针失败被杀、被优雅下线、滚动更新 | liveness 探针配置 |
记住这个公式:137 = 128 + 9(SIGKILL) ,143 = 128 + 15(SIGTERM)。超过 128 的退出码都是"被信号杀",不是应用自己退出的。
四、第 3 步:按 137 / 143 分两条路
这一节是本篇的核心,也是我踩坑最多的地方。

路径 A(exit 137 / OOMKilled) :容器内存超了 limit 被内核杀。注意一个反直觉点------Go 和 JVM 应用看到的内存 ≠ 容器 limit。Go 1.19 前默认不认 cgroup limit,JVM 也可能把堆设成物理机的 1/4。所以常出现"limit 512Mi,堆配了 600Mi"。修法:
yaml
resources:
requests: { memory: "256Mi" }
limits: { memory: "512Mi" }
env:
- name: GOMEMLIMIT # Go 1.19+,让 runtime 认容器
value: "400MiB"
- name: JAVA_OPTS
value: "-XX:MaxRAMPercentage=70"
路径 B(exit 143 / SIGTERM) :进程是被"礼貌地"杀掉的,最常见元凶是 liveness 探针 。如果 initialDelaySeconds 太短,而应用启动要 30 秒,liveness 在前 10 秒就开始打,连续失败 failureThreshold 次后 kubelet 就重启它------于是永远起不来,形成循环。修法是给启动留时间 (用 startupProbe,或把 initialDelaySeconds 加大)。
五、第 4 步:对齐另外三类高频根因
上面两条路覆盖了 60% 的现场,剩下三类按这个顺序排除:
① 镜像 entrypoint 写错(exit 0 / 2 / 126 / 127)
exit 0 最迷惑------容器"成功退出了"。典型是一个 Python 脚本用了 CMD ["python"] 没带文件名,解释器读到 EOF 就正常退出。用 docker run --rm <image> <entrypoint> 在本地复现,比在集群里瞎猜快得多。
② 配置挂载失败(表现为 exit 1 且日志报文件不存在)
挂 ConfigMap/Secret 时路径写错、key 名写错,容器起来找不到配置就退。查 kubectl describe pod 的 Volume 段,确认挂载点和你代码里读的路径一致。
③ 依赖不可达(exit 1 且日志停在"connecting to db...")
启动时连不上 DB/Redis/MQ 就直接退。这里有个坑:DNS 还没就绪。如果依赖的是同集群 Service,短时间内的 DNS 抖动就会让应用放弃。修法是在应用层加重试退避,而不是靠 kubelet 无限重启。
六、第 5 步:一个"先看哪个"的决策顺序
把上面的东西收成一张顺序表,出问题照着走:
| 顺序 | 动作 | 命令 | 命中则 |
|---|---|---|---|
| 1 | 看退出码 + 是否 OOMKilled | kubectl describe pod |
137→调内存;143→调探针 |
| 2 | 看上一次的日志 | kubectl logs --previous |
有报错→按报错修 |
| 3 | 看事件流 | kubectl describe pod Events 段 |
镜像拉取失败/探针失败一目了然 |
| 4 | 本地复现 entrypoint | docker run --rm <image> |
exit 0/126/127→改 Dockerfile |
| 5 | 检查配置挂载与依赖 | kubectl exec 进容器 ls /etc/config |
文件缺失/连不上→修挂载或加重试 |
一个可带走物 :把 kubectl describe pod 的输出按 Last State / Exit Code / Events 三段截取存成排查模板,下次直接对着填。
七、总结
- CrashLoopBackOff 是症状 不是病因,退避重启本身正常;
- 第一步永远不是改 yaml,是读
--previous日志和 exit code; - 137 = OOMKilled (调内存/堆参数),143 = SIGTERM(多半探针过严),这两个占了大多数;
- exit 0/2/126/127 基本都是镜像或命令写错,本地
docker run一次就能复现; - 依赖不可达别指望 kubelet 重启解决,要在应用层加重试退避。
留个问题给你:你们线上 Pod 重启,最常见的是哪一类 exit code?是 137(内存)还是 143(探针)?评论区说一个数字就行。