Kubernetes Pod异常问题排查实战

描述

一、概述

1.1 背景介绍

集群跑着跑着,Pod 挂了。Slack 告警一刷屏,脑子一片空白。打开终端敲 kubectl get pods,看到一堆 CrashLoopBackOff、ImagePullBackOff、Pending,不知道从哪下手。这是每个刚接触 Kubernetes 运维的人都会遇到的场景。

Pod 是 Kubernetes 最小的调度单元,也是出问题最多的地方。一个 Pod 异常可能是镜像拉不下来,可能是 OOMKilled,可能是探针配错了,也可能是节点磁盘满了导致 Evicted。原因千差万别,但排查思路是有章可循的。

这篇文章把 Pod 异常排查拆成一条完整的链路:从 Pod 状态出发,看 Events,查容器日志,排节点资源,查网络策略,最后到存储挂载。每一步给命令,给判断依据,给修复手段。适用于 Kubernetes 1.32 + containerd 2.0 环境。

1.2 基础背景 / 核心语义

术语 含义 排障关注点
Pending Pod 已被 API Server 接受,但尚未被调度到节点 资源不足、nodeSelector/affinity 不匹配、PVC 未绑定
ContainerCreating 容器正在创建中,通常在拉镜像或挂载卷 镜像拉取超时、Secret 不存在、卷挂载失败
CrashLoopBackOff 容器反复启动又退出,退避时间逐步增加 应用启动失败、配置错误、依赖服务不可用
ImagePullBackOff 镜像拉取失败并进入退避 镜像不存在、认证失败、网络不通
OOMKilled 容器因内存超限被内核杀死 limits 设置过低、内存泄漏
Evicted Pod 被节点驱逐 节点磁盘压力、内存压力、PID 压力
Terminating Pod 正在终止但未完成 finalizer 卡住、preStop 超时、进程未响应 SIGTERM
Init:Error Init 容器执行失败 Init 容器命令错误、依赖检查失败

1.3 适用场景

生产环境 Pod 异常告警后的快速定位

新应用部署后 Pod 起不来的排查

节点故障导致大批 Pod 漂移后的状态检查

日常巡检中发现异常 Pod 的处理

升级 Kubernetes 版本后兼容性问题排查

1.4 环境要求

组件 版本 说明
Kubernetes 1.32.x 支持 Sidecar Containers、改进的 Pod Lifecycle
containerd 2.0.x 默认运行时,移除了 v1 API 支持
kubectl 1.32.x 与集群版本匹配
crictl 1.32.x 用于直接与 containerd 交互排障
Linux Kernel 6.12+ 支持 cgroup v2
操作系统 Ubuntu 24.04 / Rocky 9.5 推荐 LTS 版本

1.5 排障坐标系

整个排查路径按以下顺序推进,每一层确认无问题后再进下一层:

 

Pod 状态 → Events → 容器日志 → 节点资源 → 网络策略 → 存储挂载
   │          │         │           │           │          │
   ▼          ▼         ▼           ▼           ▼          ▼
 Phase     调度/拉镜   应用异常    CPU/Mem/    CNI/DNS/   PVC/CSI/
 Reason    /挂载事件   /崩溃原因  Disk/PID    NetworkPolicy  Mount

 

排障核心原则:先广后深。先用最快的命令拿到全局视图,再根据线索逐层下钻。

二、详细步骤

2.1 先把观测面补齐

2.1.1 获取 Pod 全局视图

 

# 查看所有命名空间的异常 Pod
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded | head -50

# 查看特定命名空间的 Pod 状态(含 IP、节点信息)
kubectl get pods -n  -o wide

# 查看 Pod 的详细状态(重点看 Status、Conditions、containerStatuses)
kubectl get pod  -n  -o yaml | grep -A 30 "status:"

 

2.1.2 获取 Events

 

# 查看特定 Pod 的 Events
kubectl describe pod  -n  | tail -30

# 按时间排序查看命名空间下所有 Events
kubectl get events -n  --sort-by='.lastTimestamp' | tail -30

# 查看 Warning 级别 Events
kubectl get events -n  --field-selector type=Warning --sort-by='.lastTimestamp'

 

2.1.3 获取容器日志

 

# 当前容器日志
kubectl logs  -n  -c 

# 上一次崩溃的容器日志(CrashLoopBackOff 时必用)
kubectl logs  -n  -c  --previous

# 最近 100 行日志
kubectl logs  -n  --tail=100

# Init 容器日志
kubectl logs  -n  -c 

 

2.1.4 使用 kubectl debug 进入临时调试容器

Kubernetes 1.32 的 Ephemeral Containers 已经 GA,可以直接往运行中的 Pod 注入调试容器:

 

# 注入一个 busybox 调试容器
kubectl debug -it  -n  --image=busybox:1.37 --target=

# 使用 nicolaka/netshoot 排查网络问题
kubectl debug -it  -n  --image=nicolaka/netshoot:v0.13 --target=

# 在节点上启动调试 Pod(排查节点级问题)
kubectl debug node/ -it --image=busybox:1.37

 

2.2 第一轮判断

根据 Pod 状态快速分流:

Pod 状态 第一步动作 预期线索来源
Pending kubectl describe pod 看 Events 调度失败原因:资源不足、亲和性不匹配、PVC 未绑定
ContainerCreating kubectl describe pod 看 Events 镜像拉取状态、卷挂载状态、Secret 获取状态
ImagePullBackOff kubectl describe pod 看 Events 镜像名称/Tag 是否正确、Registry 认证、网络连通性
CrashLoopBackOff kubectl logs --previous 应用启动报错、配置缺失、端口冲突
OOMKilled kubectl describe pod 看 lastState limits.memory 值、应用实际内存用量
Evicted kubectl describe pod 看 message 节点压力类型(DiskPressure/MemoryPressure)
Terminating 卡住 kubectl describe pod 看 finalizers finalizer 未清除、preStop hook 超时
Running 但不健康 kubectl describe pod 看 Conditions readinessProbe/livenessProbe 失败

2.2.1 Pending 状态排查

 

# 查看调度失败的具体原因
kubectl describe pod  -n  | grep -A 5 "Events"

# 检查集群可用资源
kubectl top nodes

# 检查是否有 Taints 阻止调度
kubectl describe nodes | grep -A 3 "Taints"

# 检查 PVC 绑定状态
kubectl get pvc -n 

 

2.2.2 CrashLoopBackOff 排查

 

# 看上一次崩溃日志
kubectl logs  -n  --previous

# 看容器退出码
kubectl get pod  -n  -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'

# 常见退出码含义:
# 1 = 应用错误
# 137 = OOMKilled (128+9=SIGKILL)
# 139 = Segfault (128+11=SIGSEGV)
# 143 = SIGTERM (128+15)

 

2.3 第二轮下钻

2.3.1 节点资源排查

 

# 查看节点资源使用情况
kubectl top nodes

# 查看节点 Conditions
kubectl describe node  | grep -A 10 "Conditions"

# 检查节点上的 kubelet 日志
journalctl -u kubelet -n 100 --no-pager

# 检查 containerd 状态
systemctl status containerd
crictl info

 

2.3.2 网络排查

 

# 检查 Pod 网络连通性(从调试容器内)
kubectl debug -it  -n  --image=nicolaka/netshoot:v0.13 -- bash

# 在调试容器内执行
nslookup kubernetes.default.svc.cluster.local
curl -v http://..svc.cluster.local:/health
ping 

# 检查 NetworkPolicy
kubectl get networkpolicy -n 
kubectl describe networkpolicy  -n 

# 检查 DNS 是否正常
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50

 

2.3.3 存储排查

 

# 检查 PVC 状态
kubectl get pvc -n 
kubectl describe pvc  -n 

# 检查 PV 状态
kubectl get pv

# 检查 StorageClass
kubectl get sc

# 检查 CSI driver 状态
kubectl get pods -n kube-system | grep csi

# 查看挂载是否成功
kubectl describe pod  -n  | grep -A 10 "Volumes"

 

2.4 根因矩阵

现象 可能根因 关键证据 优先级
Pending 超过 5 分钟 资源不足 Events: Insufficient cpu/memory P1
Pending 超过 5 分钟 PVC 未绑定 Events: pod has unbound immediate PersistentVolumeClaims P1
ImagePullBackOff 镜像 Tag 不存在 Events: manifest unknown P2
ImagePullBackOff Registry 认证失败 Events: unauthorized P2
CrashLoopBackOff (exit 1) 应用启动失败 --previous 日志中的错误栈 P1
CrashLoopBackOff (exit 137) OOMKilled describe pod 中 reason: OOMKilled P1
Evicted 节点磁盘压力 message: The node was low on resource: ephemeral-storage P1
Running 但 0/1 Ready 探针失败 Events: Readiness probe failed P2
Terminating 卡住 Finalizer 未清除 metadata.finalizers 不为空 P3
ContainerCreating 卡住 Secret 不存在 Events: secret "xxx" not found P2

2.5 处理与验证

2.5.1 常见修复操作

 

# 强制删除 Terminating 卡住的 Pod
kubectl delete pod  -n  --grace-period=0 --force

# 手动清除 Finalizer(谨慎操作)
kubectl patch pod  -n  -p '{"metadata":{"finalizers":null}}'

# 扩容节点资源后重新触发调度
kubectl delete pod  -n 

# 修复 ImagePullSecret
kubectl create secret docker-registry regcred 
  --docker-server= 
  --docker-username= 
  --docker-password= 
  -n 

 

2.5.2 修复后验证

 

# 确认 Pod 进入 Running 且 Ready
kubectl get pod  -n  -w

# 确认 Events 无 Warning
kubectl get events -n  --field-selector type=Warning --sort-by='.lastTimestamp' | tail -10

# 确认应用健康
kubectl exec  -n  -- wget -qO- http://localhost:/health

 

三、示例代码和配置

3.1 配置样例

一个典型的 Pod 配置,包含资源限制、探针、优雅终止等排障相关字段:

 

# 文件:deployment-with-probes.yaml
# 说明:包含完整探针和资源限制的 Deployment 配置
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:
      terminationGracePeriodSeconds: 30
      containers:
      - name: web
        image: registry.internal/web-app:v2.1.0
        ports:
        - containerPort: 8080
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 500m
            memory: 512Mi
        startupProbe:
          httpGet:
            path: /health/startup
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
          failureThreshold: 30
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8080
          periodSeconds: 10
          failureThreshold: 3
        livenessProbe:
          httpGet:
            path: /health/live
            port: 8080
          periodSeconds: 15
          failureThreshold: 3
        lifecycle:
          preStop:
            exec:
              command: ["/bin/sh", "-c", "sleep 5"]
        volumeMounts:
        - name: config
          mountPath: /etc/app/config.yaml
          subPath: config.yaml
        - name: data
          mountPath: /data
      volumes:
      - name: config
        configMap:
          name: web-app-config
      - name: data
        persistentVolumeClaim:
          claimName: web-app-data

 

3.2 脚本一:快速采集脚本

 

#!/bin/bash
# 文件名:pod-diag-collect.sh
# 作用:一键采集异常 Pod 的诊断信息,输出到指定目录
# 适用场景:Pod 异常时快速收集现场证据,避免信息丢失
# 使用方式:bash pod-diag-collect.sh   [output-dir]
# 输入参数:
#   $1 - Pod 名称(必填)
#   $2 - Namespace(必填)
#   $3 - 输出目录(可选,默认 /tmp/pod-diag)
# 输出结果:在输出目录下生成包含 describe、logs、events 等信息的文件
# 风险提示:仅读取操作,不会修改任何资源;日志文件可能较大

POD_NAME="${1:?用法: $0   [output-dir]}"
NAMESPACE="${2:?缺少 namespace 参数}"
OUTPUT_DIR="${3:-/tmp/pod-diag}"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
DIAG_DIR="${OUTPUT_DIR}/${POD_NAME}_${TIMESTAMP}"

mkdir -p "${DIAG_DIR}"

echo "[INFO] 采集 Pod 描述信息..."
kubectl describe pod "${POD_NAME}" -n "${NAMESPACE}" > "${DIAG_DIR}/describe.txt" 2>&1

echo "[INFO] 采集当前容器日志..."
for CONTAINER in $(kubectl get pod "${POD_NAME}" -n "${NAMESPACE}" -o jsonpath='{.spec.containers[*].name}'); do
    kubectl logs "${POD_NAME}" -n "${NAMESPACE}" -c "${CONTAINER}" --tail=500 
        > "${DIAG_DIR}/logs_${CONTAINER}.txt" 2>&1
    kubectl logs "${POD_NAME}" -n "${NAMESPACE}" -c "${CONTAINER}" --previous --tail=500 
        > "${DIAG_DIR}/logs_${CONTAINER}_previous.txt" 2>&1
done

echo "[INFO] 采集 Init 容器日志..."
for CONTAINER in $(kubectl get pod "${POD_NAME}" -n "${NAMESPACE}" -o jsonpath='{.spec.initContainers[*].name}'); do
    kubectl logs "${POD_NAME}" -n "${NAMESPACE}" -c "${CONTAINER}" --tail=500 
        > "${DIAG_DIR}/logs_init_${CONTAINER}.txt" 2>&1
done

echo "[INFO] 采集 Events..."
kubectl get events -n "${NAMESPACE}" --field-selector "involvedObject.name=${POD_NAME}" 
    --sort-by='.lastTimestamp' > "${DIAG_DIR}/events.txt" 2>&1

echo "[INFO] 采集 Pod YAML..."
kubectl get pod "${POD_NAME}" -n "${NAMESPACE}" -o yaml > "${DIAG_DIR}/pod.yaml" 2>&1

echo "[INFO] 采集节点信息..."
NODE=$(kubectl get pod "${POD_NAME}" -n "${NAMESPACE}" -o jsonpath='{.spec.nodeName}')
if [ -n "${NODE}" ]; then
    kubectl describe node "${NODE}" > "${DIAG_DIR}/node_describe.txt" 2>&1
    kubectl top node "${NODE}" > "${DIAG_DIR}/node_top.txt" 2>&1
fi

echo "[DONE] 诊断数据已保存到: ${DIAG_DIR}"
ls -la "${DIAG_DIR}"

 

3.3 脚本二:日志归桶/诊断脚本

 

#!/bin/bash
# 文件名:pod-log-classifier.sh
# 作用:扫描指定命名空间下所有异常 Pod,按状态分类输出诊断建议
# 适用场景:集群升级后批量检查、日常巡检、大面积故障初筛
# 使用方式:bash pod-log-classifier.sh 
# 输入参数:$1 - Namespace(必填,使用 --all 扫描所有命名空间)
# 输出结果:按异常类型分组输出 Pod 列表及初步诊断建议
# 风险提示:仅读取操作;大集群下可能执行时间较长

NAMESPACE="${1:?用法: $0 }"

if [ "${NAMESPACE}" = "--all" ]; then
    NS_FLAG="-A"
else
    NS_FLAG="-n ${NAMESPACE}"
fi

echo "=========================================="
echo " Pod 异常分类诊断报告"
echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="

echo ""
echo "--- CrashLoopBackOff ---"
kubectl get pods ${NS_FLAG} --no-headers 2>/dev/null | grep "CrashLoopBackOff" | while read line; do
    POD=$(echo "$line" | awk '{print $2}')
    NS_ACTUAL=$(echo "$line" | awk '{print $1}')
    EXIT_CODE=$(kubectl get pod "${POD}" -n "${NS_ACTUAL}" -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}' 2>/dev/null)
    RESTART_COUNT=$(echo "$line" | awk '{print $5}')
    echo "  Pod: ${NS_ACTUAL}/${POD} | 退出码: ${EXIT_CODE} | 重启次数: ${RESTART_COUNT}"
    case "${EXIT_CODE}" in
        1)   echo "    -> 应用错误,检查 --previous 日志" ;;
        137) echo "    -> OOMKilled,检查 memory limits" ;;
        139) echo "    -> 段错误,检查应用二进制兼容性" ;;
        143) echo "    -> SIGTERM,可能是探针超时或优雅终止问题" ;;
        *)   echo "    -> 退出码 ${EXIT_CODE},检查应用日志" ;;
    esac
done

echo ""
echo "--- ImagePullBackOff ---"
kubectl get pods ${NS_FLAG} --no-headers 2>/dev/null | grep "ImagePullBackOff|ErrImagePull" | while read line; do
    POD=$(echo "$line" | awk '{print $2}')
    NS_ACTUAL=$(echo "$line" | awk '{print $1}')
    IMAGE=$(kubectl get pod "${POD}" -n "${NS_ACTUAL}" -o jsonpath='{.spec.containers[0].image}' 2>/dev/null)
    echo "  Pod: ${NS_ACTUAL}/${POD} | 镜像: ${IMAGE}"
    echo "    -> 检查镜像是否存在、Tag 是否正确、ImagePullSecret 是否配置"
done

echo ""
echo "--- Pending ---"
kubectl get pods ${NS_FLAG} --no-headers 2>/dev/null | grep "Pending" | while read line; do
    POD=$(echo "$line" | awk '{print $2}')
    NS_ACTUAL=$(echo "$line" | awk '{print $1}')
    echo "  Pod: ${NS_ACTUAL}/${POD}"
    echo "    -> 检查节点资源、PVC 绑定状态、nodeSelector/affinity 配置"
done

echo ""
echo "--- Evicted ---"
kubectl get pods ${NS_FLAG} --no-headers 2>/dev/null | grep "Evicted" | while read line; do
    POD=$(echo "$line" | awk '{print $2}')
    NS_ACTUAL=$(echo "$line" | awk '{print $1}')
    echo "  Pod: ${NS_ACTUAL}/${POD}"
    echo "    -> 节点资源压力导致驱逐,检查节点 Conditions"
done

echo ""
echo "=========================================="
echo " 扫描完成"
echo "=========================================="

 

3.4 脚本三:验证脚本

 

#!/bin/bash
# 文件名:pod-health-verify.sh
# 作用:修复操作后,批量验证指定命名空间下 Pod 健康状态
# 适用场景:故障修复后的批量确认、部署后的健康检查
# 使用方式:bash pod-health-verify.sh  [timeout-seconds]
# 输入参数:
#   $1 - Namespace(必填)
#   $2 - 超时时间,单位秒(可选,默认 120)
# 输出结果:输出各 Pod 的健康状态,最终给出通过/失败统计
# 风险提示:仅读取操作;对 Pod 执行 health 检查可能产生少量请求

NAMESPACE="${1:?用法: $0  [timeout-seconds]}"
TIMEOUT="${2:-120}"
PASS=0
FAIL=0
WARN=0

echo "[INFO] 开始验证 ${NAMESPACE} 命名空间 Pod 健康状态 (超时: ${TIMEOUT}s)"
echo "---"

kubectl get pods -n "${NAMESPACE}" --no-headers 2>/dev/null | while read line; do
    POD_NAME=$(echo "$line" | awk '{print $1}')
    STATUS=$(echo "$line" | awk '{print $3}')
    READY=$(echo "$line" | awk '{print $2}')
    READY_CURRENT=$(echo "${READY}" | cut -d'/' -f1)
    READY_TOTAL=$(echo "${READY}" | cut -d'/' -f2)

    if [ "${STATUS}" = "Running" ] && [ "${READY_CURRENT}" = "${READY_TOTAL}" ]; then
        echo "[PASS] ${POD_NAME} - ${STATUS} (${READY})"
    elif [ "${STATUS}" = "Completed" ] || [ "${STATUS}" = "Succeeded" ]; then
        echo "[PASS] ${POD_NAME} - ${STATUS} (Job 已完成)"
    elif [ "${STATUS}" = "Running" ] && [ "${READY_CURRENT}" != "${READY_TOTAL}" ]; then
        echo "[WARN] ${POD_NAME} - ${STATUS} (${READY}) - 部分容器未就绪"
    else
        echo "[FAIL] ${POD_NAME} - ${STATUS} (${READY})"
    fi
done

echo "---"
echo "[INFO] 检查 Warning Events..."
WARNING_COUNT=$(kubectl get events -n "${NAMESPACE}" --field-selector type=Warning --sort-by='.lastTimestamp' 2>/dev/null | tail -5 | wc -l)
if [ "${WARNING_COUNT}" -gt 1 ]; then
    echo "[WARN] 存在 ${WARNING_COUNT} 条 Warning Events,建议人工复核"
    kubectl get events -n "${NAMESPACE}" --field-selector type=Warning --sort-by='.lastTimestamp' | tail -5
else
    echo "[PASS] 无 Warning Events"
fi

echo "---"
echo "[INFO] 验证完成"

 

3.5 脚本四:回滚/批量探测脚本

 

#!/bin/bash
# 文件名:deployment-rollback-check.sh
# 作用:检查 Deployment 滚动更新状态,发现异常时提示回滚命令
# 适用场景:上线后观察滚动更新是否成功、批量检查多个 Deployment 状态
# 使用方式:bash deployment-rollback-check.sh 
# 输入参数:$1 - Namespace(必填)
# 输出结果:输出每个 Deployment 的更新状态,异常时输出回滚命令
# 风险提示:仅读取和分析操作,不会自动执行回滚;回滚命令需人工确认后执行

NAMESPACE="${1:?用法: $0 }"

echo "=========================================="
echo " Deployment 滚动更新状态检查"
echo " 命名空间: ${NAMESPACE}"
echo " 时间: $(date '+%Y-%m-%d %H:%M:%S')"
echo "=========================================="

kubectl get deployments -n "${NAMESPACE}" --no-headers 2>/dev/null | while read line; do
    DEPLOY=$(echo "$line" | awk '{print $1}')
    READY=$(echo "$line" | awk '{print $2}')
    UPTODATE=$(echo "$line" | awk '{print $3}')
    AVAILABLE=$(echo "$line" | awk '{print $4}')
    DESIRED=$(echo "${READY}" | cut -d'/' -f2)
    CURRENT_READY=$(echo "${READY}" | cut -d'/' -f1)

    # 检查是否有异常 Pod
    ABNORMAL=$(kubectl get pods -n "${NAMESPACE}" -l app="${DEPLOY}" --no-headers 2>/dev/null | grep -cE "CrashLoopBackOff|ImagePullBackOff|Error|OOMKilled")

    if [ "${CURRENT_READY}" = "${DESIRED}" ] && [ "${ABNORMAL}" -eq 0 ]; then
        echo "[OK] ${DEPLOY}: ${READY} ready, ${UPTODATE} up-to-date"
    else
        echo "[ALERT] ${DEPLOY}: ${READY} ready, 异常 Pod 数量: ${ABNORMAL}"
        echo "  最近更新历史:"
        kubectl rollout history deployment/"${DEPLOY}" -n "${NAMESPACE}" | tail -5
        echo ""
        echo "  建议回滚命令(需人工确认后执行):"
        echo "    kubectl rollout undo deployment/${DEPLOY} -n ${NAMESPACE}"
        echo "    kubectl rollout status deployment/${DEPLOY} -n ${NAMESPACE} --timeout=120s"
        echo ""
    fi
done

echo "=========================================="
echo " 检查完成"
echo "=========================================="

 

四、实际应用案例

案例一:镜像 Tag 不存在导致 ImagePullBackOff

现场现象: 研发提交了新版本上线,Deployment 更新后新 Pod 一直停在 ImagePullBackOff,旧 Pod 正常运行。

第一轮判断:

 

kubectl get pods -n production | grep web-api
# web-api-7d8f9c6b4-x2k9m   0/1   ImagePullBackOff   0   3m

 

状态明确是 ImagePullBackOff,直接看 Events。

第二轮下钻:

 

kubectl describe pod web-api-7d8f9c6b4-x2k9m -n production | tail -15

 

Events 输出:

 

Warning  Failed   2m   kubelet  Failed to pull image "registry.internal/web-api:v3.2.1":
         rpc error: code = NotFound desc = failed to pull and unpack image: manifest unknown
Warning  Failed   2m   kubelet  Error: ErrImagePull

 

关键证据:manifest unknown 说明镜像 Tag v3.2.1 在 Registry 中不存在。

根因: 研发推送镜像时 CI 流水线失败,镜像没有 push 成功,但 Deployment YAML 已经更新了 Tag。

修复动作:

 

# 确认 Registry 中实际存在的 Tag
skopeo list-tags docker://registry.internal/web-api | grep v3.2

# 实际最新 Tag 是 v3.2.0,先回滚到上一版本
kubectl rollout undo deployment/web-api -n production

 

修复后验证:

 

kubectl rollout status deployment/web-api -n production --timeout=60s
kubectl get pods -n production | grep web-api
# 所有 Pod 回到 Running 状态

 

防再发建议: CI 流水线增加镜像推送成功的校验步骤,推送失败时阻断后续 Deployment YAML 更新。

案例二:OOMKilled 引发 CrashLoopBackOff

现场现象: Java 微服务上线后频繁重启,Pod 状态反复在 Running 和 CrashLoopBackOff 之间切换,重启次数持续增长。

第一轮判断:

 

kubectl get pod order-svc-5f4d8c7a9-abc12 -n production -o jsonpath='{.status.containerStatuses[0].lastState.terminated}'

 

输出:

 

{"exitCode":137,"reason":"OOMKilled","startedAt":"2026-03-13T0200Z","finishedAt":"2026-03-13T0232Z"}

 

退出码 137,reason 明确是 OOMKilled。

第二轮下钻:

 

# 查看资源限制
kubectl get pod order-svc-5f4d8c7a9-abc12 -n production -o jsonpath='{.spec.containers[0].resources}'

 

输出:{"limits":{"cpu":"500m","memory":"256Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}

Java 应用 JVM 默认堆内存会根据容器 limits 计算,256Mi 对于该服务远远不够。

关键证据: 应用日志(--previous)中可以看到 GC 频繁,最终被 OOM killer 杀掉。

根因: 资源 limits.memory 设置过低,没有根据 Java 应用实际内存需求调整。

修复动作:

 

# 修改 Deployment 中 resources 配置
resources:
  requests:
    cpu: 200m
    memory: 512Mi
  limits:
    cpu: "1"
    memory: 1Gi

 

同时在 Deployment 中通过环境变量显式设置 JVM 参数:

 

env:
- name: JAVA_OPTS
  value: "-Xms384m -Xmx768m -XX:MaxMetaspaceSize=128m"

 

修复后验证:

 

kubectl top pod -n production | grep order-svc
# 确认内存使用在 limits 范围内,无 OOMKilled 事件
kubectl get events -n production --field-selector type=Warning | grep -i oom
# 无输出,确认无 OOM 事件

 

防再发建议: 为所有 Java 服务建立资源基线,通过 VPA Recommender 获取建议值;CI 模板中强制要求 JVM 参数显式配置。

案例三:节点磁盘满导致 Pod Evicted

现场现象: 凌晨 3 点告警,某节点上 30+ Pod 被驱逐,状态全部变为 Evicted。

第一轮判断:

 

kubectl get pods -A --field-selector=status.phase=Failed | grep Evicted | head -10

 

大量 Pod 状态为 Evicted,集中在同一个节点 node-worker-03。

第二轮下钻:

 

kubectl describe node node-worker-03 | grep -A 5 "Conditions"

 

输出:

 

Conditions:
  Type             Status  Reason
  ----             ------  ------
  MemoryPressure   False   KubeletHasSufficientMemory
  DiskPressure     True    KubeletHasDiskPressure
  PIDPressure      False   KubeletHasSufficientPID
  Ready            True    KubeletReady

 

DiskPressure 为 True,确认是磁盘压力。

 

# SSH 到节点检查磁盘
df -h /
# Filesystem   Size  Used  Avail  Use%  Mounted on
# /dev/sda1    100G   97G    3G    97%  /

 

进一步排查磁盘占用:

 

du -sh /var/lib/containerd/* | sort -rh | head -5
du -sh /var/log/* | sort -rh | head -5

 

关键证据: /var/lib/containerd/io.containerd.snapshotter.v1.overlayfs 目录占用 60G,大量未清理的容器镜像层。

根因: 镜像清理策略未配置,containerd GC 没有及时回收无用镜像层。

修复动作:

 

# 清理无用镜像
crictl rmi --prune

# 清理已完成/失败的容器
crictl rm $(crictl ps -a --state exited -q)

# 清理旧日志
journalctl --vacuum-size=500M

 

修复后验证:

 

df -h /
# Use% 降至 55%

kubectl describe node node-worker-03 | grep DiskPressure
# DiskPressure   False   KubeletHasNoDiskPressure

 

清理 Evicted Pod:

 

kubectl get pods -A --field-selector=status.phase=Failed | grep Evicted | awk '{print $2 " -n " $1}' | xargs -L1 kubectl delete pod

 

防再发建议: 配置 kubelet 的 imageGCHighThresholdPercent(默认 85%)和 imageGCLowThresholdPercent(默认 80%),在 containerd 配置中启用定期 GC;添加节点磁盘使用率告警(阈值 80%)。

案例四:NetworkPolicy 阻断导致 Pod 探针失败

现场现象: 安全团队在生产命名空间启用了默认拒绝所有入站流量的 NetworkPolicy 后,大量 Pod 的 readinessProbe 失败,Service 后端没有可用 Endpoint。

第一轮判断:

 

kubectl get pods -n production
# 所有 Pod 都是 Running 但 Ready 列显示 0/1

 

Pod 在跑但没有 Ready,说明探针失败。

 

kubectl describe pod web-app-xxx -n production | grep -A 5 "Readiness"

 

输出:Warning  Unhealthy  Readiness probe failed: Get "http://10.244.3.15:8080/health/ready": dial tcp 10.244.3.15 i/o timeout

第二轮下钻:

探针超时说明网络不通。检查 NetworkPolicy:

 

kubectl get networkpolicy -n production
# NAME              POD-SELECTOR   AGE
# deny-all-ingress           15m
kubectl describe networkpolicy deny-all-ingress -n production

 

输出确认该策略拒绝了所有入站流量,包括 kubelet 发起的探针请求。

关键证据: NetworkPolicy deny-all-ingress 没有豁免 kubelet 的探针请求源 IP。

根因: 安全团队应用了 deny-all 基线策略但未添加 kubelet 探针的白名单规则。

修复动作:

 

# 文件:allow-kubelet-probes.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-kubelet-probes
  namespace: production
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  ingress:
  - from:
    - ipBlock:
        cidr: 10.0.0.0/8
    ports:
    - protocol: TCP
      port: 8080
kubectl apply -f allow-kubelet-probes.yaml

 

修复后验证:

 

# 等待探针恢复
kubectl get pods -n production -w
# 几秒后所有 Pod 变为 1/1 Ready

kubectl get endpoints web-app-svc -n production
# 确认 Endpoints 列表有 IP

 

防再发建议: NetworkPolicy 变更纳入变更管理流程,应用前在 staging 环境验证;使用 Cilium/Calico 的 Policy Audit 模式先观察再执行。

五、最佳实践和注意事项

5.1 最佳实践

资源 requests/limits 必须配置: 不配 requests 会导致调度不可预测,不配 limits 会导致单个 Pod 耗尽节点资源。使用 VPA Recommender 获取基线值。

三种探针都要配: startupProbe 给慢启动应用足够的时间,livenessProbe 检测死锁,readinessProbe 控制流量接入。不要用 livenessProbe 做 readinessProbe 的事。

terminationGracePeriodSeconds 要合理: 默认 30 秒,对于需要排空连接的应用可能不够。配合 preStop hook 使用,确保应用有时间处理完存量请求。

日志输出到 stdout/stderr: 不要只写文件。容器崩溃后 kubectl logs --previous 只能看到标准输出的内容。日志写文件的场景要配合 sidecar 收集。

镜像 Tag 不用 latest: 每次部署使用确定的版本号或 Git SHA,方便回滚和追溯。imagePullPolicy 配合 IfNotPresent 使用。

Pod Disruption Budget 必须配置: 避免节点维护或集群自动缩容时所有副本同时被驱逐。

节点资源告警前置: CPU、内存、磁盘使用率超过 80% 就应该告警,不要等到 Eviction 发生。

使用 kubectl debug 替代 exec + 安装工具: Ephemeral Containers 不会污染业务镜像,调试完自动清理。

5.2 注意事项

--force --grace-period=0 删除 Pod 是最后手段: 强制删除可能导致存储卷未正常 unmount,造成数据损坏。

手动清除 Finalizer 要确认安全: Finalizer 存在是有原因的(比如需要清理外部资源),盲目删除可能导致资源泄漏。

Evicted Pod 不会自动清理: 需要手动或通过 CronJob 定期清理,否则 etcd 中堆积大量无用对象。

NetworkPolicy 是命名空间级别的: 跨命名空间通信需要明确在双方命名空间都配置策略。

containerd 2.0 移除了 v1 API: 从低版本升级时,确认所有工具链(crictl、cri-dockerd 等)兼容 v2 API。

5.3 常见错误清单

错误现象 常见原因 修复方法
探针成功但 Pod 被重启 livenessProbe 超时设置过短 增加 timeoutSeconds 和 failureThreshold
Pod 调度到意外节点 缺少 nodeAffinity 或 tolerations 补充调度约束配置
Init 容器卡住不退出 Init 容器逻辑有死循环 检查 Init 容器命令和退出条件
ConfigMap 更新后 Pod 没生效 subPath 挂载不支持自动更新 不用 subPath 或重启 Pod
Service 无 Endpoints selector 标签不匹配 kubectl get endpoints 确认并修正 selector
HPA 不生效 metrics-server 未部署或 Pod 无 requests 部署 metrics-server 并配置 requests
Pod 启动慢 镜像太大,拉取耗时长 优化镜像体积,使用多阶段构建
滚动更新卡住 maxUnavailable=0 且新 Pod 起不来 检查新版 Pod 日志,必要时手动回滚

六、故障排查和监控

6.1 关键指标

 

# Pod 重启次数
kubectl get pods -n  --sort-by='.status.containerStatuses[0].restartCount'

# 节点资源使用率
kubectl top nodes

# Pod 资源使用率
kubectl top pods -n  --sort-by=memory

# 集群 Events 告警数
kubectl get events -A --field-selector type=Warning | wc -l

 

6.2 指标说明

指标 PromQL 表达式 正常范围 告警阈值 说明
Pod 重启率 rate(kube_pod_container_status_restarts_total[5m]) 0 > 0 持续 5 分钟 持续重启说明有未恢复的异常
Pod 非 Ready 数 kube_pod_status_ready{condition="false"} 0 > 0 持续 10 分钟 非 Ready Pod 不接收流量
节点 CPU 使用率 1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance) < 70% > 85% 高 CPU 可能导致调度失败
节点内存使用率 1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 75% > 85% 高内存触发 Eviction
节点磁盘使用率 1 - node_filesystem_avail_bytes / node_filesystem_size_bytes < 80% > 85% 高磁盘触发 Eviction
Pending Pod 数 kube_pod_status_phase{phase="Pending"} 0 > 0 持续 5 分钟 持续 Pending 说明调度有问题
容器 OOM 次数 kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} 0 > 0 需要调整内存限制
PVC Pending 数 kube_persistentvolumeclaim_status_phase{phase="Pending"} 0 > 0 持续 5 分钟 存储卷绑定异常

6.3 告警规则

 

# Prometheus 告警规则:Pod 异常告警
# 文件:pod-alerts.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: pod-alerts
  namespace: monitoring
spec:
  groups:
  - name: pod.rules
    rules:
    - alert: PodCrashLooping
      expr: rate(kube_pod_container_status_restarts_total[15m]) * 60 * 15 > 0
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 持续重启"
        description: "15 分钟内重启次数 > 0,当前值: {{ $value }}"

    - alert: PodNotReady
      expr: kube_pod_status_ready{condition="false"} == 1
      for: 10m
      labels:
        severity: warning
      annotations:
        summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 持续未就绪"

    - alert: PodOOMKilled
      expr: kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
      for: 0m
      labels:
        severity: critical
      annotations:
        summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} 被 OOMKilled"

    - alert: NodeDiskPressure
      expr: kube_node_status_condition{condition="DiskPressure",status="true"} == 1
      for: 2m
      labels:
        severity: critical
      annotations:
        summary: "节点 {{ $labels.node }} 磁盘压力"

    - alert: PersistentVolumeClaimPending
      expr: kube_persistentvolumeclaim_status_phase{phase="Pending"} == 1
      for: 5m
      labels:
        severity: warning
      annotations:
        summary: "PVC {{ $labels.namespace }}/{{ $labels.persistentvolumeclaim }} 持续 Pending"

 

6.4 修复后验证

修复完成后执行以下验证清单:

验证项 命令 预期结果
Pod 状态 kubectl get pods -n 所有 Pod 为 Running 且 Ready
Events 无告警 kubectl get events -n --field-selector type=Warning 无新增 Warning
节点状态 kubectl get nodes 所有节点 Ready
Endpoint 注册 kubectl get endpoints -n Endpoints 列表包含 Pod IP
业务验证 curl 服务健康检查接口 HTTP 200
监控指标恢复 Grafana Dashboard 无持续异常指标

七、总结

7.1 技术要点回顾

Pod 异常排查遵循固定链路:状态 -> Events -> 日志 -> 节点 -> 网络 -> 存储,先广后深。

kubectl describe pod 是排障起点,Events 部分提供了最直接的线索。

CrashLoopBackOff 时 --previous 参数是看到上次崩溃日志的关键。

退出码有明确含义:137=OOMKilled、139=Segfault、143=SIGTERM,根据退出码快速分流。

kubectl debug 的 Ephemeral Containers 在 1.32 已经 GA,应该作为首选调试手段。

节点级问题(磁盘满、内存压力)会导致大面积 Pod Eviction,要在节点层面前置告警。

NetworkPolicy 变更是隐蔽的故障源,deny-all 策略需要配套白名单。

资源 requests/limits 和探针配置是预防 Pod 异常的基础设施,不是可选项。

7.2 进阶学习方向

Pod 调度深入: 理解 kube-scheduler 的打分机制、自定义调度器、调度框架插件。掌握 PriorityClass 和抢占调度。

实践建议:阅读 kube-scheduler 源码中的 Score 插件实现

容器运行时排障: 深入 containerd 2.0 架构,掌握 ctr、crictl 的高级用法,排查 runtime-level 问题。

实践建议:搭建测试环境,模拟 containerd shim 进程泄漏场景

Kubernetes API 审计与安全: 通过 Audit Log 追踪谁在什么时候做了什么操作,结合 RBAC 做权限最小化。

实践建议:配置 Audit Policy,观察生产集群的 API 调用模式

混沌工程: 使用 Chaos Mesh 或 Litmus 主动注入故障,验证系统韧性。

实践建议:在 staging 环境注入 Pod Kill、网络延迟、磁盘填满等故障

7.3 参考资料

Kubernetes 官方文档 - Debug Pods: https://kubernetes.io/docs/tasks/debug/debug-application/debug-pods/

Kubernetes 官方文档 - Ephemeral Containers: https://kubernetes.io/docs/concepts/workloads/pods/ephemeral-containers/

containerd 2.0 Release Notes: https://github.com/containerd/containerd/releases

kubectl debug 命令参考: https://kubernetes.io/docs/reference/kubectl/generated/kubectl_debug/

Kubernetes Failure Stories: https://k8s.af/

附录

A. 命令速查表

 

# 查看异常 Pod
kubectl get pods -A --field-selector=status.phase!=Running,status.phase!=Succeeded

# 查看 Pod 详情
kubectl describe pod  -n 

# 查看上次崩溃日志
kubectl logs  -n  --previous

# 查看退出码
kubectl get pod  -n  -o jsonpath='{.status.containerStatuses[0].lastState.terminated.exitCode}'

# 注入调试容器
kubectl debug -it  -n  --image=busybox:1.37 --target=

# 节点调试 Pod
kubectl debug node/ -it --image=busybox:1.37

# 查看 Warning Events
kubectl get events -n  --field-selector type=Warning --sort-by='.lastTimestamp'

# 强制删除 Pod
kubectl delete pod  -n  --grace-period=0 --force

# 回滚 Deployment
kubectl rollout undo deployment/ -n 

# 查看滚动更新状态
kubectl rollout status deployment/ -n 

# 查看节点资源使用
kubectl top nodes

# 查看 Pod 资源使用
kubectl top pods -n  --sort-by=memory

# 清理 Evicted Pod
kubectl get pods -A | grep Evicted | awk '{print $2 " -n " $1}' | xargs -L1 kubectl delete pod

# 查看 PVC 状态
kubectl get pvc -n 

# 查看 NetworkPolicy
kubectl get networkpolicy -n 

 

B. 配置参数详解

参数 所属资源 默认值 建议值 说明
terminationGracePeriodSeconds Pod 30 根据应用调整 收到 SIGTERM 到 SIGKILL 的等待时间
startupProbe.failureThreshold Container 3 根据启动时间调整 慢启动应用设大一些
startupProbe.periodSeconds Container 10 5-10 启动探测间隔
readinessProbe.periodSeconds Container 10 5-10 就绪探测间隔
readinessProbe.failureThreshold Container 3 3 连续失败多少次标记为未就绪
livenessProbe.periodSeconds Container 10 10-30 存活探测间隔,不宜过短
livenessProbe.failureThreshold Container 3 3-5 连续失败多少次重启容器
resources.requests.cpu Container 根据实际用量设置 影响调度决策
resources.requests.memory Container 根据实际用量设置 影响调度和 OOM 评分
resources.limits.memory Container requests 的 1.5-2 倍 超限触发 OOMKilled
imagePullPolicy Container IfNotPresent IfNotPresent latest 标签会自动变为 Always
restartPolicy Pod Always Always(Deployment) Job 用 Never 或 OnFailure

C. 术语表

术语 英文 解释
探针 Probe Kubernetes 检查容器健康状态的机制,包括 startup/readiness/liveness 三种
驱逐 Eviction 节点资源压力过大时,kubelet 主动终止低优先级 Pod 释放资源
临时容器 Ephemeral Container 注入运行中 Pod 的调试容器,不影响原有容器,退出后自动清理
终结器 Finalizer 资源删除前需要执行的清理逻辑标记,Finalizer 未移除则资源无法删除
调度 Scheduling kube-scheduler 将 Pod 分配到合适节点的过程
亲和性 Affinity 控制 Pod 调度到特定节点或与特定 Pod 同节点/不同节点的规则
污点/容忍 Taint/Toleration 节点标记不接受 Pod(Taint),Pod 声明可以接受(Toleration)
CSI Container Storage Interface 容器存储接口标准,用于对接各种存储后端
CNI Container Network Interface 容器网络接口标准,用于配置 Pod 网络

D. 错误关键词速查

关键词 出现位置 含义 排查方向
manifest unknown Events 镜像 Tag 不存在 检查 Tag 名称和 Registry
unauthorized Events Registry 认证失败 检查 ImagePullSecret
Insufficient cpu Events 节点 CPU 不足 扩容节点或清理闲置 Pod
Insufficient memory Events 节点内存不足 扩容节点或调整 requests
node(s) had taint Events 节点 Taint 不匹配 添加 Toleration 或移除 Taint
Back-off restarting Events 容器反复崩溃 查看 --previous 日志
OOMKilled containerStatuses 内存超限被杀 增加 limits.memory
Evicted status.reason 节点资源压力驱逐 检查节点 Conditions
FailedMount Events 卷挂载失败 检查 PV/PVC/Secret 状态
dial tcp ... timeout Probe Events 网络不通 检查 NetworkPolicy 和 CNI

E. 排障顺序速记

 

1. kubectl get pods -n            → 确认异常 Pod 和状态
2. kubectl describe pod  -n  → 看 Events 拿线索
3. kubectl logs  --previous      → CrashLoop 时看崩溃日志
4. kubectl top nodes                  → 确认节点资源
5. kubectl get events -n          → 全局 Events 扫描
6. kubectl debug -it             → 注入调试容器深入排查
7. kubectl get networkpolicy -n   → 排除网络策略影响
8. kubectl get pvc -n             → 排除存储问题
9. 定位根因 → 修复 → 验证 → 记录
 

 

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分