云计算百科
云计算领域专业知识百科平台

【K8S 运维实战】14-监控告警Prometheus

监控告警:Prometheus+Grafana+Alertmanager 黄金组合

一句话定位:K8s 该监控哪些指标?告警怎么不漏不噪?一套方法论加三件套(Prometheus + Grafana + Alertmanager)给你答案。

写在前面

我记得有一次凌晨 2 点被 On-Call 电话叫醒——生产 Deploy 正在"健康"运行(Readiness Probe 返回 200),但 user 的请求全部返回 503。后来复盘发现,监控只盯了"Pod 是否活着",但 Pod 处理请求的成功率和延迟完全没有监控。

这个事故让我深刻理解了那句话:你无法管理你没有度量的东西。

1 年下来,我迭代了三版监控体系,最终成型的就是 Prometheus + Grafana + Alertmanager 这套组合。这篇文章把我踩过的坑和方法论完整输出出来。

读完你能带走:

  • USE/RED/四黄金信号三套监控方法论
  • K8s 核心组件指标解读
  • Prometheus Operator 生产部署方案
  • PromQL 常用查询模式(TOP N / 分位 / 预测)
  • P0/P1/P2 告警分级规范和 Alertmanager 路由配置

核心问题

K8s 该监控哪些指标?告警怎么不漏不噪?

先说结论:不漏靠方法论,不噪靠分级+抑制。下面我们先讲方法论,再讲落地工具。


一、原理剖析

1.1 三套监控方法论

方法论不是学术概念,是帮你缩小排查范围的导航。出问题时,你不需要想"该查什么",方法论的检查清单会告诉你。

USE 方法论(Brendan Gregg 提出)—— 面向资源

USE = Utilization + Saturation + Errors

检查清单(每个资源问三个问题):
┌───────────┬──────────────┬──────────────┬──────────────┐
│ 资源 │ Utilization │ Saturation │ Errors │
│ │ 使用率多少? │ 排队多长? │ 有错误吗? │
├───────────┼──────────────┼──────────────┼──────────────┤
│ CPU │ 使用率% │ run queue │ N/A │
│ Memory │ 使用率% │ swap/page │ OOM Kill │
│ Disk │ 使用率% │ I/O wait │ 坏块/SMART │
│ Network │ 带宽% │ drop/retry │ checksum │
│ FD │ 已用/上限 │ N/A │ 打开失败 │
└───────────┴──────────────┴──────────────┴──────────────┘

适用场景:基础设施、节点、中间件监控

RED 方法论(Tom Wilkie 提出)—— 面向服务

RED = Rate + Errors + Duration

每个服务监控三件事:
Rate —— 每秒请求数(QPS/RPS)
Errors —— 失败请求数(按 HTTP 5xx / 超时 / 业务错误码)
Duration —— 请求延迟分布(P50/P90/P99)

适用场景:微服务、API 网关、RPC 调用监控

四黄金信号(Google SRE Book):

四黄金信号 = Latency + Traffic + Errors + Saturation

与 RED 的区别:把 Saturation(饱和度)加了进来
Latency = 请求耗时(对应 RED 的 Duration)
Traffic = 请求量(对应 RED 的 Rate)
Errors = 错误率(对应 RED 的 Errors)
Saturation = 服务有多"满"(连接数、队列深度、goroutine 数等)

适用场景:SRE 体系下的服务健康度评估

实战中我怎么做:基础设施层用 USE,服务层用 RED + Saturation。K8s 是一个分层系统,所以分层监控:

┌─────────────────────────────────────────────────────────────┐
│ K8s 监控分层 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 业务层: 订单量/支付成功率 (业务埋点/Business Metrics) │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ 应用层: QPS/延迟/错误率 (RED 方法论) │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ K8s层: Pod 状态/Deploy 可用副本/调度延迟 │ │
│ │ kube-state-metrics │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ 节点层: CPU/Mem/Disk/Net (USE 方法论) │ │
│ │ node-exporter / cAdvisor │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘

1.2 Prometheus 数据模型与 PromQL 精髓

Prometheus 时序数据模型:

指标名{标签1="值1", 标签2="值2"} 值@时间戳

示例:
http_requests_total{method="GET", handler="/api/users", status="200"} 10467 @1716000000
│ │ │ │
指标名 标签组 采样值 时间戳

PromQL 中最重要的几个概念,理解了就通了 80% 的查询:

瞬时向量 vs 范围向量:

瞬时向量 (Instant Vector):
http_requests_total ← 返回每个时间序列在查询瞬间的最新值

范围向量 (Range Vector):
http_requests_total[5m] ← 返回每个时间序列在过去 5 分钟的所有采样点
范围向量不能直接用,必须配合 rate()/avg() 等函数

rate() vs irate():
rate(http_requests_total[5m]) ← 5 分钟平均速率,平滑波动
irate(http_requests_total[5m]) ← 瞬时速率(最后两个采样点),敏感但易抖

生产建议:告警用 rate,排查问题用 irate

1.3 Prometheus Operator 架构

#mermaid-svg-i9bIp3KxLyyYbMzf{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-i9bIp3KxLyyYbMzf .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-i9bIp3KxLyyYbMzf .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-i9bIp3KxLyyYbMzf .error-icon{fill:#552222;}#mermaid-svg-i9bIp3KxLyyYbMzf .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-i9bIp3KxLyyYbMzf .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-i9bIp3KxLyyYbMzf .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-i9bIp3KxLyyYbMzf .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-i9bIp3KxLyyYbMzf .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-i9bIp3KxLyyYbMzf .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-i9bIp3KxLyyYbMzf .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-i9bIp3KxLyyYbMzf .marker{fill:#333333;stroke:#333333;}#mermaid-svg-i9bIp3KxLyyYbMzf .marker.cross{stroke:#333333;}#mermaid-svg-i9bIp3KxLyyYbMzf svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-i9bIp3KxLyyYbMzf p{margin:0;}#mermaid-svg-i9bIp3KxLyyYbMzf .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-i9bIp3KxLyyYbMzf .cluster-label text{fill:#333;}#mermaid-svg-i9bIp3KxLyyYbMzf .cluster-label span{color:#333;}#mermaid-svg-i9bIp3KxLyyYbMzf .cluster-label span p{background-color:transparent;}#mermaid-svg-i9bIp3KxLyyYbMzf .label text,#mermaid-svg-i9bIp3KxLyyYbMzf span{fill:#333;color:#333;}#mermaid-svg-i9bIp3KxLyyYbMzf .node rect,#mermaid-svg-i9bIp3KxLyyYbMzf .node circle,#mermaid-svg-i9bIp3KxLyyYbMzf .node ellipse,#mermaid-svg-i9bIp3KxLyyYbMzf .node polygon,#mermaid-svg-i9bIp3KxLyyYbMzf .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-i9bIp3KxLyyYbMzf .rough-node .label text,#mermaid-svg-i9bIp3KxLyyYbMzf .node .label text,#mermaid-svg-i9bIp3KxLyyYbMzf .image-shape .label,#mermaid-svg-i9bIp3KxLyyYbMzf .icon-shape .label{text-anchor:middle;}#mermaid-svg-i9bIp3KxLyyYbMzf .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-i9bIp3KxLyyYbMzf .rough-node .label,#mermaid-svg-i9bIp3KxLyyYbMzf .node .label,#mermaid-svg-i9bIp3KxLyyYbMzf .image-shape .label,#mermaid-svg-i9bIp3KxLyyYbMzf .icon-shape .label{text-align:center;}#mermaid-svg-i9bIp3KxLyyYbMzf .node.clickable{cursor:pointer;}#mermaid-svg-i9bIp3KxLyyYbMzf .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-i9bIp3KxLyyYbMzf .arrowheadPath{fill:#333333;}#mermaid-svg-i9bIp3KxLyyYbMzf .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-i9bIp3KxLyyYbMzf .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-i9bIp3KxLyyYbMzf .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-i9bIp3KxLyyYbMzf .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-i9bIp3KxLyyYbMzf .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-i9bIp3KxLyyYbMzf .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-i9bIp3KxLyyYbMzf .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-i9bIp3KxLyyYbMzf .cluster text{fill:#333;}#mermaid-svg-i9bIp3KxLyyYbMzf .cluster span{color:#333;}#mermaid-svg-i9bIp3KxLyyYbMzf div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-i9bIp3KxLyyYbMzf .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-i9bIp3KxLyyYbMzf rect.text{fill:none;stroke-width:0;}#mermaid-svg-i9bIp3KxLyyYbMzf .icon-shape,#mermaid-svg-i9bIp3KxLyyYbMzf .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-i9bIp3KxLyyYbMzf .icon-shape p,#mermaid-svg-i9bIp3KxLyyYbMzf .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-i9bIp3KxLyyYbMzf .icon-shape .label rect,#mermaid-svg-i9bIp3KxLyyYbMzf .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-i9bIp3KxLyyYbMzf .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-i9bIp3KxLyyYbMzf .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-i9bIp3KxLyyYbMzf :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

核心 CRD

ServiceMonitor定义如何从 Service 采集指标

PodMonitor定义如何从 Pod 采集指标

PrometheusRule定义告警/录制规则

Prometheus Operator 工作流

开发者编写 ServiceMonitor YAML

Operator 监听 ServiceMonitor 资源

Operator 生成 Prometheus 配置

ConfigReloader 热重载配置

Prometheus 按配置拉取指标

ServiceMonitor 工作流(ASCII 示意):

ServiceMonitor

│ selector.matchLabels.app=nginx

┌─────────────────┐
│ Service │
│ labels: │
│ app: nginx │
│ ports: │
│ – metrics:9113│
└────────┬────────┘

│ Endpoints

┌─────────────────────────────┐
│ Pod 1 │
│ labels: │
│ app: nginx │
│ annotations: │
│ prometheus.io/scrape=true│
└──────────────┬──────────────┘

│ scrape http://10.1.2.3:9113/metrics

┌─────────────┐
│ Prometheus │
│ Server │
└─────────────┘


二、实战操作

2.1 部署 kube-prometheus-stack

# 添加 Prometheus 社区 Helm 仓库
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 创建命名空间
kubectl create namespace monitoring

kube-prometheus-values.yaml —— 生产级配置:

# ============================================
# kube-prometheus-stack 生产部署配置
# Helm Chart 版本: 58.x (对应 Prometheus 2.50+, Grafana 10.x)
# ============================================

# ── Prometheus Operator ──
prometheusOperator:
enabled: true
image:
tag: v0.72.0
admissionWebhooks:
enabled: true
patch:
enabled: true
tls:
enabled: true
tolerations:
key: noderole.kubernetes.io/controlplane
operator: Exists
effect: NoSchedule

# ── Prometheus Server ──
prometheus:
enabled: true

prometheusSpec:
# 镜像版本
image:
repository: quay.io/prometheus/prometheus
tag: v2.50.1

# 数据保留
retention: 30d
retentionSize: 45GB # 达到 45GB 自动清理旧数据

# 资源配额
resources:
requests:
cpu: 500m
memory: 2Gi
limits:
cpu: 2000m
memory: 8Gi

# 持久化存储
storageSpec:
volumeClaimTemplate:
spec:
storageClassName: ssdsc
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 100Gi

# 远程写入(长期存储)
remoteWrite: []
# 生产建议配置 Thanos 或 VictoriaMetrics:
# remoteWrite:
# – url: http://victoriametrics.monitoring.svc:8428/api/v1/write

# 采集间隔
scrapeInterval: 30s
evaluationInterval: 30s

# 外部标签(所有指标都会带上)
externalLabels:
cluster: prodk8s1
region: cneast2
env: production

# 副本数(高可用)
replicas: 2

# 开启 Admin API(排查问题用)
enableAdminAPI: true

# 关闭不需要的 Etcd/KubeScheduler/KubeControllerManager 监控
# (托管 K8s 如 EKS/GKE 这些组件不可达)
kubeEtcd:
enabled: false
kubeScheduler:
enabled: false
service:
enabled: false
kubeControllerManager:
enabled: false
service:
enabled: false

# ServiceMonitor 选择器(空 = 采集所有)
serviceMonitorSelector: {}
podMonitorSelector: {}

# 告警规则选择器
ruleSelector:
matchLabels:
role: alertrules

# Alertmanager 配置
alerting:
alertmanagers:
namespace: monitoring
name: alertmanageroperated
port: web
apiVersion: v2

# 容忍度(允许调度到控制节点)
tolerations:
key: noderole.kubernetes.io/controlplane
operator: Exists
effect: NoSchedule

# ── Service ──
service:
type: ClusterIP
port: 9090
annotations:
prometheus.io/scrape: "true"

# ── Alertmanager ──
alertmanager:
enabled: true

alertmanagerSpec:
image:
tag: v0.27.0

replicas: 3 # 集群模式
clusterAdvertiseAddress: true

resources:
requests:
cpu: 100m
memory: 256Mi
limits:
cpu: 500m
memory: 1Gi

# Alertmanager 配置(外部化到 Secret)
configSecret: alertmanagerconfig

# 持久化存储(用于静默和通知状态)
storage:
volumeClaimTemplate:
spec:
storageClassName: ssdsc
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi

tolerations:
key: noderole.kubernetes.io/controlplane
operator: Exists
effect: NoSchedule

# ── Grafana ──
grafana:
enabled: true
image:
tag: 10.4.0

# Admin 密码
adminPassword: "" # 留空则使用 Secret 管理

# 数据源(Loki、Jaeger 等在下一篇配置)
additionalDataSources:
name: Prometheus
type: prometheus
url: http://prometheusoperated.monitoring.svc:9090
access: proxy
isDefault: true
jsonData:
timeInterval: 30s

# 资源
resources:
requests:
cpu: 200m
memory: 512Mi
limits:
cpu: 1000m
memory: 1Gi

# 持久化
persistence:
enabled: true
size: 20Gi
storageClassName: ssdsc

# 预装插件
plugins:
grafanapiechartpanel
grafanapolystatpanel

# Dashboard Providers
dashboardProviders:
dashboardproviders.yaml:
apiVersion: 1
providers:
name: default
orgId: 1
folder: ""
type: file
disableDeletion: true
editable: true
options:
path: /var/lib/grafana/dashboards/default

# 核心看板(K8s 集群、节点、Pod)
dashboards:
default:
k8s-cluster-overview:
gnetId: 15757 # K8s Cluster Overview
revision: 1
datasource: Prometheus
k8s-nodes:
gnetId: 15759 # K8s Nodes Detail
revision: 1
datasource: Prometheus
k8s-pods:
gnetId: 15760 # K8s Pods Detail
revision: 1
datasource: Prometheus

# ── kube-state-metrics ──
kubeStateMetrics:
enabled: true
resources:
requests:
cpu: 100m
memory: 200Mi
limits:
cpu: 200m
memory: 400Mi

# ── node-exporter ──
nodeExporter:
enabled: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi

# ── Prometheus Node Exporter ──
prometheus-node-exporter:
enabled: true
extraArgs:
collector.filesystem.mountpointsexclude=^/(dev|proc|sys|var/lib/docker/.+|var/lib/kubelet/.+)($|/)
collector.filesystem.fstypesexclude=^(autofs|binfmt_misc|cgroup|configfs|debugfs|devpts|devtmpfs|fusectl|hugetlbfs|mqueue|overlay|proc|procfs|pstore|rpc_pipefs|securityfs|sysfs|tracefs)$

部署命令:

# 部署 kube-prometheus-stack
helm upgrade –install kube-prometheus-stack \\
prometheus-community/kube-prometheus-stack \\
–namespace monitoring \\
–values kube-prometheus-values.yaml \\
–timeout 15m \\
–wait

# 验证所有组件运行正常
kubectl get pods -n monitoring

# 预期输出示例:
# NAME READY STATUS
# alertmanager-kube-prometheus-stack-alertmanager-0 2/2 Running
# kube-prometheus-stack-grafana-xxxxx 3/3 Running
# kube-prometheus-stack-kube-state-metrics-xxxxx 1/1 Running
# kube-prometheus-stack-operator-xxxxx 1/1 Running
# kube-prometheus-stack-prometheus-node-exporter-xxxxx 1/1 Running
# prometheus-kube-prometheus-stack-prometheus-0 2/2 Running

2.2 核心指标解读

以下是每个 K8s 运维必须记住的核心指标,按监控分层组织:

节点层(node-exporter):

指标含义USE告警阈值建议
node_cpu_seconds_total CPU 各模式时间 U > 80% 持续 10min
node_memory_MemAvailable_bytes 可用内存 U < 10% 总内存
node_disk_io_time_seconds_total 磁盘 I/O 时间 S > 80% 持续 10min
node_filesystem_avail_bytes 文件系统可用空间 U < 20%
node_network_receive_drop_total 网卡丢包 E > 0

K8s 对象层(kube-state-metrics):

指标含义关键查询
kube_deployment_status_replicas_available 可用副本数 与 desired 对比
kube_pod_status_phase Pod 状态 过滤 Pending/Failed
kube_node_status_condition 节点状态 condition=Ready, status=true
kube_persistentvolume_status_phase PV 状态 监控 Available/Bound 比例
kube_job_status_failed Job 失败计数 > 0

容器层(cAdvisor):

指标含义
container_cpu_usage_seconds_total 容器 CPU 总使用
container_memory_working_set_bytes 容器工作集内存(OOM 判断依据)
container_network_transmit_bytes_total 容器网络发送字节
container_fs_reads_bytes_total 容器文件系统读取

2.3 PromQL 实战速查

# ── TOP N 查询 ──

# CPU 使用 Top 5 Pod(按 namespace 聚合)
topk(5,
sum(rate(container_cpu_usage_seconds_total{
container!="", namespace!="kube-system"
}[5m])) by (namespace, pod)
)

# 内存使用 Top 10 Pod
topk(10,
sum(container_memory_working_set_bytes{
container!="", namespace!="kube-system"
}) by (namespace, pod)
)

# ── 分位查询 ──

# P99 请求延迟(应用需暴露 histogram 指标)
histogram_quantile(0.99,
sum(rate(http_request_duration_seconds_bucket[5m])) by (le, handler)
)

# P50/P90/P99 一起看(多分位)
histogram_quantile(0.50, rate(http_request_duration_seconds_bucket[5m]))
histogram_quantile(0.90, rate(http_request_duration_seconds_bucket[5m]))
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))

# ── 错误率 ──

# 5xx 错误率
sum(rate(http_requests_total{status=~"5.."}[5m])) by (service)
/
sum(rate(http_requests_total[5m])) by (service)

# ── 可用性 ──

# 服务可用副本率
sum(kube_deployment_status_replicas_available{namespace="production"})
by (deployment)
/
sum(kube_deployment_spec_replicas{namespace="production"})
by (deployment)

# ── 预测查询 ──

# 磁盘空间 24 小时预测(线性回归)
predict_linear(
node_filesystem_avail_bytes{mountpoint="/"}[6h],
24 * 3600
) < 0

# ── 变化率查询 ──

# Deployment 最近 1 小时扩容次数(判断是否有异常)
changes(kube_deployment_spec_replicas[1h])

# Pod 重启次数增长率(判断 CrashLoop 趋势)
rate(kube_pod_container_status_restarts_total[15m])

# ── 缺失检测 ──

# 服务超过 10 分钟没有收到请求(可能挂了)
absent_over_time(http_requests_total{service="payment"}[10m])


三、踩坑与排查

3.1 坑一:Prometheus 内存持续增长直到 OOM

现象:Prometheus 运行几小时后内存突破 8Gi limit,被 OOMKilled 重启,循环往复。

原因:某个 ServiceMonitor 配置的采集间隔过短(如 5 秒),且该服务暴露了大量时间序列(histogram 指标默认每个 bucket 对应一个时间序列,一个 histogram 可能有 50+ 个序列)。

定位:

# 1. 查看 Prometheus 当前序列数
kubectl port-forward -n monitoring svc/prometheus-operated 9090:9090
curl -s http://localhost:9090/api/v1/status/tsdb | jq '.data'

# 输出示例(观察 headSeries 数量):
# {
# "headStats": {
# "numSeries": 8500000, ← 序列数 850 万!明显异常
# "chunkCount": 12300000,
# …
# }
# }

# 2. 找到序列数最多的目标
curl -s http://localhost:9090/api/v1/targets | jq '.data.activeTargets[] | {labels: .labels, scrapeUrl: .scrapeUrl}' | head -20

# 3. 查询前 10 个序列数最多的指标
curl -s 'http://localhost:9090/api/v1/query?query=topk(10, count by(__name__)({__name__=~".+"}))' | jq

解决方案:

# 在 ServiceMonitor 或 PodMonitor 中配置 metric relabeling
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: myapp
spec:
endpoints:
port: metrics
interval: 30s
# 关键:只保留需要的指标
metricRelabelings:
# 仅保留 http_ 开头的指标
sourceLabels: [__name__]
regex: 'http_.*'
action: keep
# 删除高基数的 histogram bucket(保留 summary 替代)
sourceLabels: [__name__]
regex: '.*_bucket'
action: drop

同时在 Prometheus 全局配置中限制:

prometheus:
prometheusSpec:
# 限制每个目标的序列数
enforcedSampleLimit: 50000
# 目标标签数量限制
enforcedLabelLimit: 30

3.2 坑二:告警风暴

现象:集群网络抖动 2 分钟,所有服务的 P99 延迟告警同时触发,On-Call 手机被打爆,实际只有 2 个服务真正受影响。

原因:告警规则没有分级,没有做依赖链路的告警抑制。网络问题是根因,服务延迟是症状。

解决方案——Alertmanager 抑制规则:

# alertmanager-config.yaml
apiVersion: v1
kind: Secret
metadata:
name: alertmanagerconfig
namespace: monitoring
stringData:
alertmanager.yaml: |
global:
resolve_timeout: 5m
smtp_smarthost: 'smtp.company.com:587'
smtp_from: 'alertmanager@company.com'
smtp_auth_username: 'alertmanager@company.com'
smtp_auth_password: 'xxx'

# ── 路由树 ──
route:
receiver: 'default-receiver'
group_by: ['alertname', 'severity']
group_wait: 30s
group_interval: 5m
repeat_interval: 4h # 同一条告警最多 4 小时重复一次

routes:
# P0 告警:立即通知
match:
severity: P0
receiver: 'pagerduty-critical'
continue: true

# P1 告警:5 分钟分组后通知
match:
severity: P1
receiver: 'wechat-p1'
group_wait: 10s
continue: true

# P2 告警:仅记录
match:
severity: P2
receiver: 'null'

# ── 抑制规则 ──
inhibit_rules:
# 规则1: 节点 NotReady 时,抑制该节点上所有 Pod 的告警
source_match:
alertname: NodeNotReady
severity: P0
target_match_re:
severity: 'P[12]'
equal: ['node']

# 规则2: 集群级别的网络告警触发时,抑制服务级别的延迟告警
source_match:
alertname: ClusterNetworkIssue
target_match:
alertname: HighLatency
equal: ['cluster']

# 规则3: namespace 级别的告警触发时,抑制该 ns 下所有服务的告警
source_match:
alertname: NamespaceQuotaExceeded
target_match_re:
alertname: 'PodCrashLooping|DeploymentReplicasMismatch'
equal: ['namespace']

# ── 接收器 ──
receivers:
name: 'default-receiver'
webhook_configs:
url: 'http://alert-webhook.company.com/alert'

name: 'pagerduty-critical'
# 接入 PagerDuty / 电话通知

name: 'wechat-p1'
wechat_configs:
corp_id: 'ww1234567890'
to_party: '2'
agent_id: '1000001'
api_secret: 'xxx'

name: 'null'

3.3 坑三:node-exporter 采集失败(托管 K8s)

现象:在 EKS/GKE/ACK 等托管 K8s 上,node-exporter 部署正常但 Prometheus 无法采集,targets 页面显示 context deadline exceeded。

原因:托管 K8s 的节点 IP 是云厂商 VPC 内网 IP,Prometheus 可能无法直接通过 Pod 网络访问。此外,安全组/防火墙可能没有放行 node-exporter 端口(9100)。

定位:

# 1. 手动测试 node-exporter 可达性
NODE_EXPORTER_POD=$(kubectl get pod -n monitoring -l app.kubernetes.io/name=prometheus-node-exporter -o jsonpath='{.items[0].metadata.name}')
NODE_IP=$(kubectl get pod -n monitoring $NODE_EXPORTER_POD -o jsonpath='{.status.hostIP}')

# 2. 从 Prometheus Pod 内测试网络
kubectl exec -n monitoring prometheus-kube-prometheus-stack-prometheus-0 — \\
wget -qO- http://${NODE_IP}:9100/metrics

解决方案:

prometheus-node-exporter:
# 使用 hostNetwork 模式(DaemonSet 默认使用)
hostNetwork: true
hostPID: true

# 或改用 Service 端点(推荐)
service:
enabled: true
type: ClusterIP
port: 9100
targetPort: 9100
annotations:
prometheus.io/scrape: "true"


四、最佳实践

告警分级清单

P0(5 分钟内响应) P1(30 分钟内响应) P2(工作时间处理)
├─ 集群核心组件不可用 ├─ 服务可用副本 < 期望 ├─ 证书即将过期
├─ Node NotReady > 5min ├─ P99 延迟 > 1s(5min) ├─ PV 容量 > 80%
├─ 核心服务错误率 > 5% ├─ 磁盘可用 < 20% ├─ HPA 达到最大副本
├─ 证书已过期 ├─ Pod CrashLoopBackOff ├─ 内存使用 > 80%(非紧急)
└─ PV 无法绑定 └─ CPU Throttling > 50% └─ ConfigMap/Secret 超额

告警规则设计原则

  • 每条规则必须有 for 持续时间——过滤瞬时抖动,建议 P0 >= 1min,P1 >= 5min,P2 >= 10min
  • summary 写清楚影响范围,“某服务错误率高” 不如 “payment 服务 P99 延迟 2.3s(阈值 1s)”
  • description 写排查第一步,“检查 payment-* Pod 日志和 DB 连接”
  • 多用 rate(),少用 irate()——平滑告警,减少噪点
  • 告警规则做减法——如果你从来没响应过某条告警,删掉它
  • 一次告警风暴后必须做 RCA 和规则优化

看板设计清单

  • 使用变量(Variables):namespace、deployment、pod 做成下拉选择
  • 第一行放 RED 指标:Rate / Errors / Duration 一眼看到服务健康
  • 下方放 USE 指标:CPU/Mem/Disk/Network 时间序列
  • 右上角加部署标注(Annotations):发布事件在 Grafana 上标竖线
  • 告警阈值画水平线:Prometheus Alert 阈值在图表上可视化
  • 每个面板有 Description:解释指标含义和排查思路

长期存储选型

方案优点缺点适用规模
Thanos Prometheus 原生兼容,Sidecar 模式无侵入 组件多,运维复杂 大型集群(>100节点)
VictoriaMetrics 单二进制,压缩率极高(7x),查询快 非 Prometheus 官方项目 中小规模首选
Mimir Grafana 官方,对象存储原生,多租户 较新,生态不如 Thanos 多租户 SaaS
Cortex 成熟,水平扩展 运维复杂,Mimir 替代中 不推荐新项目

推荐方案:中小规模用 VictoriaMetrics,大规模式用 Thanos。


五、小结

监控体系的建设是迭代出来的,不是设计出来的。我的建议:

  • 第一周:部署 Prometheus + Grafana + kube-state-metrics + node-exporter,先看到数据
  • 第二周:按 RED 方法论给你的核心服务配上告警,告警先走邮件
  • 第三周:根据告警反馈调整阈值和分级,接入 Alertmanager 路由
  • 第四周:优化看板、添加长期存储、建立 On-Call 流程
  • 另外记住一句话:告警的价值 = 响应行动的有效性。如果你看到一条告警后不知道该做什么,说明这条告警定义有问题。


    思考题

  • Prometheus 的 rate() 函数要求至少两个采样点才能计算,如果你的目标 scrape 间隔是 30s,rate(…[1m]) 为什么比 rate(…[30s]) 更可靠?
  • 如果你的服务使用 HPA 自动扩缩容,告警规则中的 sum by (pod) 会在扩缩容时出现什么现象?如何避免?
  • 一个节点 CPU 使用率 95% 但负载(load average)只有 2,另一个节点 CPU 使用率 60% 但负载 32,哪个节点更"忙"?为什么?

  • 延伸阅读

    • Prometheus 官方文档 – 最佳实践
    • Google SRE Book – 监控分布式系统
    • Robust Perception – PromQL 进阶
    • USE 方法论原文(Brendan Gregg)
    • RED 方法论(Tom Wilkie)
    赞(0)
    未经允许不得转载:网硕互联帮助中心 » 【K8S 运维实战】14-监控告警Prometheus
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!