找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4625

积分

0

好友

610

主题
发表于 3 小时前 | 查看: 2| 回复: 0

适用场景 & 前置条件

项目 要求
适用场景 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 资源管理

反模式警告(何时不适用)

⚠️ 以下场景不推荐使用本方案:

  1. 应用正常重启:StatefulSet 滚动更新、Deployment 版本发布等计划内重启,无需排查
  2. Init 容器重启:Init 容器失败导致主容器未启动,需使用 kubectl logs <pod> -c <init-container> 专门分析
  3. Job 任务重复执行:Job 的 restartPolicy: OnFailure 导致重试,属正常行为
  4. Daemonset 节点调度:节点上下线导致 DaemonSet Pod 重启,非故障
  5. 云环境竞价实例: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 内核机制,内存不足时强制杀死进程

重启决策流程(文字描述):

  1. 容器启动 → Kubelet 通过容器运行时启动容器
  2. 健康检查 → 根据 Pod 定义执行 livenessProbe/readinessProbe
  3. 检查结果判断:
    • Liveness 失败 → 连续失败达 failureThreshold 次 → Kubelet 杀死容器 → 根据 restartPolicy 决定是否重启
    • 内存超限 → 容器实际使用超过 limits.memory → OOM Killer 杀死进程 → Kubelet 检测到退出码 137 → 标记 OOMKilled → 重启
    • 进程异常退出 → 退出码非 0 → Kubelet 记录退出原因 → 根据 restartPolicy 重启
    • 镜像拉取失败 → 无法下载镜像 → 状态变为 ImagePullBackOff → 指数退避重试(10s → 20s → 40s → … 最多 5 分钟)
  4. 重启策略执行:
    • Always(默认):无论退出原因,总是重启
    • OnFailure:仅退出码非 0 时重启
    • Never:从不重启
  5. 退避机制 → 连续失败重启时间间隔递增: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

关键参数解释:

  1. STATUS 列:CrashLoopBackOff 表示容器反复崩溃重启,OOMKilled 表示内存不足被杀死
  2. RESTARTS 列:重启次数,短时间内次数快速增长说明存在严重问题
  3. 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(节流)

判断标准:

  1. 内存使用接近 limits → 增加 limits.memory
  2. CPU 使用持续 100% → 增加 limits.cpu 或检查应用性能
  3. requests 设置过高 → 节点资源不足,导致调度失败
  4. 未设置 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 过短 → 增加启动等待时间

关键判断点:

  1. STATUS = CrashLoopBackOff → 容器启动失败,查日志找根因
  2. STATUS = OOMKilled → 内存不足,调整 limits
  3. STATUS = ImagePullBackOff → 镜像拉取问题,检查凭证/网络
  4. 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"
    }
  ]
}

最佳实践

  1. 合理设置资源限制

    resources:
      requests:
        memory: "512Mi" # 调度保证
        cpu: "250m"
      limits:
        memory: "1Gi" # 实际使用峰值的 1.5-2 倍
        cpu: "1000m" # 限制 CPU 但不建议过低
  2. 探针配置经验值

    livenessProbe:
      initialDelaySeconds: 60 # Java 应用 60-90s,Go/Python 30-60s
      periodSeconds: 10
      timeoutSeconds: 5
      failureThreshold: 3
    readinessProbe:
      initialDelaySeconds: 10 # 比 liveness 短
      periodSeconds: 5
      failureThreshold: 3
  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 # 启动后严格检查
  4. 配置 PodDisruptionBudget(防驱逐)

    apiVersion: policy/v1
    kind: PodDisruptionBudget
    metadata:
      name: app-pdb
    spec:
      minAvailable: 1 # 至少保留 1 个 Pod
      selector:
        matchLabels:
          app: web-app
  5. 启用 Pod 反亲和性(避免单点)

    affinity:
      podAntiAffinity:
        preferredDuringSchedulingIgnoredDuringExecution:
        - weight: 100
          podAffinityTerm:
            labelSelector:
              matchLabels:
                app: web-app
            topologyKey: kubernetes.io/hostname
  6. 配置合理的重启策略

    restartPolicy: Always # Deployment 默认
    # 或 Job 任务使用:
    restartPolicy: OnFailure
  7. 启用资源配额(防资源耗尽)

    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"
  8. 日志持久化(避免日志丢失)

    # 使用 EFK/PLG 栈收集日志
    # 或在节点配置日志保留策略
    
    # containerd 配置示例:
    # /etc/containerd/config.toml
    [plugins."io.containerd.grpc.v1.cri".containerd]
      max_container_log_line_size = 16384
      max_log_entries = 5000
  9. 定期演练故障恢复

    • 每月主动触发一次 OOM 测试
    • 验证告警触发时间 < 5 分钟
    • 验证自动扩容机制(HPA/Cluster Autoscaler)
  10. 监控重启次数基线

    • 正常场景:每天 < 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: 查看 containerStatusesrestartCount

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

    扩展阅读

    官方文档:

    问题排查指南:

    社区资源:

    监控工具:





    上一篇:1.9亿美元押注AI防御,Armadin如何用自主代理应对网络安全新范式?
    下一篇:OpenVPN部署全流程:基于SSL/TLS的企业异地办公VPN搭建指南
    您需要登录后才可以回帖 登录 | 立即注册

    手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

    GMT+8, 2026-3-28 07:13 , Processed in 0.679621 second(s), 41 queries , Gzip On.

    Powered by Discuz! X3.5

    © 2025-2026 云栈社区.

    快速回复 返回顶部 返回列表