K8s Pod Pending 逐层排查:从 FailedScheduling 到 PVC StorageClass 不存在的实战记录

发布时间:2026/7/19 15:18:45
K8s Pod Pending 逐层排查:从 FailedScheduling 到 PVC StorageClass 不存在的实战记录 叙事框架现象 → 排查过程 → 根因 → 修复 → 预防问题现象本文记录了一次 K8s 认证服务上线过程中 Pod 持续 Pending 的完整排查过程。故障表现为两个 auth-service Pod 部署后 8 分钟内无法进入 Running 状态用户登录接口 503。初步排查发现 FailedScheduling 事件原因是 CPU request 设置为 2000m 而集群三个节点均无法满足。调低 request 至 500m 后 Pod 仍 Pending进一步 describe 发现 FailedMount 事件——PVC auth-data-pvc 状态为 PendingProvisioningFailed 错误指明 storageclass.storage.k8s.io fast-storage not found。根因是 Deployment 中 CPU request 严重偏离实际基线同时 PVC 引用了一个在集群中不存在的 StorageClass。排查过程第一步查看调度事件锁定资源不足Pod Pending 的排查起点永远是kubectl describe pod。这条命令输出的 Events 字段会记录调度器scheduler、kubelet 和 volume controller 在各个阶段发出的关键事件。对于 Pending 状态的 PodEvents 是唯一能直接定位根因的入口。describe输出确认了猜测FailedScheduling 事件明确指出0/3 nodes are available: 1 Insufficient cpu, 2 Insufficient memory。这里的关键信息是3 个节点中有 1 个 CPU 不足、2 个内存不足preemption抢占机制没有生效——要么找不到可驱逐的受害者 Pod要么该 Pod 的优先级不支持抢占这说明 Pod 申请的资源和集群当前可分配资源之间存在冲突。到底是请求设太高还是集群确实满载张工决定查一下节点层面的资源用量。第二步查看节点资源发现请求冲突kubectl top node显示 k8s-node-02 的 CPU 使用率已达 92.2%内存 96.7%接近饱和。但问题不仅仅是节点满载这么简单。通过kubectl describe node查看节点已分配资源可以发现两个关键线索k8s-node-02 上已经运行了 prometheus-serverCPU request 500m、nginx-ingress500m、支付服务200m等多个 PodNon-terminated Pods合计的 CPU Requests 已经很接近节点的 allocatable 上限3800m。k8s-node-03 上运行着 auth-service 的旧版本CPU request 1即 1000m加上其他 Pod 后剩余资源已经不足。再看新版本的 Deployment 配置CPU request 设为了22000m。这意味着新 Pod 需要单核 2 个 CPU 的空闲资源而集群中三个节点各自剩余的可调度资源都不足以容纳。第三步调低资源后仍然 Pending张工调整了 Deployment 的 CPU request 从2降为500m重新部署后还是 Pending。这次describe显示的事件发生了变化。Pod 被调度器成功分配到了节点Scheduled但kubelet在挂载卷的阶段失败了MountVolume.SetUp failedforvolumeauth-data:pvc-xxx has not been bound yet Failed tomountPVC: persistentvolumeclaimauth-data-pvcnot found这说明 Pod 通过了调度阶段但在 kubelet 启动容器前挂载 PersistentVolumeClaim 时卡住了。PVC 没有绑定到 PV导致 Pod 无法继续推进到 Running。第四步检查 PVC 和 StorageClasskubectl get pvc -n auth确认auth-data-pvc状态为 Pending。进一步describe pvc发现了真正的根因Warning ProvisioningFailed: storageclass.storage.k8s.iofast-storagenot foundPVC 中指定的storageClassName: fast-storage在集群中不存在集群只注册了两个 StorageClassstandard默认provisioner: k8s.io/minikube-hostpathslowprovisioner: kubernetes.io/gce-pd没有fast-storage这个 StorageClass动态存储 provisioner 无法自动创建 PVPVC 永远卡在 Pending 状态。第五步确认 StorageClass 清单kubectl get sc确认了集群中的可选 StorageClass。standard被标记为(default)slow使用 GCE 持久化磁盘。问题本质上是两方面叠加Deployment 的 CPU request 设得太高2核导致调度阶段就失败PVC 引用了一个不存在的 StorageClass即使调度通过卷挂载也无法完成根因分析子原因一CPU request 严重偏离基线上图清晰展示了 2000m CPU request 与三个节点实际剩余资源的冲突。从修复后的反馈来看认证服务实际运行时的 CPU 消耗在 200-400m 之间。但新版本 Deployment 中 CPU request 却写死了22000m远超实际需要。产生这个偏差的原因是多方面的缺乏历史监控数据新版本重构了会话管理模块团队预估的 CPU 开销偏大未参考旧版本基线旧版本 auth-service-old 的 request 为 1000m1核但这是包括了未优化的缓存逻辑。优化后实际 CPU 消耗下降request 却反而翻倍资源超分问题k8s-node-02 的 allocatable CPU 为 3800m但Non-terminated Pods中 CPU Requests 总和已达 3300m减去系统预留新 Pod 的 2000m request 完全超出剩余空间子原因二Pod 排障只停留在第一层张工的排查过程揭示了一个典型的排障陷阱看到 FailedScheduling → 调整资源 → 以为万事大吉。如上图所示故障链实际上是多层的——修复第一层后第二层才暴露出来。Pod Pending 的原因可能层层叠加第一层调度阶段失败FailedScheduling第二层卷挂载阶段失败FailedMount第三层镜像拉取失败ImagePullBackOff第四层Init 容器执行失败每次修复一个原因后Pod 会推进到下一个阶段再卡住。如果只修复第一个错误就认为问题已解决“降了资源应该就好了”会被表象迷惑。子原因三StorageClass 缺乏一致性校验PVC 中引用了一个集群中不存在的 StorageClassfast-storage。进一步追溯发现方案的评审文档中确实写着fast-storage集群管理员实际创建的是standard和slow评审到部署之间没有任何自动校验环节这种命名不一致的问题在 K8s 生态中非常常见因为 Resource 之间的关系是声明式的K8s 不会在kubectl apply时检查引用的 StorageClass 是否存在——它只会在 PVC 真正需要绑定时才报错。更深层的问题在于集群中的standardStorageClass 的 provisioner 是k8s.io/minikube-hostpath这是 minikube 自带的轻量级 provisioner。而fast-storage原本期望对应的是 SSD 类型的云盘 provisioner如pd-ssd.csi.storage.gke.io。命名背后的语义在评审阶段丢失了——文档写的是性能目标而非实际存在的资源名称。累计效应调度 存储组合拳两个问题叠加的结果是即便资源问题修复后 Pod 被调度到了节点PVC 绑定失败会再次阻滞排障者看到 FailedScheduling 后容易忽略后续可能出现的 FailedMount两个问题互不相关但出现在同一次上线中排查时间直接翻倍从时间线上看资源问题让张工花了约 10 分钟定位和修复StorageClass 问题又花了 15 分钟。如果一开始就能完整查看 Events 并识别出可能存在多层问题可以更早地将两部分工作并行——王姐调资源的同时张工检查 PVC 配置。修复方案第一步修正资源限制将 CPU request 从2调低到500mmemory request 调整为512Milimits 相应下调。对比有问题的配置❌requests.cpu: 2→ 太激进无节点可容纳✅requests.cpu: 500m→ 基于旧版本实际用量的合理值limits 从 4 核降为 1 核防止单个 Pod 过度抢占节点资源第二步修正 StorageClass将 PVC 中的storageClassName从fast-storage改为standard集群中实际存在的默认 StorageClass。关键变更storageClassName: fast-storage→storageClassName: standard注意如果集群没有安装动态存储 provisioner即使 StorageClass 存在也无法自动创建 PV。本例中standard的 provisioner 为k8s.io/minikube-hostpath支持动态供给第三步CI 流水线增加 pre-deploy 校验为了避免类似问题再次发生团队在 CI 部署流水线中增加了两步检查资源请求合理性校验对比新版本的 resource request 与旧版本的历史监控数据偏差超过一定阈值则告警PVC StorageClass 存在性校验通过kubectl get sc获取集群中的 StorageClass 清单校验所有 PVC 引用的 StorageClass 是否真实存在验证结果即时指标Pod 成功运行修复后重新部署两个 Pod 很快进入 Running 状态。验证指标Pod 状态1/1 Running无重启PVC 状态Bound已成功绑定到 PV应用日志显示 Session store 初始化成功PVC 挂载路径/data/sessions正常登录接口/api/auth/login响应正常持续观察修复后观察了 45 分钟两个 Pod 的 CPU 使用稳定在 200-400m 之间内存使用约 350Mi远低于设置的 limit1 核 / 1Gi。资源设置合理没有触发 OOM 或 CPU throttling。PVC 的状态也一直保持 Bound/data/sessions目录读写正常。值得注意的一个细节旧版本 CPU request 为 1000m 时实际只用了 300m 左右但新版本资源优化后 CPU request 反而翻倍到 2000m——这说明资源评估不能靠感觉必须基于监控数据做决策。这次修复后将 request 设为 500m实际使用 200-400m留了约 30% 的余量应对流量高峰。团队复盘复盘会上团队总结了这次故障的三个改进点Resource request 设置必须有基线数据支撑——不能拍脑袋写cpu: 2排查 Pod Pending 时要看全 Events不能只看第一条错误CI 中预部署检查必须覆盖 PV/PVC/StorageClass 的存在性刘老师特别指出集群没有安装动态存储 provisioner 时PVC 根本不会自动创建 PV——这不是 StorageClass 名字写对就能解决的问题需要先确认集群的存储基础设施。避坑建议Resource request 参考历史基线不要靠估算设置 request/limit。从 Prometheus 取过去 7 天的实际用量取 P95 或 P99 作为 request 基线再乘以安全系数Pod Pending 排查要看全 Eventskubectl describe pod的 Events 字段可能包含多个阶段的问题。修完第一个 Event 后重新 describe确认没有新的错误PVC 引用的 StorageClass 必须事前确认在 YAML 提交 PR 之前先确认目标集群中是否存在对应的 StorageClass区分 scheduling 和 mounting 阶段Pod 从 Pending 到 Running 要经过调度→拉镜像→挂载卷→启动容器等多个阶段。先用kubectl describe确认卡在哪个阶段动态存储 provisioner 不是默认存在的minikube 集群自带 hostpath provisioner但云厂商或自建集群可能需要额外安装 CSI 驱动。确认集群的存储能力后再写 PVCNode Allocatable ≠ Node Capacity节点上有系统预留kube-reserved/system-reserved实际可调度资源小于 Capacity。kubectl describe node的 Allocatable 才是真正可用的值CPU request 影响调度limit 影响 throttling区分两者的作用。request 高导致调度失败limit 高会导致节点超分和 CPU 竞争Deployment 更新策略和资源不足的交互滚动更新时旧 Pod 销毁和新 Pod 创建可能有短暂重叠如果集群资源紧张新 Pod 可能 pending 更长时间。考虑用maxSurge: 0先停旧再启新附Pod Pending 排查流程图这个决策树覆盖了从 FailedScheduling 到 PVC 挂载的逐层排查路径遇到 Pod Pending 时可以按图索骥。附完整命令清单# 1. 查看 Pod 状态kubectl get pods-nauth kubectl get pods-nauth-owide# 2. 查看调度事件kubectl describe pod auth-service-c7d8b9f4f-abc12-nauth# 3. 查看节点资源kubectltopnodekubectl describenodek8s-node-02|grep-A10Capacitykubectl describenodek8s-node-02|grep-A8Non-terminated# 4. 卷挂载阶段的 eventskubectl describe pod auth-service-6d9e8f2c6b-ghj56-nauth# 5. 查看 PVC 状态kubectl get pvc-nauth kubectl describe pvc auth-data-pvc-nauth# 6. 查看 StorageClasskubectl get sc kubectl describe sc standard# 7. 查看 Deployment 配置kubectl get deploy auth-service-nauth-oyaml# 8. 查看 ReplicaSetkubectl get rs-nauth# 9. 查看 Eventskubectl get events-nauth --sort-by.lastTimestamp# 10. 应用修复后的配置kubectl apply-fdeployment-fixed.yaml kubectl apply-fpvc-fixed.yaml# 11. 验证修复kubectl get pods-nauth kubectl get pvc-nauth kubectl logs auth-service-8e9f1a3b5c-xyz12-nauth--tail5# 12. 检查集群 storage provisionerkubectl get storageclass kubectl get pods-nkube-system|grepprovisioner