2. Loki 基础架构:多租户、日志流、Ingester、Querier、Distributor 组件详解

在生产环境中,Loki 并非单进程运行,而是由多个职责分明的组件构成一个水平可扩展的日志系统。理解这些组件及其协作方式,是构建可靠 CI/CD 流水线 SLI 监控与故障排查能力的前提。本章将深入剖析 Loki 的核心架构,重点讲解多租户隔离、日志流模型以及 Distributor、Ingester、Querier 三大关键组件。

2.1 多租户与日志流模型

Loki 从设计之初就原生支持多租户。每个租户拥有独立的日志存储空间和查询视图,租户之间完全隔离。在 CI/CD 场景中,通常将每个微服务团队或每条流水线视为一个租户,从而实现权限与数据的逻辑分离。

关键概念: 租户通过 HTTP 请求头 X-Scope-OrgID 标识。如果未配置多租户,Loki 会使用默认的 fake 租户。

日志流(Log Stream) 是 Loki 中最核心的数据组织单元。一组具有完全相同标签(Label)组合的日志条目构成一个日志流。标签是键值对,用于索引和快速检索日志。

概念 说明 CI/CD 场景示例
租户 数据隔离边界,每个租户拥有独立的索引和数据 team-alpha、pipeline-build
日志流 具有相同标签集合的日志序列 {job="deploy", env="staging", service="api-gateway"}
标签 键值对,用于标识和查询日志流 pipeline_id、stage、commit_sha
日志条目 单行日志内容 + 时间戳 + 元数据 2025-03-15T10:00:00Z [INFO] Build started
最佳实践: 在 CI/CD 流水线中,建议将 pipeline_id、stage、job_name 作为标签。标签基数(Cardinality)不宜过高,否则会导致索引膨胀。避免将 request_id、trace_id 等高频变化的值作为标签。
多租户与日志流模型 租户 A:team-alpha 日志流 1 job="deploy" env="staging" service="api" 日志流 2 job="build" pipeline_id="123" stage="compile" 日志条目(时间戳 + 内容) 2025-03-15T10:00:00Z [INFO] Build started 2025-03-15T10:00:05Z [INFO] Compiling... 2025-03-15T10:00:10Z [ERROR] Compilation failed 租户 B:pipeline-build 日志流 3 job="test" env="prod" service="db" 日志流 4 job="deploy" pipeline_id="456" stage="rollout" 租户隔离边界(X-Scope-OrgID)

2.2 Distributor:日志写入的入口网关

Distributor 是 Loki 接收日志写入请求的第一站。它负责验证、转发和负载均衡,确保日志数据被正确路由到对应的 Ingester 实例。

2.2.1 核心职责

  • 请求验证: 检查日志流标签格式、时间戳合法性、租户身份。
  • 数据分片: 根据标签哈希将日志流分配到特定的 Ingester 实例(一致性哈希)。
  • 速率限制: 防止单个租户或日志流过度写入,保护后端组件。
  • 预聚合(可选): 在写入前对日志条目进行简单校验或预处理。

2.2.2 工作流程

  1. 客户端(Promtail、Fluentd 等)通过 HTTP API 推送日志。
  2. Distributor 解析请求,提取租户 ID 和日志流标签。
  3. 使用一致性哈希环计算目标 Ingester 实例。
  4. 将日志条目转发至目标 Ingester 的 gRPC 接口。
  5. 如果目标 Ingester 不可用,Distributor 会重试或返回错误。
故障排查要点: 如果日志写入失败,首先检查 Distributor 的日志和指标。常见问题包括:标签基数过高导致哈希冲突、速率限制触发、Ingester 实例宕机。监控 loki_distributor_ingester_appends_total 和 loki_distributor_ingester_append_failures_total 指标。

2.3 Ingester:日志数据的临时存储与持久化

Ingester 是 Loki 中负责接收、缓存和持久化日志数据的核心组件。它维护着内存中的日志流数据,并定期将数据刷写到对象存储(如 S3、GCS、MinIO)。

2.3.1 内存中的日志流管理

每个 Ingester 实例维护一个内存中的日志流映射表。当 Distributor 推送日志时,Ingester 将日志条目追加到对应日志流的缓存中。日志流在内存中按时间顺序排列,形成 Chunk(数据块)。

Ingester 状态 说明 对查询的影响
PENDING 刚启动,正在等待加入哈希环 不接收写入,不响应查询
JOINING 正在从其他 Ingester 转移数据 不接收写入,不响应查询
ACTIVE 正常运行,接收写入和查询 正常服务
LEAVING 正在退出,将数据转移给其他 Ingester 不接收写入,仍响应查询

2.3.2 Chunk 与持久化

  • Chunk: 日志数据的最小存储单元,包含多个日志条目和元数据。默认压缩后大小约为 1.5 MB。
  • Flush 策略: 当 Chunk 达到最大大小或最大时间窗口(默认 1 小时)时,Ingester 将其刷写到对象存储。
  • 数据可靠性: 在刷写前,数据仅存在于内存中。如果 Ingester 崩溃,未刷写的数据会丢失。建议部署多个副本并启用 WAL(Write-Ahead Log)来增强可靠性。
性能调优: 监控 loki_ingester_memory_streams 和 loki_ingester_chunks_flushed_total。如果内存使用过高,可以调整 ingester.max-chunk-age 或 ingester.chunk-target-size 参数。

2.4 Querier:日志查询的执行引擎

Querier 负责处理用户的 LogQL 查询请求。它需要从多个数据源(Ingester 内存中的最新数据 + 对象存储中的历史数据)合并结果,返回给客户端。

2.4.1 查询执行流程

  1. 接收 LogQL 查询请求,解析标签选择器和时间范围。
  2. 根据标签匹配找到对应的日志流。
  3. 并行查询所有 Ingester 实例,获取内存中的最新数据。
  4. 查询对象存储,获取已持久化的历史 Chunk 数据。
  5. 对两部分结果进行合并、去重、排序,返回最终结果。

2.4.2 查询性能优化

优化手段 说明 适用场景
标签索引 使用高基数的标签缩小查询范围 所有查询
时间范围限制 尽量缩小查询的时间窗口 历史日志查询
并行查询 Querier 自动并行查询多个 Ingester 和存储 大规模集群
缓存 启用结果缓存和 Chunk 缓存 重复查询、仪表盘
故障排查要点: 查询慢或超时,检查 loki_querier_query_duration_seconds 指标。常见原因:标签基数过高导致索引扫描慢、Ingester 内存中数据过多、对象存储延迟高。使用 topk 和 rate 函数时注意数据量。
Loki 核心组件协作流程 客户端(Promtail) HTTP Push Distributor gRPC 转发 Ingester Ingester 内部状态 内存 Chunk 缓存 WAL 日志(可选) Flush 对象存储(S3/GCS) 内存数据查询 Querier 历史数据查询

2.5 组件间通信与数据一致性

Loki 组件之间通过 gRPC 进行高效通信,并使用 一致性哈希环(Consistent Hash Ring) 来管理 Ingester 实例的发现和数据分布。哈希环存储在 etcd 或 Consul 中,所有组件共享该环。

  • Distributor 视角: 根据日志流标签的哈希值,在环上找到负责该日志流的 Ingester 实例。
  • Querier 视角: 查询时遍历所有 Ingester 实例,获取内存中的最新数据。
  • Ingester 视角: 注册到哈希环,并在状态变化时更新环信息。
数据一致性保证: Loki 采用最终一致性模型。写入数据后,立即可以在写入的 Ingester 上查询到;但其他 Querier 可能需要短暂延迟才能从对象存储中读取到刚刷写的数据。对于 CI/CD 流水线监控,这种延迟通常可以接受(秒级)。

2.6 组件故障场景与排查思路

故障场景 影响 排查方向
Distributor 不可用 日志写入失败,客户端返回 5xx 检查 Distributor 进程、网络、负载均衡器
Ingester 崩溃 未刷写的数据丢失,写入延迟增加 检查 Ingester 内存、WAL 状态、哈希环
Querier 查询超时 用户查询缓慢或失败 检查查询复杂度、Ingester 负载、存储延迟
哈希环分裂 数据分布不均,部分日志流不可用 检查 etcd/Consul 健康状态、网络分区
SRE 建议: 在 CI/CD 流水线中,为 Distributor 和 Ingester 配置适当的速率限制和资源配额。使用 loki_distributor_ingester_clients 和 loki_ingester_flush_queue_length 指标作为 SLI 的组成部分,提前发现写入瓶颈。