在 Kubernetes 集群运维中,Pod 处于 Pending 状态是常见的故障现象之一,往往意味着调度环节遇到了阻碍。究竟是什么原因导致调度器无法为 Pod 找到合适的归宿?本文将系统性地梳理 10 种导致 Pod Pending 的典型场景,并提供清晰的排查思路与解决方案。
什么是 Pending 状态
Pod 的 Pending 状态表明它已被 API Server 接收,但尚未被调度到任何节点上运行。这个阶段卡在了 kube-scheduler 的调度逻辑中,任何导致调度器无法找到合适节点的因素都会使 Pod 停留在此状态。理解调度器的运作机制,是快速定位问题的关键。
环境信息
本文的排查命令和配置基于以下典型生产环境:
- Kubernetes 版本:1.28.x
- 容器运行时:containerd 1.7.x
- 集群规模:50+ 节点,3000+ Pod
调度失败的 10 种场景与排查思路
场景一:资源不足(最常见)
这是最普遍的 Pending 原因。集群节点的 CPU 或内存资源无法满足 Pod 的 requests 配置要求。
典型症状:
执行 kubectl describe pod my-pod -n my-namespace,在 Events 中会看到类似信息:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 30s default-scheduler 0/10 nodes are available:
10 Insufficient cpu, 10 Insufficient memory.
排查步骤:
# 查看集群整体资源使用情况
kubectl top nodes
# 查看节点可分配资源概览
kubectl describe nodes | grep -A 5 "Allocated resources"
# 查看具体节点的资源详情
kubectl describe node node-1 | grep -A 20 "Allocated resources"
真实案例与解决:
一个 Java 应用的 Deployment 设置了 requests.memory: 4Gi,新建 Pod 全部 Pending。排查发现集群总内存充足,但所有节点的可用内存都被大量小容器碎片化,没有单个节点能提供连续的 4Gi 空间。
- 临时方案:清理一些可驱逐的低优先级 Pod。
- 长期方案:调整节点规格或优化应用内存申请。
资源配置建议:
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
containers:
- name: app
image: my-app:latest
resources:
requests:
memory: "256Mi" # 初始值不宜过大,为调整留有余地
cpu: "100m"
limits:
memory: "512Mi"
cpu: "500m"
场景二:节点 Taint 与 Pod Toleration 不匹配
节点被打上了污点(Taint),而 Pod 没有配置对应的容忍(Toleration)。
排查命令:
# 查看所有节点的 Taint
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
# 查看特定节点的 Taint 详情
kubectl describe node node-1 | grep -A 5 Taints
常见的系统 Taint:
node.kubernetes.io/not-ready:NoSchedule # 节点未就绪
node.kubernetes.io/unreachable:NoSchedule # 节点不可达
node.kubernetes.io/memory-pressure:NoSchedule # 内存压力
node.kubernetes.io/disk-pressure:NoSchedule # 磁盘压力
node.kubernetes.io/pid-pressure:NoSchedule # PID 压力
node.kubernetes.io/network-unavailable:NoSchedule # 网络不可用
踩坑记录与解决:
曾为 GPU 节点设置自定义 Taint:nvidia.com/gpu=true:NoSchedule,但新增 GPU 节点时忘记打上此标签,导致普通业务 Pod 被调度上去,浪费了昂贵的 GPU 资源。后来通过 Admission Webhook 强制校验。
解决方案是为需要调度到特定节点的 Pod 配置对应的 Toleration:
apiVersion: v1
kind: Pod
metadata:
name: gpu-pod
spec:
containers:
- name: gpu-app
image: gpu-app:latest
tolerations:
- key: "nvidia.com/gpu"
operator: "Equal"
value: "true"
effect: "NoSchedule"
场景三:NodeSelector 或 NodeAffinity 无法满足
Pod 通过 nodeSelector 或 nodeAffinity 指定了节点标签选择条件,但集群中没有节点满足条件。
排查命令:
# 查看 Pod 的 nodeSelector 配置
kubectl get pod my-pod -o jsonpath='{.spec.nodeSelector}'
# 查看所有节点的标签
kubectl get nodes --show-labels
# 查找带有特定标签的节点
kubectl get nodes -l disktype=ssd
Events 中的典型报错:
Warning FailedScheduling 10s default-scheduler 0/10 nodes are available:
10 node(s) didn‘t match Pod’s node affinity/selector.
真实案例与修复:
日志收集 DaemonSet 配置了 nodeSelector: logging=enabled,新扩容的节点未打此标签,导致 DaemonSet Pod 无法调度。
快速修复命令:
# 为单个节点添加标签
kubectl label nodes node-new logging=enabled
# 批量为所有 worker 节点添加标签
kubectl label nodes -l node-role.kubernetes.io/worker= logging=enabled
场景四:PVC 未绑定或 StorageClass 问题
Pod 声明挂载的 PersistentVolumeClaim (PVC) 处于 Pending 状态,未被绑定到 PersistentVolume (PV),或 StorageClass 配置有误。
排查命令:
# 查看 PVC 状态
kubectl get pvc -n my-namespace
# 查看 PVC 详情
kubectl describe pvc my-pvc -n my-namespace
# 查看 StorageClass 列表
kubectl get storageclass
PVC 处于 Pending 状态是直接线索。
踩坑记录:
使用云厂商的 NAS 作为存储后端时,一旦存储配额用尽,CSI 驱动可能不会返回明确错误,仅表现为 PVC 持续 Pending,需通过云控制台查看配额告警才能定位。
排查 CSI 问题:
# 查看 CSI provisioner Pod 日志
kubectl logs -n kube-system csi-provisioner-xxx
# 查看 StorageClass 的 provisioner 配置
kubectl get storageclass standard -o yaml
场景五:Pod 优先级与抢占问题
低优先级 Pod 无法抢占高优先级 Pod 的资源,或集群未正确配置 PriorityClass。
查看集群 PriorityClass:
kubectl get priorityclasses
输出示例:
NAME VALUE GLOBAL-DEFAULT AGE
system-cluster-critical 2000000000 false 100d
system-node-critical 2000001000 false 100d
high-priority 1000000 false 50d
low-priority 100 false 50d
配置示例:
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: high-priority
value: 1000000
globalDefault: false
description: "用于重要的业务服务"
---
apiVersion: v1
kind: Pod
metadata:
name: important-pod
spec:
priorityClassName: high-priority # 指定优先级
containers:
- name: app
image: my-app:latest
重要注意事项:
切勿滥用 system-cluster-critical 和 system-node-critical 这两个优先级,它们专为 kube-system 命名空间下的核心系统组件预留。误用可能导致关键系统组件(如 CoreDNS)被业务 Pod 抢占资源,引发集群级故障。
场景六:资源配额(ResourceQuota)限制
Namespace 配置的 ResourceQuota 资源配额已用尽,无法创建新的 Pod。
排查命令:
# 查看 namespace 的配额使用情况
kubectl describe resourcequota -n my-namespace
典型输出会清晰展示已使用(Used)和上限(Hard):
Name: compute-quota
Namespace: my-namespace
Resource Used Hard
-------- ---- ----
limits.cpu 4 8
limits.memory 8Gi 16Gi
requests.cpu 2 4
requests.memory 4Gi 8Gi
pods 10 20
Events 报错示例:
Error creating: pods “my-pod” is forbidden: exceeded quota: compute-quota,
requested: requests.memory=1Gi, used: requests.memory=4Gi, limited: requests.memory=4Gi
经验教训:
为 Namespace 设置 ResourceQuota 后,务必配置对应的监控告警,当使用率超过阈值(如80%)时及时通知,避免在业务高峰或 HPA 自动扩容时因配额用尽导致服务中断。
场景七:LimitRange 默认值问题
Namespace 中配置的 LimitRange 为未设置资源请求/限制的 Pod 注入了较大的默认值,可能导致调度需求超过节点容量。
查看 LimitRange:
kubectl describe limitrange -n my-namespace
典型问题配置:
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: my-namespace
spec:
limits:
- default: # 默认 limits
cpu: "2"
memory: "4Gi"
defaultRequest: # 默认 requests - 此值设置过大会导致调度困难
cpu: "1"
memory: "2Gi"
type: Container
如果 Pod 未明确设置 resources.requests,将会被自动注入 requests.memory: 2Gi,这可能会使 Pod 因“内存不足”而无法调度。
场景八:Pod 反亲和性(Anti-Affinity)冲突
配置了硬性(requiredDuringSchedulingIgnoredDuringExecution)Pod 反亲和性,要求同一应用的多个副本不能调度到同一节点,但集群节点数量少于副本数。
典型配置与问题:
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-app
spec:
replicas: 5 # 期望5个副本
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution: # 硬性要求
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app
topologyKey: kubernetes.io/hostname
如果集群只有3个节点,那么第4、5个 Pod 将因无法满足“不能调度到已有副本节点”的硬性规则而永远 Pending。
解决方案:
将硬性反亲和性改为软性偏好(preferredDuringSchedulingIgnoredDuringExecution),并赋予权重,这样调度器会尽量满足,但不会因无法满足而彻底放弃调度。
affinity:
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution: # 软性偏好
- weight: 100
podAffinityTerm:
labelSelector:
matchExpressions:
- key: app
operator: In
values:
- my-app
topologyKey: kubernetes.io/hostname
场景九:节点处于 SchedulingDisabled 状态
节点因维护等原因被标记为“不可调度”(cordon)。
排查命令:
# 查看节点状态,注意 STATUS 列
kubectl get nodes
输出示例中,Ready,SchedulingDisabled 表示节点就绪但已被禁止调度:
NAME STATUS ROLES AGE VERSION
node-1 Ready worker 100d v1.28.0
node-2 Ready,SchedulingDisabled worker 100d v1.28.0 # 被 cordon 了
node-3 Ready worker 100d v1.28.0
恢复调度:
kubectl uncordon node-2
运维建议:所有涉及节点调度的维护操作(如 cordon/drain)应遵循标准操作流程(SOP)并记录清单,确保操作完成后及时恢复(uncordon),避免遗忘。
场景十:调度器本身出问题(罕见但致命)
kube-scheduler 组件自身发生故障,例如 Pod 异常、领导选举失败等。
排查命令:
# 查看 scheduler Pod 状态
kubectl get pods -n kube-system -l component=kube-scheduler
# 查看 scheduler 日志
kubectl logs -n kube-system kube-scheduler-master-1
# 检查调度器内部健康端点(如果可用)
kubectl get --raw /healthz/poststarthook/scheduling-queue
真实案例:
自建集群中,主节点间 NTP 服务异常导致时钟偏差过大,进而引发 kube-scheduler 的 Leader 选举持续异常,表现为 Pod 调度出现随机延迟或 Pending。解决方案是修复时钟同步后重启 scheduler Pod。
通用排查流程
遇到 Pod Pending,建议按照以下系统化流程进行排查:
# 第一步:查看 Pod 详细状态和 Events(信息最全)
kubectl describe pod <pod-name> -n <namespace>
# 第二步:从 Events 中提取调度失败原因(FailedScheduling)
# 第三步:检查集群及节点资源状况
kubectl top nodes
kubectl describe nodes | grep -A 10 "Allocated resources"
# 第四步:检查节点状态与污点
kubectl get nodes
kubectl get nodes -o custom-columns=NAME:.metadata.name,TAINTS:.spec.taints
# 第五步:检查 PVC 状态(如果 Pod 使用了存储卷)
kubectl get pvc -n <namespace>
# 第六步:检查 ResourceQuota 和 LimitRange
kubectl describe resourcequota -n <namespace>
kubectl describe limitrange -n <namespace>
# 第七步:查看调度器组件日志
kubectl logs -n kube-system -l component=kube-scheduler --tail=100
预防措施与最佳实践
资源规划
为容器设置合理且留有余地的资源请求(requests)和限制(limits)。
resources:
requests:
cpu: "100m" # 起步值,根据监控指标动态调整
memory: "128Mi"
limits:
cpu: "500m" # limits 通常设置为 requests 的 2-5 倍
memory: "512Mi" # 避免设置过大,以防 OOMKilled 时影响范围过广
监控告警
配置告警规则,主动发现潜在问题。以下为 Prometheus 告警规则示例:
groups:
- name: k8s-scheduling
rules:
- alert: PodPendingTooLong
expr: |
kube_pod_status_phase{phase="Pending"} == 1
and on(pod, namespace)
(time() - kube_pod_created > 300)
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} Pending 超过 5 分钟"
- alert: NodeResourcePressure
expr: |
sum by (node) (
kube_pod_container_resource_requests{resource="memory"}
) / sum by (node) (
kube_node_status_allocatable{resource="memory"}
) > 0.85
for: 10m
labels:
severity: warning
annotations:
summary: "节点 {{ $labels.node }} 内存分配率超过 85%"
配额管理脚本
定期审计各命名空间的配额使用情况。
#!/bin/bash
# 检查所有 namespace 的配额使用情况
for ns in $(kubectl get ns -o jsonpath='{.items.metadata.name}'); do
echo "=== Namespace: $ns ==="
kubectl describe resourcequota -n $ns 2>/dev/null || echo "No quota defined"
echo ""
done
故障排查速查表
| 现象 |
可能原因 |
关键排查命令 |
Insufficient cpu/memory |
节点资源不足 |
kubectl top nodes |
node(s) didn't match selector |
节点标签不匹配 |
kubectl get nodes --show-labels |
node(s) had taints |
Pod 不容忍节点污点 |
kubectl describe node \| grep Taint |
pod has unbound PVC |
PVC 未绑定 PV |
kubectl get pvc |
exceeded quota |
命名空间资源配额用尽 |
kubectl describe resourcequota |
SchedulingDisabled |
节点被 cordon |
kubectl get nodes |
pod anti-affinity |
Pod 反亲和性规则冲突 |
检查 Deployment 的 affinity 配置 |
总结
Pod Pending 问题的本质,是 kube-scheduler 无法为 Pod 找到满足所有约束条件的节点。排查过程犹如破案,需要根据线索(Events)逐一验证可能的原因。
核心建议如下:
- 首要动作:养成第一时间查看
kubectl describe pod 输出中 Events 部分的习惯,绝大多数原因在此有直接或间接体现。
- 主动防御:建立涵盖资源水位、Pending Pod 时长、配额使用率等维度的监控告警体系,变被动救火为主动预防。
- 资源管理:结合业务实际监控数据,进行科学的容量规划与资源申请,为节点预留一定的缓冲资源。
- 配置审计:定期审计集群的节点标签(Label)、污点(Taint)、资源配额(ResourceQuota)等基础配置,确保其符合预期。
许多棘手的故障,根源往往在于被忽视的基础配置。在云原生技术栈的实践中,保持对细节的关注和严谨的操作流程,是保障系统稳定性的基石。希望本文梳理的排查思路和案例,能帮助你更高效地解决 Kubernetes 调度难题。欢迎在云栈社区与更多同行交流运维实践。