K8s Pod 反复重启 CrashLoopBackOff:从 exit code 到根因的 5 步排查

我不会起名字322 · 后端 / 算法 / 数据库

📘 技术栈 | 🧩 力扣 Hot100 | 🐹 Go 项目 | 🟥 Redis | 🐬 MySQL

文章目录

  • [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 三段截取存成排查模板,下次直接对着填。

七、总结

  1. CrashLoopBackOff 是症状 不是病因,退避重启本身正常;
  2. 第一步永远不是改 yaml,是读 --previous 日志和 exit code;
  3. 137 = OOMKilled (调内存/堆参数),143 = SIGTERM(多半探针过严),这两个占了大多数;
  4. exit 0/2/126/127 基本都是镜像或命令写错,本地 docker run 一次就能复现;
  5. 依赖不可达别指望 kubelet 重启解决,要在应用层加重试退避。

留个问题给你:你们线上 Pod 重启,最常见的是哪一类 exit code?是 137(内存)还是 143(探针)?评论区说一个数字就行。

相关推荐
瑞克-桑切斯18 分钟前
ERP 企业生产管理系统部署手册
运维·docker·云原生·容器·eureka
我不会起名字3221 小时前
K8s 节点磁盘打满导致 Pod 被驱逐:4 类占用与回收顺序
运维·kubernetes·k8s·diskpressure·evicted
孔令飞1 小时前
把 Agent 搬进飞书:飞书 Agent 机器人演示
云原生·go·ai编程
瑞克-桑切斯1 小时前
docker mail商城
运维·docker·容器
java_logo2 小时前
Docker 部署 ReMe:轻松搭建 AI Agent 记忆管理平台
人工智能·docker·容器·ai agent·轩辕镜像·reme·agentscope-ai
不庸.3 小时前
WPScan 实战入门:从 Docker 靶场到弱密码爆破
运维·安全·web安全·docker·容器·安全性测试·wpscan
xbzb4 小时前
Docker 架构与部署:一条 docker run 到底走了三层
docker·容器·架构·镜像·加速·daemon
java_logo15 小时前
Docker 部署 Dify:轻松搭建 LLM 应用开发平台
运维·docker·容器
方便面不加香菜16 小时前
Docker Container -- 综合实战案例详解
运维·docker·容器