如何用 Grafana 和 Elasticsearch 搭建 API 监控
从零搭建生产级 API 监控 —— Elasticsearch 数据源、Lucene 查询、Grafana 面板与告警规则。
你的 API 正在生产环境中运行。用户在访问各个端点,服务之间相互调用,一切看起来都很正常 —— 直到出问题为止。某个支付端点开始返回 500 错误;搜索路由的延迟从 200ms 悄悄爬升到 3 秒;某个下游依赖挂掉,错误率从 0.1% 飙升到 12%。如果没有监控,你只能从愤怒的用户口中得知这些问题。而有了合适的仪表盘,你能在几分钟内发现问题,往往在用户察觉之前就已经知道。
本文将带你使用 Grafana 11.x 搭配 Elasticsearch 作为数据源,构建一套完整的 API 监控仪表盘。我们会讲解整体架构、数据源配置、六个核心仪表盘面板(附带真实的查询语法)、基于 Grafana 统一告警(Unified Alerting)系统的告警配置,以及那些能让仪表盘从”随便瞥一眼”进化为”真正预防故障”的生产实战技巧。
为什么 API 监控很重要
在深入具体工具之前,值得先用现代运维团队思考可靠性的框架来铺垫一下这个话题:SLO、SLI 与错误预算。
服务水平指标(SLI) 是你实际测量的指标 —— 请求延迟、错误率、吞吐量。服务水平目标(SLO) 是你为这些指标设定的目标 —— 比如”99.5% 的请求成功完成”或”P95 延迟保持在 300ms 以下”。错误预算 则是完美与你的 SLO 之间的差距:如果 SLO 是 99.5% 的成功率,那么每个测量窗口内你就有 0.5% 的错误预算。
本文要构建的仪表盘直接测量了 Google SRE 手册中定义的四大黄金信号里的三个:
- 延迟(Latency) —— 请求耗时(P50、P95、P99)
- 流量(Traffic) —— 每分钟有多少请求流经系统
- 错误(Errors) —— 有多少百分比的请求失败
第四个信号 饱和度(Saturation) 通常来自基础设施指标(CPU、内存、磁盘),而非访问日志。这四个信号合在一起,能为你勾勒出 API 健康状况的全景图。
它的实用价值立竿见影:当凌晨 3 点因为 /api/v1/payments 的错误率超过 2% 而触发告警时,值班工程师打开仪表盘,看到这次飙升恰好始于一次新部署上线,再关联到同一端点上的延迟增长,就有了足够的上下文来决定是回滚还是深入排查具体的故障模式 —— 整个过程只需几分钟。
架构
监控管线其实很简单:API 流量产生访问日志,这些日志流入 Elasticsearch,Grafana 再查询 Elasticsearch 来驱动仪表盘和告警。
下面是各个组件的职责:
API 网关(nginx、AWS ALB、Kong 等) 作为所有 API 流量的入口。网关为每个请求写入结构化的访问日志,包括 HTTP 方法、路径、状态码、响应时间、客户端 IP 和请求大小。这里的关键决策是日志格式 —— 尽可能使用 JSON,因为它能省去下游复杂的解析规则。
一个典型的 nginx JSON 日志配置如下:
log_format json_combined escape=json
'{'
'"timestamp":"$time_iso8601",'
'"method":"$request_method",'
'"path":"$uri",'
'"status":$status,'
'"response_time":$request_time,'
'"bytes_sent":$bytes_sent,'
'"remote_addr":"$remote_addr",'
'"user_agent":"$http_user_agent",'
'"upstream_response_time":"$upstream_response_time",'
'"request_id":"$request_id"'
'}';
access_log /var/log/nginx/api_access.log json_combined;
日志采集器(Filebeat 或 Fluentd) 负责读取日志文件、解析它们,并发送到 Elasticsearch。Filebeat 更轻量,且与 Elastic Stack 原生集成。如果你需要把日志路由到多个目的地,Fluentd 则更灵活。
一个用于采集 nginx JSON 日志的最小化 Filebeat 配置:
filebeat.inputs:
- type: filestream
id: api-access-logs
paths:
- /var/log/nginx/api_access.log
parsers:
- ndjson:
target: ""
add_error_key: true
output.elasticsearch:
hosts: ["https://elasticsearch:9200"]
index: "api-access-%{+yyyy.MM.dd}"
username: "${ES_USERNAME}"
password: "${ES_PASSWORD}"
setup.ilm.enabled: true
setup.ilm.rollover_alias: "api-access"
setup.ilm.pattern: "{now/d}-000001"
Elasticsearch 存储访问日志,并提供 Grafana 查询所依赖的聚合引擎。每条访问日志都会成为时序索引(例如 api-access-2023.01.08)中的一个文档。正是 Elasticsearch 的聚合框架 —— terms、date histogram、percentiles、filters —— 让”按端点分组统计过去一小时的 P99 延迟”这类指标能够实时计算出来。
Grafana 查询 Elasticsearch 以渲染仪表盘面板并评估告警规则。Grafana 的 Elasticsearch 数据源插件原生支持 Elasticsearch 查询 DSL,因此你可以在面板编辑器中使用 Lucene 语法和聚合构建器来配置查询。
配置 Grafana 与 Elasticsearch
3.1 添加 Elasticsearch 数据源
在 Grafana 11.x 中,进入 Connections > Data sources > Add data source,选择 Elasticsearch。关键配置字段如下:
| 字段 | 值 | 说明 |
|---|---|---|
| URL | https://elasticsearch:9200 | 生产环境请使用 HTTPS |
| Authentication | Basic Auth 或 API Key | 绝不要禁用认证 |
| Index name | api-access-* | 通配符匹配每日索引 |
| Time field | timestamp | 必须与日志中的时间戳字段一致 |
| Max concurrent shard requests | 5 | 防止仪表盘查询压垮 ES |
| Min time interval | 1m | 与日志粒度保持一致 |
如果你使用的是 Amazon OpenSearch Service,配置方式完全相同 —— Grafana 的 Elasticsearch 插件与 OpenSearch 兼容。只需将 URL 指向你的 OpenSearch 域名端点,并通过 Sigv4 auth 选项使用基于 IAM 的认证即可。
3.2 索引模式与映射注意事项
要让仪表盘查询正常工作,你的 Elasticsearch 索引映射需要满足几个条件:
- timestamp 字段必须是
date类型(而非text或keyword)。 - status 字段应为
integer或keyword。如果是text,聚合将无法工作。 - response_time 字段必须是
float或double,才能进行百分位计算。 - path 字段应为
keyword(而非text),这样 terms 聚合才能返回完整的路径,而不是被分词后的碎片。
你可以用以下命令验证映射:
curl -s "https://elasticsearch:9200/api-access-*/_mapping" | jq '.[] .mappings.properties | {timestamp, status, response_time, path}'
如果字段被错误地映射成了 text,可以创建一个索引模板来强制使用正确的类型:
PUT _index_template/api-access
{
"index_patterns": ["api-access-*"],
"template": {
"mappings": {
"properties": {
"timestamp": { "type": "date" },
"method": { "type": "keyword" },
"path": { "type": "keyword" },
"status": { "type": "integer" },
"response_time": { "type": "float" },
"bytes_sent": { "type": "long" },
"remote_addr": { "type": "ip" },
"user_agent": { "type": "text" },
"request_id": { "type": "keyword" }
}
}
}
}
构建仪表盘面板
仪表盘分为两行,每行三个面板。上面一行展示高层次的健康指标(请求速率、成功率、延迟),下面一行展示详细的拆解(状态码分布、最慢端点、按路由的错误情况)。
4.1 请求速率(requests/min)
这个面板展示 API 每分钟处理多少请求,并按状态码类别拆分。
面板类型: Time series(时序图)
Elasticsearch 查询配置:
- Metric: Count
- Group by: 对
timestamp做 Date Histogram,间隔1m - Group by(第二层): 对
status做 Terms(用于按状态码拆分)
在 Grafana 面板编辑器中,查询大致如下:
Query: *
Metric: Count
Group by: Date Histogram (timestamp) — Interval: 1m
Terms (status) — Order: Top, Size: 10
如果想要一个更清爽的视图,按状态码类别(2xx、3xx、4xx、5xx)而非单个状态码分组,可以采用多条 Lucene 查询 的方式:
- Query A:
status:[200 TO 299]—— 标签:“2xx” - Query B:
status:[300 TO 399]—— 标签:“3xx” - Query C:
status:[400 TO 499]—— 标签:“4xx” - Query D:
status:[500 TO 599]—— 标签:“5xx”
每条查询都使用 Count 作为指标,并对 timestamp 做间隔为 1m 的 Date Histogram。
4.2 成功率(%)
成功率是指返回 2xx 状态码的请求所占的百分比。这是你衡量可用性的首要 SLI。
面板类型: Stat(用于展示大数字)或 Time series(用于展示趋势)
在 Grafana 中用 Elasticsearch 计算比率需要一些额外配置。有两种方法:
方法一:使用 Bucket Script(推荐用于 Grafana 11.x)
配置一条 Elasticsearch 查询:
Metric A: Count (all requests)
Metric B: Count with Lucene query filter: status:[200 TO 299]
Pipeline: Bucket Script
Expression: params.B / params.A * 100
Group by: Date Histogram (timestamp) — Interval: 1m
Bucket Script 管线聚合能让你在每个时间桶上计算 (成功请求数 / 总请求数) * 100。
方法二:使用 Grafana Transformations
创建两条查询 —— 一条统计总请求数(Count,无过滤),另一条统计成功请求数(Count,过滤条件 status:[200 TO 299])。然后添加一个 Transform > Calculate field 步骤,操作为 Query B / Query A * 100。
对于展示大数字的 Stat 面板,将 Value options > Calculation 设为 “Last” 或 “Mean”,取决于你想看当前值还是平均成功率。并在 SLO 边界处设置阈值:
{
"thresholds": {
"steps": [
{ "color": "red", "value": null },
{ "color": "orange", "value": 99 },
{ "color": "green", "value": 99.5 }
]
}
}
4.3 P50/P95/P99 延迟
延迟百分位告诉你请求对中位数用户(P50)、尾部用户(P95)以及极端尾部用户(P99)分别有多快。问题往往就藏在 P99 里 —— 某个端点对大多数用户来说可能感觉很快,但对 1% 的用户却慢得令人痛苦。
面板类型: Time series(时序图)
Elasticsearch 查询配置:
Query: *
Metric: Percentiles on field "response_time"
Percentile values: 50, 95, 99
Group by: Date Histogram (timestamp) — Interval: 1m
Elasticsearch 的百分位聚合使用 t-digest 算法,它是近似计算但内存效率很高 —— 适合处理高基数的时序数据。
要添加一条可视化的 SLO 参考线(例如”P95 必须保持在 300ms 以下”),可以使用 Dashboard > Annotations > Add annotation query,或者干脆在面板的 Thresholds 配置中添加一条阈值线,设为 300 并用红色标示。
4.4 HTTP 状态码分布
用饼图展示 HTTP 状态码的整体分布,能帮你一眼发现异常模式 —— 例如 429(限流)或 401(认证失败)突然激增。
面板类型: Pie chart(饼图)
Elasticsearch 查询配置:
Query: *
Metric: Count
Group by: Terms on "status" — Order: Top, Size: 20
可以按语义含义覆盖颜色:
| 状态码范围 | 颜色 |
|---|---|
| 200-299 | 绿色 |
| 301、302、304 | 蓝色 |
| 400-499 | 橙色 |
| 500-599 | 红色 |
4.5 最慢的端点 Top 榜
这个面板回答了一个问题:“现在哪些端点最慢?“它在部署后识别性能回退时格外有价值。
面板类型: Table(表格)
Elasticsearch 查询配置:
Query: *
Metric: Average on field "response_time"
Group by: Terms on "path" — Order by: Average response_time (desc), Size: 15
为了让表格更有用,可以添加额外的指标:
Metric A: Average on "response_time" (alias: "Avg Latency (ms)")
Metric B: Percentiles on "response_time", value: 99 (alias: "P99 (ms)")
Metric C: Count (alias: "Request Count")
Group by: Terms on "path" — Order by: Metric A desc, Size: 15
这样你就能看到每个端点的平均延迟、P99 延迟和请求量 —— 足以区分”这个端点慢是因为某个离群值”和”这个端点一直都很慢”这两种情况。
4.6 按端点划分的错误率
这个面板展示哪些具体端点的错误率最高,帮助你确定排查的优先级。
面板类型: Bar gauge(水平柱状仪表)
Elasticsearch 查询配置:
这需要一个两步聚合。首先计算每个端点的错误数和总数,然后计算比率:
Query A: status:[400 TO 599]
Metric: Count
Group by: Terms on "path" — Order: Top, Size: 10
Query B: *
Metric: Count
Group by: Terms on "path" — Order: Top, Size: 10
然后对 path 应用 Transform > Join by field,再接一个 Transform > Add field from calculation,公式为 Query A / Query B * 100。或者,也可以在单条查询中使用 Bucket Script 方式:
Metric A: Count (all requests per path)
Metric B: Count with inline filter: status:[400 TO 599]
Pipeline: Bucket Script — Expression: params.B / params.A * 100
Group by: Terms on "path" — Order by: Bucket Script desc, Size: 10
Grafana 告警
Grafana 9+ 用 统一告警(Unified Alerting) 取代了老旧的按面板告警系统。它是一个独立的告警引擎,支持多维度评估、专用的规则编辑器,以及完整的通知管线。如果你还在用旧系统,很值得迁移 —— 统一告警的能力要强大得多。
5.1 告警规则
Grafana 中的告警规则会按计划评估一个查询表达式,并在条件满足时触发。下面是一个针对高错误率的告警规则示例:
# Alert: API Error Rate > 2%
apiVersion: 1
groups:
- orgId: 1
name: api-monitoring
folder: API Alerts
interval: 1m
rules:
- uid: api-error-rate-high
title: "API Error Rate Exceeds 2%"
condition: C
data:
- refId: A
relativeTimeRange:
from: 300 # last 5 minutes
to: 0
datasourceUid: elasticsearch-ds
model:
query: "*"
metrics:
- type: count
id: "1"
bucketAggs:
- type: date_histogram
field: timestamp
id: "2"
settings:
interval: 1m
- refId: B
relativeTimeRange:
from: 300
to: 0
datasourceUid: elasticsearch-ds
model:
query: "status:[400 TO 599]"
metrics:
- type: count
id: "1"
bucketAggs:
- type: date_histogram
field: timestamp
id: "2"
settings:
interval: 1m
- refId: C
datasourceUid: __expr__
model:
type: math
expression: "$B / $A * 100"
conditions:
- evaluator:
type: gt
params: [2]
for: 5m # must breach for 5 consecutive minutes
labels:
severity: critical
team: platform
annotations:
summary: "API error rate is {{ $value }}%, exceeding 2% threshold"
dashboard_url: "https://grafana.example.com/d/api-monitoring"
对于 延迟告警,也适用类似的模式。监控 P99 延迟何时超过你的 SLO:
- uid: api-p99-latency-high
title: "API P99 Latency Exceeds 500ms"
condition: B
data:
- refId: A
datasourceUid: elasticsearch-ds
model:
query: "*"
metrics:
- type: percentiles
field: response_time
id: "1"
settings:
percents: ["99"]
bucketAggs:
- type: date_histogram
field: timestamp
id: "2"
settings:
interval: 1m
- refId: B
datasourceUid: __expr__
model:
type: threshold
expression: A
conditions:
- evaluator:
type: gt
params: [500]
for: 3m
labels:
severity: warning
team: platform
5.2 联络点(Contact Points)
联络点定义了告警发送到哪里。Grafana 支持 Slack、PagerDuty、邮件、Microsoft Teams、Opsgenie、webhook 等众多渠道。
一个 Slack 联络点配置:
apiVersion: 1
contactPoints:
- orgId: 1
name: platform-team-slack
receivers:
- uid: slack-platform
type: slack
settings:
recipient: "#platform-alerts"
token: "${SLACK_BOT_TOKEN}"
title: |
{{ `{{ .Status | toUpper }}` }} {{ `{{ .CommonLabels.alertname }}` }}
text: |
{{ `{{ range .Alerts }}` }}
*{{ `{{ .Labels.alertname }}` }}*
{{ `{{ .Annotations.summary }}` }}
Dashboard: {{ `{{ .Annotations.dashboard_url }}` }}
{{ `{{ end }}` }}
用于 PagerDuty 集成(需要呼叫值班工程师的关键告警):
- uid: pagerduty-platform
type: pagerduty
settings:
integrationKey: "${PAGERDUTY_INTEGRATION_KEY}"
severity: "{{ `{{ .CommonLabels.severity }}` }}"
class: "api-monitoring"
5.3 通知策略与路由
通知策略根据标签将告警路由到正确的联络点。你就在这里实现”关键告警发往 PagerDuty,警告发往 Slack”这样的逻辑:
apiVersion: 1
policies:
- orgId: 1
receiver: platform-team-slack # default receiver
group_by: ["alertname", "team"]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- receiver: pagerduty-platform
matchers:
- severity = critical
continue: true # also send to the default Slack receiver
- receiver: platform-team-slack
matchers:
- severity = warning
5.4 静默(Silences)与静音时段(Mute Timings)
在计划内的维护窗口期间,你不希望告警乱响。Grafana 提供了两种机制:
静音时段(Mute timings) 定义周期性的窗口(例如”每周六 UTC 时间凌晨 2 点到 6 点”),在此期间抑制告警:
apiVersion: 1
muteTimes:
- orgId: 1
name: weekly-maintenance
time_intervals:
- times:
- start_time: "02:00"
end_time: "06:00"
weekdays: ["saturday"]
静默(Silences) 是通过 Grafana UI 或 API 为特定维护事件创建的一次性抑制。它们按标签匹配告警,并在指定时长后过期。
仪表盘即代码(Dashboard as Code)
通过 Grafana UI 手动配置仪表盘在原型阶段没问题,但生产环境的仪表盘应当纳入版本控制并通过代码部署。Grafana 支持三种方式:
1. JSON Provisioning —— 把仪表盘 JSON 文件放到 Grafana 的 provisioning 目录(/etc/grafana/provisioning/dashboards/)中,Grafana 会在启动时加载它们。这是最简单的方式,也很适合基于 Git 的工作流。
# /etc/grafana/provisioning/dashboards/api-monitoring.yaml
apiVersion: 1
providers:
- name: API Monitoring
folder: Production
type: file
options:
path: /var/lib/grafana/dashboards
foldersFromFilesStructure: true
2. Terraform Provider —— Grafana Terraform provider 让你能把仪表盘、数据源、告警规则和联络点当作 Terraform 资源来管理。这与基础设施即代码的工作流天然契合:
resource "grafana_dashboard" "api_monitoring" {
config_json = file("dashboards/api-monitoring.json")
folder = grafana_folder.production.id
}
resource "grafana_rule_group" "api_alerts" {
org_id = 1
name = "api-monitoring"
folder_uid = grafana_folder.production.uid
interval_seconds = 60
rule {
name = "API Error Rate Exceeds 2%"
condition = "C"
for = "5m"
# ... data and expressions
}
}
3. Grafonnet(Jsonnet 库) —— 对于需要管理大量共享同一模式的仪表盘的团队,Grafonnet 提供了一种编程化的方式,从可复用的模板生成仪表盘 JSON。这就避免了手工编辑 JSON 常见的复制粘贴漂移问题。
生产实战技巧
7.1 用仪表盘变量做筛选
在查询中硬编码具体值会让仪表盘变得僵化。使用 Grafana 模板变量,让用户能按服务、环境或端点动态筛选。
定义一个环境变量:
Name: environment
Type: Query
Data source: Elasticsearch
Query: {"find": "terms", "field": "environment.keyword", "size": 20}
定义一个 API 路径变量:
Name: path
Type: Query
Data source: Elasticsearch
Query: {"find": "terms", "field": "path.keyword", "size": 100}
然后在面板查询中用 Lucene 语法引用它们:
environment:$environment AND path:$path
这样一来,这一个仪表盘就能服务于每个服务和每个环境。顶部的下拉框让任何人都能精确筛选出自己需要的内容。
7.2 来自 CI/CD 部署的注解
当出问题时,第一个问题永远是”有什么变化吗?“注解(Annotations) 把部署事件直接叠加在你的时序图上,让部署与指标变化之间的关联一目了然。
从你的 CI/CD 管线推送注解:
curl -X POST "https://grafana.example.com/api/annotations" \
-H "Authorization: Bearer ${GRAFANA_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"dashboardUID": "api-monitoring",
"time": '"$(date +%s)000"',
"tags": ["deploy", "api-service"],
"text": "Deployed api-service v2.4.1 (commit: abc123)"
}'
这些注解会以竖线的形式出现在时序面板上,让你能在部署与指标变化之间瞬间建立起可视化的关联。
7.3 索引生命周期管理
访问日志可能产生海量数据。配置 Elasticsearch 的 索引生命周期管理(ILM),自动滚动、收缩并删除旧索引:
PUT _ilm/policy/api-access-policy
{
"policy": {
"phases": {
"hot": {
"actions": {
"rollover": {
"max_size": "50gb",
"max_age": "1d"
}
}
},
"warm": {
"min_age": "7d",
"actions": {
"shrink": { "number_of_shards": 1 },
"forcemerge": { "max_num_segments": 1 }
}
},
"delete": {
"min_age": "30d",
"actions": { "delete": {} }
}
}
}
}
这能让你的热数据保持快速、可查询,同时自动清理旧数据。
7.4 仪表盘的组织结构
随着监控规模的增长,组织结构变得很重要。一个实用的结构:
Production/
API Monitoring/
Overview (the dashboard we built)
Per-Service Deep Dive
SLO Tracking
Infrastructure/
ECS/EC2 Metrics
RDS Performance
ElastiCache Metrics
用 仪表盘链接(dashboard links) 把相关的仪表盘串联起来。总览仪表盘应当链接到按服务的深度分析页,并预先筛选到相关的服务。这就形成了一条自然的下钻路径:告警触发,值班工程师打开总览,锁定受影响的服务,点击进入深度分析页寻找根因。
7.5 规避常见的坑
有几点是我们用血泪教训换来的:
托管版 Grafana 上的插件安装。 如果你运行的是托管版 Grafana 实例(例如 Amazon Managed Grafana、Azure Managed Grafana,或某个云厂商托管的产品),插件安装可能会受限。某些未签名的插件需要在 grafana.ini 中显式加入白名单:
[plugins]
allow_loading_unsigned_plugins = goshposh-metaqueries-datasource
不过,凭借现代 Grafana 内置的 Bucket Script 和转换能力,对第三方 MetaQuery 插件的需求已基本消失。Bucket Script 管线聚合(见 4.2 节)原生就能处理比率计算。
数据源连通性。 配置 Elasticsearch 数据源时,务必确认你使用的主机名或 IP 是从 Grafana 服务器实际可达的地址。在容器化环境中,管理控制台里显示的”内网 IP”可能是负载均衡器的 VIP,而非真正的容器 IP。请始终从 Grafana 容器内部验证连通性:
# From inside the Grafana container
curl -v "https://elasticsearch-host:9200/_cluster/health"
查询性能。 那些查询高基数字段(如 user_agent 或 remote_addr)并带有大 terms 聚合的仪表盘面板可能会很慢。把 terms 聚合中的 size 参数保持在合理范围(仪表盘面板用 10-20),并使用数据源配置中的 Min time interval 设置来防止查询粒度过细。
结语
我们构建的这套仪表盘让你对 API 健康状况有了全面的可见性:请求量、成功率、延迟百分位、状态码分布、最慢端点,以及按路由的错误率。再结合 Grafana 的统一告警,你就能在 SLO 面临风险时获得主动通知,并通过正确的渠道送达正确的人。
不过,真正的价值并不在于任何单个面板或告警,而在于它们的组合:当告警触发时,仪表盘为排查提供即时的上下文;当部署出错时,注解精确显示它发生的时刻;当有人问”API 健康吗?“时,你可以直接指向一个 URL,而不必临时跑一堆查询。
如果你是从零开始,先从最重要的三个面板做起 —— 成功率、P99 延迟和按端点的错误率 —— 并为每个配一条告警。你随时可以之后再添加更多面板,但这三个已经能捕获生产环境中绝大多数的问题。先把告警做对,再让仪表盘变得好用,最后再打磨细节。
参考资料
- Grafana documentation — Grafana
- Elasticsearch reference — Elastic
- Prometheus overview — Prometheus