适用场景 & 前置条件
| 项目 |
要求 |
| 适用场景 |
Kubernetes 生产集群 Pod 频繁重启、CrashLoopBackOff、OOMKilled 等故障排查 |
| 集群版本 |
Kubernetes 1.24+ (推荐 1.28+) |
| 容器运行时 |
containerd 1.6+ / Docker 20.10+ / CRI-O 1.24+ |
| 节点 OS |
RHEL 8.0+ / CentOS Stream 8+ / Ubuntu 20.04+ |
| 内核版本 |
Linux Kernel 5.4+ (推荐 5.15+) |
| kubectl 权限 |
集群管理员权限或 namespace 查看权限(get/describe pods, logs, events) |
| 监控工具 |
Prometheus + Grafana(可选,用于历史数据分析) |
| 技能要求 |
熟悉 kubectl 命令、容器日志分析、YAML 配置、Linux 资源管理 |
反模式警告(何时不适用)
⚠️ 以下场景不推荐使用本方案:
- 应用正常重启:StatefulSet 滚动更新、Deployment 版本发布等计划内重启,无需排查
- Init 容器重启:Init 容器失败导致主容器未启动,需使用
kubectl logs <pod> -c <init-container> 专门分析
- Job 任务重复执行:Job 的
restartPolicy: OnFailure 导致重试,属正常行为
- Daemonset 节点调度:节点上下线导致 DaemonSet Pod 重启,非故障
- 云环境竞价实例:AWS Spot/阿里云抢占式实例节点被回收,应使用节点亲和性或迁移至稳定节点
替代方案对比:
| 场景 |
推荐方案 |
理由 |
| 版本发布导致重启 |
检查 Deployment history (kubectl rollout history) |
区分计划内/外重启 |
| Init 容器故障 |
专项排查 Init 容器日志与配置 |
Init 容器失败阻断主容器启动 |
| 资源长期不足 |
集群自动扩容(Cluster Autoscaler)+ VPA |
根本解决资源瓶颈 |
| 应用内存泄漏 |
应用性能分析(jstack/pprof/py-spy) |
修复应用层问题,非 K8s 层 |
环境与版本矩阵
| 组件 |
版本要求 |
RHEL/CentOS |
Ubuntu/Debian |
测试状态 |
| Kubernetes |
1.24 - 1.29 |
1.28.5(kubeadm) |
1.28.5(kubeadm) |
[已实测] |
| containerd |
1.6+ |
1.7.11 |
1.7.11 |
[已实测] |
| Docker(已弃用) |
20.10+ |
24.0.7(兼容性测试) |
24.0.7(兼容性测试) |
[已实测] |
| kubectl |
与集群±1版本 |
1.28.5 |
1.28.5 |
[已实测] |
| 节点 OS |
- |
RHEL 8.7 / Rocky Linux 8.9 |
Ubuntu 22.04 LTS |
[已实测] |
| 内核版本 |
5.4+ |
4.18.0-477(RHEL)/ 5.14(Rocky) |
5.15.0-91 |
[已实测] |
| 最小节点规格 |
- |
2C4G / 20GB |
2C4G / 20GB |
- |
| 推荐节点规格 |
- |
4C8G / 50GB |
4C8G / 50GB |
- |
版本差异说明:
- Kubernetes 1.24 之前使用 Docker 作为运行时,1.24+ 强制使用 containerd/CRI-O
- containerd 1.7 支持 CDI(Container Device Interface),改进 GPU 设备管理
- 内核 5.15+ 对 cgroup v2 支持更完善,OOM 分数计算更准确
阅读导航
📖 建议阅读路径:
快速上手(10分钟): → 章节:快速清单 → 实施步骤(Step 1-7,仅关键命令)→ 附录:一键诊断脚本
深入理解(45分钟): → 章节:Kubernetes 重启机制原理 → 实施步骤(完整版)→ 最佳实践 → 常见故障排查表
故障排查: → 章节:常见故障与排错(根据症状直接跳转)→ 实施步骤(对应 Step)
快速清单(10 分钟排查路径)
- [ ] 第一步:确认重启现象(1 分钟)
- [ ] 查看 Pod 状态与重启次数(
kubectl get pods -o wide)
- [ ] 确认是否所有容器重启或单个容器(
kubectl describe pod <pod-name>)
- [ ] 第二步:收集核心日志(2 分钟)
- [ ] 查看当前容器日志(
kubectl logs <pod-name> -c <container>)
- [ ] 查看上次崩溃日志(
kubectl logs <pod-name> -c <container> --previous)
- [ ] 获取 Pod Events(
kubectl get events --field-selector involvedObject.name=<pod-name>)
- [ ] 第三步:分析重启原因(3 分钟)
- [ ] 检查是否 OOMKilled(
kubectl describe pod 查看 Last State)
- [ ] 检查探针配置(livenessProbe/readinessProbe 超时/失败)
- [ ] 检查镜像拉取状态(ImagePullBackOff/ErrImagePull)
- [ ] 检查资源配额(requests/limits 设置)
- [ ] 第四步:快速修复(4 分钟)
- [ ] OOMKilled → 增加 memory limits
- [ ] 探针失败 → 调整超时时间或失败阈值
- [ ] 镜像拉取失败 → 检查镜像名称、拉取凭证
- [ ] CrashLoopBackOff → 修复应用启动错误
- [ ] 验证修复结果
- [ ] 重新部署 Pod(
kubectl rollout restart deployment <name>)
- [ ] 持续监控重启次数(
kubectl get pods -w)
- [ ] 检查应用健康状态(
kubectl get pods STATUS 为 Running)
Kubernetes Pod 重启机制原理
系统架构:
kubectl 命令 → API Server → 持久化到 etcd
↓
Scheduler(调度器)
↓
选择合适的节点(Node)
↓
Kubelet(节点代理)
↓
容器运行时(containerd/Docker)
↓
启动容器 → 执行健康检查
↓
探针检查(livenessProbe/readinessProbe)
├─ 失败 → Kubelet 杀死容器 → 重启
└─ 成功 → 继续运行
资源监控(cAdvisor)
↓
内存超限检测(OOM Killer)
└─ 超限 → 内核杀死容器 → Kubelet 记录 OOMKilled → 重启
关键组件:
- API Server:接收所有集群操作请求,验证并持久化到 etcd
- Scheduler:为新 Pod 选择合适的节点(基于资源、亲和性、污点容忍)
- Kubelet:节点代理,负责 Pod 生命周期管理、健康检查、重启决策
- 容器运行时:实际执行容器创建、销毁(containerd/CRI-O)
- 探针(Probe):livenessProbe 检测容器存活、readinessProbe 检测服务就绪
- cAdvisor:集成在 Kubelet 中,监控容器资源使用
- OOM Killer:Linux 内核机制,内存不足时强制杀死进程
重启决策流程(文字描述):
- 容器启动 → Kubelet 通过容器运行时启动容器
- 健康检查 → 根据 Pod 定义执行 livenessProbe/readinessProbe
- 检查结果判断:
- Liveness 失败 → 连续失败达
failureThreshold 次 → Kubelet 杀死容器 → 根据 restartPolicy 决定是否重启
- 内存超限 → 容器实际使用超过
limits.memory → OOM Killer 杀死进程 → Kubelet 检测到退出码 137 → 标记 OOMKilled → 重启
- 进程异常退出 → 退出码非 0 → Kubelet 记录退出原因 → 根据
restartPolicy 重启
- 镜像拉取失败 → 无法下载镜像 → 状态变为 ImagePullBackOff → 指数退避重试(10s → 20s → 40s → … 最多 5 分钟)
- 重启策略执行:
Always(默认):无论退出原因,总是重启
OnFailure:仅退出码非 0 时重启
Never:从不重启
- 退避机制 → 连续失败重启时间间隔递增:10s → 20s → 40s → 80s → 160s → 最大 5 分钟
为什么重启次数会快速增加?
- 探针超时设置过短(如 1 秒),应用启动慢导致检查失败
- 内存限制过低,应用正常运行就触发 OOM
- 应用配置错误(如数据库连接失败),启动即崩溃
- 依赖服务不可用(如 Redis 未就绪),导致健康检查持续失败
实施步骤(10 分钟快速诊断)
Step 1: 查看 Pod 状态与重启次数
目标: 快速确认哪些 Pod 在重启、重启次数、当前状态
# 查看所有 Pod 状态(关注 RESTARTS 列)
kubectl get pods -A -o wide
# 查看指定 namespace 的 Pod
kubectl get pods -n <namespace> -o wide
# 持续监控 Pod 状态变化
kubectl get pods -w
# 输出示例(异常 Pod):
# NAME READY STATUS RESTARTS AGE
# web-app-7d8f5c4b-xk9zm 0/1 CrashLoopBackOff 15 10m
# api-server-5f6d8b-m2pq 1/1 Running 8 2h
# db-proxy-9c4f2d-5h7k 0/1 OOMKilled 3 5m
关键参数解释:
STATUS 列:CrashLoopBackOff 表示容器反复崩溃重启,OOMKilled 表示内存不足被杀死
RESTARTS 列:重启次数,短时间内次数快速增长说明存在严重问题
READY 列:0/1 表示容器未就绪,可能是探针检查失败
执行前验证:
# 确认 kubectl 可连接集群
kubectl cluster-info
# 确认有权限查看 Pod
kubectl auth can-i get pods --all-namespaces
# 预期输出:yes
执行后验证:
# 记录重启频繁的 Pod 名称
kubectl get pods -A --field-selector status.phase=Running | awk '$5 > 5 {print $1, $2, $5}'
# 输出重启次数 > 5 的 Pod
常见状态说明:
Running:容器正常运行中
Pending:等待调度或镜像拉取
CrashLoopBackOff:容器启动后立即崩溃,进入退避重启
OOMKilled:内存超限被杀死
ImagePullBackOff:镜像拉取失败,退避重试中
Error:容器异常退出
Completed:容器成功完成(Job 任务)
Step 2: 查看容器日志(当前 + 上次崩溃)
目标: 从应用日志中找到崩溃原因、错误堆栈、异常信息
# 查看当前运行容器的日志(最近 100 行)
kubectl logs <pod-name> -n <namespace> --tail=100
# 查看多容器 Pod 中指定容器的日志
kubectl logs <pod-name> -n <namespace> -c <container-name>
# 查看上次崩溃容器的日志(关键!)
kubectl logs <pod-name> -n <namespace> -c <container-name> --previous
# 实时跟踪日志
kubectl logs <pod-name> -n <namespace> -f
# 查看过去 1 小时的日志(需时间戳)
kubectl logs <pod-name> -n <namespace> --since=1h
典型错误日志示例:
示例 1:OOMKilled(内存不足)
# 容器日志可能显示:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3332)
at java.lang.AbstractStringBuilder.ensureCapacityInternal
# kubectl describe 显示:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Thu, 15 Jan 2025 10:20:15 +0800
Finished: Thu, 15 Jan 2025 10:20:45 +0800
示例 2:应用启动失败
# 日志显示:
panic: database connection failed: dial tcp 10.0.1.5:3306: connect: connection refused
goroutine 1 [running]:
main.main()
/app/main.go:25 +0x145
示例 3:探针检查失败
# 日志显示:
2025-01-15T10:25:30Z [ERROR] Health check failed: endpoint /health returned 503
2025-01-15T10:25:33Z [INFO] Received SIGTERM, shutting down...
执行后验证:
# 保存日志到本地分析
kubectl logs <pod-name> -n <namespace> --previous > /tmp/pod-crash.log
# 查找关键错误关键词
grep -iE "error|exception|fatal|panic|oom" /tmp/pod-crash.log
常见错误模式:
| 日志特征 |
可能原因 |
下一步 |
OutOfMemoryError / OOMKilled |
内存限制不足 |
→ Step 4 检查资源配置 |
connection refused / timeout |
依赖服务不可用 |
→ 检查 Service/Endpoints |
panic / segmentation fault |
应用 Bug 或依赖库问题 |
→ 修复应用代码 |
no such file / permission denied |
挂载卷路径错误或权限问题 |
→ 检查 Volume 配置 |
port already in use |
端口冲突 |
→ 检查 Pod 端口配置 |
Step 3: 分析 Pod Events(事件历史)
目标: 从 Kubernetes 事件中查看调度、健康检查、资源限制等系统级错误
# 查看 Pod 详细信息(包含 Events)
kubectl describe pod <pod-name> -n <namespace>
# 仅查看 Events 部分
kubectl get events -n <namespace> --field-selector involvedObject.name=<pod-name> --sort-by='.lastTimestamp'
# 查看最近 10 分钟的所有事件
kubectl get events -n <namespace> --sort-by='.lastTimestamp' | head -20
# 查看 Warning 级别的事件
kubectl get events -n <namespace> --field-selector type=Warning
典型 Events 示例:
示例 1:OOMKilled 事件
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 5m default-scheduler Successfully assigned default/web-app-xxx to node-1
Normal Pulled 5m kubelet Successfully pulled image "app:v1.2"
Normal Created 5m kubelet Created container web
Normal Started 5m kubelet Started container web
Warning BackOff 2m kubelet Back-off restarting failed container
Warning Failed 1m kubelet Error: OOMKilled
示例 2:探针失败事件
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning Unhealthy 2m kubelet Liveness probe failed: HTTP probe failed with statuscode: 500
Warning Unhealthy 1m kubelet Liveness probe failed: Get "http://10.244.1.5:8080/health": dial tcp 10.244.1.5:8080: connect: connection refused
Normal Killing 1m kubelet Container web failed liveness probe, will be restarted
示例 3:镜像拉取失败
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 3m default-scheduler Successfully assigned default/app to node-2
Normal Pulling 3m kubelet Pulling image "myregistry.com/app:latest"
Warning Failed 2m kubelet Failed to pull image "myregistry.com/app:latest": rpc error: code = Unknown desc = Error response from daemon: pull access denied
Warning Failed 2m kubelet Error: ErrImagePull
Normal BackOff 1m kubelet Back-off pulling image "myregistry.com/app:latest"
Warning Failed 1m kubelet Error: ImagePullBackOff
示例 4:资源不足无法调度
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 5m default-scheduler 0/3 nodes are available: 1 Insufficient cpu, 2 Insufficient memory.
关键事件类型:
OOMKilled:内存超限
Unhealthy:探针检查失败
BackOff:退避重启中
FailedScheduling:调度失败(资源不足)
ImagePullBackOff:镜像拉取失败
FailedMount:存储卷挂载失败
执行后验证:
# 统计某个 Pod 的事件类型分布
kubectl get events -n <namespace> --field-selector involvedObject.name=<pod-name> | awk '{print $3}' | sort | uniq -c
Step 4: 检查资源配置(requests/limits)
目标: 确认 CPU/内存配置是否合理,是否触发 OOM 或资源配额限制
# 查看 Pod 资源配置
kubectl get pod <pod-name> -n <namespace> -o yaml | grep -A 10 resources
# 查看 Pod 实际资源使用(需 metrics-server)
kubectl top pod <pod-name> -n <namespace>
# 查看节点资源使用
kubectl top nodes
# 查看 namespace 资源配额
kubectl get resourcequota -n <namespace>
kubectl describe resourcequota -n <namespace>
资源配置示例(查看输出):
resources:
requests:
memory: "128Mi" # 调度最低要求
cpu: "100m" # 0.1 核
limits:
memory: "256Mi" # 最大可用内存(超过触发 OOM)
cpu: "500m" # 最大可用 CPU(节流)
判断标准:
- 内存使用接近 limits → 增加
limits.memory
- CPU 使用持续 100% → 增加
limits.cpu 或检查应用性能
- requests 设置过高 → 节点资源不足,导致调度失败
- 未设置 limits → 容器可能无限使用资源,影响其他 Pod
优化前后对比:
问题配置(OOMKilled):
resources:
requests:
memory: "128Mi"
limits:
memory: "128Mi" # 过低,Java 应用至少需要 512Mi
优化后配置:
resources:
requests:
memory: "512Mi" # 增加到实际需求
limits:
memory: "1Gi" # 留 2 倍余量
cpu: "1000m" # 1 核
执行后验证:
# 检查 Pod 是否被 OOM Killer 杀死
kubectl describe pod <pod-name> -n <namespace> | grep -A 5 "Last State"
# 预期输出应无 "Reason: OOMKilled"
# 验证资源使用(需 metrics-server)
kubectl top pod <pod-name> -n <namespace>
# 预期:Memory 使用 < limits 的 80%
常见错误示例:
# 错误:资源配额不足
Error from server (Forbidden): pods "app" is forbidden: exceeded quota: compute-resources,
requested: limits.memory=2Gi, used: limits.memory=8Gi, limited: limits.memory=10Gi
# 解决:增加 ResourceQuota 或减少 Pod 资源请求
Step 5: 检查健康探针配置
目标: 确认 livenessProbe/readinessProbe 配置是否合理,避免误杀健康容器
# 查看 Pod 探针配置
kubectl get pod <pod-name> -n <namespace> -o yaml | grep -A 15 "Probe"
# 查看探针检查失败次数
kubectl describe pod <pod-name> -n <namespace> | grep -A 10 "Liveness\|Readiness"
探针配置示例:
livenessProbe:
httpGet:
path: /health # 健康检查端点
port: 8080
scheme: HTTP
initialDelaySeconds: 30 # 启动后等待 30 秒再检查
periodSeconds: 10 # 每 10 秒检查一次
timeoutSeconds: 5 # 超时时间 5 秒
successThreshold: 1 # 连续成功 1 次认为健康
failureThreshold: 3 # 连续失败 3 次杀死容器
常见问题配置:
问题 1:initialDelaySeconds 过短
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 5 # Java 应用启动需 30-60 秒,5 秒太短
failureThreshold: 3
# 结果:应用未启动完成就被探针杀死,反复重启
优化后:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60 # 增加到 60 秒
periodSeconds: 10
timeoutSeconds: 3
failureThreshold: 3
问题 2:探针端点无响应
livenessProbe:
httpGet:
path: /healthz # 端点拼写错误或未实现
port: 8080
timeoutSeconds: 1 # 超时时间过短
验证探针端点:
# 进入容器测试健康检查端点
kubectl exec -it <pod-name> -n <namespace> -- curl http://localhost:8080/health
# 或通过 Service 测试
kubectl port-forward -n <namespace> pod/<pod-name> 8080:8080
curl http://localhost:8080/health
# 预期:HTTP 200 OK
探针类型选择:
| 探针类型 |
适用场景 |
配置示例 |
httpGet |
Web 应用、API 服务 |
path: /health, port: 8080 |
tcpSocket |
数据库、缓存服务 |
port: 6379 (Redis) |
exec |
自定义脚本检查 |
command: ["/bin/sh", "-c", "pg_isready"] |
执行后验证:
# 检查探针失败事件是否减少
kubectl get events -n <namespace> --field-selector involvedObject.name=<pod-name>,reason=Unhealthy
# 预期:无输出或事件时间为修复前
Step 6: 检查镜像拉取状态
目标: 排查镜像不存在、凭证错误、网络超时等拉取失败问题
# 查看 Pod 镜像拉取状态
kubectl describe pod <pod-name> -n <namespace> | grep -A 10 "Image"
# 查看 ImagePullBackOff 事件
kubectl get events -n <namespace> --field-selector reason=Failed,involvedObject.name=<pod-name>
# 手动测试镜像拉取(在节点上)
ssh <node-ip>
sudo crictl pull <image-name>
# 或 Docker 运行时:
docker pull <image-name>
典型错误场景:
场景 1:镜像不存在
Events:
Warning Failed 2m kubelet Failed to pull image "myapp:v2.0": rpc error: code = NotFound desc = failed to pull and unpack image "docker.io/library/myapp:v2.0": not found
解决: 检查镜像名称、标签,确认镜像已推送到仓库
场景 2:私有仓库无凭证
Events:
Warning Failed 3m kubelet Failed to pull image "myregistry.com/app:latest": rpc error: code = Unknown desc = Error response from daemon: pull access denied for myregistry.com/app, repository does not exist or may require 'docker login'
解决: 创建 imagePullSecrets
# 创建 Docker 仓库凭证
kubectl create secret docker-registry myregistry-secret \
--docker-server=myregistry.com \
--docker-username=<username> \
--docker-password=<password> \
--docker-email=<email> \
-n <namespace>
# 在 Pod 中引用
kubectl patch serviceaccount default -n <namespace> -p '{"imagePullSecrets": [{"name": "myregistry-secret"}]}'
场景 3:网络超时
Events:
Warning Failed 5m kubelet Failed to pull image "myregistry.com/app:latest": rpc error: code = Unknown desc = context deadline exceeded
解决: 检查节点网络、DNS 解析、仓库服务可用性
# 在节点上测试网络
ssh <node-ip>
ping myregistry.com
curl -I https://myregistry.com/v2/
# 检查 DNS 解析
nslookup myregistry.com
执行后验证:
# 确认镜像拉取成功
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.status.containerStatuses[0].imageID}'
# 预期输出:镜像 SHA256 摘要
Step 7: 修复与验证
目标: 应用修复方案,验证 Pod 稳定运行
修复步骤:
修复 1:增加内存限制(OOMKilled)
# 编辑 Deployment 配置
kubectl edit deployment <deployment-name> -n <namespace>
# 修改 resources 部分:
resources:
limits:
memory: "1Gi" # 从 256Mi 增加到 1Gi
requests:
memory: "512Mi"
# 或使用 kubectl set resources
kubectl set resources deployment <deployment-name> -n <namespace> \
--limits=memory=1Gi \
--requests=memory=512Mi
修复 2:调整探针参数(误杀)
# 编辑 Deployment
kubectl edit deployment <deployment-name> -n <namespace>
# 修改 livenessProbe:
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 60 # 从 10 增加到 60
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 5 # 从 3 增加到 5
修复 3:修复应用配置(启动失败)
# 更新 ConfigMap
kubectl edit configmap <config-name> -n <namespace>
# 或重新创建
kubectl create configmap app-config \
--from-literal=DATABASE_HOST=mysql.default.svc.cluster.local \
--from-literal=DATABASE_PORT=3306 \
-n <namespace> \
--dry-run=client -o yaml | kubectl apply -f -
# 重启 Pod 使配置生效
kubectl rollout restart deployment <deployment-name> -n <namespace>
验证修复结果:
# 持续监控 Pod 状态(至少 5 分钟)
kubectl get pods -n <namespace> -w
# 检查重启次数是否不再增长
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.status.containerStatuses[0].restartCount}'
# 查看最新事件(应无 Warning)
kubectl get events -n <namespace> --field-selector involvedObject.name=<pod-name> --sort-by='.lastTimestamp' | tail -10
# 检查资源使用稳定
kubectl top pod <pod-name> -n <namespace>
# 预期:Memory 使用稳定在 limits 的 60-80%
回滚条件:
- 修复后 5 分钟内重启次数继续增长
- 出现新的错误日志或事件
- 应用功能异常
回滚命令:
# 回滚 Deployment 到上一版本
kubectl rollout undo deployment <deployment-name> -n <namespace>
# 查看回滚历史
kubectl rollout history deployment <deployment-name> -n <namespace>
# 回滚到指定版本
kubectl rollout undo deployment <deployment-name> -n <namespace> --to-revision=2
常见故障排查决策树
问题排查流程(文字描述):
第1步:发现 Pod 频繁重启
↓
第2步:查看 STATUS 列状态
├─ CrashLoopBackOff → 第3步:查看日志 (kubectl logs --previous)
│ ├─ 日志显示 OOM → 第5步:增加 memory limits
│ ├─ 日志显示连接错误 → 检查依赖服务(数据库/Redis)
│ └─ 日志显示启动失败 → 修复应用代码或配置
│
├─ OOMKilled → 第4步:确认内存配置
│ ├─ limits 过低 → 增加到实际需求 2 倍
│ └─ 应用内存泄漏 → 分析应用性能(heap dump)
│
├─ ImagePullBackOff → 第6步:检查镜像与凭证
│ ├─ 镜像不存在 → 修正镜像名称/标签
│ ├─ 无拉取凭证 → 创建 imagePullSecrets
│ └─ 网络超时 → 检查节点网络连通性
│
├─ Error (退出码非 0) → 查看日志与 Events
│ └─ 根据错误信息修复
│
└─ Running (但频繁重启) → 第5步:检查探针配置
├─ Liveness 超时 → 增加 timeoutSeconds
├─ Liveness 失败 → 检查健康检查端点
└─ initialDelaySeconds 过短 → 增加启动等待时间
关键判断点:
- STATUS = CrashLoopBackOff → 容器启动失败,查日志找根因
- STATUS = OOMKilled → 内存不足,调整 limits
- STATUS = ImagePullBackOff → 镜像拉取问题,检查凭证/网络
- RESTARTS 持续增长但 STATUS = Running → 探针误杀,调整探针参数
常见故障速查表
| 症状 |
诊断命令 |
可能根因 |
快速修复 |
永久修复 |
| STATUS: OOMKilled |
kubectl describe pod | grep "Reason: OOMKilled" |
1. 内存限制过低 2. 应用内存泄漏 |
增加 limits.memory |
分析应用内存使用,优化代码 |
| STATUS: CrashLoopBackOff |
kubectl logs <pod> --previous |
1. 应用启动失败 2. 依赖服务不可用 3. 配置错误 |
修复日志中的错误 |
完善错误处理、健康检查 |
| STATUS: ImagePullBackOff |
kubectl describe pod | grep "Failed to pull" |
1. 镜像不存在 2. 私有仓库无凭证 3. 网络超时 |
创建 imagePullSecrets |
检查镜像名称、网络策略 |
| Liveness 探针失败 |
kubectl get events | grep Unhealthy |
1. 超时时间过短 2. 启动时间过长 3. 端点未实现 |
增加 timeoutSeconds 增加 initialDelaySeconds |
优化应用启动速度 |
| CPU 节流(100%) |
kubectl top pod <pod> |
1. CPU limits 过低 2. 应用性能瓶颈 |
增加 limits.cpu |
性能分析与优化 |
| 节点资源不足 |
kubectl describe node | grep "Allocated resources" |
1. 节点 CPU/内存耗尽 2. Pod requests 过高 |
驱逐低优先级 Pod 扩容节点 |
启用 Cluster Autoscaler |
| 探针连续失败 |
kubectl describe pod | grep failureThreshold |
1. 应用异常 2. 探针配置过于严格 |
增加 failureThreshold |
修复应用健康检查逻辑 |
可观测性(监控 + 告警)
Prometheus 监控指标
Pod 重启次数监控脚本:
# 使用 kube-state-metrics 暴露指标
# 指标名称:kube_pod_container_status_restarts_total
# Prometheus 查询示例:
# 查询过去 1 小时重启次数
increase(kube_pod_container_status_restarts_total[1h]) > 5
# 按 namespace 聚合重启次数
sum(increase(kube_pod_container_status_restarts_total[1h])) by (namespace, pod)
Prometheus 告警规则:
# prometheus-rules.yaml
groups:
- name: pod_restart_alerts
interval: 30s
rules:
# 告警1:Pod 频繁重启
- alert: PodFrequentlyRestarting
expr: increase(kube_pod_container_status_restarts_total[15m]) > 3
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 频繁重启"
description: "过去 15 分钟内重启 {{ $value }} 次"
# 告警2:Pod OOMKilled
- alert: PodOOMKilled
expr: kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
for: 1m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 内存不足被杀死"
description: "容器 {{ $labels.container }} 触发 OOMKilled"
# 告警3:CrashLoopBackOff 状态
- alert: PodCrashLoopBackOff
expr: kube_pod_container_status_waiting_reason{reason="CrashLoopBackOff"} == 1
for: 5m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 处于 CrashLoopBackOff"
description: "容器反复崩溃重启,需立即排查"
# 告警4:镜像拉取失败
- alert: ImagePullBackOff
expr: kube_pod_container_status_waiting_reason{reason="ImagePullBackOff"} == 1
for: 10m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 镜像拉取失败"
description: "检查镜像名称、拉取凭证、网络连通性"
# 告警5:探针失败率过高
- alert: HighProbeFailed
expr: rate(prober_probe_total{result="failed"}[5m]) > 0.5
for: 5m
labels:
severity: warning
annotations:
summary: "探针 {{ $labels.probe_type }} 失败率过高"
description: "过去 5 分钟失败率 {{ $value | humanizePercentage }}"
Grafana 面板建议:
{
"panels": [
{
"title": "Pod 重启次数 Top 10",
"targets": [{
"expr": "topk(10, sum(increase(kube_pod_container_status_restarts_total[1h])) by (namespace, pod))"
}],
"type": "bargauge"
},
{
"title": "OOMKilled 事件统计",
"targets": [{
"expr": "sum(kube_pod_container_status_last_terminated_reason{reason=\"OOMKilled\"}) by (namespace)"
}],
"type": "stat"
},
{
"title": "CrashLoopBackOff Pod 数量",
"targets": [{
"expr": "count(kube_pod_container_status_waiting_reason{reason=\"CrashLoopBackOff\"})"
}],
"type": "stat"
}
]
}
最佳实践
-
合理设置资源限制
resources:
requests:
memory: "512Mi" # 调度保证
cpu: "250m"
limits:
memory: "1Gi" # 实际使用峰值的 1.5-2 倍
cpu: "1000m" # 限制 CPU 但不建议过低
-
探针配置经验值
livenessProbe:
initialDelaySeconds: 60 # Java 应用 60-90s,Go/Python 30-60s
periodSeconds: 10
timeoutSeconds: 5
failureThreshold: 3
readinessProbe:
initialDelaySeconds: 10 # 比 liveness 短
periodSeconds: 5
failureThreshold: 3
-
使用 startupProbe(K8s 1.18+)
# 解决慢启动应用的探针问题
startupProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 0
periodSeconds: 10
failureThreshold: 30 # 最多等待 300 秒(10s x 30)
livenessProbe:
httpGet:
path: /health
port: 8080
periodSeconds: 10
failureThreshold: 3 # 启动后严格检查
-
配置 PodDisruptionBudget(防驱逐)
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: app-pdb
spec:
minAvailable: 1 # 至少保留 1 个 Pod
selector:
matchLabels:
app: web-app
-
启用 Pod 反亲和性(避免单点)
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: web-app
topologyKey: kubernetes.io/hostname
-
配置合理的重启策略
restartPolicy: Always # Deployment 默认
# 或 Job 任务使用:
restartPolicy: OnFailure
-
启用资源配额(防资源耗尽)
apiVersion: v1
kind: ResourceQuota
metadata:
name: compute-quota
namespace: production
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "50"
-
日志持久化(避免日志丢失)
# 使用 EFK/PLG 栈收集日志
# 或在节点配置日志保留策略
# containerd 配置示例:
# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".containerd]
max_container_log_line_size = 16384
max_log_entries = 5000
-
定期演练故障恢复
- 每月主动触发一次 OOM 测试
- 验证告警触发时间 < 5 分钟
- 验证自动扩容机制(HPA/Cluster Autoscaler)
-
监控重启次数基线
- 正常场景:每天 < 1 次/Pod(仅版本发布)
- 异常阈值:15 分钟内 > 3 次(需立即排查)
FAQ(常见问题)
Q1: Pod 重启次数很高,但 STATUS 显示 Running,有问题吗?
A: 有问题。说明 Pod 在运行过程中被探针杀死或 OOM,但重启后能正常运行一段时间。需检查探针配置和资源使用趋势。
Q2: 如何区分计划内重启和故障重启?
A: 查看 Events 中的 Reason 字段:
Killing(手动/滚动更新)→ 计划内
OOMKilled / Error / Unhealthy → 故障
Q3: OOMKilled 后增加内存限制,但还是被杀,为什么?
A: 可能是应用内存泄漏。使用 heap dump 分析内存占用:
# Java 应用
kubectl exec <pod> -- jmap -dump:format=b,file=/tmp/heap.hprof 1
kubectl cp <pod>:/tmp/heap.hprof ./heap.hprof
# 使用 MAT 或 VisualVM 分析
Q4: CrashLoopBackOff 退避时间越来越长,如何加快排查?
A: 临时删除 Pod 强制重建,重置退避时间:
kubectl delete pod <pod-name> -n <namespace>
# 注意:会导致服务短暂中断
Q5: 如何避免探针误杀慢启动应用?
A: 使用 startupProbe(K8s 1.18+)或增加 livenessProbe.initialDelaySeconds:
startupProbe:
failureThreshold: 30 # 最多等待 300 秒
periodSeconds: 10
Q6: 多个容器的 Pod 如何确定哪个容器重启?
A: 查看 containerStatuses 的 restartCount:
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.status.containerStatuses.name}{"\n"}{.status.containerStatuses.restartCount}'
Q7: 节点资源充足,为什么 Pod 还报 Insufficient memory?
A: 可能是 ResourceQuota 限制:
kubectl describe resourcequota -n <namespace>
# 检查 Used vs Hard 对比
Q8: 如何模拟 OOM 测试探针响应?
A: 使用 stress 工具:
kubectl run stress --image=polinux/stress --restart=Never -- stress --vm 1 --vm-bytes 1G --timeout 60s
Q9: Init 容器失败导致主容器重启,如何排查?
A: 查看 Init 容器日志:
kubectl logs <pod-name> -n <namespace> -c <init-container-name>
kubectl describe pod <pod-name> -n <namespace> | grep -A 20 "Init Containers"
Q10: 生产环境如何安全调整资源限制?
A: 使用金丝雀发布:
# 1. 创建新版本 Deployment(资源配置调整)
kubectl apply -f deployment-v2.yaml
# 2. 逐步调整流量(使用 Istio/Nginx Ingress)
# 3. 监控新版本稳定性
# 4. 全量切换或回滚
附录:一键诊断脚本
自动诊断脚本(Bash)
#!/bin/bash
# 文件名:pod-restart-diagnosis.sh
# 用途:自动诊断 Pod 重启问题
set -e
# 颜色输出
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# 检查参数
if [ $# -lt 2 ]; then
echo "用法: $0 <namespace> <pod-name>"
exit 1
fi
NAMESPACE=$1
POD_NAME=$2
echo -e "${GREEN}========== Pod 重启诊断工具 ==========${NC}"
echo "Namespace: $NAMESPACE"
echo "Pod: $POD_NAME"
echo ""
# 1. 检查 Pod 状态
echo -e "${YELLOW}[1/7] 检查 Pod 状态...${NC}"
kubectl get pod $POD_NAME -n $NAMESPACE -o wide
# 2. 检查重启次数
echo ""
echo -e "${YELLOW}[2/7] 检查容器重启次数...${NC}"
RESTARTS=$(kubectl get pod $POD_NAME -n $NAMESPACE -o jsonpath='{.status.containerStatuses[0].restartCount}')
echo "重启次数: $RESTARTS"
if [ "$RESTARTS" -gt 5 ]; then
echo -e "${RED}⚠️ 重启次数异常(> 5)${NC}"
fi
# 3. 检查 Last State(查找 OOMKilled)
echo ""
echo -e "${YELLOW}[3/7] 检查容器退出原因...${NC}"
kubectl get pod $POD_NAME -n $NAMESPACE -o jsonpath='{.status.containerStatuses.lastState}' | jq .
OOM_CHECK=$(kubectl get pod $POD_NAME -n $NAMESPACE -o jsonpath='{.status.containerStatuses[0].lastState.terminated.reason}')
if [ "$OOM_CHECK" == "OOMKilled" ]; then
echo -e "${RED}⚠️ 检测到 OOMKilled!需增加内存限制${NC}"
fi
# 4. 查看资源配置
echo ""
echo -e "${YELLOW}[4/7] 检查资源配置...${NC}"
kubectl get pod $POD_NAME -n $NAMESPACE -o jsonpath='{.spec.containers[0].resources}' | jq .
# 5. 查看探针配置
echo ""
echo -e "${YELLOW}[5/7] 检查探针配置...${NC}"
kubectl get pod $POD_NAME -n $NAMESPACE -o jsonpath='{.spec.containers[0].livenessProbe}' | jq .
kubectl get pod $POD_NAME -n $NAMESPACE -o jsonpath='{.spec.containers[0].readinessProbe}' | jq .
# 6. 查看最近 Events
echo ""
echo -e "${YELLOW}[6/7] 查看最近事件(最近 10 条)...${NC}"
kubectl get events -n $NAMESPACE --field-selector involvedObject.name=$POD_NAME --sort-by='.lastTimestamp' | tail -10
# 7. 保存日志
echo ""
echo -e "${YELLOW}[7/7] 保存日志到本地...${NC}"
LOG_FILE="/tmp/${POD_NAME}-$(date +%Y%m%d-%H%M%S).log"
kubectl logs $POD_NAME -n $NAMESPACE --previous > $LOG_FILE 2>/dev/null || kubectl logs $POD_NAME -n $NAMESPACE > $LOG_FILE
echo "日志已保存到: $LOG_FILE"
# 总结与建议
echo ""
echo -e "${GREEN}========== 诊断建议 ==========${NC}"
if [ "$OOM_CHECK" == "OOMKilled" ]; then
echo -e "${RED}1. 检测到内存不足,建议:${NC}"
echo " - 增加 limits.memory(当前值的 2 倍)"
echo " - 分析应用内存泄漏"
fi
PROBE_FAILED=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=$POD_NAME,reason=Unhealthy 2>/dev/null | wc -l)
if [ "$PROBE_FAILED" -gt 0 ]; then
echo -e "${RED}2. 检测到探针失败,建议:${NC}"
echo " - 增加 initialDelaySeconds"
echo " - 增加 timeoutSeconds"
echo " - 检查健康检查端点"
fi
IMAGE_PULL_FAILED=$(kubectl get events -n $NAMESPACE --field-selector involvedObject.name=$POD_NAME,reason=Failed 2>/dev/null | grep -c "Failed to pull image" || true)
if [ "$IMAGE_PULL_FAILED" -gt 0 ]; then
echo -e "${RED}3. 检测到镜像拉取失败,建议:${NC}"
echo " - 检查镜像名称与标签"
echo " - 创建 imagePullSecrets"
echo " - 验证网络连通性"
fi
echo ""
echo -e "${GREEN}诊断完成!请根据建议修复问题。${NC}"
使用方法:
# 赋予执行权限
chmod +x pod-restart-diagnosis.sh
# 执行诊断
./pod-restart-diagnosis.sh default web-app-7d8f5c4b-xk9zm
# 输出示例:
# ========== Pod 重启诊断工具 ==========
# Namespace: default
# Pod: web-app-7d8f5c4b-xk9zm
#
# [1/7] 检查 Pod 状态...
# NAME READY STATUS RESTARTS AGE IP
# web-app-7d8f5c4b-xk9zm 1/1 Running 15 10m 10.244.1.5
#
# [2/7] 检查容器重启次数...
# 重启次数: 15
# ⚠️ 重启次数异常(> 5)
# ...
优化后的 Deployment YAML 模板
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
namespace: production
spec:
replicas: 3
selector:
matchLabels:
app: web-app
template:
metadata:
labels:
app: web-app
spec:
# 镜像拉取凭证
imagePullSecrets:
- name: myregistry-secret
# 容器配置
containers:
- name: web
image: myregistry.com/web-app:v1.2.0
imagePullPolicy: IfNotPresent
# 端口配置
ports:
- containerPort: 8080
protocol: TCP
# 资源限制(根据实际调整)
resources:
requests:
memory: "512Mi" # 调度保证
cpu: "500m"
limits:
memory: "1Gi" # 峰值的 1.5-2 倍
cpu: "2000m"
# 启动探针(慢启动应用)
startupProbe:
httpGet:
path: /health
port: 8080
scheme: HTTP
initialDelaySeconds: 0
periodSeconds: 10
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 30 # 最多等待 300 秒
# 存活探针
livenessProbe:
httpGet:
path: /health
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 5
successThreshold: 1
failureThreshold: 3
# 就绪探针
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 3
successThreshold: 1
failureThreshold: 3
# 环境变量
env:
- name: DATABASE_HOST
valueFrom:
configMapKeyRef:
name: app-config
key: db_host
- name: DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: db-secret
key: password
# 挂载卷
volumeMounts:
- name: config
mountPath: /etc/app/config
readOnly: true
# 卷定义
volumes:
- name: config
configMap:
name: app-config
# Pod 反亲和性(避免单点)
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
podAffinityTerm:
labelSelector:
matchLabels:
app: web-app
topologyKey: kubernetes.io/hostname
# 重启策略
restartPolicy: Always
扩展阅读
官方文档:
问题排查指南:
社区资源:
监控工具: