容器资源耗尽告警延迟故障排查Prometheus实时监控与Grafana可视化完整配置指南
先说说你遇到的痛点
前几天有个朋友半夜被钉钉消息炸醒,说线上服务又挂了几十个Pod,查了半天发现是内存OOM。他当时就懵了——明明配了Prometheus告警,为什么没提前预警?后来才发现,告警规则配的是500m内存使用率,但容器实际用的是cgroup v2,Prometheus抓的数据源不对,导致一直显示”正常”。
这类问题太常见了。很多团队搭了监控系统,但告警要么延迟几分钟,要么根本告不出来,等真正收到通知的时候,火已经烧到大腿了。
今天咱们把这套东西从头到尾捋清楚,不只是怎么配置,更重要的是理解背后的原理,这样出了问题你才能快速定位。
一、容器资源监控的核心逻辑
1.1 容器资源从哪来
容器资源数据主要走两条路:
- cgroup文件系统:Linux内核为每个容器创建的隔离环境,直接暴露CPU、内存、IO等信息
- CRI接口:容器运行时接口(Container Runtime Interface),通过kubelet暴露Pod级别的资源指标
在Kubernetes集群里,我们通常不需要直接去读cgroup文件,因为kubelet已经把这些数据整理好暴露成了/metrics接口。但有个坑——cgroup v1和v2的指标路径不一样,这就是很多人告警延迟的根源。
1.2 关键指标梳理
| 指标名称 | 含义 | 常见阈值 |
|---|---|---|
container_memory_working_set_bytes |
容器实际使用的内存(不含缓存) | 超过Request的80%就要注意 |
container_cpu_usage_seconds_total |
CPU累计使用时间 | 持续99%使用率说明打满 |
container_network_transmit_bytes_total |
网络发送流量 | 突发流量时容易打满带宽 |
container_fs_reads_bytes_total |
磁盘读流量 | IO密集型应用关注 |
container_oom_events_total |
OOM事件计数 | 只要>0就是危险信号 |
这些指标看起来简单,但实际用起来你会发现:内存指标有好几个,用错了就白忙活。
比如container_memory_usage_bytes包含页面缓存,container_memory_working_set_bytes是真正”用掉”的内存。如果你用前者配告警,会发现数值一直很高但容器没事;用后者配,又可能突然飙高被OOM killer干掉。
建议:内存告警用working_set_bytes,资源限制配置用usage_bytes,两者分开看。
二、Prometheus配置完整指南
2.1 基础部署
如果你用Kubernetes,最省心的方式是直接上Prometheus Operator。但这里我先把裸Prometheus的配置讲清楚,这样你理解原理后转Operator也毫无压力。
先写个基础的prometheus.yml:
global:
scrape_interval: 15s # 抓取间隔,15秒是平衡点,太短会压垮apiserver
evaluation_interval: 15s # 规则评估间隔,建议和scrape_interval保持一致
scrape_timeout: 10s # 单次抓取超时,超过10s算失败
# 从Kubernetes API获取目标
scrape_configs:
- job_name: 'kubernetes-nodes'
kubernetes_sd_configs:
- role: node
relabel_configs:
- source_labels: [__address__]
regex: '(.*):10250'
target_label: __address__
replacement: '${1}:9100' # 把kubelet端口换成node-exporter端口
- 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_path]
action: replace
target_label: __metrics_path__
regex: (.+)
- source_labels: [__address__, __meta_kubernetes_pod_annotation_prometheus_io_port]
action: replace
regex: ([^:]+)(?::\d+)?;(\d+)
replacement: $1:$2
target_label: __address__
- action: labelmap
regex: __meta_kubernetes_pod_label_(.+)
- source_labels: [__meta_kubernetes_namespace]
action: replace
target_label: kubernetes_namespace
- source_labels: [__meta_kubernetes_pod_name]
action: replace
target_label: kubernetes_pod_name
这段配置的核心逻辑是:让Prometheus去Kubernetes API注册中心找有prometheus.io/scrape: "true"注解的Pod,然后去抓它们的metrics。
2.2 容器资源抓取配置
光有上面的配置还不够,你需要让Prometheus专门去抓容器级别的指标。在Kubernetes里,kubelet暴露的指标在https://<node>:10250/metrics/cadvisor,但这个接口需要认证,而且返回的是cgroup指标。
更推荐的做法是搭配node-exporter和kube-state-metrics一起用:
scrape_configs:
# 集群节点资源
- job_name: 'node-exporter'
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
# kubelet cadvisor指标(容器级别)
- job_name: 'kubernetes-cadvisor'
scheme: https
tls_config:
ca_file: /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
bearer_token_file: /var/run/secrets/kubernetes.io/serviceaccount/token
kubernetes_sd_configs:
- role: node
relabel_configs:
- action: labelmap
regex: __meta_kubernetes_node_label_(.+)
- target_label: __metrics_path__
replacement: /metrics/cadvisor
# kube-state-metrics(Pod/Deployment级别元数据)
- job_name: 'kube-state-metrics'
static_configs:
- targets: ['kube-state-metrics.monitoring.svc.cluster.local:8080']
注意看kubernetes-cadvisor这段,它是通过https访问kubelet的,需要service account的token。很多团队在这一步出问题,导致抓取失败但没发现。
排查技巧:去Prometheus的Targets页面,看kubernetes-cadvisor的状态。如果是DOWN,先看错误信息是认证失败还是连接超时。认证失败一般是token问题,超时一般是网络策略把10250端口封了。
2.3 告警规则配置
这是最关键的部分。很多团队的告警规则写得不对,导致要么太敏感天天误报,要么太迟钝根本告不出来。
groups:
- name: container-resource-alerts
rules:
# 容器内存使用率超过阈值
- alert: ContainerMemoryHigh
expr: |
sum by (pod, namespace, container) (
container_memory_working_set_bytes
) / sum by (pod, namespace, container) (
container_spec_memory_limit_bytes
) * 100 > 85
for: 5m
labels:
severity: warning
annotations:
summary: "容器{{ $labels.pod }}内存使用率超过85%"
description: "命名空间{{ $labels.namespace }}中{{ $labels.container }}的内存使用率为{{ $value | printf \"%.2f\" }}%,已持续5分钟。当前使用{{ humanize1024 (sum by (pod) (container_memory_working_set_bytes{pod=\"$labels.pod\"})) }},限制{{ humanize1024 (sum by (pod) (container_spec_memory_limit_bytes{pod=\"$labels.pod\"})) }}"
# 容器CPU使用率超过阈值
- alert: ContainerCPUHigh
expr: |
sum by (pod, namespace, container) (
rate(container_cpu_usage_seconds_total[5m])
) / sum by (pod, namespace, container) (
container_spec_cpu_quota / container_spec_cpu_period
) * 100 > 90
for: 5m
labels:
severity: warning
annotations:
summary: "容器{{ $labels.pod }}CPU使用率超过90%"
description: "命名空间{{ $labels.namespace }}中{{ $labels.container }}的CPU使用率为{{ $value | printf \"%.2f\" }}%"
# 容器OOM事件
- alert: ContainerOOM
expr: increase(container_oom_events_total[1m]) > 0
for: 0m
labels:
severity: critical
annotations:
summary: "容器{{ $labels.pod }}发生OOM"
description: "命名空间{{ $labels.namespace }}中的{{ $labels.container }}在最近1分钟内发生了OOM事件"
# 容器重启次数异常
- alert: ContainerCrashLoop
expr: increase(kube_pod_container_status_restarts_total[1h]) > 3
for: 0m
labels:
severity: warning
annotations:
summary: "容器{{ $labels.pod }}1小时内重启超过3次"
- name: node-resource-alerts
rules:
# 节点内存不足
- alert: NodeMemoryPressure
expr: |
(1 - node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes) * 100 > 90
for: 10m
labels:
severity: warning
annotations:
summary: "节点{{ $labels.instance }}内存使用率超过90%"
# 节点磁盘压力
- alert: NodeDiskPressure
expr: |
(1 - node_filesystem_avail_bytes{mountpoint="/"} / node_filesystem_size_bytes{mountpoint="/"}) * 100 > 85
for: 10m
labels:
severity: warning
annotations:
summary: "节点{{ $labels.instance }}根分区使用率超过85%"
这段规则里有几个设计细节值得说:
1. 内存告警用了5分钟窗口期(for: 5m)
这是故意的。容器启动时内存会瞬间飙高,然后稳定下来。如果你设置for: 0m,每次Deployment更新都会触发告警。5分钟窗口能过滤掉这种瞬时尖刺。
2. CPU计算用了rate()函数
container_cpu_usage_seconds_total是累计值,不能直接拿来比较。必须用rate()算出5分钟内的使用速率,再除以CPU限制得到百分比。
3. OOM告警没有窗口期
OOM是紧急事件,发现就要立刻通知,所以for: 0m。但要注意,这个告警会重复触发,因为每次scrape都会计算increase(),建议配合Alertmanager的分组和抑制规则。
三、告警延迟的常见原因和排查方法
3.1 延迟来源分析
告警从”资源超限”到”你收到通知”,中间要经过这几步:
资源超限 → Prometheus抓取 → 规则评估 → Alertmanager路由 → 通知渠道
↑ ↑ ↑ ↑
实际状态 15s间隔 15s评估 配置延迟
每一步都可能引入延迟。最常见的问题出在前三步。
3.2 抓取延迟排查
问题现象:资源明明已经打满了,Prometheus里数据还是正常的,过了好几分钟才飙上去。
排查步骤:
# 1. 检查Prometheus的抓取间隔配置
grep scrape_interval /etc/prometheus/prometheus.yml
# 2. 查看目标抓取状态
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | select(.labels.job=="kubernetes-cadvisor") | .health'
# 3. 看最近一次抓取的延迟
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {job: .labels.job, lastScrape: .lastScrape, lastError: .lastError}'
常见原因:
scrape_interval设置太大(比如60s),数据更新慢- 网络延迟导致抓取超时,Prometheus自动降频
- 被抓取的目标响应慢,
scrape_timeout设置不合理
解决方案:把scrape_interval调到10-15s,scrape_timeout设成8s。记住,太短的间隔会给apiserver带来压力,15s是个平衡点。
3.3 规则评估延迟排查
问题现象:数据已经到了,但告警规则没触发,等了很久才报。
排查步骤:
# 查看规则评估状态
curl -s http://localhost:9090/api/v1/rules | jq '.data.groups[] | .rules[] | {name: .name, lastEvaluation: .lastEvaluation, evaluationTime: .evaluationTime}'
# 查看规则错误
curl -s http://localhost:9090/api/v1/rules | jq '.data.groups[] | .rules[] | select(.error != null) | {name: .name, error: .error}'
常见原因:
evaluation_interval和scrape_interval不一致,导致评估时数据还没更新完- PromQL表达式写错了,规则一直处于error状态
- 规则文件有语法错误,Prometheus启动时加载失败
解决方案:确保evaluation_interval <= scrape_interval,并且定期检查规则的健康状态。
3.4 cgroup版本导致的指标缺失
这是最隐蔽的问题。Kubernetes 1.20+默认使用cgroup v2,但很多Prometheus规则是基于cgroup v1写的。
cgroup v1的内存指标:
container_memory_usage_bytes
container_memory_working_set_bytes
cgroup v2的内存指标路径不同:
# cgroup v2下,kubelet暴露的cadvisor指标路径:
/cgroup/memory/current
/cgroup/memory/max
但kubelet做了兼容层,通常会自动映射。如果你发现内存指标缺失或数值不对:
# 检查集群的cgroup版本
cat /proc/cgroups | grep memory
# 或者
cat /sys/fs/cgroup/cgroup.controllers
# 检查Prometheus里实际有哪些内存指标
curl -s 'http://localhost:9090/api/v1/label/__name__/values' | jq '.data[]' | grep memory
如果指标名是container_memory_working_set_bytes,说明兼容层正常。如果完全找不到这个指标,就需要检查kubelet的配置:
# /var/lib/kubelet/config.yaml
cgroupDriver: cgroupfs # 或 systemd
# 确保以下配置开启
featureGates:
CSINodeInfo: true
ReadOnlyAPI: true
四、Alertmanager配置与通知优化
4.1 基础配置
告警规则触发后,数据会发到Alertmanager。这一步配不好,你就会收到一堆无效通知。
global:
resolve_timeout: 5m
smtp_smarthost: 'smtp.example.com:587'
smtp_from: 'alert@example.com'
smtp_auth_username: 'alert@example.com'
smtp_auth_password: 'your_password'
route:
group_by: ['alertname', 'namespace'] # 按告警名和命名空间分组
group_wait: 30s # 第一组告警发送前等待30秒,让同类告警合并
group_interval: 5m # 同一组告警的发送间隔
repeat_interval: 4h # 未恢复的告警重复发送间隔
receiver: 'default-wechat'
routes:
# OOM告警直接钉钉,不走分组
- match:
alertname: ContainerOOM
receiver: 'critical-dingtalk'
group_wait: 0s
repeat_interval: 1h
# 警告级别走企业微信
- match:
severity: warning
receiver: 'default-wechat'
# 关键告警走钉钉+电话
- match:
severity: critical
receiver: 'critical-dingtalk'
continue: true
receivers:
- name: 'default-wechat'
wechat_configs:
- corp_id: 'your_corp_id'
to_user: '@all'
agent_id: '1000002'
api_secret: 'your_secret'
message: '{{ range .Alerts }}{{ .Annotations.summary }}{{ end }}'
- name: 'critical-dingtalk'
dingtalk_configs:
- webhook: 'https://oapi.dingtalk.com/robot/send?access_token=your_token'
message: '{{ range .Alerts }}## 🚨 {{ .Annotations.summary }}
{{ .Annotations.description }}
---
*触发时间*: {{ .StartsAt }}
*告警级别*: {{ .Labels.severity }}
*命名空间*: {{ .Labels.namespace }}
*容器*: {{ .Labels.container }}'
msg_type: 'markdown'
4.2 告警抑制和静默
同一个问题可能引发几十条告警,比如一个节点宕机会让上面所有Pod都报”失联”。这时候需要抑制规则:
inhibit_rules:
# 如果节点NotReady,抑制该节点上所有Pod的告警
- source_match:
alertname: NodeNotReady
target_match:
severity: warning
equal: ['instance']
# 如果OOM告警已触发,抑制同容器的内存高告警
- source_match:
alertname: ContainerOOM
target_match:
alertname: ContainerMemoryHigh
equal: ['pod', 'namespace']
4.3 测试告警流程
配置改完之后,一定要测试!很多团队的告警配了半天,线上真出事时发现通知根本发不出去。
# 测试Alertmanager配置语法
alertmanager --config.test=/etc/alertmanager/alertmanager.yml
# 手动触发测试告警
curl -X POST http://localhost:9093/api/v2/alerts -d '[{
"labels": {"alertname": "TestAlert", "severity": "warning"},
"annotations": {"summary": "测试告警", "description": "这是测试"}
}]'
# 查看Alertmanager状态
curl -s http://localhost:9093/api/v2/status | jq '.config.original'
五、Grafana可视化面板配置
5.1 容器资源监控大盘
Grafana的配置我直接给你JSON,你可以导入使用。先说几个核心面板的设计思路:
内存使用率面板:
- 显示每个容器的working set内存 vs 限制
- 用阈值颜色区分状态:绿色<70%,黄色70-85%,红色>85%
- 按namespace分组,方便定位问题服务
CPU使用率面板:
- 显示瞬时使用率和5分钟平均值
- 叠加CPU请求和限制线
- 注意区分
rate()和average()的不同用途
IO和Network面板:
- 突刺型流量容易掩盖持续高负载
- 用双Y轴分别显示读写和入出流量
下面是完整的Grafana Dashboard JSON,你可以直接导入:
{
"annotations": {
"list": []
},
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 1,
"id": null,
"links": [],
"panels": [
{
"title": "容器内存使用率",
"type": "timeseries",
"gridPos": {"h": 8, "w": 12, "x": 0, "y": 0},
"options": {
"legend": {"displayMode": "list", "placement": "bottom"},
"tooltip": {"mode": "multi", "sort": "desc"}
},
"targets": [
{
"datasource": {"type": "prometheus", "uid": "Prometheus"},
"expr": "sum by (pod, namespace, container) (container_memory_working_set_bytes) / sum by (pod, namespace, container) (container_spec_memory_limit_bytes) * 100",
"legendFormat": "{{namespace}}/{{container}}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 70},
{"color": "red", "value": 85}
]
},
"unit": "percent"
}
}
},
{
"title": "容器CPU使用率",
"type": "timeseries",
"gridPos": {"h": 8, "w": 12, "x": 12, "y": 0},
"targets": [
{
"datasource": {"type": "prometheus", "uid": "Prometheus"},
"expr": "sum by (pod, namespace, container) (rate(container_cpu_usage_seconds_total[5m])) / sum by (pod, namespace, container) (container_spec_cpu_quota / container_spec_cpu_period) * 100",
"legendFormat": "{{namespace}}/{{container}}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 75},
{"color": "orange", "value": 90},
{"color": "red", "value": 100}
]
},
"unit": "percent"
}
}
},
{
"title": "容器OOM事件",
"type": "stat",
"gridPos": {"h": 4, "w": 6, "x": 0, "y": 8},
"targets": [
{
"datasource": {"type": "prometheus", "uid": "Prometheus"},
"expr": "sum(increase(container_oom_events_total[24h])) by (namespace, container)",
"legendFormat": "{{namespace}}/{{container}}",
"refId": "A"
}
],
"fieldConfig": {
"defaults": {
"color": {"mode": "thresholds"},
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 1},
{"color": "red", "value": 5}
]
}
}
}
},
{
"title": "节点资源压力",
"type": "table",
"gridPos": {"h": 8, "w": 18, "x": 6, "y": 8},
"targets": [
{
"datasource": {"type": "prometheus", "uid": "Prometheus"},
"expr": "100 - (node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes * 100)",
"legendFormat": "{{instance}}",
"refId": "A"
},
{
"datasource": {"type": "prometheus", "uid": "Prometheus"},
"expr": "100 - (node_filesystem_avail_bytes{mountpoint=\"/\"} / node_filesystem_size_bytes{mountpoint=\"/\"} * 100)",
"legendFormat": "{{instance}}-disk",
"refId": "B"
}
],
"fieldConfig": {
"defaults": {
"custom": {
"displayMode": "color-background"
},
"color": {"mode": "thresholds"},
"thresholds": {
"mode": "absolute",
"steps": [
{"color": "green", "value": null},
{"color": "yellow", "value": 70},
{"color": "red", "value": 90}
]
}
}
}
}
],
"schemaVersion": 39,
"tags": ["kubernetes", "containers", "resources"],
"templating": {
"list": [
{
"name": "namespace",
"type": "query",
"datasource": {"type": "prometheus", "uid": "Prometheus"},
"query": "label_values(container_memory_working_set_bytes, namespace)",
"multi": true,
"includeAll": true
},
{
"name": "cluster",
"type": "query",
"datasource": {"type": "prometheus", "uid": "Prometheus"},
"query": "label_values(container_memory_working_set_bytes, cluster)",
"multi": false,
"includeAll": false
}
]
},
"time": {"from": "now-6h", "to": "now"},
"timepicker": {},
"timezone": "",
"title": "容器资源监控",
"uid": "container-resource-monitor",
"version": 1
}
5.2 告警历史追踪面板
光看实时监控不够,你还需要一个面板专门看告警历史,方便事后复盘:
{
"panels": [
{
"title": "告警触发历史",
"type": "table",
"targets": [
{
"datasource": {"type": "prometheus", "uid": "Prometheus"},
"expr": "ALERTS{alertstate=\"firing\"}",
"legendFormat": "{{alertname}} - {{namespace}}/{{pod}}",
"refId": "A"
}
]
},
{
"title": "告警频率统计(近7天)",
"type": "graph",
"targets": [
{
"datasource": {"type": "prometheus", "uid": "Prometheus"},
"expr": "sum(increase(ALERTS{alertstate=\"firing\"}[7d])) by (alertname)",
"legendFormat": "{{alertname}}",
"refId": "A"
}
]
}
]
}
六、完整部署示例(Helm方式)
如果你用Helm部署Prometheus Stack,配置文件会简洁很多。下面是生产环境推荐配置:
# 添加官方仓库
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
# 创建自定义values.yaml
cat > values.yaml << 'EOF'
prometheus:
prometheusSpec:
scrapeInterval: "15s"
evaluationInterval: "15s"
ruleSelectorNilUsesHelmValues: false
alerting:
alertmanagers:
- name: alertmanager-main
namespace: monitoring
port: web
resources:
requests:
memory: "500Mi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
additionalPrometheusRules:
- name: container-resource-rules
groups:
- name: container-resource
rules:
- alert: ContainerMemoryHigh
expr: |
sum by (pod, namespace, container) (
container_memory_working_set_bytes
) / sum by (pod, namespace, container) (
container_spec_memory_limit_bytes
) * 100 > 85
for: 5m
labels:
severity: warning
annotations:
summary: "容器{{ $labels.pod }}内存使用率超过85%"
description: "命名空间{{ $labels.namespace }}中{{ $labels.container }}的内存使用率为{{ $value | printf \"%.2f\" }}%"
- alert: ContainerOOM
expr: increase(container_oom_events_total[1m]) > 0
for: 0m
labels:
severity: critical
annotations:
summary: "容器{{ $labels.pod }}发生OOM"
alertmanager:
alertmanagerSpec:
resources:
requests:
memory: "100Mi"
cpu: "100m"
limits:
memory: "200Mi"
cpu: "200m"
config:
route:
group_by: ['alertname', 'namespace']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: 'wechat'
routes:
- match:
alertname: ContainerOOM
receiver: 'dingtalk-critical'
group_wait: 0s
secretTemplates:
- name: alertmanager-wechat-secret
data:
WECHAT_CORP_ID: "your-corp-id"
WECHAT_SECRET: "your-secret"
DINGTALK_TOKEN: "your-token"
grafana:
enabled: true
adminPassword: admin123
sidecar:
rules:
enabled: true
searchNamespace: ALL
datasources:
enabled: true
searchNamespace: ALL
dashboards:
default:
container-resource:
gnetId: 14543 # Kubernetes集群监控大盘
revision: 3
datasource: Prometheus
persistence:
enabled: true
size: 10Gi
nodeExporter:
enabled: true
resources:
requests:
memory: "100Mi"
cpu: "100m"
limits:
memory: "200Mi"
cpu: "200m"
kubeStateMetrics:
enabled: true
resources:
requests:
memory: "200Mi"
cpu: "100m"
limits:
memory: "500Mi"
cpu: "200m"
EOF
# 部署
kubectl create namespace monitoring
helm install prometheus-stack prometheus-community/kube-prometheus-stack \
-n monitoring \
-f values.yaml
部署完之后,验证几件事:
# 1. 检查Pod状态
kubectl get pods -n monitoring
# 2. 检查Prometheus目标状态
kubectl port-forward -n monitoring svc/prometheus-stack-prometheus 9090:9090
# 浏览器访问 http://localhost:9090/targets
# 3. 检查告警规则是否加载
kubectl port-forward -n monitoring svc/prometheus-stack-prometheus 9090:9090
# 访问 http://localhost:9090/api/v1/rules
# 4. 检查Grafana
kubectl port-forward -n monitoring svc/prometheus-stack-grafana 3000:80
# 浏览器访问 http://localhost:3000,默认账号admin/admin123
七、故障排查实战案例
案例1:告警延迟10分钟才触发
现象:内存打满后,要等10分钟才能收到告警。
排查:
# 查看Prometheus配置
grep -E 'scrape_interval|evaluation_interval' /etc/prometheus/prometheus.yml
# 发现两个都是60s
# 查看规则评估时间
curl -s http://localhost:9090/api/v1/rules | jq '.data.groups[0].rules[0].evaluationTime'
# 发现评估间隔也是60s
原因:scrape_interval和evaluation_interval都设成了60s。数据每60秒抓一次,规则每60秒评估一次,最坏情况下要等120秒才能发现异常。再加上for: 5m的窗口期,总延迟可能达到7-8分钟。
解决:
global:
scrape_interval: "15s"
evaluation_interval: "15s"
同时把告警规则里的for时间根据业务容忍度调整。对于OOM这种紧急事件,可以考虑for: 1m甚至0m。
案例2:内存指标一直显示0
现象:Prometheus里container_memory_working_set_bytes全是0。
排查:
# 检查kubelet的cgroup驱动
cat /var/lib/kubelet/config.yaml | grep cgroupDriver
# 发现是systemd
# 检查cgroup版本
cat /proc/cgroups | grep memory
# 发现是cgroup v2
# 检查cadvisor指标
curl -k https://localhost:10250/metrics/cadvisor | grep container_memory
# 发现指标名不一样
原因:集群升级过cgroup v2,但Prometheus的规则还是基于v1写的。cgroup v2下,kubelet会自动做指标映射,但有些旧版本的kubelet可能没做兼容。
解决:
# 升级kubelet到1.22+
# 或者在Prometheus规则里用兼容的指标名
# cgroup v2下也可以用这个指标:
sum by (pod, namespace) (container_memory_working_set_bytes)
# 如果还是0,尝试:
sum by (pod, namespace) (container_memory_usage_bytes)
案例3:告警发了但通知没收到
现象:Prometheus Rules页面显示告警已触发,但企业微信没收到消息。
排查:
# 检查Alertmanager状态
kubectl port-forward -n monitoring svc/prometheus-stack-alertmanager 9093:9093
# 访问 http://localhost:9093/api/v2/status
# 查看告警路由
curl -s http://localhost:9093/api/v2/status | jq '.config.original.route'
# 查看通知历史
curl -s http://localhost:9093/api/v2/alerts
原因:Alertmanager的group_wait设了30秒,但实际配置里continue: true导致告警被路由到多个receiver,其中一个失败了。
解决:检查每个receiver的配置,确保webhook地址正确、secret配置正确。测试通知:
# 手动发送测试告警
curl -X POST http://localhost:9093/api/v2/alerts -H 'Content-Type: application/json' -d '[{
"labels": {"alertname": "Test", "severity": "warning"},
"annotations": {"summary": "测试告警"}
}]'
八、最佳实践总结
8.1 告警阈值设计原则
不要简单地用固定百分比。不同业务有不同的容忍度:
- 核心支付服务:内存>70%就告警,因为OOM会导致交易失败
- 后台批处理任务:内存>90%才告警,可以容忍短时高峰
- 有状态服务(数据库):不要只盯内存,IO和连接数同样重要
8.2 多层告警策略
建议设计三级告警:
- 预防级(70-80%):通过企业微信/钉钉群通知,让开发提前关注
- 警告级(85-90%):电话+短信通知值班人员
- 紧急级(OOM/宕机):直接触发应急预案,自动扩容或迁移
8.3 数据保留策略
Prometheus默认只保留15天数据,这对于故障复盘可能不够。建议:
prometheus:
prometheusSpec:
retention: "30d" # 高分辨率数据保留30天
retentionSize: "10GB" # 磁盘上限
如果需要长期保留,可以配合Thanos或Cortex做长期存储。
8.4 定期演练
告警系统配置好不是终点,要定期验证:
- 每周抽查告警历史,确认通知是否正常送达
- 每月做一次故障演练,模拟资源耗尽场景
- 每季度review一次告警规则,清理无效告警
九、常见误区提醒
误区1:告警规则越多越好 实际上,告警太多会导致”告警疲劳”,大家最后都选择性忽略。原则是:只告你能采取行动的问题。如果告警了你也不知道怎么办,那不如不告。
误区2:阈值设得越高越安全 阈值设太高(比如95%),可能已经OOM了还没告警。阈值设太低(比如50%),又天天误报。建议从80%开始,根据实际业务调整。
误区3:监控配完就完事了 监控系统是活的,业务在变、指标在变、阈值也要跟着调。建议每月review一次告警数据,把频繁误报的规则优化掉。
好了,这套东西讲得差不多了。核心就一句话:监控不是配完就行的,要持续观察、调整、验证。 Prometheus和Grafana给你提供了强大的工具,但怎么用、调到什么程度,得结合你自己的业务场景来定。
如果 deploying 过程中遇到具体问题,把错误日志贴出来,大家一起来解决。
