ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

开源可观测性三支柱实战:Prometheus+Grafana+Loki+Tempo部署

2026/8/30 3:30:03 拓冰建站 浏览量
开源可观测性三支柱实战:Prometheus+Grafana+Loki+Tempo部署 先聊一个问题服务日志打了监控面板也挂了为什么线上出问题还是定位不到根因很多团队从“能用监控”走向“能做可观测性”时都会遇到这个坎。传统监控告诉你系统“挂了没有”可观测性要回答的是到底哪里坏了、为什么坏、影响面有多大。这篇文章不再重复可观测性的概念定义而是直接拆解三支柱、常用开源技术栈、最小可落地部署方案以及日常排错时可以照做的操作步骤。Monitor 这个词最近在几个具体方向同时变热。一边是 MStar 方案的 Monitor OSD 菜单制作把控制与状态显示能力落到显示器本身的屏幕调节层另一边是 UE 插件 Hardware Monitor Plugin让游戏引擎里的开发者能实时读取硬件状态。这说明 Monitor 的覆盖面可以从底层硬件、驱动、显示链路一直延伸到应用层但贯穿始终的目标只有一个把难以名状的系统状态变成可读、可查、可告警的数据。本文的重心放在通用可观测性工程上用一套基于 Prometheus、Grafana、Loki、Tempo 和 OpenTelemetry 的开源组合跑通采集、存储、查询、告警的完整闭环。整套方案不绑定特定云厂商单机可跑也能横向扩容。后面会给出 Docker Compose 部署示例、接口调用方式、批量采集思路和排错清单。你可以直接把这篇文章当作搭建第一套可观测性平台的操作手册。适合的读者是后端开发、运维、SRE以及第一次搭监控平台的小团队。如果只是给一台小服务器看 CPU 内存系统自带 top 和 htop 就够了不需要引入这套体系但只要服务数量超过三五个或者开始出现跨服务调用这套思路就能帮你省下大量排错时间。1. 可观测性核心能力速览能力项说明体系类型可观测性工程实践不绑定特定云厂商三支柱Metrics 指标、Logs 日志、Traces 链路追踪核心组件Prometheus、Grafana、Loki、Tempo、OpenTelemetry Collector采集方式HTTP 拉取 / Agent 推送 / Exporter / OpenTelemetry SDK推荐硬件2 核 4G 内存起步适合演示生产环境按数据量评估支持平台Linux、Windows、macOS均可通过 Docker 运行启动方式Docker Compose 一键启动也可二进制部署是否支持 API支持Prometheus HTTP API 与 Grafana HTTP API是否支持批量任务支持批量指标抓取、批量日志接入、批量目标巡检适合场景微服务、云原生环境、容器平台、数据库与中间件巡检、故障排查三支柱的具体分工是Metrics 回答“系统现在是什么状态”Logs 回答“系统执行时输出了什么”Traces 回答“一次请求经过了哪些服务、每一跳花了多长时间”。三者不能互相替代。比如指标能告诉你订单接口延迟升高日志能告诉你订单服务打印了异常堆栈链路能告诉你延迟升高发生在调用下游 Redis 的那一毫秒里。只有把三类数据关联起来看才算真正进入可观测性的工作方式。这套组合的选型逻辑也很简单Prometheus 是事实标准Exporter 生态最全Grafana 统一展示层支持多数据源Loki 主打低成本日志标签索引方式更适合和 Prometheus 共用同一套标签体系Tempo 用对象存储或本地存储保存链路查询入口直接集成在 Grafana 里OpenTelemetry 负责应用埋点标准化避免每个语言写一套上报 SDK。整体学习曲线适中社区资料多遇到问题基本都能搜到解决方案。2. 可观测性与传统监控的区别很多人把监控和可观测性当成同一个东西实际两者解决的问题不同。监控建立在“已知可能发生的故障”之上提前设好阈值和告警比如 CPU 超过 90% 就报警可观测性面向的是“未知的故障形态”允许你在出问题之后提出新的查询问题比如“所有调用订单服务的请求里错误率最高的那个版本是什么”。监控是可观测性的一部分但可观测性更强调数据保留、上下文关联和按需查询。对比维度传统监控可观测性核心问题系统挂了没有为什么会挂影响谁数据形态固定指标、固定阈值指标、日志、链路支持动态查询工具形态黑白盒探测、阈值告警全量采集 多维分析典型场景宕机发现、资源水位根因定位、容量评估、性能调优上手成本低配置告警即可偏高需要埋点和数据治理回报周期短马上能看到告警长持续积累数据后才体现价值新手团队最容易犯的错误是以为上了 Prometheus 和 Grafana 就完成了可观测性建设。实际上指标面板只是第一步。真正有价值的是三类数据互相关联日志里出现timeout指标面板里能看到错误率上升链路上能看到外部依赖接口耗时超标三者对齐后根因几乎不需要猜。这也是后文强调“统一标签”的原因没有统一的 service、env、instance 标签三类数据无法在查询时关联到一起可观测性就变成了三个孤岛。3. 适用场景与使用边界3.1 适用场景微服务架构服务数量多、调用链长需要链路追踪定位某一跳的耗时。云原生环境容器实例频繁重建难以登录到具体节点排查必须依赖集中采集。数据库与中间件巡检MySQL、Redis、Kafka 等组件可以通过 Exporter 批量接入指标。业务指标监控自定义埋点统计订单量、转化率、接口 QPS用 Prometheus 存储和告警。故障复盘与容量规划保留历史数据和趋势曲线用于评估扩容规模和优化方向。3.2 不适合什么场景单机小工具只有一台服务器、一个进程直接用 systemctl status、top、journalctl 更快。完全不准备维护数据的团队可观测性平台需要持续投入存储和人力成本上了不维护数据很快就会失真。对数据隐私要求极高的场景日志和链路中包含业务敏感字段时必须提前做脱敏和权限隔离。3.3 使用边界与合规提醒这部分必须重视。采集业务日志、链路数据、硬件指标时需要确保数据来源合法明确哪些字段可以采集、哪些字段不允许留存。涉及用户信息或敏感业务数据的要按平台和安全规范做脱敏处理。链路追踪时尤其要注意请求参数中可能携带 token、手机号、地址等敏感内容建议在 SDK 侧配置对查询参数和请求头的过滤策略。无论是自用还是商用数据使用范围都要限定在授权场景内不允许把采集到的数据用于与业务无关的用途。4. 环境准备与前置条件4.1 硬件与操作系统一套最小验证环境建议 2 核 CPU、4GB 内存、20GB 以上磁盘空间。这个配置可以跑起全部组件也可以支撑小规模演示数据。生产环境的资源需求波动很大必须根据每天的指标样本量、日志写入量、链路采样率来推算不要按照演示配置直接上线。操作系统没有硬性限制Linux 服务器最顺因为 Docker 支持完整node_exporter采集系统指标也最全。Windows 和 macOS 也可以运行但注意部分系统指标采集在容器内会受限需要映射宿主机/proc、/sys目录不同系统路径有差异。磁盘性能尽量选 SSDLoki 和 Tempo 对写入和查询都有消费机械盘在日志量大时会产生明显卡顿。4.2 端口规划端口组件用途9090PrometheusWeb UI 与 HTTP API3000Grafana可视化面板3100Loki日志写入与查询 API3200Tempo链路存储与查询 API4317OpenTelemetry CollectorOTLP gRPC 接收端口4318OpenTelemetry CollectorOTLP HTTP 接收端口9100Node Exporter主机指标暴露端口8888OpenTelemetry CollectorPrometheus 自指标端口8889OpenTelemetry Collector指标导出 PROM 端口如果服务器上已经有服务占用这些端口启动容器前需要先调整映射否则 Docker 可以直接拒绝启动并提示端口冲突。建议在正式环境把 Grafana 放到反向代理后面控制访问范围。4.3 基础依赖需要安装 Docker 和 Docker Compose 插件。检查方式docker --version docker compose version如果能正常输出版本号就可以继续。没有 Docker 的话可以选择二进制部署各组件但步骤更繁琐不推荐在第一次实验时使用。5. 部署与启动一套最小可观测性栈下面给出一套完整的 Docker Compose 部署文件。它包含 Prometheus、Grafana、Loki、Tempo、OpenTelemetry Collector 和 Node Exporter适合在一台机器上跑通可观测性闭环。5.1 docker-compose.ymlversion: 3.8 services: prometheus: image: prom/prometheus:latest container_name: obs-prometheus ports: - 9090:9090 volumes: - ./prometheus.yml:/etc/prometheus/prometheus.yml command: - --config.file/etc/prometheus/prometheus.yml - --storage.tsdb.path/prometheus restart: unless-stopped grafana: image: grafana/grafana:latest container_name: obs-grafana ports: - 3000:3000 environment: - GF_SECURITY_ADMIN_PASSWORDadmin volumes: - ./grafana-datasources.yml:/etc/grafana/provisioning/datasources/datasources.yml restart: unless-stopped loki: image: grafana/loki:latest container_name: obs-loki ports: - 3100:3100 command: -config.file/etc/loki/local-config.yaml restart: unless-stopped tempo: image: grafana/tempo:latest container_name: obs-tempo ports: - 3200:3200 - 4317:4317 - 4318:4318 restart: unless-stopped otel-collector: image: otel/opentelemetry-collector-contrib:latest container_name: obs-otel ports: - 8888:8888 - 8889:8889 - 13133:13133 volumes: - ./otel-collector.yaml:/etc/otelcol-contrib/config.yaml depends_on: - tempo - prometheus - loki restart: unless-stopped node-exporter: image: prom/node-exporter:latest container_name: obs-node-exporter network_mode: host pid: host restart: unless-stopped注意node-exporter这里使用了network_mode: host这样它可以直接访问宿主机网络并把采集端口暴露在宿主机 9100 上。如果你的 Docker 环境不支持 host 模式可以改成普通端口映射但部分系统指标的采集精度会受影响。5.2 prometheus.ymlglobal: scrape_interval: 15s evaluation_interval: 15s scrape_configs: - job_name: prometheus static_configs: - targets: [localhost:9090] - job_name: node static_configs: - targets: [host.docker.internal:9100] - job_name: otel-collector static_configs: - targets: [otel-collector:8889]这里host.docker.internal是 Docker Desktop 环境里访问宿主机的固定域名Linux 原生 Docker 需要把它改成宿主机 IP或者直接用node-exporter的容器 IP。实际部署时以你的网络环境为准。5.3 otel-collector.yamlreceivers: otlp: protocols: grpc: http: processors: batch: exporters: prometheus: endpoint: 0.0.0.0:8889 namespace: otel otlp: endpoint: tempo:4317 tls: insecure: true service: pipelines: metrics: receivers: [otlp] processors: [batch] exporters: [prometheus] traces: receivers: [otlp] processors: [batch] exporters: [otlp]这个配置的作用是应用通过 OTLP 协议把指标和链路数据发给 CollectorCollector 把指标转成 Prometheus 格式并暴露在 8889 端口Prometheus 定期抓取链路数据则转发给 Tempo 保存。日志接入不经过 Collector可以直接推给 Loki或者用 Promtail、Grafana Alloy 采集容器日志。5.4 Grafana 数据源自动配置创建grafana-datasources.ymlapiVersion: 1 datasources: - name: Prometheus type: prometheus access: proxy url: http://prometheus:9090 isDefault: true - name: Loki type: loki access: proxy url: http://loki:3100 - name: Tempo type: tempo access: proxy url: http://tempo:3200启动前先创建好目录和文件然后在项目目录执行mkdir -p /opt/observability cd /opt/observability # 将上面的配置分别写入 docker-compose.yml、prometheus.yml、otel-collector.yaml、grafana-datasources.yml docker compose up -d启动完成后通过docker compose ps查看所有容器状态。如果所有容器都是Up打开http://127.0.0.1:3000使用admin / admin登录 Grafana左侧 Data Sources 可以看到 Prometheus、Loki、Tempo 三个数据源已经自动配置完成。6. 功能测试与效果验证6.1 指标采集验证先验证 Prometheus 是否正常抓取目标。打开 Prometheus 的http://127.0.0.1:9090/targets在列表中应该能看到node、prometheus、otel-collector三个 job状态为UP。如果某个 target 是DOWN说明 Prometheus 连不上采集接口需要检查目标地址、端口映射和网络隔离。在 Prometheus 查询框里执行up如果返回值为1说明该 target 在线。再执行一个系统指标node_cpu_seconds_total正常会出现当前机器 CPU 的多组时间序列。这里看到的数据就是 Node Exporter 采集并暴露给 Prometheus 抓取的主机指标。也可以在命令行用 curl 验证curl http://127.0.0.1:9090/api/v1/query?queryup返回结果应该是一个包含status: success的 JSON里面列出所有 target 的up值。6.2 应用埋点与追踪验证指标和链路采集的核心是应用侧埋点。下面用 Python 演示 OpenTelemetry 的标准接入方式。先安装依赖pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp-proto-http然后写一段链路追踪代码from opentelemetry import trace from opentelemetry.sdk.trace import TracerProvider from opentelemetry.sdk.trace.export import BatchSpanProcessor from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter provider TracerProvider() exporter OTLPSpanExporter(endpointhttp://127.0.0.1:4318/v1/traces) provider.add_span_processor(BatchSpanProcessor(exporter)) trace.set_tracer_provider(provider) tracer trace.get_tracer(__name__) with tracer.start_as_current_span(demo-request): print(handle demo request)运行后打开 Grafana进入 Explore选择Tempo数据源搜索服务名或 span 名称应该能看到这条demo-request链路。如果查不到优先检查程序是否请求到了 Collector 的 4318 端口、Collector 的 trace pipeline 是否配置了 Tempo exporter、Tempo 和 Grafana 的数据源指向是否一致。再验证指标埋点from opentelemetry import metrics from opentelemetry.sdk.metrics import MeterProvider from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader from opentelemetry.exporter.otlp.proto.http.metric_exporter import OTLPMetricExporter reader PeriodicExportingMetricReader( OTLPMetricExporter(endpointhttp://127.0.0.1:4318/v1/metrics) ) metrics.set_meter_provider(MeterProvider(metric_readers[reader])) meter metrics.get_meter(__name__) counter meter.create_counter(demo_requests_total, descriptiondemo request counter) counter.add(1, {service: demo, env: test})运行几次后在 Prometheus 里查询otel_demo_requests_total因为 Collector 配置了namespace: otel指标名会带上otel_前缀。看到计数增长说明指标链路已经打通。6.3 日志接入验证日志接收走 Loki 的 push API。下面直接用 curl 向 Loki 推送一行测试日志先验证链路是否可用curl -X POST http://127.0.0.1:3100/loki/api/v1/push \ -H Content-Type: application/json \ -d { streams: [ { stream: { service: demo, level: info }, values: [ [$(date %s%N), hello observability] ] } ] }在 Grafana Explore 中选择 Loki 数据源查询{servicedemo}能看到hello observability这条日志说明日志链路可用。生产环境不会用 curl 手动推日志一般是通过 Promtail 或 Grafana Alloy 采集容器标准输出再自动推送。但手动 push 验证很简单适合第一次确认核心链路是否通。6.4 跨数据源关联验证只验证单链路还不够最好把指标、日志、链路串起来看。做法是统一标签应用埋点上报时都带service和env标签日志流也带同样标签trace 里设置相同的service.name。这样在 Grafana 中可以从一条指标的 metric 跳到对应日志也可以从日志中的trace_id直接跳转到 Tempo 的链路详情。更完整的方式是在日志中打印trace_id和span_id。OpenTelemetry 的 SDK 会自动生成这些 ID应用在记录日志时把它们带出来Loki 端就能把日志和 trace 关联。具体做法在不同语言里差异较大建议先做最简单的统一标签关联再逐步推进 trace_id 注入。7. 接口 API 与批量采集7.1 Prometheus HTTP APIPrometheus 提供完整的 HTTP API适合把监控数据接到自己的系统或自动化脚本里。最常用的是即时查询curl http://127.0.0.1:9090/api/v1/query?queryup范围查询用于绘制趋势图curl http://127.0.0.1:9090/api/v1/query_range?querynode_cpu_seconds_totalstart1720000000end1720003600step60返回 JSON 结构固定包含status、data.resultType、data.result三个字段。脚本调用时先判断status是否为success再解析 result 列表。7.2 Grafana HTTP APIGrafana API 可以做数据源管理、Dashboard 导入导出、用户管理等操作。创建一个 Prometheus 数据源的示例curl -X POST http://127.0.0.1:3000/api/datasources \ -H Authorization: Bearer grafana-api-key \ -H Content-Type: application/json \ -d { name: Prometheus, type: prometheus, url: http://prometheus:9090, access: proxy, isDefault: true }注意这里的grafana-api-key需要先在 Grafana 后台创建 Service Account 并生成 Token。创建数据源也可以直接用本文第 5.4 节的 provisioning 方式如果不需要动态管理数据源用文件自动配置更省事。7.3 批量机器采集当机器数量多起来后不可能手动维护几十个 target。常见做法是用file_sd_configs实现文件发现。在 prometheus.yml 里添加- job_name: node-batch file_sd_configs: - files: - /etc/prometheus/targets/*.json refresh_interval: 1m然后准备好 targets 文件[ { targets: [192.168.1.10:9100, 192.168.1.11:9100], labels: { env: prod } } ]Prometheus 每分钟读取一次该文件自动把新的 target 加入采集列表。批量采集任务的设计核心是目标清单与配置分离机器增删只改 JSON 文件不重载 Prometheus 配置。再往下还可以接 Consul 服务发现、Kubernetes 服务发现原理都是动态生成 target 列表。7.4 批量 HTTP 探测如果需要批量监控多个 HTTP 接口的可用性和响应时间可以用 Blackbox Exporter。它接收一组目标地址和探测模块按模块定义执行 HTTP、TCP、ICMP 探测然后把结果指标暴露给 Prometheus 抓取。- job_name: blackbox metrics_path: /probe params: module: [http_2xx] static_configs: - targets: - https://example.com/health - https://api.example.com/health relabel_configs: - source_labels: [__address__] target_label: __param_target - source_labels: [__param_target] target_label: instance这个方案适合批量巡检大量 HTTP 接口也能覆盖内网服务的可用性探测。每次新增接口只需要在 target 列表里加一行配置成本很低。8. 资源占用与性能观察8.1 观察方法整套栈跑在同一台机器上时用 Docker 自带命令观察最方便docker stats --format table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.MemPerc}}这个命令会实时刷新各容器的 CPU 和内存占用。第一次实验时重点观察两个阶段刚启动时的初始化占用以及数据写入一段时间后的稳定占用。8.2 各组件资源特征Prometheus内存消耗与指标基数相关标签组合越少、序列越稳定内存越可控。采集周期越短CPU 占用越高。Grafana服务端本身内存消耗相对平稳高消耗主要出现在浏览器端渲染大量面板时。如果一个人同时打开十几个 Dashboard浏览器比 Grafana 服务端更吃力。Loki写入通常比较轻日志会压缩后存储。消耗最大的是查询查询范围越大、正则越复杂读放大越明显。控制日志保留周期和查询标签粒度是降低 Loki 成本的关键。Tempo主要成本在后端存储。接收链路数据时会做索引和临时缓存存储量取决于采样率。生产环境建议按比例采样不要全量保存链路数据。OpenTelemetry Collector资源消耗与每秒接收的数据量、batch 大小、处理器数量强相关。批量处理器能显著降低写入压力不要关闭。8.3 如何压缩资源占用调低扫描频率Prometheus 的scrape_interval从 15s 调整为 30s指标数量少时对排错影响不大。控制采集指标数量只在 Exporter 里启用业务关心的 collector。链路采样建议一开始就配置比例采样比如保留 10% 的链路排错效果通常已经够用。日志分级接入debug 日志不入库只保留 info 及以上。设置数据保留周期Prometheus 本地存储和 Loki 的保留时间需要显式配置防止磁盘被写满。9. 常见问题与排查方法问题现象可能原因排查方式解决方案Prometheus target 一直是 DOWN采集地址不可达、端口映射错误、容器网络隔离在 Prometheus 容器内 curl 目标地址更正 target 地址确认端口映射和网络模式Grafana 登录密码忘记初始化密码被修改或丢失查看容器日志或环境变量配置重置 admin 密码或用环境变量重新指定Loki 接收日志但查询不到标签选择错误、时间范围不对在 Explore 里放大时间范围用{service~.}查全部修正查询标签确认日志提交时的时间戳是纳秒Tempo 查不到 trace应用没上报到 Collector或 Collector 没转发给 Tempo检查 Collector 日志确认 4317/4318 端口是否收到数据修复 OTLP endpoint 配置确认 pipeline 包含 tracesOpenTelemetry Collector 启动失败配置文件格式错误或端口冲突查看容器日志检查配置缩进用官方 config 校验工具检查调整端口映射指标基数爆炸标签中存在高基数字段如用户 ID、URL查询topk(10, count by (__name__)({__name__~.}))删除高基数标签改为记录到日志或离线数仓磁盘被监控数据写满未设置保留时间采集量增长查看每个组件数据目录大小设置--storage.tsdb.retention.time、Loki 表保留周期告警反复发送像“狼来了”阈值过窄、缺少恢复通知、静默时间不够查看告警规则命中历史对比指标抖动加宽阈值加 pending 窗口统一告警分级第一次搭这套栈最常见的问题不是组件本身跑不起来而是容器之间网络不通。Docker Compose 默认创建的项目网络里服务名可以作为域名互相访问但如果你使用network_mode: host或手动指定的外部网络就需要改用宿主机 IP 或实际可达的地址。排查时先进入一个容器用 curl 访问目标服务docker exec -it obs-prometheus sh # 进入容器后执行 wget -q -O - http://node-exporter:9100/metrics能拿到指标输出说明网络链路通问题出在 Prometheus 配置拿不到则优先检查端口映射和容器网络。10. 最佳实践与使用建议10.1 先定义问题再选组件不要为了“上可观测性”而上组件。先列出你想回答的问题比如“哪个接口 P95 延迟最高”“下单失败集中在哪个地域”“发布新版本后错误率有没有变化”。带着问题选组件和配置埋点效果比盲目堆面板好得多。10.2 统一标签体系所有指标、日志、trace 尽量共享同一套标签命名规则至少包含service、env、instance。没有统一标签三支柱就无法互相跳转。可以建立一份团队内部的标签规范新增服务时必须按照规范上报避免到处用app、project、application之类同义不同名的标签导致数据无法关联。10.3 告警要可执行每条告警消息应包含当前值、阈值、影响描述、排查建议。不能只发“Node CPU 高”要写“node-exporter 所在节点 CPU 持续 15 分钟超过 90%请检查是否有定时任务或请求突增执行top和docker ps查看进程”。可执行告警能显著降低值班压力。10.4 数据接入要可控生产环境不要允许所有应用随意上报。可以建立统一的接入规范哪些指标必须带service标签、日志必须过滤哪些敏感字段、trace 是否允许携带请求参数。接入前先在小流量环境验证再放开到生产。防止某个应用误打高基数标签把 Prometheus 内存打爆。10.5 保留恢复演练习惯可观测性平台本身也可能出问题。定期演练一次模拟 Prometheus 数据源不可用、Loki 积压、链路大批量丢失看团队能不能快速定位问题范围。这个动作能帮你提前发现组件依赖和配置漏洞避免在真实故障时手忙脚乱。11. 总结与下一步这套方案最值得尝试的点是用一个 Docker Compose 文件跑通可观测性的三支柱把指标、日志、链路放到同一个 Grafana 界面里查询。对你来说最先应该验证的是写一段带 OpenTelemetry 埋点的小服务制造一次请求然后分别从 Prometheus、Loki、Tempo 三个入口查到它的踪迹并把日志中的 trace_id 和链路详情关联起来。完成这一步可观测性的核心价值就已经落地了。最容易踩的坑有三个第一标签不统一导致指标、日志、链路无法关联第二告警阈值拍脑袋上线后告警风暴第三存储保留不做规划运行一段时间后磁盘写满。针对这三个坑建议在建好平台后马上制定标签规范、告警分级和存储保留周期。后续可以继续扩展的方向包括接入 Alertmanager 并绑定消息通知渠道完善 Kubernetes 环境下的自动服务发现在 Java、Go、Node.js 等不同语言服务里统一接入 OpenTelemetry SDK建立 SLO 指标并在 Grafana 里做可靠性看板。可观测性建设不是一次性工程数据越积累排错效率的提升越明显。