排查常见的 Kubernetes 性能瓶颈
学习系统性地诊断和解决常见的 Kubernetes 性能瓶颈,包括 CPU 节流、内存 OOMKill 和调度延迟。本指南提供了可操作的命令和最佳实践,用于调整资源请求、优化 HPA 扩缩容以及识别底层集群约束,以确保最佳的应用性能。
排查常见的 Kubernetes 性能瓶颈
Kubernetes 性能问题很少会直接表现为“Kubernetes 运行缓慢”。你可能会看到部署卡住、API 突然返回 5xx 错误、队列停止消费、或者 Pod 看起来健康但用户抱怨延迟。集群只是其中的一部分,但你可以通过一组一致的命令来检查它。
关键在于避免直接跳到节点大小或副本数量。首先要确定你遇到了哪种瓶颈:CPU 节流、内存压力、调度延迟、扩缩容缓慢、网络延迟、存储延迟,或者应用程序本身执行的工作超出了预期。
第一阶段:识别症状
在深入具体组件之前,先明确定义观察到的性能下降。常见症状通常属于以下类别之一:
- 部署/更新缓慢: Pod 创建耗时过长,或滚动更新停滞。
- 应用无响应: Pod 正在运行,但无法响应应用层流量(例如,高延迟、5xx 错误)。
- 资源突增: 节点或特定部署的 CPU 或内存利用率出现无法解释的峰值。
- 调度延迟: 新 Pod 无限期地停留在
Pending状态。
第二阶段:诊断资源限制(CPU 和内存)
资源管理不当是 Kubernetes 性能问题最常见的原因。不正确的请求和限制设置会导致节流或 OOMKill。
1. 检查资源利用率和限制
首先使用 kubectl describe 和 kubectl top 检查受影响应用的资源分配。
可操作检查: 将 requests 和 limits 与指标服务器报告的实际使用情况进行比较。
# 获取命名空间中所有 Pod 的资源使用情况
kubectl top pods -n <namespace>
# 检查特定 Pod 的资源请求/限制
kubectl describe pod <pod-name> -n <namespace>
同时检查所属的工作负载,以了解问题是影响单个 Pod 还是整个 Deployment:
kubectl get deploy <deployment-name> -n <namespace> -o yaml
kubectl get pods -n <namespace> -l app=<label> -o wide
如果只有一个 Pod 运行缓慢,并且它与其他 Pod 位于不同的节点上,那么节点级别的压力可能性更大。如果每个副本都运行缓慢,则资源设置、下游依赖项或应用程序行为需要更多关注。
2. CPU 节流
如果容器的 CPU 使用率反复达到其定义的 limit,内核将对其进行节流,即使节点本身有可用容量,也会导致严重的延迟峰值。这通常被误认为是普遍的 CPU 饥饿。
诊断提示: 即使 kubectl top 没有显示 节点 的 CPU 使用率达到 100%,也要寻找高延迟响应。节流发生在 每个容器 级别。
如需更深入的确认,如果你的指标系统暴露了容器 CPU 节流指标,请使用它。在基于 Prometheus 的设置中,团队通常会关注诸如节流的 CPU 周期数以及请求延迟等指标。仅凭原始 CPU 使用率可能会隐藏节流,因为容器在看起来使用完整节点核心之前就可能已被节流。
解决方案:
- 如果工作负载确实需要更多处理能力,请增加 CPU
limit。 - 如果应用程序在忙等待,请优化应用程序代码,而不是简单地增加限制。
- 如果符合你的平台策略,可以考虑为某些延迟敏感的服务移除 CPU 限制,同时保留 CPU 请求。这可以避免硬节流,同时仍然为调度器提供有用的放置信息。
3. 内存压力和 OOMKill
如果容器超过其内存 limit,Kubernetes 会触发内存不足(OOM)杀死,导致容器反复重启。
诊断: 检查 Pod 状态是否频繁重启(查看 kubectl get pods 中的 RESTARTS 列),并检查日志中是否有 OOMKilled 事件。
# 检查最近的 OOMKill 事件
kubectl get events --field-selector involvedObject.name=<pod-name> -n <namespace>
解决方案:
- 如果 OOMKill 频繁发生,立即增加内存
limit。 - 对于长期修复,对应用程序进行性能分析,以发现并修复内存泄漏或减少堆大小。
内存的行为与 CPU 不同。CPU 可以被节流,进程继续缓慢运行。内存限制违规通常会导致进程被杀死。这使得内存问题看起来像是可靠性事件:重启、连接断开、缓存冷启动以及正在处理的请求失败。
最佳实践:明智地设置请求。 确保资源
requests设置得合理接近预期的最小使用量。如果requests设置得太低,调度器可能会过度提交节点,导致所有 Pod 同时达到其需求时发生争用。
第三阶段:调查调度瓶颈
当 Pod 停留在 Pending 状态时,问题在于调度器无法找到合适的节点。
1. 分析待处理 Pod
在待处理的 Pod 上使用 kubectl describe pod 来读取 Events 部分。此部分通常包含调度失败的明确解释。
常见调度器消息:
0/3 nodes are available: 3 Insufficient cpu.(节点容量问题)0/3 nodes are available: 3 node(s) had taint {dedicated: infra}, that the pod didn't tolerate.(污点/容忍度不匹配)0/3 nodes are available: 1 node(s) had taint {NoSchedule: true}, that the pod didn't tolerate.(节点压力或维护)
2. 集群资源饱和
如果由于缺乏 CPU/内存而导致调度延迟,则集群缺乏足够的聚合容量。
解决方案:
- 向集群添加更多节点。
- 验证节点利用率是否因资源请求配置错误而人为过高(参见第二阶段)。
- 如果在云提供商上运行,请使用 集群自动缩放器(CA),以便在待处理 Pod 累积时动态添加节点。
如果启用了集群自动缩放器但未添加节点,请在假设云提供商出现问题之前读取其日志。自动缩放器可能拒绝添加节点,因为节点组已达到最大大小、待处理 Pod 具有任何节点组都无法满足的约束、或者配额阻止了新实例。
第四阶段:扩缩容机制的性能问题
自动扩缩容应快速响应,但水平 Pod 自动缩放器(HPA)或垂直 Pod 自动缩放器(VPA)的配置错误可能会导致问题。
1. 水平 Pod 自动缩放器(HPA)延迟
HPA 依赖指标服务器来报告准确的 CPU/内存利用率或自定义指标。
诊断步骤:
- 验证指标服务器健康状态: 确保指标服务器正在运行且可访问。
kubectl get --raw "/apis/metrics.k8s.io/v1beta1/nodes" - 检查 HPA 状态: 检查 HPA 配置和最近事件。
查找指示指标源是否不可用或扩缩容决策循环是否正常运行的的消息。kubectl describe hpa <hpa-name> -n <namespace>
瓶颈: 如果使用自定义指标,请确保外部指标提供程序正常运行,并且足够频繁地报告数据,以便 HPA 做出有用的决策。
HPA 是反应式的。除非你的指标反映了流量峰值,否则它不会知道流量峰值即将到来。对于突发工作负载,你可能需要更高的最小副本数、更快的自定义指标、基于队列的扩缩容,或者在已知事件之前进行预扩缩容。
2. 垂直 Pod 自动缩放器(VPA)交互
虽然 VPA 会自动调整资源请求,但在其调整阶段,如果它频繁重启或调整 Pod 大小,可能会导致性能不稳定,尤其是对于无法容忍重启的有状态应用。
建议: 首先使用 VPA 的 Recommender 模式,或者使用 updateMode: "Off" 仅观察建议而不自动应用,从而避免不必要的调整中断。
第五阶段:网络和存储性能
当计算资源看起来正常时,网络或持久存储可能是瓶颈。
1. CNI(容器网络接口)问题
如果 Pod 之间的通信(尤其是跨节点)缓慢或间歇性失败,则 CNI 插件可能过载或配置错误。
故障排除:
- 检查 CNI DaemonSet Pod 的日志(例如,Calico、Flannel)。
- 使用
ping或curl测试不同节点上 Pod 之间的基本连接。
2. 持久卷(PV)延迟
严重依赖磁盘 I/O 的应用(数据库、日志系统)如果底层持久卷延迟较高,将会受到影响。
可操作检查: 确认供应器类型(例如,AWS EBS gp3 与 io1),并验证卷是否满足所需的 IOPS/吞吐量规格。
存储警告: 切勿在不了解底层磁盘性能特征的情况下,直接在标准
hostPath卷上运行高吞吐量数据库。对于要求苛刻的工作负载,请使用托管云存储解决方案或高性能本地存储供应器。
节点级瓶颈
有时,节点上的所有 Pod 会同时变慢。这是你停止盯着一个 Deployment 并检查节点的信号。
kubectl describe node <node-name>
kubectl top node <node-name>
kubectl get pods --all-namespaces -o wide | grep <node-name>
查找 MemoryPressure、DiskPressure 和 PIDPressure 条件。磁盘压力很容易被忽略,因为应用程序的症状可能是启动缓慢、镜像拉取失败或驱逐,而不是明显的磁盘错误。
在节点本身上,如果你有访问权限,请检查:
df -h
iostat -x 1
free -h
journalctl -u kubelet --since "30 minutes ago"
托管 Kubernetes 服务可能会限制直接节点访问,但同样的思路仍然适用:使用提供商指标、kubelet 事件和节点条件来确定节点是否是共享瓶颈。
控制平面和 API 压力
大多数应用程序延迟并非由 Kubernetes API 服务器引起。你的 Web 请求通常不会在每个用户请求时都调用 API 服务器。但控制平面压力可能会损害操作性能:部署缓慢、调度延迟、端点更新缓慢或控制器落后。
症状包括:
- 整个集群的
kubectl命令执行缓慢。 - 部署创建 Pod 的时间比平时长。
- 控制器落后于期望状态。
- 事件显示重复的 API 超时。
检查问题是影响正常的应用程序流量还是集群操作。如果只有部署和调度缓慢,请检查 API 服务器健康状态、控制器管理器行为、准入 Webhook 以及在你管理控制平面的集群中的 etcd 健康状态。
准入 Webhook 值得特别关注。即使节点有足够的容量,缓慢或不可用的 Webhook 也可能延迟 Pod 创建。如果部署在创建时卡住,并且事件提到 Webhook 调用,请在调整节点大小之前调查 Webhook 服务。
实用的故障排除顺序
从用户可见的症状开始:
- HTTP 请求缓慢:比较应用延迟、CPU 节流、内存重启、下游延迟和网络路径。
- Pod 启动缓慢:检查镜像拉取时间、调度事件、卷挂载时间和初始化容器。
- Pod 待处理:检查请求、节点容量、污点、亲和性、配额和自动缩放器限制。
- 周期性延迟峰值:检查 CPU 节流、垃圾回收、吵闹的邻居、存储延迟和 HPA 扩缩容时机。
- 随机重启:检查 OOMKilled、存活探针、节点压力以及来自先前容器的应用程序日志。
然后一次证明或排除一个层面。例如,如果延迟峰值与 CPU 节流完全吻合,你就有了一个强有力的线索。如果延迟峰值发生在 CPU、内存、网络和存储都看起来平静的时候,那么瓶颈可能位于应用程序内部或 Kubernetes 外部的下游服务。
无需猜测的请求和限制调整
错误的资源设置会导致许多性能问题:
- 请求过低:调度器将太多繁忙的 Pod 打包到同一节点上。
- 请求过高:即使实际使用量不大,Pod 也会保持待处理状态。
- CPU 限制过低:延迟敏感的应用被节流。
- 内存限制过低:容器被杀死而不是变慢。
- 没有设置请求:调度变得不那么可预测,关键工作负载可能与吵闹的邻居严重竞争。
使用最近的生产指标作为起点,然后为正常的峰值留出余量。对于 Java、Node.js、Go、Python 和类似数据库的工作负载,内存行为可能非常不同,因此不要仅仅因为容器镜像大小相似就将限制从一个服务复制到另一个服务。
后续步骤
最好的 Kubernetes 性能调查在好的方面是平淡无奇的:定义症状,检查 Pod,检查节点,检查扩缩容,然后检查网络和存储。kubectl describe 和 kubectl top 只是开始,但它们通常会告诉你哪个方向值得跟进。
- 实施健壮的 资源配额,以防止吵闹的邻居饿死关键应用。
- 定期检查 Pod 重启次数,以便及早发现细微的 OOM 或应用行为失败。
- 利用 Prometheus/Grafana 仪表板,专门跟踪 CPU 节流指标,而不仅仅是原始使用率。