场景 :Kubernetes Pod 反复重启,状态卡在
CrashLoopBackOff,排查全流程
难度 :⭐⭐⭐ 中级(运维必会)
适用:所有 K8s 集群(ACK / TKE / 自建 / k3s)
一、先搞懂 CrashLoopBackOff 是什么
1. Pod 生命周期回顾
Pod 从创建到运行,会经历这些阶段:
Pending → Running → Succeeded / Failed
当容器退出后,K8s 会自动尝试重启它。但如果反复重启仍然失败 ,就会进入 CrashLoopBackOff 状态:
Container 启动 → 运行一段 → 退出(非0) → K8s 重启
↑ │
└──────────────────── 循环 ←────────────────┘
(默认最多重试5次,之后进入退避等待)
CrashLoopBackOff 的含义:
Crash:容器崩溃退出了Loop:反复循环发生BackOff:K8s 在加大重启间隔时间(10s → 20s → 40s → 80s → 160s → 300s 上限)
2. 常见触发原因速查
| 原因类别 | 典型情况 | 出现频率 |
|---|---|---|
| 📛 应用自身错误 | 代码 bug、未捕获异常、端口冲突 | ★★★★★ 最常见 |
| 🔗 依赖不可达 | 数据库/Redis/外部 API 连不上 | ★★★★☆ |
| 🔧 配置错误 | 环境变量写错、ConfigMap 挂载失败 | ★★★★☆ |
| 💾 资源不足 | OOMKilled、磁盘满 | ★★★☆☆ |
| 🔐 权限问题 | ServiceAccount 缺少 RBAC、挂载 secret 失败 | ★★☆☆☆ |
| 📦 镜像问题 | 镜像拉取失败、ENTRYPOINT 错误 | ★★☆☆☆ |
二、标准排查流程(6 步法)
Step 1:确认 Pod 状态
bash
# 查看 Pod 当前状态和事件
kubectl get pod <pod-name> -n <namespace> -o wide
# 查看详细信息(含最近一次的退出码)
kubectl describe pod <pod-name> -n <namespace>
重点关注输出中的:
- State:当前状态(Running / Waiting / Terminated)
- Last State :上一次退出的退出码(Exit Code) 和原因
- Restart Count:重启次数
- Events:底部的事件列表(往往直接告诉你原因)
- Containers: readinessProbe / livenessProbe 配置
Step 2:查看容器日志
bash
# 查看当前容器的日志(最后 100 行)
kubectl logs <pod-name> -n <namespace> --tail=100
# 如果已经重启过,查看上一次(previous)容器的日志!这个很关键
kubectl logs <pod-name> -n <namespace> --previous --tail=100
# 查看所有容器日志(多容器 Pod)
kubectl logs <pod-name> -n <namespace> -c <container-name> --previous
# 实时跟踪日志(适合复现问题时用)
kubectl logs -f <pod-name> -n <namespace>
⚠️ 关键技巧 :Pod CrashLoopBackOff 时,当前容器可能正在启动中还没崩溃,
所以一定要加
--previous查看上次崩溃时的日志,那才是有价值的报错信息!
Step 3:根据 Exit Code 定位原因
退出码是最直接的线索:
| Exit Code | 含义 | 排查方向 |
|---|---|---|
| 0 | 正常退出 | 可能是应用逻辑问题(执行完就退了) |
| 1 | 应用错误 | 应用代码异常、配置错误 |
| 137 (128+9) | 被 SIGKILL 杀掉 | OOMKilled(内存超限)或被系统杀 |
| 139 (128+11) | Segmentation Fault | C/C++ 程序内存访问越界 |
| 143 (128+15) | 被 SIGTERM 终止 | 收到终止信号(正常关闭) |
| 126 | 权限问题 | 执行文件没有可执行权限 |
| 127 | 命令找不到 | ENTRYPOINT/CMD 写错了路径 |
| 其他非 0 | 应用自定义退出码 | 查应用文档 |
快速检查是否 OOMKilled:
bash
kubectl describe pod <pod-name> -n <namespace> | grep -i oom
看到 OOMKilled: true 或 Reason: OOMKilled 就确定是内存不够了。
Step 4:进容器内部排查(如果能启动的话)
有时候容器能短暂运行一会儿才崩,可以趁机进去看看:
bash
# 进到容器内部
kubectl exec -it <pod-name> -n <namespace> -- /bin/sh
# 或 bash(取决于镜像基础镜像)
kubectl exec -it <pod-name> -n <namespace] -- /bin/bash
# 进去后可以做的操作:
# 1. 检查环境变量:env | grep -i db (数据库连接串对不对?)
# 2. 检查端口占用:netstat -tlnp (有没有端口冲突?)
# 3. 检查配置文件:cat /app/config.yaml (配置加载了没?)
# 4. 手动执行入口命令看报错:./entrypoint.sh (手动跑一遍)
Step 5:检查资源配置和限制
bash
# 查看 Pod 的资源请求和限制
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.spec.containers[*].resources}'
# 查看节点资源使用情况(确认节点本身够不够)
kubectl top nodes
kubectl top pods -n <namespace>
常见坑:
limits.memory设太小,应用一跑就 OOM → 调大 limits 或优化应用内存requests.cpu设太大,节点调度不了 Pending → 合理设置 requests- 没设 limit,单个 Pod 吃光节点资源导致其他 Pod 被 evict → 务必设 limit
Step 6:检查健康探针配置
bash
# 查看探针配置
kubectl get pod <pod-name> -n <namespace> -o yaml | grep -A 20 probe
经典踩坑场景:
yaml
# ❌ 坑 1:livenessProbe 太严格,应用启动慢就被 kill
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 5 # 应用要 30s 才能起来,这里只等 5s
periodSeconds: 10 # 结果每次检测都失败 → kill → 重启 → 又失败...
# ✅ 改法:给足启动时间
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 30 # 等应用完全启动
failureThreshold: 3 # 连续失败 3 次再杀
yaml
# ❌ 坑 2:startupProbe 没配 + livenessProbe 太早
# 新版本 Kubernetes(1.18+)推荐用 startupProbe 解决慢启动问题
startupProbe: # ✅ 用 startupProbe 管理启动期
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 30 # 允许最长 300s(30×10)的启动时间
三、典型案例 & 解决方案
案例 1:依赖服务不可达
现象: 日志里全是 connection refused / timeout / DNS 错误
排查清单:
bash
# 1. 检查 DNS 解析是否正常
kubectl exec -it my-pod -- nslookup mysql-service.default.svc.cluster.local
# 2. 检查 Service 是否存在
kubectl get svc -n <namespace>
# 3. 检查 Endpoint 是否有后端 Pod
kubectl endpoints -n <namespace>
# 4. 从 Pod 内部测试网络连通性
kubectl exec -it my-pod -- wget -qO- --timeout=5 http://mysql-service:3306
# 5. 检查依赖 Pod 本身是不是也 CrashLoopBackOff
kubectl get pods -n <namespace> | grep mysql
常见解法:
- Service 名字写错 → 修正
service_name - Namespace 不一致 → 加
.<namespace>后缀 - 依赖 Pod 还没 Ready → 加
initContainer等依赖就绪 - 网络策略(NetworkPolicy)阻断了流量 → 检查并放行规则
案例 2:OOMKilled(Exit Code 137)
现象:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
排查与解决:
bash
# 1. 确认 OOM 原因
kubectl describe pod my-pod | grep -A 5 "Last State"
# 2. 查看实际内存使用
kubectl top pod my-pod
# 3. 解决方案三选一:
# 方案 A:调大内存 limit(治标)
kubectl patch deployment my-app -p '{"spec":{"template":{"spec":{"containers":[{"name":"my-app","resources":{"limits":{"memory":"1Gi"}}}]}}}}'
# 方案 B:优化应用内存使用(治本)
# - Java: 调整 JVM 堆大小 -Xmx512m
# - Python: 检查是否有内存泄漏
# - Go: 检查 goroutine 泄漏
# 方案 C:增加 HPA 弹性伸缩(长期方案)
kubectl autoscale deployment my-app --cpu-percent=70 --min=2 --max=10
案例 3:配置文件缺失或格式错误
现象: 日志显示 config file not found / invalid YAML / key not found
排查:
bash
# 1. 确认 ConfigMap / Secret 存在
kubectl get configmap my-config -o yaml
# 2. 确认挂载到了正确路径
kubectl describe pod my-pod | grep -A 10 "Mounts"
# 3. 进容器看实际的挂载内容
kubectl exec -it my-pod -- cat /etc/config/app.yaml
# 典型坑:
# - ConfigMap 里用了 {{}} 模板语法没被渲染
# - base64 编码的 secret 内容不对
# - subPath 挂载时 key 名写错
案例 4:镜像拉取失败
现象:
Events:
Warning Failed 12s kubelet Failed to pull image "xxx": rpc error: code = Unknown desc = Error response from daemon: ...
解决:
bash
# 私有仓库需要 imagePullSecrets
kubectl create secret docker-registry liux-secret \
--docker-server=<registry-url> \
--docker-username=<username> \
--docker-password=<password>
# 然后在 Deployment 里引用
imagePullSecrets:
- name: liux-secret
案例 5:Java 应用经典 --- 堆内存溢出
这是运维最常遇到的情况之一!
# 日志类似:
java.lang.OutOfMemoryError: Java heap space
# 或者
Exit Code: 137 (OOMKilled)
根本原因: Java 默认使用容器全部可用内存作为堆上限,但 JVM 不知道 cgroup 限制,申请超过 limit 就被 K8s kill。
正确做法:
yaml
env:
# 让 JVM 识别容器内存限制
- name: JAVA_OPTS
value: "-XX:+UseContainerSupport -XX:MaxRAMPercentage=75.0"
# 或者固定堆大小(不推荐,不够灵活)
# - name: JAVA_OPTS
# value: "-Xms512m -Xmx768m"
resources:
requests:
memory: "2Gi"
limits:
memory: "2Gi"
四、快速排障命令速查表
bash
# ════════════════════════════════════════
# 🚨 一键诊断脚本(复制即用)
# ════════════════════════════════════════
POD_NAME="你的pod名字"
NS="你的namespace"
echo "=== 1. Pod 状态 ==="
kubectl get pod $POD_NAME -n $NS -o wide
echo -e "\n=== 2. 最近事件 ==="
kubectl describe pod $POD_NAME -n $NS | tail -20
echo -e "\n=== 3. 上次崩溃日志 ==="
kubectl logs $POD_NAME -n $NS --previous --tail=50
echo -e "\n=== 4. 当前日志 ==="
kubectl logs $POD_NAME -n $NS --tail=30
echo -e "\n=== 5. 资源使用 ==="
kubectl top pod $POD_NAME -n $NS 2>/dev/null || echo "(metrics-server 未安装)"
echo -e "\n=== 6. 所在节点 ==="
kubectl get pod $POD_NAME -n $NS -o jsonpath='{.spec.nodeName}'
五、预防措施(避免再次出现)
1. Pod 最佳实践
| 实践项 | 说明 |
|---|---|
| ✅ 设好 resources | 必须同时设 requests 和 limits |
| ✅ 配置 startupProbe | 慢启动应用必须配,避免被 livenessProbe 误杀 |
| ✅ livenessProbe 别太严 | failureThreshold ≥ 3,给点余量 |
| ✅ readinessProbe 分离 | 就绪探针和存活探针职责分开 |
| ✅ graceful shutdown | 处理 SIGTERM,做好优雅关闭 |
| ✅ 日志规范输出到 stdout | 方便 kubectl logs 直接查看 |
| ✅ 设置 restartPolicy | 一般用 OnFailure(非 Always)避免死循环 |
2. 架构层面防护
yaml
# 使用 PodDisruptionBudget 保护关键应用
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: my-app
# 使用 HPA 自动扩缩容
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: app-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: my-app
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
3. 监控告警(别等人报警再查)
yaml
# Prometheus 告警规则示例
groups:
- name: pod_crashloop
rules:
- alert: PodCrashLooping
expr: kube_pod_container_status_restarts_total{container!="POD"} > 5
for: 10m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 反复重启"
description: "容器 {{ $labels.container }} 已重启 {{ $value }} 次,请检查!"
六、总结:排查思路图
Pod CrashLoopBackOff
│
▼
┌─────────────┐
│ kubectl │ ──→ 看状态、Events、Exit Code
│ describe │
└──────┬──────┘
│
▼
┌─────────────┐
│ kubectl │ ──→ 加 --previous!看崩溃日志
│ logs │
└──────┬──────┘
│
┌────┴────┬─────────┬──────────┐
▼ ▼ ▼ ▼
Exit Code=1 Code=137 Code=127 其他
应用错误 OOMKilled 命令找不到 看具体日志
│ │ │
▼ ▼ ▼
查日志+进容器 加内存/优化 检查CMD/
检查依赖 JVM参数 ENTRYPOINT
核心口诀:先 describe 看 Events,再 logs 加 previous,按 Exit Code 对症下药。
作者:小刘运维 · K8s 排坑系列第 02 期
更多排坑实战,关注「小刘运维」持续更新 🐾