VOL. 01 — POD LIFECYCLE

K8s 故障
排查手册

THE OPS FIELD MANUAL
Pod & Container Issues
———————
Production Edition
Pending CrashLoopBackOff ImagePullBackOff OOMKilled Evicted Terminating

Pod 卡住的状态本身就是 诊断信号。Pending 是调度器在喊冤,CrashLoopBackOff 是容器在求救,ImagePullBackOff 是 kubelet 在敲门没人应。读懂状态码,比盲目重启高效十倍。

01

Pod 一直 Pending,调度不上去

SCHEDULER → NODE BINDING FAILURE
高频 入门级
  • kubectl get pod 显示 STATUS 为 Pending,READY 为 0/1
  • describe 输出包含 FailedScheduling 事件
  • Pod 没有被分配到任何节点(NODE 列为 <none>)
  • 资源不足:节点 CPU/内存不够满足 requests
  • 节点污点:Pod 没有匹配的 toleration
  • nodeSelector / affinity 没有匹配节点
  • PVC 没有可用 PV(StorageClass 配置错误)
  • 所有节点都 NotReady,或处于 cordon 状态
# 1. 看调度失败的具体原因(最关键的一步)
kubectl describe pod <pod-name> | grep -A 10 Events

# 2. 看节点资源使用情况
kubectl top nodes
kubectl describe node <node-name> | grep -A 5 "Allocated resources"

# 3. 看节点污点
kubectl describe node <node-name> | grep Taint

# 4. 看 PVC 是否绑定成功
kubectl get pvc
kubectl describe pvc <pvc-name>
陷阱 · requests 和 limits 不是同一个概念。调度看的是 requests,运行时杀进程看的是 limits。requests 设过高会调度失败,limits 设过低会 OOMKilled。
# 资源不足:扩节点 或 降低 Pod requests
kubectl edit deployment <name>
# 修改 spec.template.spec.containers[].resources.requests

# 节点污点不匹配:加 toleration 或 去掉污点
kubectl taint nodes <node> key=value:NoSchedule-  # 末尾减号是删除

# 节点被 cordon:解除调度禁用
kubectl uncordon <node-name>
02

ContainerCreating 卡住不动

KUBELET → CRI / CNI / VOLUME STUCK
高频 进阶
  • Pod 已分配节点,但状态停在 ContainerCreating 几分钟以上
  • events 里可能有 FailedCreatePodSandBox / FailedMount
  • CNI 网络插件异常:Calico/Flannel Pod 未运行,IP 池耗尽
  • Volume 挂载失败:NFS 不通、ConfigMap/Secret 不存在
  • 镜像下载慢(没有进入 ImagePull 状态时容易混淆)
  • containerd / docker 服务异常
# 1. 看 events 里的具体卡点
kubectl describe pod <pod> | tail -30

# 2. 登录所在节点看 kubelet 日志
ssh <node>
journalctl -u kubelet -f --since "5 min ago"

# 3. 检查 CNI 插件 Pod 状态
kubectl -n kube-system get pod -o wide | grep -E "calico|flannel|cni"

# 4. 容器运行时是否健康
systemctl status containerd
crictl ps -a   # containerd 用这个查容器
速判技巧 · describe 里出现 FailedCreatePodSandBox → 99% 是 CNI 问题;MountVolume.SetUp failed → 卷的问题;啥都没有 → 看节点 kubelet 日志。
03

ImagePullBackOff / ErrImagePull

REGISTRY AUTH OR NETWORK FAILURE
高频 入门级
  • 镜像名/tag 拼错,或镜像根本不存在
  • 私有仓库未配置 imagePullSecrets
  • 节点访问不到镜像仓库(网络/DNS/代理)
  • 仓库要求 HTTPS,但配置的是 HTTP(或反之)
  • HTTPS 仓库证书不被信任(自签证书)
# 1. 看具体报错信息
kubectl describe pod <pod> | grep -A 5 "Failed"

# 2. 节点直接拉镜像,定位是 K8s 问题还是节点问题
crictl pull <image:tag>
# 或 docker 环境
docker pull <image:tag>

# 3. 检查私有仓库 secret
kubectl get secret <secret-name> -o yaml
kubectl get secret <secret> -o jsonpath='{.data.\.dockerconfigjson}' | base64 -d
# 创建 docker-registry 类型 secret
kubectl create secret docker-registry regcred \
  --docker-server=harbor.example.com \
  --docker-username=admin \
  --docker-password='Harbor12345' \
  --docker-email=a@b.com
# Pod / Deployment 中引用
spec:
  imagePullSecrets:
    - name: regcred
  containers:
    - name: app
      image: harbor.example.com/proj/app:v1
HTTP 仓库 · containerd 需要在 /etc/containerd/config.toml 配置 insecure registry,改完 systemctl restart containerd。docker 改 /etc/docker/daemon.json 的 insecure-registries。
04

CrashLoopBackOff — 容器反复重启

APPLICATION EXITED, KUBELET RETRYING
高频 进阶
  • STATUS 在 CrashLoopBackOff 和 Running 之间循环
  • RESTARTS 计数不断增加
  • kubelet 按指数退避重试: 10s → 20s → 40s → 80s → 最长 5min
  • 应用启动报错:配置错误、依赖服务不可达
  • 启动命令拼错(command/args 不对)
  • 启动后立即退出(没有前台进程)
  • liveness 探针太激进,启动慢被误杀
  • OOM 被 kill 后又重启(看 RESTARTS 高 + Last State 是 OOMKilled)
# 1. 看当前日志
kubectl logs <pod>

# 2. 看上一次崩溃的日志(最重要!当前已经被新容器替换)
kubectl logs <pod> --previous

# 3. 看退出码和原因
kubectl describe pod <pod> | grep -A 5 "Last State"
退出码含义常见场景
0正常退出没有前台进程,容器跑完就结束
1应用错误代码异常、配置文件解析失败
125容器命令调用失败docker run 参数错误
126命令不可执行权限不足,文件不是可执行文件
127命令未找到command 拼错、镜像里没这个二进制
137SIGKILLOOM 被杀,或 liveness 探针失败强杀
139SIGSEGV 段错误程序崩溃
143SIGTERM正常停机信号(Pod 删除)
临时调试技巧 · 如果起不来连日志都看不到,改 command 让它先睡着:command: ["sleep","3600"] 然后 kubectl exec -it 进去手动跑启动命令,看实际报错。
05

OOMKilled — 内存超限被杀

CGROUP MEMORY LIMIT EXCEEDED
高频 进阶
  • describe 输出 Last State: Terminated, Reason: OOMKilled, Exit Code: 137
  • RESTARTS 持续增加
  • 节点 dmesg 里有 "Out of memory: Killed process"
  • Pod 级 OOM:容器内存超过自己的 limits → cgroup 杀进程
  • 节点级 OOM:节点整体内存不足 → 内核 OOM Killer 按优先级杀
# 1. 确认是不是 OOM
kubectl describe pod <pod> | grep -E "OOMKilled|137|Last State"

# 2. 看节点 dmesg(节点级 OOM 才有)
ssh <node> "dmesg -T | grep -i 'killed process'"

# 3. 看 Pod 实际内存使用
kubectl top pod <pod> --containers
# 方案 A:调高 limits(如果应用确实需要)
resources:
  requests:
    memory: "512Mi"
  limits:
    memory: "1Gi"      # 之前是 512Mi 不够,翻倍

# 方案 B:优化应用(JVM 应用必看)
env:
  - name: JAVA_OPTS
    value: "-Xmx768m -XX:MaxRAMPercentage=75"
    # 容器内 JVM 默认按宿主机内存算,会爆;必须显式设置
QoS 等级影响驱逐顺序 · Guaranteed (requests=limits) > Burstable > BestEffort。生产环境关键服务设成 Guaranteed,节点 OOM 时最后才被杀。
06

Evicted — Pod 被节点驱逐

NODE PRESSURE EVICTION
中频 进阶
  • memory.available 低于阈值(默认 100Mi)
  • nodefs.available 低于 10% (磁盘满)
  • imagefs.available 低于 15% (镜像盘满)
  • nodefs.inodesFree 低于 5% (inode 耗尽)
# 1. 看所有被驱逐的 Pod
kubectl get pod -A | grep Evicted

# 2. 看驱逐原因
kubectl describe pod <pod> | grep -A 3 "Status:"

# 3. 看节点磁盘和内存
ssh <node>
df -h
df -i           # 看 inode
free -h

# 4. 一键清理所有 Evicted 状态的 Pod
kubectl get pod -A | grep Evicted | awk '{print $1, $2}' | \
  xargs -n 2 kubectl delete pod -n
预防 > 治疗 · 镜像盘满最常见,加个 cron 定期清理:crictl rmi --prune (containerd) 或 docker system prune -af
07

Terminating 卡死,删不掉

FINALIZER OR NODE LOST
中频 进阶
  • 节点失联(NotReady),kubelet 无法上报 Pod 已删除
  • Finalizer 没被清理(常见于 PVC、CRD 资源)
  • 容器进程僵死,无法响应 SIGTERM
  • Volume 卸载失败(NFS 不通最常见)
# 1. 优雅删除(给 0 秒宽限期)
kubectl delete pod <pod> --grace-period=0 --force

# 2. 如果还卡着,移除 finalizers(谨慎使用)
kubectl patch pod <pod> -p '{"metadata":{"finalizers":null}}'

# 3. 节点失联导致的,等 5 分钟会自动清理
# 或者直接删节点对象,Pod 会被一起清
kubectl delete node <dead-node>

# 4. PVC 卡 Terminating
kubectl patch pvc <pvc> -p '{"metadata":{"finalizers":null}}'
force delete 的副作用 · 强删只是从 etcd 里抹掉,如果实际容器还在节点上跑,可能造成"幽灵 Pod"。删之前最好确认节点状态,或登录节点 crictl ps 检查实际情况。
08

Liveness 探针失败导致循环重启

PROBE TUNING ISSUE
中频 进阶
  • events 出现 "Liveness probe failed",紧接着 "Killing"
  • 应用日志里看不到崩溃,但容器被 kubelet 杀掉
  • 典型场景:Java 应用启动慢,30 秒内还没起来就被探针 KO
探针失败后果用途
startupProbe失败重启容器慢启动应用,通过后才开始 liveness
livenessProbe失败重启容器判断"活着没",死了就重启
readinessProbe从 Service 摘除判断"能接流量没",失败不重启
# 推荐配置(Java 应用为例)
startupProbe:        # 启动慢的应用必加
  httpGet:
    path: /actuator/health
    port: 8080
  failureThreshold: 30  # 30 * 10s = 5 分钟启动窗口
  periodSeconds: 10

livenessProbe:
  httpGet:
    path: /actuator/health/liveness
    port: 8080
  periodSeconds: 20     # 不要太频繁,20-30s 即可
  failureThreshold: 3   # 连续 3 次失败才重启

readinessProbe:
  httpGet:
    path: /actuator/health/readiness
    port: 8080
  periodSeconds: 5
  failureThreshold: 3
常见错误 · 把 liveness 和 readiness 检查同一个端点,且包含数据库依赖。数据库一抖动,所有 Pod 都被重启。正确做法:liveness 只检查进程本身,readiness 才检查依赖。