
OpenTelemetry 全景监控
服务出了问题,通常先从指标发现异常,再到链路中定位耗时,最后结合日志查明原因。实际使用时,这三类数据往往来自不同的采集工具,字段名称也不一致。指标里叫 service,日志里可能叫 app_name;链路中有 trace_id,日志却没有记录。数据虽然都在,查询时却很难互相关联。
业务代码如果直接使用某家监控厂商的 SDK,问题会更麻烦。更换后端时,不仅要迁移数据和仪表盘,还可能要重新修改各个服务的埋点代码。
OpenTelemetry 用一套通用规范解决采集环节的问题,下文简称 OTel。它统一埋点接口、上下文传播、数据模型和传输协议。至于数据如何存储、查询和展示,仍由后端系统负责。
本文从 OTel 的基本概念讲起,再介绍 Collector、VictoriaMetrics、VictoriaLogs 和链路后端如何配合,最后讨论基数、采样、容量和高可用等实际问题。
一、OpenTelemetry 解决什么问题
OTel 是一套厂商中立的可观测性框架,包括规范、各语言的 API 与 SDK、自动埋点工具、OpenTelemetry Protocol(开放遥测协议,OTLP)以及 Collector。按照官方定义,OTel 负责生成、采集和导出链路、指标、日志等遥测数据。数据的存储、查询、告警和展示由后端系统完成。
Collector 没有查询页面,因为它不是数据库。它负责接收和处理数据,也可以补充属性或把不同数据发送到不同后端。
1.1 从 OpenTracing 与 OpenCensus 到 OpenTelemetry
早期的分布式追踪主要有两套方案。OpenTracing 提供抽象 API,使应用不必依赖具体的链路实现;OpenCensus 则同时提供 API、库和数据采集能力。两者目标相近,但互不兼容,使用者仍要在它们之间选择。
2019 年 5 月,OpenTracing 与 OpenCensus 合并为 OpenTelemetry。OTel 保留了两者的主要设计:库只依赖稳定的 API,应用通过 SDK 配置采样、处理和导出;同一套协议可以传输多种遥测数据,也能兼容原有生态。合并过程可参阅 CNCF 的项目历史说明。
OpenTelemetry 于 2019 年进入 CNCF,2021 年进入孵化阶段,2026 年 5 月成为毕业项目。不过,项目毕业不等于所有语言实现、Collector Contrib 组件和语义约定都已稳定。选用组件时仍要看各自的 Stability 标记。
1.2 指标、链路和日志各有用途
指标、链路和日志常被称为可观测性的三大支柱。三者可以互相关联,但数据结构和用途并不相同。
| 信号 | 最擅长回答的问题 | 典型数据结构 | 主要成本来源 |
|---|---|---|---|
| Metrics | 现在是否异常,异常影响多大,趋势如何 | 按属性集合划分的时间序列 | 活跃序列数、采样频率、保留时间 |
| Traces | 一次请求经过哪里,时间花在哪一段 | 同一 trace_id 下有父子关系的 Spans | 请求量、每条链路的 Span 数、采样率 |
| Logs | 某个时刻具体发生了什么,带了哪些上下文 | 带时间、正文、级别和属性的 LogRecord | 日志字节量、索引字段、保留时间 |
指标适合聚合。例如,将 http.server.request.duration 按服务、路由和状态码记录为直方图,可以计算错误率和延迟分位数,但无法查看某一次请求的完整信息。
一条 Trace 由多个 Span 组成。Span 表示一次有起止时间的操作,例如处理 HTTP 请求、查询数据库或发送消息。Span 之间可以形成父子关系。请求跨进程传递时,W3C Trace Context 等格式会把链路上下文带到下游。链路可以还原调用过程,但高流量服务通常不能长期保存所有请求,否则存储成本会很高。
日志格式最灵活,可以记录错误堆栈、业务字段和文字说明,但自由文本不便于稳定地聚合查询。OTel 不要求项目更换现有日志库,而是把日志转换成统一的数据模型,并补充 trace_id、span_id 和 Resource 等上下文。
采集三类数据只是第一步,还要建立它们之间的关联。例如:
告警:checkout 请求 p99 > 2 s
│
├─ Metrics:service.name=checkout,http.route=/orders/{id}
│ 找到异常开始时间和影响范围
│
├─ Exemplars:从直方图异常样本跳到 trace_id=4bf9...
│
├─ Traces:checkout -> inventory -> PostgreSQL
│ inventory Span 耗时 1.8 s,status=ERROR
│
└─ Logs:按 trace_id=4bf9... 查询
看见 deadlock detected 与重试次数这类关联依赖统一的时间和字段。通常只要规范 service.name、deployment.environment.name、http.route 和 trace_id,就能满足大部分查询需求。
1.3 用语义约定统一字段
如果不同服务分别使用 service、app_name 和 k8s_app 表示应用名称,后端就要为每种写法增加转换规则。
OpenTelemetry Semantic Conventions(语义约定)为常见的资源、协议和操作规定字段名称及含义,例如 service.name、http.request.method、http.response.status_code 和 db.system.name。自动埋点、手工埋点、Collector 和后端采用同一套约定后,就不必反复转换字段。语义约定目前覆盖 Trace、Metric、Log、Profile 与 Resource,详见官方语义约定概览。
语义约定仍在演进。字段分为 Development、Stable 等状态,旧字段也可能被新字段取代。升级 SDK 或自动埋点 Agent 时,除了确认程序可以启动,还要检查仪表盘和告警使用的属性是否发生变化。生产环境应固定依赖版本,记录所用的语义约定版本,并测试关键查询。
自定义业务属性也要有命名规则。例如,可以用 acme.payment.result 表示支付结果,避免含义模糊的 status 与 HTTP、RPC 状态混淆。属性值还要控制基数:订单类型可以作为指标属性,完整订单号通常不适合。
1.4 API 与 SDK 的分工
理解 OTel,先要分清 API 与 SDK。API 是埋点代码依赖的公共接口。HTTP 框架可以调用 Tracer API 创建 Span,但采样比例、批处理大小和导出地址属于应用配置。SDK 才是 API 的具体实现,负责 Provider、Processor、Sampler、Exporter 与 Resource 等运行时行为。
业务库 / 框架
│ 只依赖 OTel API,创建 Span、Metric、Event
▼
应用进程
│ 安装和配置 OTel SDK
├─ Resource:这是谁产生的数据
├─ Sampler / View:保留什么,如何聚合
├─ Processor:批处理、上下文处理
└─ Exporter:通过 OTLP 发往哪里因此,公共 Go 库只依赖 API,不应自行创建全局 SDK,更不该写死某家 APM 的地址。是否安装 SDK、数据发往哪里,都由最终应用决定;没有 SDK 时,稳定 API 调用通常退化为低开销的 no-op。这也是 OpenTelemetry 客户端设计原则给出的分工。
自动埋点同样没有推翻这个分工。Java Agent、.NET 自动埋点、Python Agent 或 Kubernetes Operator 能替常见框架插入 OTel API/SDK 行为,适合快速拿到 HTTP、RPC 和数据库 Span;业务阶段、缓存命中原因、队列任务类型等语义仍只有业务代码知道,需要手工补充。
所谓「零代码」是指无需修改业务源码即可采集通用遥测数据,并不表示无需配置。Agent 会影响进程启动参数、类加载或运行时行为,上线前仍要测试版本兼容性、启动耗时和性能开销。
1.5 Collector:可编程的遥测管道
OpenTelemetry Collector 由组件拼成 Pipeline。最常见的三个角色是:
- Receiver 接收数据,例如 OTLP、Prometheus Scrape、Jaeger 或文件日志;
- Processor 在内存中批处理、过滤、采样、脱敏或补充属性;
- Exporter 把数据发送到 VictoriaMetrics、VictoriaLogs、Jaeger 或其他后端。
当前 Collector 还包含 Connector 与 Extension。Connector 可以把一条 Pipeline 的输出接到另一条 Pipeline,例如从 Span 生成指标;Extension 则提供健康检查、鉴权等不直接处理遥测数据的能力。完整组件模型见 Collector Components。
图中省略了各类数据的独立配置。实际使用时,指标、日志和链路各有一条 Pipeline,彼此不会共用 Processor。Receiver、Processor 和 Exporter 还必须写入 service.pipelines,否则不会启用。
Processor 的顺序会影响资源消耗和处理结果。memory_limiter 应尽量放在前面,过滤和采样也应早于成本较高的属性补充,batch 通常放在末尾。具体顺序可参考 Collector 官方的处理器配置建议。
1.6 区分上下文传播和遥测数据传输
上下文传播和 OTLP 都会在服务之间传递信息,但用途不同。
Context Propagation 发生在业务请求路径上。网关调用订单服务时,把 traceparent、tracestate 等上下文放进 HTTP Header;订单服务创建子 Span,再把上下文传给库存服务。它解决的是「这些操作属于同一条 Trace,父子关系是什么」。即使 Collector 暂时不可用,业务调用仍应继续,传播也不应中断。
OTLP Export 发生在遥测数据路径上。SDK 把已经完成或聚合的 Span、Metric、LogRecord 批量发给 Collector,常用 OTLP/gRPC 的 4317 端口或 OTLP/HTTP 的 4318 端口。它解决的是「如何把数据交给下一跳」。OTLP/HTTP 的三个标准信号路径通常是 /v1/traces、/v1/metrics 和 /v1/logs;VictoriaMetrics 家族在后端入口前增加了自己的路径前缀,所以 Collector 配置使用了完整 *_endpoint。
Collector Pipeline 则是接收 OTLP 以后执行的本地处理图。它可以把 OTLP 转成 Prometheus Remote Write,也可以扇出到两个 Trace 后端,但不会替业务请求传播 traceparent。如果上游 Span 根本没建立父子关系,Collector 无法靠时间戳可靠推断回来。
业务数据平面
Client ──traceparent──> Gateway ──traceparent──> Checkout ──traceparent──> DB Client
遥测数据平面
Gateway SDK ─┐
Checkout SDK ─┼──OTLP──> Collector ──OTLP / Remote Write──> Backends
DB Span ─┘两条路径的故障处理也不同。Collector 不可用时,SDK Exporter 应限制重试时间和队列大小,避免阻塞业务线程。Context Header 无效时,下游可以创建新的 Root Span,但不应中断业务请求。遥测队列同样需要容量上限,否则 Collector 的短暂故障可能导致应用内存溢出。
OTLP 也不是「发出即持久化」协议。SDK 收到 Collector 成功响应,最多表示这一跳已经接受数据,不代表数据已经安全写入最终存储。Collector 后续的 Processor 可能过滤它,Exporter 可能重试失败,进程重启也可能丢失内存队列。如果审计记录有严格的持久性与不可抵赖要求,应该使用专门的审计链路,不能只依赖普通 OTel 日志导出。
还有一个常见误区是把 gRPC 与 HTTP 端口互换。OTLP/gRPC 使用 gRPC Service,OTLP/HTTP 使用 Protobuf 或 JSON 负载和 HTTP 路径;它们共享数据模型,不表示线上报文相同。客户端配置成 gRPC,却把地址指向只开放 4318 的 HTTP Receiver,常见结果是连接成功前就出现协议错误。排障时应同时确认协议、端口、TLS 与路径,不能只确认主机名能 Ping 通。
二、组件为什么会越来越多
OTel 只统一采集和传输,后端仍可按数据类型分别选择。这样便于选用合适的存储系统,但也会增加部署和维护的组件数量。
2.1 SDK、Agent 与 Collector 不在同一层
「我们已经装了 OTel Agent,还需要 Collector 吗?」这个问题里的 Agent 可能指两种东西。
第一种是语言自动埋点 Agent,例如 Java Agent。它在应用进程内创建和导出遥测,解决的是「怎样低成本给应用加埋点」。第二种是 Collector 的 Agent 部署模式,即在每台主机或每个 Kubernetes Node 上运行 DaemonSet,解决的是「怎样就近接收、补充主机上下文和收集文件日志」。二者可以同时存在:Java Agent 把 OTLP 发给本机 Collector Agent,再由后者发往中心 Gateway。
| 组件 | 运行位置 | 主要职责 | 不适合承担的职责 |
|---|---|---|---|
| OTel API / SDK | 应用进程内 | 生成信号、传播上下文、初步采样与导出 | 集中治理所有团队的数据 |
| 自动埋点 Agent | 应用进程内或运行时旁路 | 为常见框架自动创建遥测 | 理解全部业务语义 |
| Collector Agent | 节点或 Pod 附近 | 就近接收、采集宿主机与文件数据 | 保存长期历史 |
| Collector Gateway | 集群、机房或区域入口 | 集中鉴权、治理、采样与分流 | 代替查询后端 |
小规模环境可以让 SDK 直接写支持 OTLP 的后端,但应用会直接持有后端凭据,也失去了集中重试、脱敏和路由的缓冲层。生产上更常见的是 Agent、Gateway 或二者组合。多一层 Collector 会增加延迟和维护成本,不应为了架构图对称而硬加;真正的判断标准是是否需要本地采集、集中策略、多后端扇出或故障缓冲。
2.2 经典的开源组合
在 OTel 普及之前,各信号已有成熟生态,今天仍然如此。
Metrics 常以 Prometheus 为入口。Prometheus 拉取目标并在本地保存时间序列,PromQL 与告警生态成熟;需要更长保留期、对象存储和横向扩展时,可以加入 Thanos,或者使用 Cortex。Grafana Mimir 源自 Cortex,但二者现在是独立项目,选型时要分别看当前维护状态和迁移成本,不能只当作同一软件的两个名字。
Traces 常见 Jaeger 与 Zipkin。现代 Jaeger 可以直接接收 OTLP,负责 Trace 查询与依赖关系展示。Zipkin 的模型和协议仍被许多框架支持。也可以选择 Tempo、ClickHouse 方案或 VictoriaTraces;重点是确认 OTLP 数据类型、采样语义、查询 API 与 Grafana 关联能力是否匹配。
Logs 常见 Elasticsearch 与 Loki。Elasticsearch 的全文检索和通用文档模型很强,代价是索引、Heap 与集群调优并不轻;Loki 借鉴 Prometheus 标签思路,以日志流标签建立索引,日志正文不做同等程度的全文索引,常与 Grafana 配合。
一个很常见的堆栈是:
Prometheus + Thanos 保存 Metrics
Jaeger + Elasticsearch 保存 Traces
Loki + 对象存储 保存 Logs
Grafana 统一入口
OpenTelemetry Collector 统一采集和分流这些组件各有用途,但组合部署后,维护成本也会随之增加。
2.3 维护成本和基数问题
组件多并不只是多几份 YAML。每个系统都有自己的:
- 高可用模型、分片方式、压缩与合并过程;
- 身份认证、租户隔离、备份和升级路径;
- 查询语言、告警规则、容量估算与故障指标;
- 标签或索引模型,以及完全不同的高基数后果。
同一个 trace_id,在链路系统中可以作为主要查询条件,在日志中适合作为普通字段,在指标中却不适合用作标签。统一字段名称不代表三类系统都要为这些字段建立索引。数据模型不同,字段的存储方式也应不同。
高基数(High Cardinality)是遥测系统最常见的成本放大器。对时间序列而言,一个 Metric Name 与一组完整 Label Values 通常定义一条序列:
http_server_request_duration_seconds{
service_name="checkout",
route="/orders/{id}",
method="GET",
status_code="200"
}如果将模板路由换成原始路径 /orders/934857,每个订单都可能产生新的时间序列。再叠加 Pod、区域、状态码和直方图 Bucket,活跃序列数会按属性组合增长。实例频繁更新还会造成 Churn:旧序列不断消失,新序列不断产生,索引和缓存都要处理这些生命周期很短的序列。
日志系统的情况不同。正文中出现大量 user_id,并不会直接产生同等数量的日志流;只有将它设为 Stream Label,日志流数量才会快速增加。因此,评估后端时还要了解哪些字段会进入索引,以及采用什么索引方式。
2.4 OpenTelemetry 减少锁定,但不会消灭锁定
OTLP 与统一 API 让替换采集和传输后端容易很多。应用从「直写厂商 A SDK」改为「写 OTel API,经 Collector 分流」以后,迁移后端通常不再需要重新埋遍每个服务。
但 Vendor Lock-in 不会凭空消失,它只是从代码层后退了几步。查询语言、仪表盘、告警表达式、长期数据、后端特有的直方图语义和日志索引方式仍然可能绑定具体产品。更现实的目标是:
- 业务代码依赖稳定的 OTel API 与语义约定;
- 传输优先使用 OTLP 等开放协议;
- Collector 中保留明确、可测试的数据变换;
- 接受后端查询层仍有差异,把迁移成本控制在平台层,而不是散落到每个服务。
这样可以把迁移工作集中在平台层,但无法完全消除迁移成本。
三、使用 VictoriaMetrics 系方案
当团队已经决定使用 OTel 统一采集,接下来要选的是遥测后端。VictoriaMetrics 最初以时间序列数据库为核心,VictoriaLogs、VictoriaTraces 又分别覆盖日志和链路。三者使用相似的部署与运维思路,但仍是针对不同信号优化的独立数据库。
本文重点使用 VictoriaMetrics 保存指标、VictoriaLogs 保存日志。Trace 可以继续进 Jaeger 或 Tempo,也可以进入支持 OTLP 的 VictoriaTraces。
3.1 为什么选择 VictoriaMetrics
VictoriaMetrics 可以从单节点开始部署。中小规模环境先用一个进程验证数据模型和查询负载;对容量、可用性或多租户有更高要求时,再迁移到由 vminsert、vmselect、vmstorage 等组件组成的集群。这样可以根据实际需求逐步扩展。
它围绕时间序列的压缩、索引缓存和后台合并设计存储路径,较低的每样本磁盘占用通常也会减少磁盘 I/O 和长期保留成本。VictoriaMetrics、VictoriaLogs 与 VictoriaTraces 虽是三个独立数据库,但配置、监控和 Grafana 集成方式相近,平台团队不必同时维护三套差异很大的运维习惯。
「高压缩、低 CPU/内存、高吞吐」是 VictoriaMetrics 项目长期强调的特点,也有公开 Benchmark;但 Benchmark 很容易被硬件、基数、乱序比例、查询范围和副本数左右。本文不拿某个厂商给出的倍数当采购结论。更可靠的做法是用自己的标签分布、保留期和典型 Dashboard 回放一周数据,测量写入延迟、查询 p95、Compaction I/O、工作集内存与每样本磁盘占用。
3.2 原生 OTLP 与 Prometheus 兼容路径
VictoriaMetrics 单节点、vmagent 与 Cluster 的 vminsert 可以接收 OTLP Metrics。单节点 HTTP 路径为 /opentelemetry/v1/metrics;VictoriaLogs 的 OTLP Logs 路径为 /insert/opentelemetry/v1/logs;VictoriaTraces 的 OTLP Traces 路径为 /insert/opentelemetry/v1/traces。Collector 使用通用 otlphttp Exporter 即可直连,不需要一个名为「VictoriaMetrics Exporter」的专用插件。
Metrics 还可以走 Prometheus Remote Write。两条路径的取舍如下:
| 路径 | 优点 | 需要确认的语义 |
|---|---|---|
| OTLP/HTTP 直连 | 保留 OTel 数据模型,配置直接,三种信号形式一致 | Metric 名称、Resource Attributes 提升、Temporality、Histogram 转换 |
| Prometheus Remote Write | 适合既有 Prometheus 生态,Relabel 经验成熟 | OTel 到 Prometheus 的名称、类型、Resource 与 Label 映射 |
VictoriaMetrics 官方文档建议通过 otlphttp 发送,并指出它更适合 Cumulative Temporality 累积时序;如果上游产生 Delta,可在 Collector 中用 deltatocumulative Processor 转换。具体行为与命名选项见 VictoriaMetrics 的 OTel Metrics 文档。这类转换不能等上线后看到图形觉得不对劲了再处理,应该用 Counter 重启、Histogram Bucket 和进程滚动场景提前验证。
3.3 基本部署结构
小规模环境可以从单层 Gateway 起步:
规模扩大后,可以在每个 Node 部署 Agent Collector,采集容器文件日志、主机指标和应用 OTLP,再发给中心 Gateway。中心层统一鉴权、脱敏和路由。Tail Sampling 需要看到同一 Trace 的所有 Span,届时要按 trace_id 做一致路由,不能简单轮询到任意副本。
Collector、VictoriaMetrics 和 VictoriaLogs 本身也需要监控,关键告警最好通过独立路径发送。如果告警仍依赖已经无法写入的后端,故障发生后可能无法送达。
3.4 OTLP 与 Prometheus 的指标模型差异
OTLP Metrics 与 Prometheus 都描述时间序列,却不是换个编码就能无损互转。落地时有四处最容易踩坑。
Temporality 决定 Counter 报告累计值还是区间增量。一个 Counter 从 100 增长到 130,Cumulative(累积)报告 130,Delta(增量)报告 30。VictoriaMetrics 更适合累积语义;上游若只产生 Delta,可以在 Collector 中用 deltatocumulative 转换,但同一序列必须稳定路由到同一个转换状态。若多个无状态副本各收到一部分数据,转换结果自然不完整。
Histogram 也有模型差异。传统 Prometheus Histogram 使用固定 Bucket,OpenTelemetry 还支持 Exponential Histogram。并非每条导出路径和每个查询后端都能用同样方式保存两者。启用前要核对 Collector Exporter、VictoriaMetrics 版本和 Grafana 查询能力,必要时在 SDK View 中统一边界。
Resource 与 Data Point Attributes 在 Prometheus 的扁平 Label 模型里没有完全对等的层次。导出时,一部分 Resource Attributes 会被提升为 Label;VictoriaMetrics 的原生 OTLP 接入也默认这样处理。服务名和区域通常没问题,容器 ID、进程参数和临时实例标识却可能让每条序列继续分裂。上线前应查看真实数据,而不是凭字段名猜基数。
Metric Name 同样需要提前定规矩。OTel 语义约定常用点分名称,Prometheus 生态习惯下划线、单位和类型后缀。VictoriaMetrics 可以做 Prometheus 兼容命名转换,但 Dashboard 看到的名称也会随之改变。接入初期不定下来,后面就要迁查询和告警。
这些差异来自 OTLP 和 Prometheus 的数据模型,并非 VictoriaMetrics 独有。接入测试应包含单调 Counter、进程重启后的 Counter、Gauge、使用不同 Bucket 的 Histogram、Exemplar,以及带完整 Resource 的样本。只有完成这些端到端测试,才能确认数据转换符合预期。
Exemplar 会在少量指标样本中附带链路上下文,可以从异常的直方图样本跳转到具体请求。它不会把所有 trace_id 都保存为指标标签,因此既能维持指标的低基数,也能建立指标与链路之间的关联。
四、Collector 与 VictoriaMetrics 的配置示例
下面假设三个后端都运行在同一个受信网络中:
- VictoriaMetrics 单节点:
victoria-metrics:8428; - VictoriaLogs 单节点:
victoria-logs:9428; - VictoriaTraces 单节点:
victoria-traces:10428。
示例使用 OpenTelemetry Collector Contrib,因为 Tail Sampling、Kubernetes Attributes 等常用组件并不全在 Core Distribution 中。生产上应固定 Collector 镜像版本,并用该版本的 otelcol-contrib validate --config=... 校验。Collector 组件名近年做过规范化,升级时尤其要看 Release Notes;不要默认网上一份三年前的配置仍能原样加载。
4.1 otel-collector-config.yaml
extensions:
health_check:
endpoint: 0.0.0.0:13133 # 仅供存活与就绪探测,不对公网暴露
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
max_recv_msg_size_mib: 16 # 限制异常大批次,值需按真实 Span/Log 大小验证
http:
endpoint: 0.0.0.0:4318
processors:
memory_limiter:
check_interval: 1s
limit_percentage: 75 # 以容器可见内存为基准的软硬限制参数
spike_limit_percentage: 20 # 为两次检查之间的流量尖峰留余量
resource/common:
attributes:
- key: deployment.environment.name
value: production
action: upsert
batch:
timeout: 1s
send_batch_size: 8192
send_batch_max_size: 16384 # 真正上限,避免一个批次无界增长
exporters:
otlphttp/victoriametrics:
compression: gzip
encoding: proto
metrics_endpoint: http://victoria-metrics:8428/opentelemetry/v1/metrics
otlphttp/victorialogs:
compression: gzip
encoding: proto
logs_endpoint: http://victoria-logs:9428/insert/opentelemetry/v1/logs
headers:
# 只让稳定、低基数、能标识应用实例的字段构成日志流。
# trace_id、user_id、request_id 应保留为普通字段,绝不能放进这里。
VL-Stream-Fields: service.name,deployment.environment.name,host.name
otlphttp/victoriatraces:
compression: gzip
encoding: proto
traces_endpoint: http://victoria-traces:10428/insert/opentelemetry/v1/traces
service:
extensions: [health_check]
pipelines:
metrics:
receivers: [otlp]
processors: [memory_limiter, resource/common, batch]
exporters: [otlphttp/victoriametrics]
logs:
receivers: [otlp]
processors: [memory_limiter, resource/common, batch]
exporters: [otlphttp/victorialogs]
traces:
receivers: [otlp]
processors: [memory_limiter, resource/common, batch]
exporters: [otlphttp/victoriatraces]这里有几个名字很容易误导人。otlphttp/victoriametrics 中的 /victoriametrics 只是自定义组件 ID,Exporter 仍是通用 otlphttp。若改成 otlp/victoriametrics,Collector 会使用 OTLP/gRPC,而上面的地址都是 HTTP 路径,两者不能混用。
memory_limiter 只能在达到软限制时施加背压、拒绝部分数据并尝试触发 GC,不能避免所有内存溢出。进程超过容器的内存上限后,仍可能被 OOM Kill。示例在三条 Pipeline 中引用同一份配置,每条 Pipeline 都会创建独立的 Processor 实例,但它们共用一个进程 Heap,因此要按 Collector 进程的总内存设置限制。Memory Limiter 文档建议将 check_interval 从 1s 起步,Spike Limit 可以先按硬限制的 20% 估算,再通过压力测试调整。
示例中的明文 HTTP 只适合隔离良好的内部测试网络。跨 Node、跨集群或跨公网传输时要配置 TLS 和鉴权,写入端点也不能直接暴露在公网。日志和 Span 里可能带着 Cookie、Token、SQL 参数与个人信息;出口统一以后,这里也会成为最需要防守的数据边界之一。
4.2 使用 Prometheus Remote Write 的替代配置
如果团队已经围绕 Prometheus 命名、Relabel 与 Remote Write 建立治理,可以只替换 Metrics Exporter:
exporters:
prometheus_remote_write/victoriametrics:
endpoint: http://victoria-metrics:8428/api/v1/write
resource_to_telemetry_conversion:
enabled: true # 把 Resource Attributes 转成时间序列标签,开启前先审计基数
service:
pipelines:
metrics:
receivers: [otlp]
processors: [memory_limiter, resource/common, batch]
exporters: [prometheus_remote_write/victoriametrics]较旧 Collector 版本的组件名常写作 prometheusremotewrite。2026 年的文档与发行版正在统一使用 prometheus_remote_write 等别名,因此复制配置前应以正在运行的二进制为准。本文主配置选 otlphttp,正是为了减少这层转换,也与 VictoriaMetrics 当前的Collector 接入示例一致。
启用 resource_to_telemetry_conversion.enabled 后,Resource Attributes 会转换成指标标签。service.name、区域和环境通常适合作为标签;进程命令行、Pod UID、容器 ID 以及频繁变化的 service.instance.id 可能显著增加序列数量。启用前应先筛选需要转换的字段。
4.3 应用侧最少要提供什么
Collector 只能处理应用已经产生的数据。应用至少要提供稳定的 Service Resource,并正确传播上下文。下面是一组通用环境变量:
export OTEL_SERVICE_NAME=checkout
export OTEL_RESOURCE_ATTRIBUTES='service.namespace=shop,deployment.environment.name=production,service.version=2026.09.14'
export OTEL_EXPORTER_OTLP_ENDPOINT='http://otel-collector:4317'
export OTEL_EXPORTER_OTLP_PROTOCOL='grpc'
# 先全量或高比例验证埋点质量,再按容量调整。
export OTEL_TRACES_SAMPLER='parentbased_traceidratio'
export OTEL_TRACES_SAMPLER_ARG='0.10'不同语言对环境变量的支持成熟度并不完全一样,要以对应 SDK 文档为准。service.name 不能省;没有它的服务常落入 unknown_service,后续所有 Dashboard 都得猜这是谁。
日志建议继续使用成熟日志库,通过 Appender/Bridge 注入 Trace Context,再由 OTLP 或文件采集进入 Collector。至少保留这些结构化字段:
{
"timestamp": "2026-09-14T02:13:45.231+08:00",
"severity": "ERROR",
"message": "reserve inventory failed",
"service.name": "checkout",
"trace_id": "4bf92f3577b34da6a3ce929d0e0e4736",
"span_id": "00f067aa0ba902b7",
"error.type": "DeadlockDetected",
"retry.count": 2
}不要为了结构化把密码、Authorization Header、完整请求体和身份证号也结构化。脱敏最好在源头完成,因为敏感数据一旦到达 Collector,就已经经过网络、缓冲区和进程内存;Collector 的 Redaction/Transform 只能作为第二道防线。
日志有两条接入路径,需要按运行环境选一条作为主路。
应用可以通过 OTel Log Appender 和 SDK 主动推送 OTLP。这样 Resource、Severity、Trace Context 与结构化字段在进程内就已确定,不必再解析文本;代价是日志导出和业务共享 CPU、内存与网络,进程崩溃前留在异步队列里的记录也可能来不及发出。各语言日志信号和 Appender 的成熟度还要分别确认。
另一种做法是继续向 stdout 或文件写结构化日志,再由 Collector 的 filelog Receiver 采集。这更符合容器平台的习惯,应用退出后,文件中的内容仍有机会被读走;相应地,Collector 要处理容器运行时前缀、多行堆栈、文件轮转和读取位置。最小配置如下:
receivers:
filelog/application:
include:
- /var/log/pods/*/*/*.log
start_at: end # 首次启动不回读历史文件;是否符合要求要明确决定
include_file_path: true
operators:
- type: container # 解析 CRI/Docker 格式,并重组被拆开的多行日志
service:
pipelines:
logs:
receivers: [otlp, filelog/application]
processors: [memory_limiter, resource/common, batch]
exporters: [otlphttp/victorialogs]container Operator 属于 Collector Contrib 的 Stanza 日志组件,实际可用字段要以固定版本为准。Kubernetes 中还要把宿主机日志目录只读挂载给 Agent Collector,并用 Kubernetes Attributes Processor 补上 Pod、Namespace 和 Container 信息。这个 Processor 依赖接收端连接信息或资源关联规则,经过某些 Batch 以后 Context 会丢失,因此应在 Batch 前执行。
start_at: end 只控制 Collector 第一次看见文件时从哪里开始;重启后的续读位置还涉及 File Storage Extension。没有持久化读取位置时,重启可能造成重复或漏读,具体取决于文件是否轮转、旧文件是否仍在以及 Receiver 如何识别文件。日志数量很大时,读取状态本身也需要控制过期和磁盘空间。
同一应用不应长期同时使用两条路径。应用通过 OTLP Appender 发送日志后,如果 filelog 又从 stdout 采集一次,VictoriaLogs 会保存两份相同的数据。迁移期间需要双路验证时,可以增加临时的来源字段,完成对比后关闭旧路径。
传统日志字段映射也需要约定。level 应映射为 OTel Severity,而不只是正文里的字符串;异常类型、异常消息与堆栈应拆成明确字段;事件发生时间优先使用应用提供的时间,Collector 观察到它的时间用于诊断传输延迟。原始日志只有一行文本时可以先整体放入 Body,别为了看起来结构化就用一个脆弱的正则把每条消息切碎。解析失败应有计数和样本告警,否则日志格式升级后,查询字段会安静地全部变空。
指标也有 Push 与 Pull 两条路径。应用 OTel SDK 通常定期通过 OTLP Push;已有 /metrics Endpoint 的服务可以继续由 Prometheus Receiver Scrape,再统一发往 VictoriaMetrics。迁移时最重要的是避免同一 Instrument 被两条路径重复采集。基础设施 Exporter 已经输出 Node CPU 时,Host Metrics Receiver 再采一份并不一定增加信息,只可能增加名称转换和对账工作。
4.4 怎样确认链路真的通了
接入验收不应以 Collector 没报错为标准,至少沿数据路径检查四层:
- 应用 SDK 是否生成并导出数据,Context 是否跨进程传播;
- Collector Receiver 是否接收,Processor 是否拒绝,Exporter Queue 是否积压;
- 后端是否写入,是否因时间戳、鉴权、格式或磁盘空间丢弃;
- Grafana 查询是否使用了正确数据源与字段名。
Collector 应重点监控 Accepted、Refused、Sent、Failed、Queue Size/Capacity 等内部指标。VictoriaMetrics 看写入错误、Active Series、Churn、慢写与磁盘余量;VictoriaLogs 可看 vl_rows_ingested_total、vl_rows_dropped_total 和新建 Stream 数量。Collector 扩容文档建议在 Exporter Queue 长期达到容量的 60% ~ 70% 时调查和考虑扩容;如果后端本身写不动,盲目增加 Collector 只会更快地压垮后端,见官方扩容指南。
还应测试后端短暂中断的情况。例如暂停后端 2 分钟,观察队列、重试、内存和丢弃数量,恢复后再确认积压是否处理完成。默认的内存队列不会在 Collector 重启后保留,进程停止期间的数据能否重发取决于上游 SDK 和部署方式。要降低数据丢失风险,必须同时考虑容量、持久化和背压。
4.5 四类接入故障,分别从哪里找
第一类是完全没有数据。先在应用侧临时启用 SDK 的 Debug/Console Exporter,确认 Instrumentation 是否真的创建了 Span 或 Metric;再检查 OTEL_SDK_DISABLED、Exporter 类型、Endpoint 和 Protocol。容器里的 localhost:4317 指向容器自己,不是旁边的 Collector。Collector 侧可临时增加受控的 Debug Exporter,但不要在高流量生产环境输出每条完整遥测,否则诊断本身会制造日志风暴和敏感数据泄漏。
如果 Collector Receiver 计数增长而后端没有数据,问题位于 Processor、Exporter 或后端入口。检查 Pipeline 是否真的引用了组件,Exporter 是否报 404、401、TLS 握手失败或 Deadline Exceeded。404 常见于 OTLP/HTTP 路径错误:把 VictoriaMetrics 的 /opentelemetry/v1/metrics 写成标准 Receiver 路径 /v1/metrics,请求到了正确主机,也会被正确地拒绝。
第二类是有数据,但服务关系断裂。打开一条 Trace,检查每个下游 Span 的 Trace ID 是否一致、Parent Span ID 是否指向真实父节点。HTTP Header 被网关 Allowlist 删除、消息队列没有注入或提取 Context、异步任务在错误的 Context 上创建 Span,都会生成几段看起来时间接近却互不相识的 Trace。此时增加 Collector Buffer 没有用,问题发生在业务传播路径。
消息队列尤其不能照搬同步 HTTP。Producer Span 表示发送,Consumer Span 表示处理;消息可能排队很久、被多个消费者处理或批量消费,父子与 Link 的选择要遵循对应 Messaging Semantic Conventions。硬把整个消费者生命周期挂在一个长期 Producer Span 下,会得到持续几小时、结构失真的链路。
第三类是图有数据,但数值不可信。Counter 在应用重启后出现负 Rate,要检查 Temporality、Start Time 和后端查询;延迟分位数不合理,要核对单位、Histogram Bucket 与聚合范围;同一服务出现两套 Metric Name,要检查自动埋点版本和 Prometheus 命名转换。还要留心重复采集:SDK 已经 Push 一份 Runtime Metric,Prometheus Receiver 又从应用 /metrics Scrape 一份,Dashboard 相加以后当然会凭空翻倍。
时钟也是经常被低估的依赖。Trace 跨多个节点,节点时钟偏差可能让子 Span 看起来早于父 Span;日志时间戳若错误,VictoriaLogs 会按 Retention 丢弃过旧或过于超前的数据。生产节点应有可靠时间同步,并同时观察事件发生时间与采集观察时间。不要用 Collector 收到数据的时刻粗暴覆盖原始时间,否则网络拥塞会被伪装成业务延迟。
第四类是开始正常,过一阵逐渐丢数据。这通常不是协议问题,而是容量或基数问题。按顺序看 Receiver 接收速率、Processor 拒绝、Tail Sampling Buffer、Exporter Queue、后端写入延迟、磁盘余量与 Active Series/Streams。Queue 持续单向增长说明出水速度低于进水速度;增加容量只能延迟溢出。需要继续定位是网络带宽、后端 Compaction、查询抢占资源,还是上游突然引入了新 Label。
排障时最好保留一条人工生成的 Canary Telemetry:每分钟从固定服务发送一个已知 Metric、Log 和 Trace,三者带可预测的关联字段,并从独立位置查询。它不能证明所有业务数据完整,却能快速区分「业务没有请求」与「管道某一层断了」。Canary 自己也要低基数,别把当前时间戳塞进 Label 来证明它每分钟都是新的。
4.6 配置变更也需要测试与灰度
Collector 配置看起来只是 YAML,实际效果却接近一段流处理程序。一个 delete_key 可以让几十个 Dashboard 失效,一条 Filter 可以静默丢掉整类错误 Trace,一个正则表达式也可能在高流量下吃掉大量 CPU。
建议为配置建立三层检查。第一层是静态校验,用目标版本 Collector 的 validate 子命令发现不存在的组件、拼错的字段和信号不兼容。第二层是样本回放,准备包含正常、错误、慢请求、高基数与敏感字段的固定 OTLP 数据,比较变更前后输出。第三层是小流量灰度,让一部分 Agent 或服务发送到新 Gateway,观察接收/导出数量、延迟和字段差异。
双写验证时要设置结束时间。长期双写不仅成本翻倍,还会让团队继续维护两套并不完全一致的 Dashboard,最后谁也不敢关。迁移验收应该事先定义:关键指标误差范围、Trace 关联成功率、日志字段完整度、查询延迟和允许丢失比例。满足条件就切换,不满足就根据数据回退,而不是凭「新界面看着差不多」做决定。
五、高基数治理
再高的压缩率,也救不了无限增长的序列和日志流。高基数治理必须发生在数据进入长期存储之前。
5.1 指标标签只保存查询维度
判断一个字段能否成为 Label,可以连问四个问题:
- 它是否会用于聚合、筛选、告警或 SLO?
- 它的可能值是否有可解释的上界?
- 实例滚动、用户增长和攻击流量会不会让它持续产生新值?
- 能否用日志或 Trace 保存细节,而不是让每个指标样本携带?
常见处理如下:
| 原始字段 | Metrics 中的处理 | Logs / Traces 中的处理 |
|---|---|---|
/users/83471/orders/991 | 使用模板 http.route=/users/{user_id}/orders/{order_id} | 原始路径可脱敏后保留 |
user_id | 删除,或映射成有限用户等级 | 作为普通可搜索属性,遵守隐私策略 |
trace_id / request_id | 不作 Label;需要时使用 Exemplar | 作为关联字段保留 |
| HTTP 状态码 | 可保留,值域有限 | 保留具体值与错误详情 |
service.version | 通常可保留,但注意短周期发布 Churn | 保留 |
| Pod UID / Container ID | 一般删除或只在短期基础设施指标保留 | 故障定位需要时保留 |
尤其要警惕自动埋点产生的 url.full、数据库语句、消息目的地与 Header。攻击者可以构造随机 URL 或 Header,把你的指标系统变成一台按请求内容创建索引的机器。对外部输入先归一化、设长度上限和 Allowlist,比事后增加内存有效得多。
VictoriaMetrics 的 Cardinality Explorer 可以找出活跃序列最多、增长最快但很少查询的 Metric 与 Label;官方 FAQ 也把 user_id、原始 url 和 ip 列为典型来源。对已知无用字段可在 SDK View、Collector Transform/Filter、vmagent Relabel 或写入前聚合阶段删除。越靠近源头修正,浪费的网络和 Collector 资源越少。
基数治理不该只靠文档。可以建立预算:每个服务的活跃序列上限、每日新序列率、单 Metric 最大序列数,并在 CI 中对常见路由和属性做基数测试。超预算时由服务所有者解释用途,而不是等存储 OOM 后平台团队猜谁加了 customer_email。
5.2 VictoriaLogs:Stream Fields 必须稳定
VictoriaLogs 把 _time 作为时间、_msg 作为正文,并用 Stream Fields 标识日志流。适合做 Stream Fields 的是应用实例生命周期内相对稳定、查询时经常用于缩小范围的字段,例如:
service.name;deployment.environment.name;host.name、k8s.namespace.name、k8s.container.name;- 在实例生命周期内稳定的
region或cluster。
不适合的则是每条日志都可能变化的 trace_id、user_id、request_id、客户端 IP 和原始 URL。它们可以作为普通字段保存和查询,只是不进入 _stream。VictoriaLogs 的关键概念文档明确提醒:高基数字段作为普通字段没有同样的问题,把它们放进 Stream 才会显著增加 CPU、内存、磁盘和查询成本。
这也是主配置显式设置 VL-Stream-Fields 的原因。VictoriaLogs 的 OTLP 接入默认会把 Resource Attributes 当作日志流字段;自动探测出来的 Resource 很丰富,其中并非所有字段都稳定。生产前必须把 Stream Fields 收紧成自己的 Allowlist,不能把默认行为当成最佳实践。
可以监控 vl_streams_created_total。它在每日分区创建新 Stream 时增长,因此跨天会有正常跳变;如果同一天内增长速度突然远高于实例数与发布节奏,就该检查新 Stream 日志或字段分布。
5.3 Traces:属性也有成本,只是爆炸方式不同
Trace 后端不像 Prometheus 那样为每个 Label 组合创建时间序列,但高基数和大属性仍会增加 Span 大小、网络流量、索引与查询成本。把完整 SQL、请求体或异常堆栈塞进每个 Span,很快会让 Sampling 节省下来的容量从另一边漏掉。
Span Name 应描述稳定操作,例如 GET /orders/{id},不要直接使用原始 URL。事件适合记录一次 Span 内少量关键变化,日志更适合详细堆栈和重复诊断信息。一个请求循环 10 万次时,也不要机械地产生 10 万个子 Span;先问这些 Span 是否真能改变排障决策。
六、如何设置采样
Trace 的成本通常与请求量近似线性增长,因此生产系统很少永久保留 100% Trace。Sampling 的目标是在容量预算内保留最有诊断价值的链路。
6.1 Head-based Sampling:便宜、及时,但不知道结局
Head-based Sampling 在 Trace 开始时决定是否采样。parentbased_traceidratio 会尊重上游父 Span 的决定,并对新的 Root Trace 按 Trace ID 比例采样。它的优点是决定早:未采样 Trace 不必在每个服务构造和传输完整 Span,成本可预测,横向扩展也简单。
缺点同样来自「决定早」。刚收到请求时不知道它最终会不会 500、会不会超过 5 s,也不知道某个下游是否死锁。1% 比例采样理论上对总体分布近似公平,却可能恰好漏掉低频但重要的错误。
Head Sampling 适合高吞吐、错误已有 Metrics/Logs 兜底,或者预算极其严格的服务。它还要保证上下游传播采样位;每个服务各自重新抽签,会得到断裂的 Trace。
6.2 Tail-based Sampling:看到结果再选,代价是有状态
Tail-based Sampling 先把同一 trace_id 的 Span 暂存在 Collector,等候一段时间后按完整结果决定。例如:
- 保留任何包含
ERRORSpan 的 Trace; - 保留总时长超过 2 s 的 Trace;
- 支付、退款等关键操作全量保留;
- 对剩余正常流量保留 1%。
processors:
tail_sampling:
decision_wait: 15s
num_traces: 100000
expected_new_traces_per_sec: 5000
decision_cache:
sampled_cache_size: 500000
non_sampled_cache_size: 1000000
policies:
- name: keep-errors
type: status_code
status_code:
status_codes: [ERROR]
- name: keep-slow
type: latency
latency:
threshold_ms: 2000
- name: baseline
type: probabilistic
probabilistic:
sampling_percentage: 1
service:
pipelines:
traces:
receivers: [otlp]
processors: [memory_limiter, resource/common, tail_sampling, batch]
exporters: [otlphttp/victoriatraces]这组数字只是展示字段含义,不是通用生产参数。decision_wait 要覆盖绝大多数 Trace 的到达时间,又不能大到让所有 Span 长期占内存;num_traces 至少要容纳等待窗口内的新 Trace,并给突发留余量;Decision Cache 用来让迟到 Span 尽量继承既有决定。Tail Sampling Processor 默认是有状态组件,详细限制见其官方 README。
如果缓冲区装不下,旧 Trace 会在作出采样决定前被移除。应该监控 otelcol_processor_tail_sampling_sampling_trace_dropped_too_early、采样决定延迟和 Late Span 年龄,而不是只看最终存了多少条 Trace。
6.3 Tail Sampling 需要按 Trace ID 路由
Tail Sampling 必须让同一个 Trace 的所有 Span 到达同一个 Collector 实例。普通 Kubernetes Service 按连接或请求负载均衡,可能把不同服务上报的同一 Trace 分到不同副本,每个副本只看见半条链路,错误和延迟判断都会失真。
一种常见做法是两级 Gateway:第一级使用 Load Balancing Exporter 按 traceID 路由,第二级执行 Tail Sampling。OpenTelemetry 的 Gateway 部署文档专门描述了这个模型。
扩缩容会改变路由集合,正在等待决定的 Trace 仍可能受影响;Sampler 重启也会丢失内存状态。Tail Sampling 提升的是样本价值,不提供事务式不丢保证。若团队无法承担这套状态管理,稳定的 Head Sampling 加错误日志关联,往往比一套经常漏半条链的 Tail Sampling 更有用。
6.4 指标与日志不要照搬 Trace 采样
Metrics 已经是聚合结果,对 Counter 随机丢点会破坏 Rate 和告警语义,不能把 Trace 的 1% Sampling 机械套过去。Metrics 应通过减少无用 Instrument、降低采集频率、限制 Label、View 聚合、Recording Rules 或 Stream Aggregation 控制成本。
日志可以采样重复、低价值的 Debug/Info,但错误和审计日志需要独立规则。每秒重复一万次的同类错误可以限速并附带被抑制数量;涉及资金与安全的审计记录则不能因为 Collector 忙就随意抽掉。Sampling Policy 是数据治理政策的一部分,要由业务、SRE、安全和合规共同确定。
七、生产环境中的高可用设计
增加 Deployment 的副本数只能减少单个进程故障的影响。高可用还要考虑流量分配、状态保存、后端故障时的缓冲,以及进程退出前的数据处理。
7.1 Agent 与 Gateway 怎样分工
一个比较稳妥的 Kubernetes 结构是:
- Node 上的 Agent Collector 以 DaemonSet 部署,采集容器日志、主机指标和本地应用 OTLP;
- 中心 Gateway 以 Deployment 部署,集中做鉴权、租户标识、基数治理和导出;
- Tail Sampling 单独成为按 Trace ID 路由的有状态逻辑层;
- 存储按各信号独立扩容,不让一场日志风暴把 Metrics 查询一并拖死。
Agent 就近收集可以降低应用对中心地址变化的感知,也能补充 Node/Pod 属性。Gateway 让后端凭据不必下发到每个业务 Pod。代价是多一次网络跳转和两层配置管理,所以应通过配置发布、版本锁定和端到端演练控制复杂度。
Prometheus Receiver 还需要避免重复采集。多个配置相同的 Collector 副本会抓取同一 Target,产生重复或乱序样本。因此,Scrape 任务需要分片或使用 Target Allocator,不能直接通过增加副本扩容。
7.2 memory_limiter、Batch 与 Queue 一起算内存
Collector 的内存主要不只来自当前请求,还来自:
- Receiver 解码后的批次;
- Tail Sampling 等有状态 Processor;
- Batch Processor 等待中的数据;
- Exporter Sending Queue 与失败重试;
- Go Runtime、组件缓存和临时转换对象。
假设容器 Limit 为 2 GiB,把 memory_limiter.limit_percentage 设成 95% 并不稳妥。Limiter 面向进程 Heap,RSS 还包含其他内存;在两次检查之间也可能出现尖峰。通常应给 Runtime、瞬时批次和 Sidecar 留出余量,再用压力测试确定值。
Queue 过小,后端短暂抖动就丢数据;Queue 过大,后端持续故障时只是把 OOM 延后几分钟。队列容量应该按「允许吸收多长时间的故障 × 每秒数据量 × 平均元素大小」估算,并设置对应告警。若业务真的要求 Collector 重启后仍保留队列,还要评估持久化 Queue/Storage Extension,而不是误以为内存 Queue 会写盘。
优雅退出也值得测试。滚动升级时给 Pod 足够的 terminationGracePeriodSeconds,先停止接收新流量,再让 Batch 与 Exporter 尽量排空。这个时间不是越长越好,必须与 Kubernetes 驱逐、发布窗口和上游重试配合。
7.3 存储层的单节点与集群
VictoriaMetrics 单节点适合中小规模和明确可接受恢复时间的环境。它的部署简单、故障面小,但进程或磁盘故障期间会中断写入和查询。定期快照与异机备份解决的是恢复,不是无中断服务。
当写入、查询、容量或可用性目标超过单机边界时,再评估 Cluster:vminsert 接入写入,vmselect 处理查询,vmstorage 保存数据。组件拆分让不同路径独立扩展,也增加了网络、路由、复制和升级工作。VictoriaLogs 与 VictoriaTraces 也应按其当前版本的 Cluster 文档评估,不能照抄 VictoriaMetrics 的拓扑后假定参数完全相同。
多副本会直接增加磁盘和网络成本。设置 Replication Factor 前,应根据允许的数据丢失窗口、备份恢复时间、跨可用区费用和查询降级策略确定副本数。
7.4 自监控与告警
至少为遥测管道建立这些告警:
| 层 | 应观察的现象 | 含义 |
|---|---|---|
| SDK / Agent | 导出失败、队列满、进程开销异常 | 数据可能还没离开应用 |
| Collector Receiver | Refused Items 增长 | 内存限制、格式或下游背压 |
| Collector Exporter | Queue 接近容量、Send Failed | 后端或网络跟不上 |
| Tail Sampling | Trace 过早淘汰、Late Span 增长 | 决策窗口或容量不足 |
| VictoriaMetrics | Active Series、Churn、Slow Insert、磁盘余量 | 基数或存储压力 |
| VictoriaLogs | Rows Dropped、新 Stream 增速、磁盘余量 | 时间戳错误或 Stream 爆炸 |
| Grafana / Alerting | 查询错误、规则延迟、通知失败 | 有数据但没人能及时看到 |
遥测系统内部指标的 Label 同样会产生高基数。Exporter 失败日志不要把目标请求体完整打出来,否则一次后端故障会同时制造日志风暴。
7.5 多租户、鉴权与隐私边界
一个公司只有一个 Collector Endpoint,不等于所有服务都应该能读写所有遥测。OTLP 入口至少要区分三个边界:谁可以写入、数据属于哪个租户、谁可以查询。
写入端可通过 mTLS、Bearer Token 或网关身份认证限制来源。凭据不应硬编码在 Collector YAML 并提交仓库,可以从环境变量、文件挂载或受支持的 Secret Provider 注入。TLS 证书校验不能为了省事长期设成 insecure: true;测试配置最容易在复制粘贴中长成生产配置。
租户标识应由可信的基础设施写入,不能直接接受客户端声明。否则客户端可以自行设置 tenant_id=finance,绕过基于字段的隔离。通过认证的入口可以根据客户端证书、Namespace 或独立 Endpoint 注入租户标识,并删除客户端提交的同名字段。VictoriaMetrics Cluster、VictoriaLogs 和 VictoriaTraces 的租户能力与鉴权方式并不完全相同,需要分别确认。
查询端应执行最小权限。开发人员通常需要看自己服务的应用日志,不一定需要查看其他团队的审计日志;含用户标识、SQL 与错误堆栈的数据可能受隐私和合规约束。Grafana Data Source Credential 若使用一个全局管理员账号,Dashboard 权限再精细,也可能被 Explore 或代理接口绕过数据边界。真正的隔离应由后端或查询网关执行,界面权限只是一层体验控制。
还要建立字段分级和生命周期。Token、密码、Session Cookie 和私钥属于禁止采集;身份证号、手机号、邮箱等个人数据需要脱敏、访问审计和更短 Retention;内部主机名、版本与错误类型通常可以进入常规可观测性系统。规则应同时落实在日志库配置、SDK Attribute Filter、Collector Processor 与后端权限中,形成纵深防御。
OTel Baggage 需要格外谨慎。Baggage 会沿业务请求传播到下游,适合携带少量用于关联的上下文,不适合放密码、完整用户资料或无限增长的调试字段。它不只会进入遥测系统,还可能跨越服务、团队甚至信任边界。把 user.email 放进 Baggage 也许能让查询方便一点,却可能让每个下游都收到本来无权知道的数据。
OTLP 入口还要防止资源耗尽。随机属性、过大的日志正文、过深的异常堆栈和包含大量 Span 的 Trace,都会消耗 Collector 与后端资源。入口应限制请求大小、并发和速率,并为不同租户设置配额。memory_limiter 只能保护 Collector 进程,无法限制单个租户的用量。缺少租户配额时,一个服务产生的大量调试数据就可能影响整个平台。
八、根据用途设置保留时间
数据的保留时间应根据用途确定,而不是简单地占满现有磁盘。指标、日志和链路数据的使用周期并不相同。
故障发生后,原始 Trace 和详细日志对排查问题很有帮助。过了几个月,团队通常更关心错误率、SLO 和容量趋势,很少再查询某次成功请求的完整 Span。因此,可以按用途分层保留:
- Metrics 保留 90 天到 1 年,用于趋势、容量和 SLO;高分辨率短保留,Recording Rules 结果长保留;
- 普通应用日志保留 7~30 天,审计日志按合规要求单独存储并控制访问;
- Traces 保留 3~14 天,错误和慢请求可通过 Sampling 获得更高覆盖;
- 冷数据按确有需要归档到更便宜的对象存储,不要因为「也许有一天会查」无限续费。
8.1 VictoriaMetrics 与 VictoriaLogs 的保留参数
VictoriaMetrics 单节点和 Cluster 的 vmstorage 默认保留约 1 个月数据,通过 -retentionPeriod 修改:
victoria-metrics \
-storageDataPath=/var/lib/victoria-metrics \
-retentionPeriod=90d \
-storage.minFreeDiskSpaceBytes=20GB-storage.minFreeDiskSpaceBytes 达到阈值后会进入只读保护,避免把文件系统彻底写满,但它不是容量规划替代品。VictoriaMetrics 的保留删除受分区与后台合并影响,磁盘占用不会在某个样本刚过期时立刻按字节下降。具体边界见单节点 Retention 文档。
VictoriaLogs 默认保留 7 天,可同时按时间和磁盘空间设置边界:
victoria-logs \
-storageDataPath=/var/lib/victoria-logs \
-retentionPeriod=14d \
-retention.maxDiskUsagePercent=80-retention.maxDiskUsagePercent 与绝对值参数 -retention.maxDiskSpaceUsageBytes 二选一,不能同时设置;时间 Retention 可以与其中一个磁盘边界并用。磁盘阈值是周期检查,写入尖峰期间仍可能越线,官方容量建议至少保留约 20% 空闲空间。VictoriaLogs 至少保留最近两个日期分区,因此最近两天本身就超过阈值时,磁盘使用也可能高于配置值。
8.2 根据实际数据估算容量
Metrics 的粗略容量可以从下面几个量开始:
磁盘需求 ≈ 每秒样本数
× 每样本压缩后字节数
× 保留秒数
× 副本数
× 合并与索引余量VictoriaMetrics 官方容量指南给出的经验是平均每个压缩样本约 1 Byte 或更低,但高 Churn 会降低压缩效率,索引还需要额外空间。因此更可靠的「每样本字节数」应由自己的 vm_data_size_bytes / vm_rows 计算,再为后台 Merge 保留至少约 20% 空间。参考公式见容量估算指南。
日志容量则要从每日接入的未压缩字节、压缩比、Stream 数、字段分布与索引开销实测。Trace 还受平均 Span 数、属性大小和采样比例影响。不要拿 Metrics 的压缩率推算 JSON 日志,也不要拿正常工作日的均值覆盖促销峰值。
成本优化的顺序通常应该是:
- 删除没有查询与告警用途的数据;
- 治理高基数与重复日志;
- 调整采集频率、Sampling 和聚合;
- 按信号设置不同 Retention;
- 最后才是继续堆 CPU、内存和磁盘。
如果一项指标从未用于仪表盘、告警、SLO 或临时查询,就应重新评估是否需要继续采集。
九、Grafana 中怎样把三种信号串起来
数据分别写入三个数据库后,还要在 Grafana 中配置关联。VictoriaMetrics 可以使用 Prometheus 数据源或专用插件,VictoriaLogs 使用 VictoriaLogs 插件,VictoriaTraces 可以通过兼容 Jaeger 的查询接口或对应插件接入。然后使用 Derived Fields 或 Correlations 配置跳转条件。
一个可操作的排障入口可以这样设计:
- Metrics Dashboard 按
service.name、环境和http.route展示 RED 指标,即 Rate、Errors、Duration; - 延迟直方图附带 Exemplar 时,点击样本跳到对应 Trace;
- Trace 视图按
trace_id跳到 VictoriaLogs,并把时间范围缩到 Trace 前后几十秒; - 日志页面保留回到 Metrics 的服务与时间条件;
- Dashboard Variables 使用相同的 Resource 命名,不再维护三套
app/service/job翻译。
如果 SDK 没有把 Trace Context 写入日志,Grafana 就无法按 trace_id 精确跳转。另一方面,也不能为了关联而把 trace_id 保存为指标标签,否则会产生大量时间序列。指标通过 Exemplar 关联少量 Trace 样本,日志则将 trace_id 保存为普通字段。三类数据不需要采用相同的索引方式。
仪表盘应按排障过程设计。使用者需要先确认受影响的服务和时间范围,再定位耗时的调用,最后查看相关日志。具体使用了哪个存储后端,不应影响这条查询路径。
十、从一条调用链开始改
改造时不必一次替换所有监控系统。可以先选择一条关键调用链,打通指标、链路和日志的查询,再逐步扩大接入范围。
10.1 第一阶段:统一身份与出口
选择一个非核心但流量真实的服务,接入 OTel SDK 或自动埋点,先固定:
service.name、Namespace、Environment、Version 的命名规则;- W3C Trace Context 的跨服务传播;
- OTLP 到 Collector 的唯一出口;
- 日志中的
trace_id与span_id; - 敏感字段与禁止成为 Metrics Label/Log Stream 的字段清单。
此时不必替换旧监控。Collector 可以把数据送到新旧两个后端做对照,但要注意双写会增加流量和成本,也可能因为两个 Exporter 速度不同产生不同丢失情况。
10.2 第二阶段:接入指标和告警
为服务建立 RED Dashboard 和基本 SLO,接入 VictoriaMetrics,观测 Active Series、Churn 和查询性能。先回答「数据是否可信、告警是否有用」,再扩大服务范围。
自动埋点产生的指标不必全部保留。应根据容量、错误率和延迟等实际用途选择需要的 Instrument。统一名称不能替代数据治理。
10.3 第三阶段:打通 Trace 与日志
选一条跨 3~5 个服务的关键调用链,检查父子关系、异步消息传播、错误状态和业务事件。把 Trace ID 注入日志,配置 Grafana 跳转。用几次真实故障或演练验证:是否能从告警在 5 分钟内定位到具体慢 Span 和相关日志。
完成这一步后再设置采样。团队需要先了解 Trace 的正常持续时间、Span 的到达延迟以及需要保留的错误类型,才能确定合适的采样规则。直接设置一个固定比例,可能会丢失重要数据。
10.4 第四阶段:统一治理和容量管理
当接入服务增多,再把共性固化为:
- SDK/Agent 版本矩阵与升级节奏;
- Collector 配置测试、灰度与回滚;
- 每服务基数、日志量、Trace 量预算;
- Schema/Semantic Conventions 变更检查;
- Retention、备份、恢复与故障演练;
- Collector 与后端自身的 SLO。
平台提供默认值,服务保留必要的覆盖能力。默认值应让大多数服务安全接入,而不是为了追求「一份配置走天下」把差异藏起来。
十一、总结
OpenTelemetry 统一了埋点接口、上下文传播、数据模型和传输协议,使业务代码不必依赖具体的存储后端。Collector 负责集中处理和转发数据,但不负责存储和查询。
自建时,可以用 VictoriaMetrics 保存指标、VictoriaLogs 保存日志,再根据需求选择 VictoriaTraces、Jaeger 或 Tempo 保存 Trace。这套方案可以从单节点开始部署,各组件的运维方式也比较接近。不过,高基数仍需在采集端治理;Tail Sampling 仍需按 Trace ID 路由;单节点即使定期备份,也无法提供不间断服务。
实施时可以先统一 service.name,把 trace_id 写入日志,再通过 Collector 将指标、日志和链路发送到相应后端。完成一条关键调用链的接入和查询后,再根据实际数据调整采样、基数限制、保留时间和集群规模。
