说实话,刚接手 K8s 集群那会儿,我被 OOM(内存溢出)和节点负载飙高折腾得够呛。记得有一次周五晚上,集群里几个核心服务突然全部雪崩,报警电话被打爆。我冲上去一看,Prometheus 大盘上一片火红,但具体是哪个 Pod 在作妖、为什么 OOM、负载是从哪儿来的,完全是一团浆糊。那种无助感,相信很多 K8s 工程师都经历过。
今天这篇指南,我不想跟你堆砌理论,而是把我踩过的坑、总结的一套“实战排查+监控落地”方法论掏心窝子讲给你听。咱们一步步来,从“怎么查”到“怎么防”,最后给你一套可落地的 Prometheus 监控方案。
一、先搞懂:OOM 和负载飙高,到底发生了什么?
在动手查之前,咱们得先脑子里有个清晰的图景。不然就像蒙着眼睛打靶,瞎忙活。
1. Pod OOM 的本质
在 K8s 里,每个 Pod 都有内存限制(resources.limits.memory)。当 Pod 里的容器实际内存使用量超过这个限制时,Linux 内核的 OOM Killer 会介入,直接把这个进程(或者整个 cgroup)杀掉。这就是 OOMKilled。
关键点:K8s 报的 OOMKilled,只是告诉你“内存超标被杀了”,但不告诉你为什么超标。这才是排查的核心难点。
常见原因:
- 内存泄漏:程序代码有 bug,分配了内存但不释放。
- 突发流量:正常业务波动,瞬时内存需求超过限制。
- JVM/运行时问题:比如 Java 应用,堆内存设置不当,或者 Full GC 频繁。
- 缓存未设上限:应用里开了本地缓存,无限增长。
2. 节点负载飙高的本质
节点负载(Load Average)高,通常意味着 CPU 密集型任务或大量进程在等待 I/O。在 K8s 集群里,这往往和以下几个因素相关:
- Pod 过多:节点上调度了太多 Pod,资源争抢。
- 有“刺头”Pod:某个 Pod 算力或内存需求异常,吃光节点资源。
- 后台任务:备份、日志收集、监控 Agent 等占用资源。
- 系统进程:Linux 内核层面的问题,比如网络拥塞控制。
关键点:节点负载高,不一定是你的业务 Pod 的问题。有可能是基础设施组件(如 kube-proxy、metrics-server、日志收集 DaemonSet)在“抢地盘”。
二、实战排查:OOM 怎么追?负载高怎么挖?
这部分是重头戏。我给你一套“望闻问切”的排查流程,每一步都有具体的命令和工具。
场景一:Pod 频繁 OOMKilled
第一步:确认是不是真的 OOM
先用 kubectl 查看 Pod 状态:
kubectl get pods -n <namespace> -o wide
kubectl describe pod <pod-name> -n <namespace>
重点看 Last State 部分:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
如果看到 Exit Code: 137 且 Reason: OOMKilled,那基本实锤了。
第二步:分析容器内存使用历史
光看当前状态没用,得看历史。这时候 Prometheus 就派上用场了(后面会讲怎么搭)。但在此之前,我们可以用 kubectl 临时查看:
# 查看 Pod 的内存使用量(需要开启 metrics-server)
kubectl top pod <pod-name> -n <namespace>
# 查看容器历史资源使用情况(如果配了 vpa 或者 cAdvisor)
kubectl get pod <pod-name> -n <namespace> -o jsonpath='{.status.containerStatuses[0]}'
更有效的做法:检查容器日志,看 OOM 前有没有异常输出:
kubectl logs <pod-name> -n <namespace> --previous
--previous 参数会显示上一个已终止容器的日志,这很关键!因为 OOM 发生时,当前 Pod 可能已经重启了,你看不到当时的报错。
第三步:深入分析——是泄漏还是突发?
这是最考验功底的一步。我给你两个思路:
思路 A:如果是 Java 应用
Java 应用的内存问题,90% 是 JVM 堆内存配置不当或内存泄漏。你可以这样做:
进入 Pod 执行 jmap(如果镜像里有 jdk):
kubectl exec -it <pod-name> -n <namespace> -- sh # 在容器内 jps # 找到 Java 进程 ID jmap -histo <pid> # 查看对象直方图,找出占用内存最多的类抓取堆Dump(如果应用支持):
# 在 Pod 里配置 JVM 参数,让它在 OOM 时自动 dump -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/tmp/heapdump.hprof然后用
jhat或Eclipse MAT分析 dump 文件。
思路 B:如果是通用进程(Go/Python/C++)
使用
pprof(Go 应用): 如果应用暴露了 pprof 端口,你可以直接抓取:kubectl port-forward <pod-name> 6060:6060 -n <namespace> # 然后在本地 go tool pprof http://localhost:6060/debug/pprof/heap使用
valgrind或perf(需要调试环境): 这比较重,通常用于定位 C++ 应用的内存泄漏。观察内存曲线: 这是最简单的办法。在 Prometheus Grafana 上画一条图,X轴是时间,Y轴是容器内存使用量。
- 如果曲线是锯齿状,周期性飙升然后回落,可能是定时任务或 GC 导致的。
- 如果曲线是单向斜坡上升,那大概率是内存泄漏。
- 如果曲线是突然垂直飙升,那是突发流量或数据量异常。
第四步:调整资源限制
分析完原因后,你得给出解决方案。常见做法:
- 如果是突发流量:适当调高
memory.limit,并设置合理的memory.request(Request 是 QoS 和调度的依据,Limit 是硬限制)。 - 如果是内存泄漏:联系开发修复代码,或者先临时调高 Limit 作为缓冲,同时推动修复。
- 如果是 JVM 问题:调整
-Xmx和-Xms,确保不超过容器 Limit。建议Limit = Xmx * 1.2,留一点非堆内存的空间。
场景二:节点负载飙高
第一步:定位是哪个节点
kubectl top nodes
或者用 kubectl describe node <node-name> 看详细资源分配情况。
第二步:找出“罪魁祸首”Pod
在问题节点上,执行:
# 查看节点上所有 Pod 的资源使用
kubectl top pods -n <namespace> --field-selector spec.nodeName=<node-name>
# 或者直接用 kubectl get pods 看状态
kubectl get pods -A -o wide --field-selector spec.nodeName=<node-name>
如果某个 Pod 的 CPU 或内存使用率远高于其他 Pod,那它很可能就是问题源。
第三步:深入分析节点内部
有时候,问题不在 Pod 里,而在节点本身。你可以 SSH 到节点(如果允许),执行:
# 查看系统负载
uptime
top -bn1 | head -20
# 查看哪些进程占用 CPU 高
ps aux --sort=-%cpu | head -10
# 查看 I/O 等待
iostat -x 1 3
# 查看网络连接
ss -s
常见坑点:
- kube-proxy:如果集群里有大量 Service 和 Endpoint,kube-proxy 的 iptables 规则会非常多,导致 CPU 飙升。这时候可以考虑切换到
ipvs模式。 - metrics-server:数据采集频率过高,或者集群规模大,metrics-server 本身压力大。
- 日志收集 Agent:如 fluentd、filebeat,如果配置不当,会疯狂读磁盘或网络,占用大量资源。
第四步:缓解措施
- 清理无用 Pod:把跑飞的、测试的、废弃的 Pod 都干掉。
- 调整 Pod 资源请求:确保每个 Pod 的
resources.requests合理,避免过度调度。 - 启用 VPA:Vertical Pod Autoscaler 可以自动调整 Pod 的资源限制,避免资源争抢。
- 节点隔离:把关键业务 Pod 通过
nodeAffinity或podAntiAffinity分散到不同节点,避免单点压力过大。
三、Prometheus 监控落地:打造你的“仪表盘”
排查靠的是临时命令,预防靠的是监控。下面我给你一套经过实战检验的 Prometheus 监控落地方案。
1. 架构选型
推荐 Prometheus + Grafana + AlertManager 的标配组合。对于大型集群,可以考虑 Thanos 或 Cortex 做长期存储和去重,但初期先用原生 Prometheus 就够了。
监控组件:
- kube-state-metrics:监听 K8s API,暴露 Pod、Deployment、Node 等资源的状态指标(如重启次数、镜像版本、资源请求/限制)。
- cAdvisor:Kubelet 内置,暴露容器级别的资源使用指标(CPU、内存、网络、磁盘)。
- node-exporter:暴露在物理机/虚拟机层面的硬件和 OS 指标(如 CPU 负载、磁盘 IO、内存使用)。
- Prometheus Adapter:将 Prometheus 指标转换为 HPA 可用的指标,实现自动扩缩容。
2. 关键监控指标
我给你列几个最核心的指标,这些是排查 OOM 和负载的“命门”。
Pod 层面:
container_memory_working_set_bytes:容器实际内存使用量(关键!用于判断是否接近 Limit)。container_cpu_usage_seconds_total:容器 CPU 使用量。kube_pod_container_status_last_terminated_reason:容器上次终止原因(直接看是不是 OOMKilled)。kube_pod_container_status_restarts_total:容器重启次数(异常重启频繁要警惕)。
节点层面:
node_load1,node_load5,node_load15:节点 1/5/15 分钟负载。node_memory_MemAvailable_bytes:节点可用内存。node_cpu_seconds_total:节点 CPU 使用率(需要计算100 - (avg by(instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100))。
告警规则示例:
在 Prometheus 的 rules.yml 里配置:
groups:
- name: k8s-alerts
rules:
- alert: PodOOMKilled
expr: kube_pod_container_status_last_terminated_reason{reason="OOMKilled"} == 1
for: 0m
labels:
severity: critical
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} was OOMKilled"
description: "Pod {{ $labels.namespace }}/{{ $labels.pod }} in cluster {{ $labels.cluster }} has been OOM killed. Current memory usage: {{ $value }}"
- alert: HighNodeLoad
expr: node_load15 > 0.8 * count(count by (cpu) (node_cpu_seconds_total))
for: 10m
labels:
severity: warning
annotations:
summary: "High load on node {{ $labels.node }}"
description: "Node {{ $labels.node }} has load {{ $value }} for 10 minutes."
- alert: PodMemoryUsageHigh
expr: container_memory_working_set_bytes / container_spec_memory_limit_bytes > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "Pod {{ $labels.namespace }}/{{ $labels.pod }} memory usage is high"
description: "Memory usage is {{ $value | humanizePercentage }} of the limit."
3. Grafana 仪表盘
别只依赖告警,你要能“看见”数据。我给你几个建议的仪表盘视图:
- 集群概览:展示所有节点的 CPU、内存、网络总览。
- Pod 详情:选择某个 Pod,展示其 CPU、内存、重启次数、网络流量随时间的变化。
- 节点资源争抢:展示每个节点上 Pod 的资源请求总和 vs 节点实际容量,找出“超卖”严重的节点。
- OOM 事件追踪:专门画一张图,展示最近 7 天所有 OOMKilled 事件的分布,方便找规律。
分享一个我常用的 Grafana JSON 模板思路:
在 Grafana 里新建 Dashboard,添加 Panel:
- 类型:Time series
- 查询:
sum by (namespace, pod) (container_memory_working_set_bytes) / sum by (namespace, pod) (container_spec_memory_limit_bytes) - 条件:设置 Threshold,超过 80% 标黄,超过 95% 标红。
- 标题:
Pod Memory Usage % of Limit
这样,你在 Grafana 上就能看到每个 Pod 内存使用的百分比,一目了然。
4. 落地步骤(循序渐进)
部署 kube-prometheus-stack(推荐用 Helm 安装):
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts helm install prometheus prometheus-community/kube-prometheus-stack -n monitoring --create-namespace这会一键部署 Prometheus、Grafana、AlertManager、kube-state-metrics、node-exporter 等。
配置持久化存储: Prometheus 默认是内存存储,重启就丢。务必配置 PVC 或外部存储(如 Thanos + S3)。
配置告警路由: 在 AlertManager 里配置路由,把不同严重程度的告警发送到不同的渠道(钉钉、企业微信、邮件、PagerDuty 等)。
自定义仪表盘: 根据上面的建议,在 Grafana 里慢慢搭建你的专属仪表盘。
压测和调优: 用工具(如
k6或ycsb)对集群进行压测,观察 Prometheus 是否能承受指标量,Grafana 查询是否流畅。
四、避坑指南:这些细节没人告诉你
坑 1:cAdvisor 指标不准
cAdvisor 默认采集的是容器的“working set”内存,但不包括 cache。在 Linux 里,cache 是可以被快速回收的。所以,当你看到容器内存使用量接近 Limit,但实际可用内存还很多时,别慌。要看 container_memory_working_set_bytes,而不是 container_memory_usage_bytes。
坑 2:Prometheus 采集频率过高
默认采集间隔是 15s,对于大集群来说,这会产生海量数据,导致 Prometheus 存储压力巨大。建议根据业务需求调整:
- 关键业务 Pod:15s
- 普通业务 Pod:30s 或 60s
- 节点级别指标:60s
在 prometheus.yml 里配置:
scrape_configs:
- job_name: 'kubernetes-pods'
kubernetes_sd_configs:
- role: pod
relabel_configs:
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape]
action: keep
regex: true
- source_labels: [__meta_kubernetes_pod_annotation_prometheus_io_scrape_interval]
action: replace
target_label: __scrape_interval
regex: (.+)
然后在 Pod 注解里指定采集间隔:
metadata:
annotations:
prometheus.io/scrape: "true"
prometheus.io/scrape_interval: "30s"
坑 3:忽略 K8s 自身组件的负载
很多工程师只关注业务 Pod,忽略了 kube-system 命名空间里的组件。比如 coredns、kube-proxy、metrics-server。这些组件一旦出问题,整个集群都会受影响。务必给它们也配置监控和告警。
坑 4:没有设置资源 Request
resources.requests 是 K8s 调度的依据。如果不设置,K8s 会按 Limit 来调度,导致节点过度调度,负载不均。务必给每个业务 Pod 都设置合理的 Request 和 Limit。
五、总结:从救火到防火
好了,以上就是我为你整理的 K8s Pod OOM 和节点负载飙高的排查与监控落地指南。
回顾一下核心要点:
- 排查 OOM:看日志(
--previous)、分析内存曲线(Prometheus)、深入进程(jmap/pprof)、调整资源限制。 - 排查负载:定位节点、找出“刺头”Pod、检查节点内部进程(kube-proxy、日志 Agent)、调整调度策略。
- 监控落地:部署 kube-prometheus-stack,配置关键指标告警,搭建 Grafana 仪表盘,循序渐进优化。
- 避坑:注意 cAdvisor 指标含义、调整采集频率、关注 K8s 自身组件、设置资源 Request。
记住,监控不是为了报警,而是为了预防。当你能够通过 Grafana 一眼看出哪个 Pod 的内存使用在缓慢上升,你就能在它 OOM 之前干预,而不是等到半夜被电话吵醒。
希望
