在Kubernetes集群中,OOM(Out of Memory)崩溃是最常见且令人头疼的问题之一。当一个容器被系统杀死并返回OOMKilled信号时,不仅业务中断,还可能引发连锁反应,影响整个集群的稳定性。本文将带你从零开始,深入理解OOM崩溃的排查方法,并通过Prometheus和Grafana搭建一套完整的实时监控报警系统。
一、OOM崩溃的本质:为什么容器会被杀死?
1.1 Linux内存管理基础
理解OOM崩溃的第一步,是明白Linux内核如何处理内存。每个容器都运行在一个cgroup(控制组)中,而cgroup通过memory.limit_in_bytes参数限制容器可用的内存总量。当容器内的进程尝试分配的内存超过这个限制时,内核的OOM Killer就会被触发,选择”最合适的”进程进行杀死,以释放内存。
这里的关键点是:OOM Killer杀死的是容器内的进程,而不是整个容器。但在K8s中,由于容器通常是单进程运行,所以表现为容器重启。
1.2 K8s中的内存限制
在K8s中,内存限制分为两个重要概念:
- requests(请求):容器启动时 guaranteed 分配的最小内存量
- limits(限制):容器运行时允许使用的最大内存量
当容器实际使用的内存超过limits时,就会触发OOM。需要注意的是,这里的内存使用量包括:
- 应用进程的RSS( Resident Set Size,常驻内存集)
- 页面缓存(Page Cache)
- 共享内存
- 各种内核结构体占用
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
labels:
app: memory-demo
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
requests:
memory: "200Mi"
cpu: "250m"
limits:
memory: "500Mi"
cpu: "500m"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "600M", "--vm-hang", "1"]
上面的YAML示例中,我们请求200Mi内存,限制500Mi,但stress工具尝试分配600Mi,这必然触发OOM。
二、OOM崩溃的排查方法:从现象到根源
2.1 第一步:确认OOM事件
当容器突然重启,第一个要检查的就是是否因为OOM。使用以下命令:
# 查看容器重启次数和原因
kubectl get pods -A --field-selector=status.phase!=Running
# 查看具体Pod的事件历史
kubectl describe pod <pod-name> -n <namespace>
# 查看容器日志,寻找OOMKilled相关记录
kubectl logs <pod-name> -n <namespace> --previous
在describe输出的事件中,你会看到类似这样的信息:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Mon, 01 Jan 2024 10:00:00 +0000
Finished: Mon, 01 Jan 2024 10:05:00 +0000
Exit Code 137 是SIGKILL的信号,结合Reason OOMKilled,可以确认是内存溢出导致的。
2.2 第二步:分析容器内存使用历史
确认OOM后,需要回顾容器在崩溃前的内存使用模式。这时候Prometheus的历史数据就派上用场了。通过查询Prometheus,我们可以画出容器内存使用的时间线图:
# 查询特定Pod的容器内存使用量
container_memory_working_set_bytes{pod="<pod-name>", namespace="<namespace>"}
# 查询容器内存RSS(更准确反映真实内存压力)
container_memory_rss{pod="<pod-name>", namespace="<namespace>"}
# 查询容器内存使用百分比(相对于limit)
container_memory_working_set_bytes{pod="<pod-name>", namespace="<namespace>"}
/ container_spec_memory_limit_bytes{pod="<pod-name>", namespace="<namespace>"} * 100
2.3 第三步:定位内存泄漏或突发流量
内存问题通常分为两类:内存泄漏和突发流量。通过对比不同时间段的内存曲线,可以判断是哪类问题。
内存泄漏的特征:
- 内存使用量随时间持续上升
- 没有明显的峰值后回落
- 最终稳定在limits附近触发OOM
突发流量的特征:
- 内存使用量在短时间内急剧上升
- 达到峰值后可能回落(如果流量减少)
- 或者持续高位运行直到OOM
2.4 第四步:深入容器内部分析
如果需要在容器内部进行更细致的分析,可以进入容器查看:
# 进入容器
kubectl exec -it <pod-name> -n <namespace> -- /bin/sh
# 查看进程内存使用
ps aux --sort=-%mem | head -20
# 查看内存详情
cat /proc/meminfo
# 查看cgroup内存限制
cat /sys/fs/cgroup/memory/memory.limit_in_bytes
# 查看当前内存使用
cat /sys/fs/cgroup/memory/memory.usage_in_bytes
如果是Java应用,还可以使用更专业的工具:
# 查看Java堆内存详情
jmap -heap <pid>
# 生成堆转储文件(谨慎使用,可能影响性能)
jmap -dump:format=b,file=heap.hprof <pid>
# 分析堆转储(需要在容器外进行)
jhat heap.hprof
2.5 第五步:分析Pod驱逐情况
有时OOM不仅影响单个容器,还可能导致节点级别的问题。K8s的kubelet会根据节点资源压力驱逐Pod:
# 查看节点资源状态
kubectl describe node <node-name>
# 查看被驱逐的Pod
kubectl get pods -A --field-selector=status.phase!=Running
# 查看节点事件
kubectl get events -n <namespace> --field-selector involvedObject.name=<node-name>
三、搭建Prometheus监控体系
3.1 Prometheus部署方案
对于K8s集群,推荐使用Prometheus Operator方式部署,它提供了更现代化的CRD(Custom Resource Definition)管理方式。
首先,创建一个ServiceMonitor资源来监控特定的服务:
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: app-monitor
namespace: monitoring
labels:
release: prometheus
spec:
selector:
matchLabels:
app: myapp
namespaceSelector:
matchNames:
- default
endpoints:
- port: metrics
interval: 30s
path: /metrics
然后,创建Prometheus实例:
apiVersion: monitoring.coreos.com/v1
kind: Prometheus
metadata:
name: main
namespace: monitoring
spec:
replicas: 2
version: v2.45.0
storage:
volumes:
- name: prometheus-data
capacity: 50Gi
type: persistentVolumeClaim
resources:
requests:
memory: 400Mi
cpu: 250m
limits:
memory: 2Gi
cpu: "1"
ruleSelector:
matchLabels:
role: alert-rules
prometheus: main
alerting:
alertmanagers:
- name: alertmanager-main
namespace: monitoring
port: web
serviceMonitorSelector:
matchLabels:
team: backend
3.2 核心监控指标
对于OOM排查和资源监控,需要关注以下核心指标:
容器内存指标:
# 容器内存工作集(最常用)
container_memory_working_set_bytes
# 容器内存RSS
container_memory_rss
# 容器内存缓存
container_memory_cache
# 容器内存限制
container_spec_memory_limit_bytes
# 容器内存请求
container_spec_memory_request_bytes
容器CPU指标:
# 容器CPU使用率(百分比)
rate(container_cpu_usage_seconds_total{container!=""}[5m]) * 100
# 容器CPU限制
container_spec_cpu_shares
节点资源指标:
# 节点内存使用率
1 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes)
# 节点CPU使用率
1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m])) by (instance)
# 节点磁盘I/O
rate(node_disk_io_time_seconds_total[5m])
四、Grafana可视化面板搭建
4.1 内存监控面板
创建一个详细的内存监控Dashboard,包含以下关键图表:
1. 容器内存使用趋势图
# 每个容器的内存使用量
container_memory_working_set_bytes{namespace="<namespace>"}
# 按Pod分组显示
container_memory_working_set_bytes{namespace="<namespace>"}
by (pod)
2. 内存使用百分比(相对于limit)
# 计算内存使用百分比
(container_memory_working_set_bytes{namespace="<namespace>"}
/ container_spec_memory_limit_bytes{namespace="<namespace>"} ) * 100
3. 内存泄漏检测
# 内存增长率
deriv(container_memory_working_set_bytes{namespace="<namespace>", pod=~"myapp.*"}[1h])
4.2 CPU监控面板
1. CPU使用率
# 容器CPU使用率
rate(container_cpu_usage_seconds_total{container!=""}[5m]) * 100
# 节点CPU使用率
100 - (avg by (instance) (rate(node_cpu_seconds_total{mode="idle"}[5m])) * 100)
2. CPU限流(Throttling)
# CPU限流时间
rate(container_cpu_cfs_throttled_seconds_total[5m])
# CPU限流百分比
rate(container_cpu_cfs_throttled_seconds_total[5m])
/ rate(container_cpu_cfs_periods_total[5m]) * 100
4.3 网络与I/O监控
1. 网络流量
# 网络接收字节
rate(container_network_receive_bytes_total[5m])
# 网络发送字节
rate(container_network_transmit_bytes_total[5m])
# 网络错误
rate(container_network_receive_packets_dropped_total[5m])
2. 磁盘I/O
# 磁盘读取字节
rate(node_disk_read_bytes_total[5m])
# 磁盘写入字节
rate(node_disk_written_bytes_total[5m])
# I/O操作时间
rate(node_disk_io_time_seconds_total[5m])
五、构建智能报警系统
5.1 报警规则设计
合理的报警规则是及时发现问题的关键。以下是一套完整的报警规则配置:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: oom-alerts
namespace: monitoring
labels:
team: backend
spec:
groups:
- name: container.memory
rules:
# 容器内存使用超过80%时警告
- alert: ContainerMemoryHigh
expr: (container_memory_working_set_bytes / container_spec_memory_limit_bytes) * 100 > 80
for: 5m
labels:
severity: warning
team: backend
annotations:
summary: "Container memory usage is high"
description: "Container {{ $labels.container }} in pod {{ $labels.pod }} is using {{ $value | printf \"%.2f\" }}% of its memory limit."
# 容器内存使用超过90%时紧急警告
- alert: ContainerMemoryCritical
expr: (container_memory_working_set_bytes / container_spec_memory_limit_bytes) * 100 > 90
for: 2m
labels:
severity: critical
team: backend
annotations:
summary: "Container memory usage is critical"
description: "Container {{ $labels.container }} in pod {{ $labels.pod }} is using {{ $value | printf \"%.2f\" }}% of its memory limit. OOM is imminent!"
# 容器已经发生OOM
- alert: ContainerOomKilled
expr: changes(container_last_seen[10m]) > 0 and container_memory_working_set_bytes == 0
for: 1m
labels:
severity: critical
team: backend
annotations:
summary: "Container was OOM killed"
description: "Container {{ $labels.container }} in pod {{ $labels.pod }} was OOM killed."
# 节点内存压力过大
- alert: NodeMemoryPressure
expr: (1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 85
for: 10m
labels:
severity: warning
team: infrastructure
annotations:
summary: "Node memory pressure is high"
description: "Node {{ $labels.instance }} memory usage is {{ $value | printf \"%.2f\" }}%."
# 节点存在被驱逐的风险
- alert: NodeMemoryEvictionRisk
expr: node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes < 0.1
for: 5m
labels:
severity: critical
team: infrastructure
annotations:
summary: "Node memory eviction risk"
description: "Node {{ $labels.instance }} has less than 10% memory available. Pods may be evicted."
- name: container.cpu
rules:
# CPU使用率过高
- alert: ContainerCpuHigh
expr: rate(container_cpu_usage_seconds_total[5m]) * 100 > 80
for: 5m
labels:
severity: warning
team: backend
annotations:
summary: "Container CPU usage is high"
description: "Container {{ $labels.container }} in pod {{ $labels.pod }} is using {{ $value | printf \"%.2f\" }}% CPU."
# CPU限流严重
- alert: ContainerCpuThrottled
expr: rate(container_cpu_cfs_throttled_seconds_total[5m]) / rate(container_cpu_cfs_periods_total[5m]) * 100 > 20
for: 5m
labels:
severity: warning
team: backend
annotations:
summary: "Container CPU is throttled"
description: "Container {{ $labels.container }} in pod {{ $labels.pod }} is being throttled for {{ $value | printf \"%.2f\" }}% of the time."
5.2 Alertmanager配置
报警需要正确地传递到相关人员。配置Alertmanager来处理不同的报警级别:
apiVersion: monitoring.coreos.com/v1
kind: Alertmanager
metadata:
name: main
namespace: monitoring
spec:
replicas: 3
logLevel: info
config:
global:
resolve_timeout: 5m
route:
group_by: ['alertname', 'namespace', 'pod']
group_wait: 10s
group_interval: 10s
repeat_interval: 4h
receiver: 'default-receiver'
routes:
- match:
severity: critical
receiver: 'critical-receiver'
repeat_interval: 1h
- match:
team: infrastructure
receiver: 'infra-receiver'
receivers:
- name: 'default-receiver'
webhook_configs:
- url: 'http://alertmanager-webhook.default.svc.cluster.local/webhook'
send_resolved: true
- name: 'critical-receiver'
webhook_configs:
- url: 'http://critical-webhook.default.svc.cluster.local/webhook'
send_resolved: true
slack_configs:
- channel: '#critical-alerts'
send_resolved: true
- name: 'infra-receiver'
slack_configs:
- channel: '#infrastructure-alerts'
send_resolved: true
六、实战案例:排查一个真实的OOM问题
6.1 问题现象
某天早上,运营团队反馈系统响应变慢,检查K8s集群发现多个Pod频繁重启。查看Pod状态:
$ kubectl get pods -n production
NAME READY STATUS RESTARTS AGE
myapp-deployment-5d8f7c6b9-x2k4l 0/1 OOMKilled 5 2h
myapp-deployment-5d8f7c6b9-m9n7p 1/1 Running 0 2h
myapp-deployment-5d8f7c6b9-q8r2s 0/1 OOMKilled 3 2h
6.2 排查过程
第一步:查看详情
kubectl describe pod myapp-deployment-5d8f7c6b9-x2k4l -n production
输出显示:
Last State: Terminated
Reason: OOMKilled
Exit Code: 137
Started: Mon, 01 Jan 2024 08:00:00 +0000
Finished: Mon, 01 Jan 2024 08:45:00 +0000
第二步:查看Prometheus历史数据
在Grafana中查询该Pod的内存使用曲线,发现内存使用量在8:
