Java Spring Boot 可观测性监控告警平台从零搭建指南
面向 Java 后端工程师的 Prometheus、Alertmanager、Grafana、Loki 与 Grafana Alloy 实战手册。
面向 Java 后端工程师的 Prometheus、Alertmanager、Grafana、Loki 与 Grafana Alloy 实战手册
1. 文档目标
本文帮助一名具备 Spring Boot 基础、但没有监控平台经验的 Java 后端工程师,从零搭建一套包含指标、日志、可视化和告警通知的可观测性平台,并理解每个组件为什么存在、每项配置解决什么问题。
全文分为两条落地路径:
- 使用 Docker Compose 在 Windows/Linux 上搭建单机可运行环境,用于开发、验证和中小规模内部环境。
- 将同一套设计迁移到 Kubernetes,使用 Prometheus Operator、ServiceMonitor、PrometheusRule 和 AlertmanagerConfig 管理生产配置。
本文对应的本地示例项目使用 Java 21、Spring Boot 3.5.14、Prometheus 3.12.0、Alertmanager 0.32.1、Loki 3.7.2、Grafana Alloy 1.16.1 和 Grafana 13.1.0。版本应固定并经过升级测试,不要在生产环境直接使用 latest。
2. 先理解平台解决什么问题
应用出现故障时,通常需要回答四类问题:
- 服务是否存活,流量和错误率是否异常?
- JVM Heap、GC、线程和 CPU 是否异常?
- 哪个业务接口或业务流程出了问题?
- 对应时刻的日志和异常堆栈是什么?
指标擅长回答“发生了多少、持续多久、趋势如何”;日志擅长回答“具体发生了什么”。完整平台必须同时建设两条链路。
指标链路:
Spring Boot → Micrometer → /actuator/prometheus
→ Prometheus → PromQL → Grafana
→ 告警规则 → Alertmanager → 邮件/企业微信/钉钉/Webhook
日志链路:
Spring Boot JSON 日志 → Alloy → Loki → LogQL → Grafana
不要把日志全部转换成指标,也不要试图只靠指标替代日志。订单 ID、用户 ID、异常堆栈适合进入日志;成功率、P95 延迟、积压数量适合成为指标。
3. 每个组件的职责
| 组件 | 核心职责 | 不负责什么 |
|---|---|---|
| Spring Boot Actuator | 暴露健康状态和 Prometheus 格式指标 | 不保存历史指标 |
| Micrometer | 在 Java 中统一定义 Counter、Gauge、Timer 等指标 | 不负责持久化和告警通知 |
| Prometheus | 定时抓取、存储时间序列、执行 PromQL 和规则 | 不适合存储日志正文 |
| Alertmanager | 告警去重、分组、路由、抑制、静默和通知 | 不计算告警表达式 |
| Alloy | 发现、读取、解析、添加标签并转发日志 | 不负责长期日志查询 |
| Loki | 存储日志并提供 LogQL 查询 | 不直接读取宿主机日志文件 |
| Grafana | 查询 Prometheus/Loki 并展示仪表盘 | 不是指标或日志的权威存储 |
关键边界是:Prometheus 决定“是否满足告警条件”,Alertmanager 决定“相同告警怎样合并、发给谁、何时重复发送”。
4. 目录结构
建议把监控配置与业务代码一起纳入 Git:
project/
├── pom.xml
├── src/main/java/...
├── src/main/resources/application.yml
├── logs/
├── monitoring/
│ ├── docker-compose.yml
│ ├── prometheus/
│ │ ├── prometheus.yml
│ │ └── rules/
│ │ ├── java-alerts.yml
│ │ └── business-alerts.yml
│ ├── alertmanager/
│ │ ├── alertmanager.yml
│ │ └── templates/
│ ├── alloy/config.alloy
│ ├── loki/loki.yml
│ └── grafana/
│ ├── provisioning/datasources/datasources.yml
│ ├── provisioning/dashboards/dashboards.yml
│ └── dashboards/java-monitoring.json
└── scripts/
这样做的原因是:指标、规则、仪表盘和业务版本可以一起评审、回滚和发布,避免只能在 UI 中人工维护。
4.1 配置文件职责速查
| 文件 | 归属组件 | 作用 | 修改时机 |
|---|---|---|---|
pom.xml |
Spring Boot | 引入 Actuator 和 Prometheus Registry | 首次接入或升级依赖 |
application.yml |
Spring Boot | 暴露端点、公共标签、日志格式和滚动策略 | 应用、环境、安全或日志策略变化 |
monitoring/docker-compose.yml |
Docker Compose | 定义镜像、网络、端口、挂载、数据卷和依赖 | 新增组件、升级版本或调整资源 |
prometheus/prometheus.yml |
Prometheus | 定义抓取周期、目标、规则文件和 Alertmanager 地址 | 新增应用或修改抓取方式 |
prometheus/rules/*.yml |
Prometheus | 定义 recording rules 和 alerting rules | 增加告警或根据基线调阈值 |
alertmanager/alertmanager.yml |
Alertmanager | 定义分组、路由、抑制、时间窗口和接收器 | 调整团队、渠道或值班策略 |
alloy/config.alloy |
Alloy | 定义日志发现、读取、解析、标签和发送地址 | 日志路径、格式或后端变化 |
loki/loki.yml |
Loki | 定义端口、索引、存储和保留策略 | 调整存储、保留或生产拓扑 |
grafana/provisioning/*.yml |
Grafana | 自动创建数据源并加载仪表盘 | 数据源或加载策略变化 |
grafana/dashboards/*.json |
Grafana | 定义面板、查询、布局、单位和阈值 | 增加面板或优化排障视图 |
配置变更应先做语法检查和端到端验证,再通过 Git 发布;不要直接进入容器修改,因为容器重建后会丢失且无法审计。
5. Spring Boot 接入指标
5.1 Maven 依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
<scope>runtime</scope>
</dependency>
Actuator 提供管理端点和自动指标,Prometheus Registry 把 Micrometer 指标转换成 Prometheus 可抓取格式。Spring Boot 官方说明 /actuator/prometheus 是 Prometheus 的 scrape endpoint,且需要 micrometer-registry-prometheus 依赖:Spring Boot Metrics。
5.2 application.yml
spring:
application:
name: order-service
management:
prometheus:
metrics:
export:
enabled: true
endpoints:
web:
exposure:
include: health,info,prometheus
endpoint:
prometheus:
access: read-only
metrics:
tags:
application: ${spring.application.name}
environment: ${APP_ENV:local}
配置说明:
export.enabled:启用 Prometheus Registry 输出。exposure.include:只通过 HTTP 暴露必要端点。不要为了方便暴露env、heapdump、configprops和threaddump。access: read-only:Prometheus 端点只读。- 公共标签
application、environment:让多应用、多环境数据可以被筛选和聚合。
生产环境建议使用独立管理端口、网络策略、TLS 和认证保护 Actuator。health 是否显示详细依赖状态也应按安全要求控制。
5.3 自动获得的指标
接入后通常可以获得:
- JVM 内存:
jvm_memory_used_bytes、jvm_memory_max_bytes。 - GC:
jvm_gc_pause_seconds_count/sum/max、jvm_gc_overhead。 - 线程:
jvm_threads_live_threads、jvm_threads_states_threads。 - 进程:
process_cpu_usage、process_uptime_seconds。 - HTTP:
http_server_requests_seconds_count/sum/max。 - Tomcat 与执行器:Session、线程池和队列指标。
- 日志计数:
logback_events_total。
先验证端点:
curl http://localhost:8080/actuator/prometheus
如果端点返回 404,依次检查依赖、端点暴露范围和 Registry 是否启用。
6. 自定义业务指标
6.1 先设计再编码
指标应回答具体业务问题:
| 问题 | 类型 | Micrometer 名称 | Prometheus 名称 |
|---|---|---|---|
| 创建订单成功/失败多少次 | Counter | business.orders.created |
business_orders_created_total |
| 创建订单用了多久 | Timer | business.orders.create.duration |
business_orders_create_duration_seconds_* |
| 当前待处理订单数 | Gauge | business.orders.pending |
business_orders_pending |
| 订单金额分布 | DistributionSummary | business.orders.amount |
business_orders_amount_* |
Counter 只增不减;Gauge 表示可升可降的当前值;Timer 记录次数与时间分布;DistributionSummary 记录非时间数值分布。Micrometer 提供这些厂商无关的接口:Micrometer Reference。
6.2 集中封装业务指标
package com.example.order.metrics;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.Gauge;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 集中管理订单领域指标,避免指标名称和标签散落在业务代码中。
*/
@Component
public class OrderMetrics {
private final Counter successCounter;
private final Counter failureCounter;
private final Timer createTimer;
private final DistributionSummary amountSummary;
private final AtomicInteger pending = new AtomicInteger();
public OrderMetrics(MeterRegistry registry) {
successCounter = Counter.builder("business.orders.created")
.description("订单创建次数")
.tag("result", "success")
.register(registry);
failureCounter = Counter.builder("business.orders.created")
.description("订单创建次数")
.tag("result", "failure")
.register(registry);
createTimer = Timer.builder("business.orders.create.duration")
.description("订单创建耗时")
.publishPercentileHistogram()
.serviceLevelObjectives(
Duration.ofMillis(100),
Duration.ofMillis(500),
Duration.ofSeconds(1),
Duration.ofSeconds(3))
.register(registry);
amountSummary = DistributionSummary.builder("business.orders.amount")
.description("订单金额分布")
.baseUnit("fen")
.serviceLevelObjectives(10_000, 50_000, 100_000, 500_000)
.register(registry);
Gauge.builder("business.orders.pending", pending, AtomicInteger::get)
.description("当前待处理订单数量")
.register(registry);
}
public Timer.Sample startCreate(MeterRegistry registry) {
return Timer.start(registry);
}
public void success(Timer.Sample sample, long amountFen) {
sample.stop(createTimer);
successCounter.increment();
amountSummary.record(amountFen);
pending.incrementAndGet();
}
public void failure(Timer.Sample sample) {
sample.stop(createTimer);
failureCounter.increment();
}
public void completed() {
pending.updateAndGet(value -> Math.max(0, value - 1));
}
}
业务 Service 在真实结果确定的位置记录:
public Order create(CreateOrderCommand command) {
Timer.Sample sample = metrics.startCreate(meterRegistry);
try {
Order order = repository.save(toOrder(command));
metrics.success(sample, order.getAmountFen());
return order;
} catch (RuntimeException exception) {
metrics.failure(sample);
throw exception;
}
}
不要仅因为 Controller 返回 200 就记录业务成功;应在数据库提交或业务状态真正成功后记录。
6.3 标签与高基数
可以使用有限枚举标签:
result=success|failure
channel=web|app|admin
payment_method=wechat|alipay|card
禁止把订单 ID、用户 ID、手机号、IP、完整 URL、异常消息或时间戳作为标签。每一种标签值组合都会生成新时间序列,动态标签会快速消耗 Prometheus 内存、CPU 和磁盘。Prometheus 官方也明确反对将用户 ID、邮箱等无界集合放入标签:Metric and label naming。这些详细字段应写入日志。
6.4 验证自定义指标被采集
第一层,确认应用已经注册并暴露:
curl -s http://localhost:8080/actuator/prometheus | grep business_orders
第二层,确认 Prometheus target 为 UP:
up{job="spring-boot"}
第三层,查询自定义指标:
business_orders_created_total{job="spring-boot"}
如果第一层有、第三层没有,问题通常在 metrics_path、target 地址、网络或 Prometheus 最近一次抓取错误。查看 http://localhost:9090/targets 的 Last scrape 和 Last error。
6.5 自定义指标测试
使用 SimpleMeterRegistry 做单元测试:
@Test
void shouldRecordSuccessfulOrder() {
SimpleMeterRegistry registry = new SimpleMeterRegistry();
OrderMetrics metrics = new OrderMetrics(registry);
Timer.Sample sample = metrics.startCreate(registry);
metrics.success(sample, 12_800);
assertThat(registry.get("business.orders.created")
.tag("result", "success").counter().count()).isEqualTo(1.0);
assertThat(registry.get("business.orders.pending").gauge().value())
.isEqualTo(1.0);
}
集成测试还应请求 /actuator/prometheus,断言转换后的 Prometheus 名称存在。
7. Prometheus 配置
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- /etc/prometheus/rules/*.yml
alerting:
alertmanagers:
- static_configs:
- targets: ["alertmanager:9093"]
scrape_configs:
- job_name: spring-boot
metrics_path: /actuator/prometheus
static_configs:
- targets: ["host.docker.internal:8080"]
labels:
environment: local
配置项说明:
scrape_interval:每隔多久拉取一次指标。越短越及时,但网络、存储和 CPU 成本越高。evaluation_interval:多长时间计算一次 recording/alerting rules。rule_files:加载规则文件的 glob。alerting.alertmanagers:把 firing 告警发送到哪个 Alertmanager。job_name:一组同类 target 的稳定逻辑名称。metrics_path:Spring Boot 的 Prometheus 端点。targets:实际抓取地址。
Compose 服务名如 alertmanager、loki 能直接作为主机名,是因为同一个 Compose 网络内有 Docker DNS。宿主机应用则使用 host.docker.internal。Prometheus 官方配置文档说明配置文件负责抓取任务、实例和规则加载,并支持在启用 lifecycle 后通过 POST /-/reload 热加载:Prometheus configuration。
修改配置前后执行:
promtool check config prometheus.yml
promtool check rules rules/*.yml
curl -X POST http://localhost:9090/-/reload
8. 告警规则设计
8.1 基础 Java 告警
groups:
- name: java-service
rules:
- alert: SpringBootApplicationDown
expr: up{job="spring-boot"} == 0
for: 1m
labels:
severity: critical
team: backend
annotations:
summary: "Spring Boot 应用不可访问"
description: "Prometheus 连续一分钟无法抓取 {{ $labels.instance }}。"
runbook_url: "https://wiki.example.com/runbooks/app-down"
- alert: JvmHeapUsageHigh
expr: |
sum by(instance) (jvm_memory_used_bytes{job="spring-boot",area="heap"})
/
sum by(instance) (jvm_memory_max_bytes{job="spring-boot",area="heap"})
> 0.80
for: 10m
labels:
severity: warning
team: backend
annotations:
summary: "JVM Heap 使用率持续超过 80%"
for 避免瞬时抖动直接通知;labels 用于路由;annotations 用于人读信息、仪表盘和 Runbook 链接。Prometheus 将持续满足 for 但尚未到期的告警标记为 pending,到期后才 firing:Alerting rules。
阈值不能机械照抄。生产环境应先观察基线,再结合 JVM 最大堆、流量周期和用户影响设定。优先对错误率、延迟、可用性等用户症状告警,而不是对每个内部原因都发通知。
8.2 业务告警
订单失败率:
- alert: OrderCreateFailureRateHigh
expr: |
sum(rate(business_orders_created_total{result="failure"}[5m]))
/
clamp_min(sum(rate(business_orders_created_total[5m])), 0.001)
> 0.10
for: 5m
labels:
severity: critical
team: order
annotations:
summary: "订单创建失败率超过 10%"
P95 延迟:
histogram_quantile(
0.95,
sum by(le) (
rate(business_orders_create_duration_seconds_bucket[5m])
)
)
使用 Histogram 的原因是 bucket 可以跨实例聚合,随后由 Prometheus 计算分位数。经典 Histogram 的 histogram_quantile 聚合必须保留 le 标签:Prometheus query functions。
待处理积压:
- alert: PendingOrdersHigh
expr: sum(business_orders_pending) > 100
for: 10m
labels:
severity: warning
team: order
annotations:
summary: "待处理订单持续积压"
每条可行动告警都应包含负责人、严重级别、症状、影响、仪表盘和 Runbook。
9. Alertmanager 配置与实际通知
9.1 路由、分组和重复通知
global:
resolve_timeout: 5m
route:
receiver: default-email
group_by: [alertname, application, environment]
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
routes:
- matchers:
- severity="critical"
receiver: critical-webhook
repeat_interval: 30m
- matchers:
- team="order"
receiver: order-team
receivers:
- name: default-email
- name: critical-webhook
- name: order-team
receiver:默认接收器。group_by:具有相同标签的告警合并成一条通知。group_wait:第一个告警出现后等待一段时间,以收集同组告警。group_interval:同一组新增或恢复告警后,两次通知之间的最小间隔。repeat_interval:同一 firing 告警多久提醒一次。- 子
routes:按severity、team、environment路由。
Alertmanager 的核心能力正是去重、分组和路由:Alertmanager overview。
9.2 邮件
global:
smtp_smarthost: "smtp.example.com:465"
smtp_from: "monitor@example.com"
smtp_auth_username: "monitor@example.com"
smtp_auth_password: "__INJECT_AT_DEPLOY_TIME__"
smtp_require_tls: true
receivers:
- name: default-email
email_configs:
- to: "backend-oncall@example.com"
send_resolved: true
headers:
subject: "[{{ .Status | toUpper }}] {{ .CommonLabels.alertname }}"
不同 SMTP 服务商对 465 隐式 TLS、587 STARTTLS、授权码和 From 地址有不同要求。先用测试邮箱验证 firing 与 resolved 两种消息。
9.3 通用 Webhook
receivers:
- name: critical-webhook
webhook_configs:
- url: "http://notification-adapter:8080/alertmanager"
send_resolved: true
max_alerts: 20
timeout: 10s
Alertmanager 会 POST 一个包含 status、groupLabels、commonLabels 和 alerts 数组的 JSON。Webhook 接收端必须实现鉴权、幂等、超时、重试可见性和日志。官方配置格式见:Alertmanager configuration。
9.4 企业微信
Alertmanager 原生 wechat_configs 对接的是企业微信应用消息,需要企业 ID、应用 Agent ID 和应用 Secret:
global:
wechat_api_url: "https://qyapi.weixin.qq.com/cgi-bin/"
wechat_api_corp_id: "__CORP_ID__"
wechat_api_secret: "__APP_SECRET__"
receivers:
- name: order-team
wechat_configs:
- agent_id: "1000002"
to_party: "2"
send_resolved: true
message: |-
{{ range .Alerts }}
[{{ .Status }}] {{ .Annotations.summary }}
实例:{{ .Labels.instance }}
{{ end }}
如果使用“企业微信群机器人 Webhook”,其请求体格式与 Alertmanager 通用 Webhook 不同,应增加一个通知适配器,把 Alertmanager JSON 转换成机器人要求的 text/markdown JSON,不要假设 URL 直连即可工作。
9.5 钉钉群机器人
钉钉同样需要格式适配。常见做法是部署经过安全评估并固定版本的 prometheus-webhook-dingtalk,或者由团队维护统一通知网关:
receivers:
- name: dingtalk-order
webhook_configs:
- url: "http://dingtalk-adapter:8060/dingtalk/order/send"
send_resolved: true
适配器再持有钉钉机器人 access token 和签名密钥。密钥不要放在 Git、镜像、Grafana 面板或告警 annotation 中。
9.6 Secret 管理
Alertmanager 配置文件不是通用 shell 模板,不能默认假设 ${PASSWORD} 会自动展开。可采用:
- CI/CD 在部署时从密钥系统渲染配置。
- Docker Secret/Kubernetes Secret 以文件挂载,优先使用配置项提供的
*_file/url_file能力。 - 使用 Vault、云 Secret Manager 或统一通知网关隔离真实密钥。
渲染后的文件应限制权限,日志中不得输出完整 Webhook URL。
9.7 抑制与静默
当服务整体 Down 时,可以抑制同一实例的次级告警:
inhibit_rules:
- source_matchers:
- alertname="SpringBootApplicationDown"
target_matchers:
- severity=~"warning|info"
equal: [application, environment, instance]
静默(Silence)适用于有时间边界的维护窗口;不能通过删除规则或永久扩大阈值实现“临时静音”。
10. Spring Boot 结构化日志
logging:
file:
name: logs/order-service.log
structured:
format:
console: logstash
file: logstash
logback:
rollingpolicy:
max-file-size: 20MB
max-history: 7
total-size-cap: 200MB
结构化 JSON 让 Alloy 可以稳定提取时间、级别、logger 和 trace ID;滚动策略防止应用磁盘被日志占满。日志中应包含 trace_id、request_id、业务操作和错误类型,但要脱敏密码、令牌、身份证号等敏感数据。
11. Alloy 配置
local.file_match "spring_boot_logs" {
path_targets = [{
"__path__" = "/var/log/order-service/*.log",
"job" = "spring-boot",
"application" = "order-service",
"environment" = "prod",
}]
}
loki.source.file "spring_boot_logs" {
targets = local.file_match.spring_boot_logs.targets
forward_to = [loki.process.spring_boot_logs.receiver]
}
loki.process "spring_boot_logs" {
stage.json {
expressions = {
level = "level",
trace_id = "trace_id",
}
}
stage.labels {
values = { level = "" }
}
stage.structured_metadata {
values = { trace_id = "" }
}
forward_to = [loki.write.local.receiver]
}
loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}
local.file_match:发现匹配日志文件并附加静态标签。loki.source.file:持续读取新增行,并保存读取位置。loki.process:按顺序解析和加工日志。stage.labels:把低基数字段变成 Loki 索引标签。stage.structured_metadata:保留 trace ID 等高基数元数据,但不作为索引标签。loki.write:推送到 Loki API。
Alloy 官方文档说明 loki.source.file 读取文件并把新日志转发给下游组件:loki.source.file。不要把 logger_name、用户 ID、订单 ID 和 trace ID全部变成 Loki 标签,否则同样会发生高基数问题。
12. Loki 配置
单机开发配置:
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
replication_factor: 1
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
schema_config:
configs:
- from: 2024-01-01
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
retention_period: 168h
compactor:
working_directory: /loki/compactor
retention_enabled: true
delete_request_store: filesystem
auth_enabled: false:只适用于隔离的单租户开发环境。path_prefix:Loki 本地数据根目录。replication_factor: 1:单副本,没有高可用。store: tsdb:使用 Loki 推荐的 TSDB 索引。object_store: filesystem:简单但无副本,磁盘损坏会丢数据。retention_period:保留七天。compactor.retention_enabled:由 Compactor 执行保留策略。
Grafana 官方说明 filesystem 适合单机和本地开发,但没有复制保护;生产环境应使用 S3、GCS、Azure Blob、OSS 或兼容 S3 的对象存储:Loki storage。
常用 LogQL:
{application="order-service"}
{application="order-service",level="ERROR"}
{application="order-service"} |= "Exception"
{application="order-service"} | json | trace_id="abc123"
sum(rate({application="order-service",level="ERROR"}[5m]))
13. Grafana 配置即代码
数据源:
apiVersion: 1
datasources:
- name: Prometheus
uid: prometheus
type: prometheus
access: proxy
url: http://prometheus:9090
isDefault: true
editable: false
- name: Loki
uid: loki
type: loki
access: proxy
url: http://loki:3100
editable: false
access: proxy 表示浏览器请求先到 Grafana 后端,再由 Grafana 访问容器网络中的数据源。uid 是仪表盘引用数据源的稳定标识。
仪表盘 Provider:
apiVersion: 1
providers:
- name: Java监控
folder: Java监控
type: file
disableDeletion: true
updateIntervalSeconds: 10
options:
path: /var/lib/grafana/dashboards
仪表盘 JSON 中最重要的是:
panels:所有面板。datasource:Prometheus 或 Loki UID。targets[].expr:PromQL/LogQL。fieldConfig:单位、上下限和显示阈值。gridPos:24 列网格中的位置和大小。refresh:页面刷新周期。time:默认时间范围。uid:仪表盘稳定地址。
文件 Provisioning 让数据源和仪表盘可以进入版本控制;Grafana 官方提示,Provisioning 源后续更新可能覆盖 UI 中保存的版本,因此正式修改应回写 JSON:Grafana provisioning。
推荐仪表盘至少包含:应用 UP、请求速率、错误率、P50/P95/P99、Heap、GC、CPU、线程、业务成功率、业务积压和关联日志。
14. Docker Compose 部署
核心原则:配置只读挂载,数据使用命名卷,管理端口只绑定 127.0.0.1,镜像固定版本。
services:
prometheus:
image: prom/prometheus:v3.12.0
command:
- --config.file=/etc/prometheus/prometheus.yml
- --storage.tsdb.path=/prometheus
- --web.enable-lifecycle
ports:
- "127.0.0.1:9090:9090"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml:ro
- ./prometheus/rules:/etc/prometheus/rules:ro
- prometheus-data:/prometheus
alertmanager:
image: prom/alertmanager:v0.32.1
ports:
- "127.0.0.1:9093:9093"
volumes:
- ./alertmanager/alertmanager.yml:/etc/alertmanager/alertmanager.yml:ro
- alertmanager-data:/alertmanager
loki:
image: grafana/loki:3.7.2
ports:
- "127.0.0.1:3100:3100"
alloy:
image: grafana/alloy:v1.16.1
volumes:
- ./alloy/config.alloy:/etc/alloy/config.alloy:ro
- ../logs:/var/log/order-service:ro
- alloy-data:/var/lib/alloy/data
grafana:
image: grafana/grafana:13.1.0
ports:
- "127.0.0.1:3001:3000"
volumes:
- ./grafana/provisioning:/etc/grafana/provisioning:ro
- ./grafana/dashboards:/var/lib/grafana/dashboards:ro
- grafana-data:/var/lib/grafana
数据卷的作用:Prometheus 保存 TSDB,Alertmanager 保存静默等状态,Loki 保存日志,Alloy 保存读取位置,Grafana 保存用户和 UI 状态。日常 docker compose down 不应附加 --volumes,否则会删除数据卷。
启动顺序:
# 1. 启动并验证 Spring Boot
curl http://localhost:8080/actuator/health
# 2. 启动监控栈
docker compose -f monitoring/docker-compose.yml up -d
# 3. 检查容器和目标
docker compose -f monitoring/docker-compose.yml ps
curl http://localhost:9090/-/ready
curl http://localhost:3100/ready
15. 端到端验收与故障演练
15.1 指标链路
/actuator/prometheus中存在 JVM 和业务指标。- Prometheus
/targets中spring-boot为 UP。 - PromQL 能查到数据。
- Grafana 面板能显示相同数据。
15.2 日志链路
- 应用日志文件持续写入合法 JSON。
- Alloy
/ready正常且没有 push 错误。 - Loki
/ready返回 200。 - Grafana Explore 中
{application="order-service"}有结果。
15.3 告警链路
建议保留一个无停机测试告警,通过受控 500 或专用低基数测试 Counter 触发,并带上:
labels:
severity: info
test: "true"
验收必须覆盖 pending、firing、Alertmanager active、通知发送和 resolved 恢复。生产环境不要公开任意制造 CPU、内存或异常的测试端点;测试端点必须受认证、网络隔离或只存在于非生产 profile。
16. 常见故障排查
Prometheus target DOWN
- 宿主机应用是否监听 Docker 可访问的地址,而不是只监听
127.0.0.1? host.docker.internal或容器服务名是否解析?metrics_path是否为/actuator/prometheus?- 防火墙、NetworkPolicy 和认证是否允许访问?
Alertmanager 页面没有告警
- Prometheus
/alerts中规则是否 inactive、pending 或 firing? - 规则是否满足
for持续时间? - Prometheus 是否配置了 Alertmanager target?
- Alertmanager 只展示收到的活动告警,不展示所有 inactive 规则。
Grafana 没有日志
- 应用是否真的写入 Alloy 挂载的目录?
- 文件是否为一行一个 JSON 事件?
- Alloy 是否保存了 position,是否有权限读取?
- Loki 标签查询是否与 Alloy 添加的标签完全一致?
自定义指标没有出现
- 指标是否在代码路径执行前已经注册?
- Counter 是否从未被调用,Gauge 引用对象是否被回收?
- 指标是否被
MeterFilter禁用? - 应用端有而 Prometheus 没有时,重点检查 target 和最近抓取错误。
告警通知没有发送
- receiver 是否被 route 实际选中?
group_wait是否尚未结束?- SMTP/Webhook 网络和 TLS 是否可达?
- 适配器是否接受 Alertmanager 的 JSON?
- 查看 Alertmanager 自身日志和通知失败指标。
17. 生产环境设计
单机 Compose 是学习和验证方案,不等于完善的生产架构。生产至少考虑:
- Prometheus 双副本;需要长时存储时评估 Thanos/Mimir 等方案。
- Alertmanager 集群,避免通知单点。
- Loki 使用对象存储、合理副本和容量规划。
- Grafana 使用企业认证、最小权限和数据库备份。
- 所有管理端点经过 TLS、认证、网络策略或反向代理保护。
- 指标、日志、告警规则、仪表盘和 Runbook 纳入 GitOps。
- 监控系统也要监控自身:抓取失败、规则失败、通知失败、磁盘、日志拒绝和查询延迟。
- 建立容量预算:活跃时间序列数、采样间隔、保留周期、日志日增量和对象存储成本。
- 建立升级流程:测试环境验证配置兼容性、数据迁移和回滚。
18. Kubernetes 迁移
推荐使用 Prometheus Operator。它通过 CRD 管理 Prometheus、Alertmanager、ServiceMonitor、PodMonitor、PrometheusRule 和 AlertmanagerConfig:Prometheus Operator Introduction。
18.1 Service 与 ServiceMonitor
apiVersion: v1
kind: Service
metadata:
name: order-service
labels:
app: order-service
spec:
selector:
app: order-service
ports:
- name: management
port: 8080
targetPort: 8080
---
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
name: order-service
labels:
release: monitoring
spec:
selector:
matchLabels:
app: order-service
endpoints:
- port: management
path: /actuator/prometheus
interval: 30s
ServiceMonitor 选择 Service,Prometheus 实例再通过 serviceMonitorSelector 选择 ServiceMonitor。标签不匹配是 Kubernetes 中最常见的“资源存在但没有被抓取”原因。官方示例见:Using ServiceMonitors。
18.2 PrometheusRule
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: order-service-rules
labels:
release: monitoring
spec:
groups:
- name: order-service
rules:
- alert: OrderServiceDown
expr: up{service="order-service"} == 0
for: 2m
labels:
severity: critical
team: order
annotations:
summary: "订单服务不可抓取"
Operator 会协调并动态加载 PrometheusRule,不需要手工进入 Pod 修改配置。
18.3 AlertmanagerConfig 与 Secret
通知密钥放入 Kubernetes Secret,由 AlertmanagerConfig 引用;不要把 token 写入 CRD 明文或 Helm values Git 仓库。不同 kube-prometheus-stack 版本的字段可能变化,必须以集群安装版本的 CRD schema 为准。
18.4 Kubernetes 日志
Alloy 使用 loki.source.kubernetes 或 loki.source.podlogs 发现 Pod 日志,不再依赖宿主机项目目录。通过 namespace、app、container 等低基数标签组织日志;对象存储承担 Loki 的持久化。
19. 上线检查表
应用
- Actuator 只暴露必要端点。
- 自定义指标有清晰名称、单位、类型和负责人。
- 没有用户 ID、订单 ID等高基数标签。
- JSON 日志脱敏并包含关联 ID。
- Heap、GC 和日志滚动参数有容量依据。
Prometheus
- 所有 target 为 UP,抓取错误可告警。
- 配置和规则通过
promtool。 - 采样间隔、保留周期和磁盘容量匹配。
- 核心 PromQL 已用真实数据验证。
Alertmanager
- 路由、分组、重复周期、抑制和静默策略清楚。
- firing 和 resolved 通知都已演练。
- 接收人、值班表、Runbook 和升级路径有效。
- 密钥未进入 Git 和日志。
Loki/Alloy
- Alloy 可以读取日志并持久化 position。
- Loki 保留策略真实生效。
- 标签基数受控,对象存储和备份明确。
Grafana
- 数据源和仪表盘已 Provisioning/GitOps。
- 面板单位、时间窗口、变量和阈值正确。
- 生产管理员密码已更换,登录接入统一身份认证。
20. 推荐实施顺序
- 先接入 Actuator 和 Prometheus,确认默认 JVM/HTTP 指标。
- 为一个关键业务流程增加成功数、失败数、耗时和积压指标。
- 建立 Grafana 的流量、错误、延迟和饱和度面板。
- 建立少量可行动告警和 Runbook。
- 接入 Alertmanager 实际通知并做 firing/resolved 演练。
- 输出结构化日志,使用 Alloy→Loki→Grafana 打通日志链路。
- 建设监控系统自身监控、容量、备份、安全和高可用。
- 迁移 Kubernetes 后用 ServiceMonitor、PrometheusRule、AlertmanagerConfig 和 Secret 实现 GitOps。
监控平台的完成标准不是“容器都启动了”,而是:业务异常能够产生可信指标,Prometheus 能抓取和计算,Grafana 能帮助定位,Alertmanager 能把可行动信息发给正确的人,日志能提供具体证据,恢复后通知能自动闭环。