#DevOps

Prometheus + Grafana:搭建可观测

服务跑着,但你不知道它健康不健康、慢不慢——缺可观测。本文讲清 Prometheus + Grafana 这套经典组合:指标(metric types)、pull 模型、PromQL 基础、给应用埋指标、Grafana 画看板 + 告警,以及日志/链路如何补全可观测三支柱。

本文要点

  • 主题聚焦:DevOps
  • 适合人群:独立开发者、全栈工程师、AI 应用开发者
  • 核心价值:快速理解背景、实践路径、常见误区和工程建议
作者 @admin·发布于 2026年6月29日·更新于 2026年7月23日

服务上线那天,运维最常被问的一句话不是"架构图发我看看",而是"现在到底健康不健康?"。你登机器敲 top、df -h、kubectl get pods,一堆数字摆出来,对方还是不满意——因为"CPU 50%"说明不了"用户请求慢不慢","内存没用满"也回答不了"有没有人在报错"。没有可观测,你只是在猜。

Prometheus + Grafana 这套组合,过去八年几乎是云原生可观测的事实标准。这篇文章把它讲透:数据模型、指标类型、埋点、PromQL、看板和告警,最后补全日志和链路,凑齐可观测三支柱。

一、可观测三支柱:指标、日志、链路

可观测(Observability)这个词被滥用了,但它的内核很朴素——从系统的外部输出,推断系统内部状态。它有三个支柱,各管一件事:

  • 指标(Metrics):带时间戳的数值序列。"过去 5 分钟 QPS 多少""p95 延迟多少""错误率多少"。聚合的、便宜的、适合报警。这是 Prometheus 的主场。
  • 日志(Logs):带时间戳的事件记录。"这次请求报了什么错""用户 ID 是谁""栈是什么"。离散的、贵的、适合排查。
  • 链路(Traces):一次请求经过的所有服务的树状记录。"这笔慢订单,慢在支付服务还是库存服务"。跨服务的、因果的、适合定位。

三者关系不是替代,是配合:指标告诉你"有问题"(错误率从 0.1% 涨到 5%),链路告诉你"问题在哪一段"(支付服务调外部网关超时),日志告诉你"具体为什么"(网关返回了某条 4xx)。本文聚焦指标,但一定要记着:只看指标做不好可观测,后文第七节会讲怎么补全。

二、Prometheus 的模型:pull、时间序列、标签

理解 Prometheus,先忘掉"日志上报"那套思路。它的核心模型是时间序列(time series):一个指标名 + 一组标签,在时间轴上不断有新的浮点数值落下来。

text
http_requests_total{method="POST", path="/api/order", status="200"}  1432
http_requests_total{method="POST", path="/api/order", status="500"}  7

每一行就是一个独立的时间序列。标签的每一种组合都是一条新序列——这点很关键,后面埋点时要避免标签维度爆炸(千万别把 user_id、order_id 当标签,那会瞬间产生几十万条序列,直接把 Prometheus 撑爆)。

第二个关键是 pull 模型。Prometheus 不等你推数据,而是它主动来**抓(scrape)**你的 /metrics 接口。这跟 StatsD、Datadog Agent 的 push 模型正好相反。pull 的好处很明显:

  1. 应用不需要知道监控系统的地址——它只要暴露 HTTP 接口,Prometheus 来不来拉是监控的事。应用和监控解耦。
  2. Prometheus 主动感知应用死活。如果一个实例拉不到了,Prometheus 自己就知道,不用额外做心跳。
  3. 拉取频率可控,采集压力可预测,不会因为某个应用突然疯狂打点把后端打挂。

代价是需要服务发现:Prometheus 得知道"哪些实例需要拉、地址是多少"。在 Kubernetes 里这天然方便(kubernetes_sd_configs 自动发现 Pod);在静态部署里写几行 static_config 也很简单。短任务(life cycle 太短、来不及被拉)是 pull 模型的盲区,这时要用 Pushgateway 做中转——但这是例外,不是常规。

三、四种指标类型:Counter / Gauge / Histogram / Summary

埋指标前必须分清四种类型,选错类型,后面 PromQL 怎么写都不对。

Counter(计数器):单调递增,只能加不能减(重启归零)。代表"累计发生的事"。比如 http_requests_total(累计请求数)、errors_total(累计错误数)。注意它本身不能直接当 QPS 看——它一直涨是正常的,要看"涨得多快"得用 rate()。计数器突然下降,要么重启了,要么负数,都是异常信号。

Gauge(瞬时值):可增可减,代表"当前状态"。比如 memory_usage_bytes(当前内存)、active_connections(当前连接数)、queue_depth(队列长度)、temperature_celsius(温度)。看板直接画它的值就行,不用 rate。

Histogram(直方图):把数据分桶统计分布。这是测延迟的正确姿势。比如设桶 [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] 秒,每个请求按耗时落到对应桶里,累计计数。配合 histogram_quantile() 能算任意分位(p50/p95/p99)。桶的选择是 Histogram 设计的核心——桶太粗,p95 误差巨大;桶太细,序列爆炸。一般按业务 SLA 的量级定,比如 HTTP API 用 [0.01 ... 10] 这套默认桶基本够用。

Summary(摘要):在客户端直接算好分位数再上报。看起来比 Histogram 省事,但它有个致命缺陷:多个实例的分位数不能聚合。你有 10 台机器,各自报了 p95,你没法从这 10 个 p95 算出"全局 p95"——数学上不存在这种聚合。所以生产上几乎无脑选 Histogram,Summary 只在单实例场景或固定标签聚合下用。

一句话记忆:累计的用 Counter,瞬时的用 Gauge,分布的用 Histogram。

四、给应用埋指标:client 库 + /metrics + RED 方法

理论落地,就是给代码埋点。以 Node.js 为例,官方推荐 prom-client:

js
import { Registry, Counter, Histogram, collectDefaultMetrics } from 'prom-client');

const registry = new Registry();
collectDefaultMetrics({ register: registry }); // 自动采集 Node 进程的 CPU/内存/GC 等

const httpRequestTotal = new Counter({
  name: 'http_requests_total',
  help: 'Total HTTP requests',
  labelNames: ['method', 'path', 'status'],
  registers: [registry],
});

const httpDuration = new Histogram({
  name: 'http_request_duration_seconds',
  help: 'HTTP request duration',
  labelNames: ['method', 'path', 'status'],
  buckets: [0.01, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10],
  registers: [registry],
});

// 中间件里埋点
app.use((req, res, next) => {
  const start = Date.now();
  res.on('finish', () => {
    const labels = { method: req.method, path: routePattern(req), status: String(res.statusCode) };
    httpRequestTotal.inc(labels);
    httpDuration.observe(labels, (Date.now() - start) / 1000);
  });
  next();
});

// 暴露 /metrics
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', registry.contentType);
  res.end(await registry.metrics());
});

三个细节决定埋点质量:

  1. path 要用路由模板,别用真实 URL。path="/api/order/:id" 而不是 path="/api/order/12345"。后者会把每个订单 ID 变成一条新序列,几小时就把内存撑爆。这是新手第一大坑。
  2. label 数量要克制。method、path、status 这种基数低的可以;user_id、session_id 这种基数高的坚决不行。标签的基数乘上序列数,要能装进一张表。
  3. 必埋的三类指标按 RED 方法:Rate(请求速率)、Errors(错误数)、Duration(延迟分布)。这是 Google SRE 书里给微服务定的最小集,几乎所有 HTTP 服务都适用。再加一个 Saturation(饱和度,如连接池占用),就是 USE 方法,适合基础设施。RED 管服务,USE 管资源,两条线一起上,可观测的骨架就立起来了。

五、PromQL 基础:rate / sum by / histogram_quantile

指标埋好了,接下来是查询。PromQL 是声明式的,初看别扭,掌握三个函数能解决 80% 场景。

查 QPS(每秒请求数):

promql
sum(rate(http_requests_total[5m])) by (path)

rate() 把 Counter 的累计值变成"每秒增量"——这是看 Counter 的唯一正确姿势。[5m] 是窗口,窗口太短抖动大,太长反应慢,5 分钟是常用值。sum by (path) 把所有 method、status、实例的序列按 path 聚合。

查错误率:

promql
sum(rate(http_requests_total{status=~"5.."}[5m])) by (path)
  /
sum(rate(http_requests_total[5m])) by (path)

分子是 5xx 的速率,分母是全部速率,两个 sum by (path) 维度对齐才能除。status=~"5.." 是正则匹配 500–599。错误率比绝对错误数更有信息量——QPS 涨了错误跟着涨是正常的,错误率窜上来才是真问题。

查 p95 延迟:

promql
histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))

这是 Histogram 的杀手锏。注意几点:必须用 _bucket 结尾的序列(分桶数据),by (le) 不可省(le 是桶上界),rate 在前 sum 在中 histogram_quantile 在后,顺序错了结果就错。这条查询能跨所有实例聚合出全局 p95——这正是 Histogram 相对 Summary 的核心优势。

把这三条查询画成三个面板,一个服务的健康度就一目了然了。

六、Grafana:数据源、看板、告警

Prometheus 自己的 UI 只够调试用,真正给人看的是 Grafana。

接数据源:Grafana 里加 Prometheus 数据源,填地址(http://prometheus:9090),保存即用。Prometheus 本身存的是本地 TSDB,默认 15 天左右,数据量大了要接远程存储(VictoriaMetrics、Thanos、Mimir)做长期保存和高可用。

画看板:每个 Panel 写一条 PromQL,选可视化类型(时序图、Stat、Gauge、 heatmap)。Hero 建议:一个服务的看板顶部放四个大数字——QPS、错误率、p95 延迟、实例数。下面再画各维度的时序曲线。这样的看板自上而下"总量→结构→趋势",人一眼能读出健康度。

告警:Grafana 内置告警(managed alerting),但生产里更常见的是 Prometheus 自带的 Alertmanager——它跟 Prometheus 解耦,负责"告警去重、分组、抑制、路由到不同渠道"。一条告警规则长这样:

yaml
groups:
- name: api-alerts
  rules:
  - alert: HighErrorRate
    expr: |
      sum(rate(http_requests_total{status=~"5.."}[5m])) by (path)
        / sum(rate(http_requests_total[5m])) by (path) > 0.05
    for: 10m            # 持续 10 分钟才触发,避免抖动误报
    labels:
      severity: page
    annotations:
      summary: "{{ $labels.path }} 错误率超 5%"
      description: "当前错误率 {{ $value | humanizePercentage }},持续 10 分钟"

for: 10m 这条很重要——没有 for 的告警会在瞬时抖动时狂响,把你和值班同事轰炸到麻木,最后所有告警都被忽略。这是"告警疲劳"的源头,务必克制。

告警设计有几条实战铁律:只报需要人介入的(自动恢复的别报)、按 SLO 报而不是按阈值报(错误率影响用户才报,不是 CPU 过 80% 就报)、分级(page / ticket / slack,别所有事都打电话)。一个健康的告警系统,每天每值班人不超过 5 条,超过了说明阈值或规则该改。

七、补全三支柱:日志(Loki)+ 链路(OpenTelemetry / Jaeger)

指标只是可观测的第一根柱子。当你看到"错误率涨了",下一步必然是"去看具体错在哪"——这时候需要日志和链路。

日志侧:Loki。Grafana 自家的日志系统,设计哲学跟 Prometheus 一脉相承——只索引标签,不索引日志正文,因此写入和查询都便宜。Loki 的标签最好和 Prometheus 的标签对齐(同一个 service、instance),这样在 Grafana 里可以从一个指标面板直接下钻到对应日志——这就是"指标和日志关联"的体感:看到 p95 飙高的那个时间点,点一下就能把那台机器那段时间的日志拉出来。

链路侧:OpenTelemetry + Jaeger / Tempo。OpenTelemetry(OTel)是 CNCF 推的可观测数据采集标准,一套 SDK 同时产出 metrics、logs、traces 三种信号,且能通过 trace context(traceparent HTTP 头)把跨服务的请求串起来。Tempo 是 Grafana 的链路后端,跟 Loki、Prometheus 一起能做一件事:从指标 → 链路 → 日志的三段式下钻。

举个具体场景:Grafana 上看到 POST /api/order 错误率从 0.2% 涨到 6%。

  1. 点错误率面板,下钻出这段时间的 trace 列表。
  2. 选一条慢 trace,看到它经过了 api → payment → external_gateway,external_gateway 这一段花了 4.8 秒。
  3. 再点这一段,下钻出当时的日志,看到网关返回了 connection reset。

三支柱用同一个 trace_id、同一组标签串起来,排障路径从"猜"变成了"顺着走"。这才是"可观测"该有的样子——不是三个孤立的系统,是一张能来回跳的网。

小结

  • 指标、日志、链路缺一不可:指标告诉你"有问题",链路告诉你"在哪段",日志告诉你"具体为什么"。Prometheus + Grafana 解决指标这一支柱,但别止步于此。
  • Prometheus 的灵魂是 pull + 时间序列 + 标签:应用只管暴露 /metrics,采集交给 Prometheus;标签是维度,但高基数标签(user_id、order_id)是序列爆炸的元凶,坚决不埋。
  • 四种指标类型选对再写 PromQL:Counter 用 rate,Gauge 直接看,Histogram 算分位数,Summary 几乎别用(跨实例不能聚合)。一句话:累计用 Counter、瞬时用 Gauge、分布用 Histogram。
  • RED 是微服务最小指标集:Rate / Errors / Duration 三条线,配上 histogram_quantile 的 p95,一个服务的健康度看板就立住了。
  • 告警要克制,看板要分层:for 持续时间防抖动,按 SLO 报而非按阈值报,每天每值班人不超过 5 条才算健康。

一句话收尾:可观测不是堆工具,是让你在凌晨三点被告警叫醒时,十分钟内定位到那台机器那行代码。Prometheus + Grafana 这套经典组合,加上 Loki 和 OpenTelemetry,把这件事从玄学变成了工程。

Related

相关推荐