深色模式
Pod 异常状态排障全景
摘要:本文面向生产 SRE / 平台工程师,建立一套覆盖 Pod 全生命周期的排障清单,针对
Pending、ImagePullBackOff、CrashLoopBackOff、OOMKilled、探针失败等高频异常,给出「现象 → 原因 → 定位命令 → 修复 → 回滚」的可复制路径。覆盖版本:Kubernetes v1.28+。
适用版本与前提
- Kubernetes:v1.28+(ephemeral containers 自 v1.25 稳定;
kubectl debug的--profile自 v1.28 可用)。 - 工具:
kubectl、crictl(节点层)、kubectl debug。 - 前提:具备目标命名空间读写权限与节点 SSH /
kubectl debug node/能力。
背景与问题
Pod 是 K8s 调度与运行的最小单元,但它本身并不直接承载异常——异常几乎都来自容器运行时的退出、资源约束、调度失败或探针判定。官方文档明确:CrashLoopBackOff 不是独立错误,而是一种状态——容器反复崩溃、Kubelet 施加指数退避后呈现的结果(来源:Kubernetes 官方文档 - Pod Lifecycle)。因此排障的第一原则是:先读 status 与 events,再决定查容器还是查节点,不要一上来就改 YAML。
核心概念:Pod 状态机
Pod 有两个维度需要区分:
- Phase(阶段):
Pending/Running/Succeeded/Failed/Unknown,是粗粒度结论。 - Conditions(状况):
PodScheduled、Initialized、ContainersReady、Ready,是过程信号。 - containerStatuses.lastState:记录上一次退出的
reason(如OOMKilled、Error)与exitCode,是定位的黄金字段。
心智模型
Pending = 还没跑起来(调度/镜像层问题);CrashLoopBackOff = 跑起来了但反复死(应用/配置/资源层问题);OOMKilled = 内存超了被内核杀;探针失败 = 应用活着但「不健康」。先归类,再下钻。
排障决策树
清单 1:Pending(调度失败)
- 现象:
kubectl get pod显示Pending,READY 0/N。 - 原因:节点资源不足(
Insufficient cpu/memory)、节点有污点无容忍、PVC 无法绑定、ResourceQuota 耗尽。 - 定位:bash
kubectl get pod myapp-7d4b8c9f6-x2j9k -n app -o wide kubectl describe pod myapp-7d4b8c9f6-x2j9k -n app | sed -n '/Events:/,$p' kubectl get events -n app --sort-by='.lastTimestamp' | tail -201
2
3 - 修复:根据 events 提升 request(若确为配额不足)或扩容节点池;污点问题加
tolerations。 - 回滚:若刚改过 request 导致无法调度,回退到原值:bash
kubectl rollout undo deployment/myapp -n app1
注意
FailedScheduling 中的 Insufficient 指 requests 而非 limits。调度只看 request,OOM 才是 limit 的问题,二者不要混淆。
清单 2:ImagePullBackOff / ErrImagePull
- 现象:状态在
ImagePullBackOff与ErrImagePull间抖动。 - 原因:镜像名/Tag 拼写错、
imagePullSecret缺失或失效、私有仓库不可达、节点磁盘满拉不下来。 - 定位:bash
kubectl describe pod myapp -n app | sed -n '/Events:/,$p' # 节点层确认仓库可达性与磁盘 crictl pull registry.example.com/app:1.2.3 df -h /var/lib/containerd1
2
3
4 - 修复:修正镜像引用;创建/更新
imagePullSecret(注意 secret 必须与被调度命名空间一致);kubectl rollout restart触发重新拉取。 - 回滚:
kubectl rollout undo deployment/myapp -n app。
清单 3:CrashLoopBackOff
- 现象:
RESTARTS持续增长,状态CrashLoopBackOff。 - 原因:应用启动即退出、依赖服务不可用、配置/Secret 缺失、启动命令错误、存活/启动探针过激、OOM(exitCode 137)。
- 定位(先看上一实例日志,最常直接给答案):bash
kubectl logs myapp-7d4b8c9f6-x2j9k -n app --previous kubectl get pod myapp-7d4b8c9f6-x2j9k -n app \ -o jsonpath='{.status.containerStatuses[0].lastState.terminated}' # 关键字段: reason / exitCode1
2
3
4 - 修复:
- exitCode 1:修应用或配置。
- exitCode 137:内存不足,调大
limits.memory(见性能篇)。 - 探针误杀:增大
initialDelaySeconds或改用startupProbe。 - 依赖未就绪:加
initContainers等待依赖:yamlinitContainers: - name: wait-for-db image: busybox:1.36 command: ['sh', '-c', 'until nc -z db-service 5432; do sleep 2; done']1
2
3
4
- 回滚:
kubectl rollout undo deployment/myapp -n app。
exitCode 速查
0:成功退出(若仍被重启,检查restartPolicy)。1:一般应用错误。137:SIGKILL,多为 OOMKilled(内存 limit 触发内核杀进程)。139:SIGSEGV,段错误。143:SIGTERM,被优雅终止。
清单 4:OOMKilled(exitCode 137)
- 现象:容器被反复重启,
describe显示Last State: Terminated Reason: OOMKilled。 - 原因:容器内存超过
limits.memory,cgroup 触发内核 OOM Killer。 - 定位:bash
kubectl describe pod myapp -n app | grep -A3 'OOMKilled' kubectl top pod myapp -n app --containers # 节点内核视角(需节点权限) kubectl debug node/<node> -it --image=ubuntu -- chroot /host dmesg | grep -i oom-kill | tail1
2
3
4 - 修复:调大 limit;对 JVM 类应用务必设置
-XX:MaxRAMPercentage对齐容器上限,否则 JVM 按宿主机内存估算而超限被杀。 - 回滚:
kubectl rollout undo。
生产危险
直接调大 limits.memory 只是止血。若每次调大后 OOM 间隔翻倍(典型内存泄漏),根因是应用泄漏,扩 limit 会拖延而非解决。务必抓取 heap dump 再决策。
清单 5:探针失败(就绪/存活)
- 现象:Pod
Running但NotReady,或周期性重启(liveness 杀掉尚在启动的容器)。 - 原因:
readinessProbe失败→不进 Service 后端;livenessProbe过激→杀掉慢启动容器;缺少startupProbe保护慢启动应用。 - 定位:bash
kubectl describe pod myapp -n app | grep -A5 'Conditions:' kubectl get pod myapp -n app -o jsonpath='{.status.conditions[?(@.type=="Ready")]}'1
2 - 修复:为慢启动应用加
startupProbe,把 liveness 的判定延后:yamlstartupProbe: httpGet: { path: /healthz, port: 8080 } failureThreshold: 30 periodSeconds: 5 # 最多允许 150s 启动 livenessProbe: httpGet: { path: /healthz, port: 8080 } initialDelaySeconds: 10 periodSeconds: 101
2
3
4
5
6
7
8 - 回滚:
kubectl rollout undo。
通用回滚与清理
bash
# 回滚到上一版
kubectl rollout undo deployment/<name> -n <ns>
# 清理已 Failed/Ejected 的僵尸 Pod 对象(仅删对象,不删运行中的)
kubectl delete pods -n <ns> --field-selector=status.phase=Failed1
2
3
4
2
3
4
常见坑
- 只看当前日志不看
--previous,错过崩溃现场。 - 把
request与limit的语义搞反(调度看 request,杀进程看 limit)。 - 在
CrashLoopBackOff时反复delete pod,Deployment 会立即重建,纯属无用功。