3. Loki Operator 基础:Loki 架构简介、LokiStack CRD、日志采集与存储模型

本章是《PromOperator 日志索引治理》课程中从 Prometheus 指标监控转向日志治理的关键桥梁。Loki 作为 Grafana Labs 推出的日志聚合系统,专为 Kubernetes 环境设计,其核心设计哲学是“仅索引元数据,不全文索引日志内容”。理解 Loki 的架构、LokiStack CRD 以及日志采集与存储模型,是后续进行日志索引治理、成本优化和性能调优的前提。

3.1 Loki 核心架构与组件

Loki 采用微服务架构,主要组件包括 Distributor、Ingester、Querier、Query Frontend 和 Compactor。每个组件均可独立扩展,适应不同规模的日志处理需求。

架构设计要点:Loki 不全文索引日志内容,而是将日志流按标签(Labels)进行索引,日志内容以压缩块(Chunks)形式存储在对象存储中。这种设计大幅降低了存储成本和查询延迟,但要求用户合理设计标签策略。
Loki 核心架构组件与数据流 Promtail / 采集器 Distributor Ingester Querier Query Frontend 对象存储 (S3/GCS/MinIO) Compactor 日志流 转发/复制 写入 Chunk 读取 Chunk 查询分发 合并/去重 说明:采集器(Promtail/Alloy/Docker)将日志流推送至 Distributor,Distributor 校验并转发至 Ingester;Ingester 将日志压缩为 Chunk 写入对象存储;Querier 从对象存储和 Ingester 读取数据响应查询;Compactor 定期合并 Chunk 并清理重复数据。

3.2 LokiStack CRD:Operator 的核心抽象

Loki Operator 引入 LokiStack 自定义资源定义(CRD),作为部署和管理 Loki 集群的声明式 API。通过 LokiStack CRD,用户可以一站式定义 Loki 集群的拓扑、存储后端、认证授权、租户模式等配置。

字段路径 类型 说明 治理关注点
spec.size 枚举 集群规模:1x.extra-small ~ 5x.large 影响副本数、资源配额、索引分片数
spec.storage 对象 定义对象存储后端类型及连接信息 存储成本、Chunk 生命周期
spec.tenants 对象 多租户模式配置(openshift/static/disabled) 租户隔离、索引权限治理
spec.limits 对象 全局/每租户的 ingestion 和 query 限制 日志丢弃策略、速率限制、标签数量上限
spec.replicationFactor 整数 Ingester 数据复制因子,默认 3 影响存储冗余和索引一致性
治理提示:在定义 LokiStack 时,spec.limits.ingestionLimits.maxLabelsPerSeries 和 maxLabelNameLength 是索引治理的关键参数。建议将 maxLabelsPerSeries 设置为 15~20,避免因标签爆炸导致索引膨胀和查询性能下降。

以下是一个典型的 LokiStack 资源定义示例,展示了如何配置一个中等规模、使用 MinIO 作为存储后端的 Loki 集群:

apiVersion: loki.grafana.com/v1
kind: LokiStack
metadata:
  name: example-lokistack
  namespace: loki-system
spec:
  size: 1x.medium
  storage:
    schemas:
      - version: v13
        effectiveDate: "2024-01-01"
    secret:
      name: loki-storage-secret
      type: s3
  storageClassName: standard
  tenants:
    mode: openshift
  limits:
    global:
      ingestionLimits:
        maxGlobalStreamsPerTenant: 50000
        maxLabelsPerSeries: 20
        maxLabelNameLength: 1024
      queryLimits:
        maxEntriesLimitPerQuery: 5000
    overrides:
      tenant-a:
        ingestionLimits:
          maxGlobalStreamsPerTenant: 100000
  replicationFactor: 3

3.3 日志采集模型:从 Pod 到 Loki 的管道

在 Kubernetes 环境中,日志采集通常由 Promtail、Grafana Alloy 或 Vector 等采集器完成。采集器通过以下步骤将日志送入 Loki:

  1. 发现目标:采集器通过 Kubernetes API 监听 Pod 变化,或通过文件系统发现日志文件。
  2. 提取标签:从 Pod 元数据(namespace、pod name、container name)或文件路径中提取标签。
  3. 日志处理:支持多行合并、正则提取、时间戳解析、静态标签添加等处理。
  4. 推送至 Distributor:将日志流以 gRPC 或 HTTP 协议推送到 Loki Distributor。
日志采集与推送管道 Pod A stdout/stderr Promtail 标签提取+处理 Distributor 校验+转发 Ingester Chunk 写入 日志流 gRPC/HTTP 转发 标签提取示例: {__meta_kubernetes_namespace="prod", __meta_kubernetes_pod_name="api-7d8f9c", __meta_kubernetes_container_name="nginx", job="prod/nginx"} 治理要点:标签基数控制——每个唯一的标签组合构成一个日志流(Stream)。如果 Pod 名称作为标签,每次 Pod 滚动都会创建新流,导致索引膨胀。建议将 Pod 名称等高频变化字段作为结构化元数据(Structured Metadata)而非标签。 Loki 2.9+ 支持结构化元数据,可将高基数字段存储在日志内容中而非索引中,显著降低索引开销。

3.4 存储模型:Chunk、Index 与 Object Store

Loki 的存储模型分为两个核心部分:Index(索引)和 Chunk(数据块)。理解这两者的关系是日志索引治理的基础。

存储组件 存储内容 存储后端 治理关注点
Index 标签键值对 → 时间范围 → Chunk 指针 对象存储(v13+)或 BoltDB(已弃用) 标签基数直接影响 Index 大小和查询性能
Chunk 压缩后的日志内容(通常为 gzip/lz4/snappy) 对象存储(S3/GCS/MinIO/Azure Blob) Chunk 大小、压缩率、生命周期策略
Structured Metadata 日志中的结构化字段(如 trace_id, user_id) 与 Chunk 一同存储,不参与索引 替代高基数标签,降低索引膨胀
常见陷阱:在 v13 之前的存储模式中,Index 使用 BoltDB 并存储在本地磁盘,导致扩展性受限。v13 模式将 Index 也写入对象存储,实现了真正的无状态扩展。升级到 v13 模式是索引治理的前提条件之一。

Chunk 的写入和合并过程直接影响存储效率和查询性能:

  • Chunk 写入:Ingester 将一段时间内的日志流压缩为一个 Chunk,默认每 30 分钟或达到 1.5MB 时刷新到对象存储。
  • Chunk 合并:Compactor 定期扫描对象存储中的 Chunk,将多个小 Chunk 合并为更大的 Chunk(默认目标大小 1.5MB),并清理重复数据。
  • Chunk 生命周期:通过 spec.storage.retention 配置日志保留时间,Compactor 会删除过期的 Chunk 和对应的 Index 条目。
# 查看当前 Loki 集群的 Chunk 和 Index 状态(通过 LogCLI 或 Grafana Explore)
logcli series --analyze-labels --since=1h

# 输出示例:
# Label: namespace, 值数量: 12, 日志流数: 45
# Label: pod, 值数量: 230, 日志流数: 230
# Label: container, 值数量: 15, 日志流数: 45

# 分析:pod 标签导致每个 Pod 对应一个日志流,建议将 pod 标签改为结构化元数据。

3.5 本章小结