K8s 排坑 02:Pod 一直 CrashLoopBackOff 怎么办?

场景 :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: trueReason: 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 期
更多排坑实战,关注「小刘运维」持续更新 🐾

相关推荐
shinelord明8 小时前
【云计算】k8sclient API 镜像操作 Java 类封装
java·kubernetes·云计算
liux35288 小时前
K8s 排坑 01:Pod 一直 Pending 怎么办?
云原生·容器·kubernetes
认真的薛薛8 小时前
Terraform:AWS VPC
云原生·aws·terraform
运维老郭9 小时前
Kubernetes Pod 从创建到运行全流程拆解:5 个阶段 + 排错实录
运维·云原生·kubernetes
jiayong239 小时前
微服务无感迁移上云方案深度解析
微服务·云原生·架构
万里侯9 小时前
Kubernetes多租户管理:实现资源隔离与安全的完整指南
微服务·容器·k8s
JiaWen技术圈9 小时前
使用 Terraform Grafana Provider 实现 Grafana 全栈 IaC 一体化管理的完整方案
云原生·grafana·terraform
爱吃龙利鱼10 小时前
ubuntu2026.04部署k8s1.36版本的傻瓜式教程(注:运行时为docker,网络插件为calico)
运维·网络·笔记·docker·云原生·kubernetes
万里侯10 小时前
云原生数据库管理:在Kubernetes上运行数据库的完整指南
微服务·容器·k8s