跳到主要内容
EN
返回

Kubernetes 排障手册:从 CrashLoopBackOff 到丝滑运行

Zhou Xunyou 19 分钟阅读 devops
分享

Kubernetes 排障是每个运维和开发人员的日常。Pod 卡在 CrashLoopBackOff、OOMKilled、ImagePullBackOff……这些状态背后的原因各不相同,需要系统化的诊断方法。本文将按照故障分类,给出完整的排查流程和命令速查表。

排障核心原则

从内到外,逐步排查:Container → Pod → Service → Ingress → CNI

排障时最常见的错误是直接去看日志,而忽略了事件和状态。正确的顺序是:

1. kubectl get pods — 看状态
2. kubectl describe pod — 看事件
3. kubectl logs — 看日志
4. kubectl exec — 进容器确认

Pod 异常状态速查

状态 含义 首要排查命令
CrashLoopBackOff 容器启动后崩溃,反复重启 kubectl logs --previous
OOMKilled 内存超限被杀 kubectl describe pod
ImagePullBackOff 镜像拉取失败 kubectl describe pod
Pending 无法调度到节点 kubectl describe pod
ContainerCreating 一直创建中 kubectl describe pod
Completed 容器正常退出 检查 restartPolicy
Error 容器异常退出 kubectl logs
Unknown 节点失联 kubectl get nodes

CrashLoopBackOff 排查

最常见的 Pod 故障,容器启动后崩溃,Kubernetes 不断重启它。

排查流程

# 1. 查看 Pod 状态和重启次数
kubectl get pod <pod-name> -o wide

# 2. 查看上一次崩溃的日志(关键!)
kubectl logs <pod-name> --previous

# 3. 如果 --previous 也没有,看当前日志
kubectl logs <pod-name>

# 4. 查看 Pod 事件
kubectl describe pod <pod-name>

# 5. 关注 Events 部分的 Warning 信息

常见原因及修复

原因一:应用启动失败(配置错误)

# 典型日志
# Error: Config file not found: /etc/app/config.yml
# panic: failed to connect to database

# 诊断
kubectl logs <pod-name> --previous | head -50

# 修复:检查 ConfigMap/Secret 是否正确挂载
kubectl get configmap <config-name> -o yaml
kubectl describe pod <pod-name> | grep -A5 Mount

原因二:健康检查失败

# 典型事件
# Liveness probe failed: Get "http://:8080/health": dial tcp :8080: connect: connection refused
# Back-off restarting failed container

# 诊断
kubectl describe pod <pod-name> | grep -A10 "Events"

# 修复:调整探针参数
# 增加初始延迟和超时时间
livenessProbe:
  httpGet:
    path: /health
    port: 8080
  initialDelaySeconds: 30  # 给应用足够的启动时间
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3      # 允许连续失败 3 次
readinessProbe:
  httpGet:
    path: /ready
    port: 8080
  initialDelaySeconds: 10
  periodSeconds: 5
  failureThreshold: 3

原因三:主进程前台运行问题

# 容器启动后立即退出,因为主进程结束了
# 典型场景:运行一个脚本后退出

# 诊断
kubectl logs <pod-name> --previous
# 可能看到脚本输出后就结束了

# 修复:确保主进程保持前台运行
# Dockerfile 中使用 ENTRYPOINT 而非 RUN
# 或者添加 tail -f /dev/null 保持容器运行

OOMKilled 排查

容器内存使用超出 limit,被内核 OOM Killer 杀掉。

诊断步骤

# 1. 确认是否 OOMKilled
kubectl describe pod <pod-name> | grep -A5 "Last State"
# 输出:Reason: OOMKilled

# 2. 查看内存 limit
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[0].resources}'

# 3. 查看实际内存使用
kubectl top pod <pod-name>

# 4. 查看节点内存情况
kubectl describe node <node-name> | grep -A10 "Allocated resources"

修复方案

# 方案一:增加 memory limit
resources:
  requests:
    memory: "256Mi"
    cpu: "100m"
  limits:
    memory: "512Mi"    # 增大 limit
    cpu: "500m"

# 方案二:优化应用内存使用
# JVM 应用:设置 -Xmx 为 limit 的 70-80%
# Go 应用:调试内存泄漏
# Node.js:设置 --max-old-space-size

# 方案三:设置 QoS 为 Guaranteed(requests = limits)
resources:
  requests:
    memory: "512Mi"
    cpu: "500m"
  limits:
    memory: "512Mi"   # requests = limits
    cpu: "500m"

JVM 应用的 OOM 配置

# 常见错误:JVM 堆大小超过容器 limit
# 现代 JDK(8u191+)支持容器感知

env:
  - name: JAVA_OPTS
    value: >-
      -XX:MaxRAMPercentage=75.0
      -XX:InitialRAMPercentage=50.0
      -XX:+UseContainerSupport
      -XX:+HeapDumpOnOutOfMemoryError
      -XX:HeapDumpPath=/tmp/heapdump.hprof
# 不要用 -Xmx 硬编码,用 MaxRAMPercentage 动态计算

ImagePullBackOff 排查

镜像拉取失败,通常是因为镜像不存在、权限问题或网络问题。

诊断步骤

# 1. 查看详细错误
kubectl describe pod <pod-name> | grep -A5 "Events"
# 常见错误:
#   Failed to pull image "xxx": rpc error: code = NotFound
#   Failed to pull image "xxx": failed to authorize
#   Failed to pull image "xxx": dial tcp: lookup registry.example.com

# 2. 确认镜像是否存在
docker pull <image-name>  # 在本地测试

# 3. 检查 imagePullSecrets
kubectl get secret <secret-name> -o yaml

常见原因修复

# 原因一:镜像标签错误或不存在
# 修复:确认镜像标签
kubectl set image deployment/<name> <container>=<image>:<correct-tag>

# 原因二:私有仓库认证失败
# 修复:创建 imagePullSecret
kubectl create secret docker-registry regcred \
  --docker-server=registry.example.com \
  --docker-username=user \
  --docker-password=password

# 在 Deployment 中引用
# spec.template.spec.imagePullSecrets:
#   - name: regcred

# 原因三:网络问题(无法访问仓库)
# 修复:配置容器运行时的代理或 mirror
# /etc/containerd/config.toml
[plugins."io.containerd.grpc.v1.cri".registry.configs."docker.io".auth]
  [plugins."io.containerd.grpc.v1.cri".registry.mirrors]
    [plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
      endpoint = ["https://mirror.gcr.io"]

Pending 状态排查

Pod 无法被调度到任何节点。

# 查看调度失败原因
kubectl describe pod <pod-name> | grep -A20 "Events"
# 常见原因:
#   0/3 nodes are available: 3 Insufficient cpu
#   0/3 nodes are available: 3 node(s) didn't match Pod's node affinity
#   0/3 nodes are available: 3 Insufficient memory
#   0/3 nodes are available: 3 node(s) had taint {dedicated: true}, pod didn't have toleration

# 查看节点资源
kubectl top nodes
kubectl describe node <node-name> | grep -A15 "Allocated resources"

# 查看 Pod 的资源请求
kubectl get pod <pod-name> -o jsonpath='{.spec.containers[*].resources.requests}'

修复方案

# 资源不足:
#   1. 降低 requests
#   2. 扩容节点
#   3. 清理不需要的 Pod

# 亲和性/反亲和性不满足:
#   检查 nodeSelector、affinity 配置

# 污点/容忍不匹配:
#   给 Pod 添加 toleration 或移除节点污点
kubectl taint nodes <node-name> dedicated-  # 移除污点

网络排障

网络问题是最难排查的,需要分层诊断。

Pod 内网络诊断

# 进入 Pod 检查 DNS
kubectl exec -it <pod-name> -- nslookup kubernetes.default
kubectl exec -it <pod-name> -- nslookup <service-name>.<namespace>.svc.cluster.local

# 检查 Service 连通性
kubectl exec -it <pod-name> -- curl -v http://<service-name>:<port>/health

# 检查外部网络
kubectl exec -it <pod-name> -- curl -v https://google.com

# 临时调试 Pod(如果目标 Pod 没有 curl)
kubectl run debug --image=busybox -it --rm -- sh
# 在里面执行 wget/curl/nslookup

Service 诊断

# 1. 检查 Service 是否有 Endpoints
kubectl get endpoints <service-name>
# 如果为空 → 标签选择器不匹配

# 2. 对比标签
kubectl get pods -l app=my-app --show-labels
kubectl get svc <service-name> -o jsonpath='{.spec.selector}'

# 3. 检查 Service 类型
kubectl get svc <service-name> -o wide

# 4. 直接访问 ClusterIP 测试
kubectl exec -it <any-pod> -- curl http://<cluster-ip>:<port>/

CNI 排障

# 检查 CNI 插件状态
ls /etc/cni/net.d/
ls /opt/cni/bin/

# 常见 CNI 问题
# Calico:
kubectl get pods -n kube-system -l k8s-app=calico-node
kubectl logs -n kube-system -l k8s-app=calico-node

# Flannel:
kubectl get pods -n kube-system -l app=flannel
kubectl logs -n kube-system -l app=flannel

# 检查 iptables 规则
iptables -t nat -L KUBE-SERVICES | head -20

# 检查节点路由
ip route show

kubectl 排障命令速查表

# === 资源查看 ===
kubectl get all -n <namespace>                    # 查看所有资源
kubectl get pods -A -o wide                       # 所有命名空间的 Pod
kubectl get events --sort-by='.lastTimestamp'      # 按时间排序的事件
kubectl api-resources                             # 查看可用资源类型

# === Pod 排查 ===
kubectl describe pod <pod>                        # Pod 详情和事件
kubectl logs <pod> -c <container>                 # 指定容器日志
kubectl logs <pod> --previous                     # 上次崩溃的日志
kubectl logs <pod> -f --tail=100                  # 实时跟踪最后100行
kubectl logs -l app=my-app --all-containers       # 按标签查所有容器日志
kubectl exec -it <pod> -- /bin/sh                 # 进入容器
kubectl port-forward <pod> 8080:80                # 端口转发

# === 节点排查 ===
kubectl describe node <node>                      # 节点详情
kubectl top nodes                                 # 节点资源使用
kubectl get nodes -o wide                         # 节点状态
kubectl cordon <node>                             # 标记节点不可调度
kubectl drain <node> --ignore-daemonsets          # 驱逐 Pod

# === 网络排查 ===
kubectl get svc,endpoints,pods -l app=my-app      # 关联查看
kubectl run tmp --image=busybox -it --rm -- sh    # 临时调试 Pod

# === 高级 ===
kubectl get pod <pod> -o yaml                     # 完整 YAML
kubectl explain pod.spec.containers               # 查看 API 文档
kubectl diff -f deployment.yaml                   # 预览变更

日志聚合技巧

# 使用 stern 聚合多 Pod 日志(比 kubectl logs -l 更强大)
# 安装:brew install stern

# 跟踪特定 Deployment 的所有 Pod 日志
stern my-app -n production

# 正则过滤
stern my-app -n production --pattern "ERROR|WARN"

# 多命名空间
stern my-app -n staging,production

# 输出 JSON 格式
stern my-app -o json

# 使用 kubectl 的 jsonpath 过滤
kubectl logs -l app=my-app --tail=100 | jq 'select(.level=="error")'

常见陷阱

1. 忽略 initContainer 失败

# Pod 一直卡在 Init:0/2,但 describe 可能不明显
kubectl describe pod <pod> | grep -A10 "Init Containers"

# 查看 initContainer 日志
kubectl logs <pod> -c <init-container-name>

2. 忽略资源配额限制

# Pod 创建失败但不是 Pending
# 错误可能在 namespace 的 ResourceQuota 或 LimitRange
kubectl describe resourcequota -n <namespace>
kubectl describe limitrange -n <namespace>

3. PVC 未绑定导致 Pod 卡住

# Pod 一直 ContainerCreating
kubectl describe pod <pod> | grep -A5 "Volumes"
kubectl get pvc
# 如果 PVC 是 Pending,检查 StorageClass 和 PV
kubectl describe pvc <pvc-name>

总结

Kubernetes 排障是一项需要系统化思维的技能:

  • 先看状态,再看日志describe 中的 Events 往往比日志更有诊断价值
  • 从内到外:Container → Pod → Service → Ingress,逐层排查
  • 善用 --previous:CrashLoopBackOff 一定要看上一次的日志
  • 关注资源:OOMKilled 和 Pending 大多是资源问题
  • 网络分层:DNS → Service → CNI → 外部网络

记住排障的黄金法则:不要猜测,用数据说话。先收集信息(状态、事件、日志),再定位根因,最后修复验证。

评论