Tempo接入云原生日志平台变更门禁后的权限边界和审计链路 - 第2节

2.1 Tempo 架构概览:从数据采集到查询的端到端链路

Tempo 是 Grafana 实验室推出的高可用、低成本、只追加的分布式追踪后端。其核心设计哲学是“将存储与索引解耦”,通过对象存储(如 S3、GCS、Azure Blob)作为主要数据载体,并依赖外部组件(如 Loki、Prometheus)完成索引与查询加速。以下架构图展示了 Tempo 的核心组件与数据流。

架构要点: Tempo 不构建自己的全文索引,而是利用 Trace ID 进行块查找,大幅降低写入路径的 CPU 与内存开销。
Tempo 核心架构与数据流 Tempo 核心架构与数据流 应用 / SDK Distributor Ingester 对象存储 Querier Query Frontend 说明: 写入路径:SDK → Distributor → Ingester → 对象存储(批量 flush)。 查询路径:Query Frontend → Querier → 对象存储(按 Trace ID 直接读取块)。

2.1.1 核心组件职责

组件职责关键特性
Distributor接收来自 SDK 的 span 批次,验证、采样、哈希分片后转发给 Ingester。无状态,水平扩展;支持概率/速率采样。
Ingester将 span 数据缓冲在内存中,按 Trace ID 构建块(block),定期 flush 到对象存储。有状态,建议使用持久化磁盘;块大小可配置(默认 512MB)。
Querier接收查询请求,从对象存储中拉取对应块,合并后返回完整 Trace。无状态;支持缓存(memcached/redis)加速块查找。
Query Frontend查询路由与聚合层,支持分片查询、结果合并、缓存。可选组件;大幅提升高并发查询性能。
对象存储持久化存储所有 trace 块,以及索引元数据(如 bloom filter)。推荐 S3/GCS;成本低,无限扩展。
架构设计启示: Tempo 的“写放大”极低——写入时只做一次校验和分片,不做索引构建。这使得单机写入吞吐可达 10万+ spans/s,非常适合云原生场景下的高并发日志平台。

2.2 数据模型:Span、Trace 与块结构

Tempo 的数据模型严格遵循 OpenTelemetry (OTel) 规范,核心概念包括 Span、Trace 和 Block。理解这三层结构是掌握查询语言和权限边界的基础。

2.2.1 Span 与 Trace 的层次关系

  • Span:一次操作的最小单元,包含操作名称、开始/结束时间、属性(Attributes)、事件(Events)、状态(Status)以及 Trace ID 和 Span ID。
  • Trace:由一组具有相同 Trace ID 的 Span 组成,通过 Parent Span ID 形成有向无环图(DAG),代表一次完整的请求链路。
  • Block:Tempo 内部存储单元,包含多个 Trace 的压缩数据。每个 Block 附带一个 Bloom Filter 和 索引,用于快速判断 Trace ID 是否存在。
Span、Trace 与 Block 层次结构 Span、Trace 与 Block 层次结构 Block(对象存储中的压缩文件) Trace A (Trace ID: abc123) Span: /api/order Span: /db/query Span: /cache/get Span: /svc/pay Trace B (Trace ID: def456) Span: /api/login Span: /auth/check Span: //verify Span: /user/info 每个 Block 包含多个 Trace,每个 Trace 包含多个 Span。Bloom Filter 用于快速判断 Trace ID 是否在该 Block 中。

2.2.2 关键数据字段与权限边界

在云原生日志平台中,Tempo 数据模型中的字段直接决定了 权限边界 的粒度。以下表格列出了与门禁控制最相关的字段。

字段类型权限控制意义
resource.service.name字符串标识微服务名称,常用于按服务划分访问权限。
span.attributes.environment字符串标识环境(prod/staging/dev),用于环境级隔离。
span.attributes.team字符串标识所属团队,用于团队级 RBAC。
trace_id16字节全局唯一标识,查询入口;权限校验时需验证用户是否有权访问该 Trace 所属的服务。
span.status.code整数状态码(OK=1, ERROR=2);可用于审计链路中过滤异常 span。
权限边界设计要点: 在变更门禁场景中,必须确保用户只能查询到其有权访问的 resource.service.name 和 span.attributes.environment 组合。Tempo 本身不提供行级权限,需要在 Query Frontend 或代理层注入过滤条件。

2.3 查询语言基础:TraceQL 入门

TraceQL 是 Tempo 的原生查询语言,语法类似 PromQL 和 LogQL,但专门针对 Trace 结构设计。它允许用户通过 Span 属性、资源属性 和 时间范围 来筛选 Trace。

2.3.1 基本查询结构

# 查询所有 service.name 为 "order-service" 的 Trace
{ resource.service.name = "order-service" }

# 查询包含错误状态 span 的 Trace
{ span.status.code = 2 }

# 组合条件:查询 order-service 中耗时超过 1s 的 Trace
{ resource.service.name = "order-service" && span.duration > 1s }

# 使用正则:查询 service 名称以 "pay" 开头的 Trace
{ resource.service.name =~ "pay.*" }

2.3.2 查询结果与审计链路

TraceQL 查询结果返回一组匹配的 Trace 列表。每个 Trace 包含完整的 Span 树。在审计链路中,我们通常关注以下指标:

  • Trace 数量:匹配条件的请求总数。
  • 错误率:包含 span.status.code = 2 的 Trace 占比。
  • P95/P99 延迟:通过 span.duration 计算。
  • 服务依赖图:通过 Trace 中的 Parent Span ID 关系生成。
审计场景示例: 当变更门禁触发时,自动执行 TraceQL 查询:{ resource.service.name = "order-service" && span.duration > 5s },若返回 Trace 数量超过阈值(如 10 个),则判定为“性能劣化”,阻止变更并通知 SRE。

2.3.3 TraceQL 与权限过滤的结合

在云原生日志平台中,TraceQL 查询必须经过 权限注入。例如,用户 A 只允许访问 team = "payment" 的 Trace,则实际执行的查询会被改写为:

# 用户原始查询
{ span.duration > 2s }

# 权限注入后的实际查询
{ span.duration > 2s && resource.service.name =~ "payment-.*" }

这种改写发生在 Query Frontend 的 权限中间件 中,确保审计链路中记录的是改写后的完整查询,而非用户原始输入。

学习建议: 建议在本地部署 Tempo 并导入示例数据,使用 Grafana Explore 的 TraceQL 编辑器进行交互式练习。重点关注 resource 和 span 两个命名空间下的常用属性。