3. Loki 日志采集架构:Promtail 配置、日志流(Stream)概念、标签(Label)与元数据(Metadata)的区别

在构建弹性伸缩健康检查的告警降噪体系时,理解 Loki 的日志采集架构是基础。错误的标签设计会导致日志流膨胀、查询效率低下,进而引发告警风暴。本节将深入剖析 Promtail 的配置核心、日志流(Stream)的构成原理,以及标签(Label)与元数据(Metadata)的本质区别。

3.1 Promtail:日志采集的“守门员”

Promtail 是 Loki 生态中的日志采集代理,负责从目标节点抓取日志、添加标签,并将日志条目推送到 Loki。在弹性伸缩场景中,Promtail 通常以 DaemonSet 形式部署在每个 Kubernetes 节点上,确保每个 Pod 的日志都能被采集。

Promtail 的核心配置包含三个关键阶段:发现目标(scrape_configs)、处理管道(pipeline)、标签注入(relabel_configs)。

关键配置示例: 以下是一个针对 Kubernetes Pod 日志的 Promtail 配置片段,展示了如何通过 relabel 控制标签的最终形态。
scrape_configs:
  - job_name: kubernetes-pods
    kubernetes_sd_configs:
      - role: pod
    relabel_configs:
      # 保留原始 Pod 名称作为临时标签
      - source_labels: [__meta_kubernetes_pod_name]
        target_label: __tmp_pod_name
      # 将 namespace 映射为正式标签
      - source_labels: [__meta_kubernetes_namespace]
        target_label: namespace
      # 将容器名称映射为正式标签
      - source_labels: [__meta_kubernetes_pod_container_name]
        target_label: container
      # 丢弃高基数的 Pod 名称标签,避免流爆炸
      - action: drop
        regex: .*
        source_labels: [__tmp_pod_name]
    pipeline_stages:
      - regex:
          expression: '^(?P<level>(INFO|WARN|ERROR|DEBUG))\s'
      - labels:
          level:
      - timestamp:
          source: time
          format: RFC3339
最佳实践: 在弹性伸缩场景中,永远不要将 Pod 名称、IP 地址或实例 ID 作为正式标签。这些字段的基数与副本数成正比,会导致 Loki 索引膨胀和查询性能下降。应使用 namespace、app、service 等低基数标签。

3.2 日志流(Stream):标签的唯一组合

在 Loki 中,日志流(Stream) 是由一组完全相同的标签(Label)组合唯一标识的日志序列。每个日志条目必须属于且仅属于一个流。流的数量直接决定了 Loki 的索引大小和写入性能。

流的计算公式为:

Stream 数量 = 标签1的基数 × 标签2的基数 × ... × 标签N的基数

例如,如果使用 {namespace, app, pod_name} 三个标签,且 namespace 有 5 个,app 有 10 个,pod_name 有 100 个(弹性伸缩峰值),则最大流数量为 5 × 10 × 100 = 5000 个流。如果去掉 pod_name,流数量骤降至 50 个。

流爆炸风险: 当弹性伸缩触发大量 Pod 创建和销毁时,高基数标签会导致流数量急剧增加。Loki 的 ingester 需要为每个流维护内存中的日志缓冲区,过多的流会耗尽内存并触发 OOM,同时导致查询时需要扫描大量空流,严重影响告警规则的执行效率。

下图展示了从日志采集到流形成的完整链路:

日志流(Stream)形成链路 日志源 Pod / 文件 / Stdout Promtail 采集 + 处理管道 标签组合 {k1=v1, k2=v2} 日志流 Stream 流爆炸条件: 高基数标签(如 pod_name、instance_id) × 弹性伸缩副本数 → 流数量指数级增长 后果:Loki ingester 内存耗尽 → 写入失败 → 告警规则无法查询到最新日志 标签(Label) • 参与索引,用于流标识 • 基数必须低(建议 < 100) • 用于查询过滤和告警分组 • 示例:namespace, app, service 元数据(Metadata) • 不参与索引,仅作为日志内容 • 基数无限制,可包含高基数信息 • 通过 LogQL 内容过滤访问 • 示例:pod_name, request_id, trace_id

3.3 标签(Label)与元数据(Metadata)的本质区别

这是 Loki 使用中最容易混淆的概念,也是告警降噪的关键决策点。标签和元数据在 Loki 架构中扮演着完全不同的角色:

对比维度 标签(Label) 元数据(Metadata)
存储位置 索引(Index) 日志内容(Log Entry)
查询方式 {label="value"} 流选择器 |= "keyword" 内容过滤
基数要求 低基数(建议 < 100 个唯一值) 无限制(可包含高基数数据)
对性能影响 影响索引大小和写入吞吐 影响查询时的内容扫描
典型用途 环境、服务名、集群、级别 Pod 名称、请求 ID、错误堆栈
告警场景 用于分组和聚合告警 用于告警详情和上下文
核心原则: 在弹性伸缩健康检查中,标签用于“定位流”,元数据用于“描述日志”。将高基数信息(如 Pod 名称、IP 地址)作为元数据保留在日志行中,通过 LogQL 的 |~ 或 |= 操作符进行过滤,而不是将其提升为标签。

3.4 标签设计决策树

在配置 Promtail 时,可以使用以下决策树来判断一个字段应该作为标签还是元数据:

  1. 该字段是否用于告警分组? 是 → 考虑作为标签;否 → 进入下一步。
  2. 该字段的唯一值数量是否超过 100? 是 → 作为元数据;否 → 进入下一步。
  3. 该字段是否会在查询中被频繁用作流选择器? 是 → 作为标签;否 → 作为元数据。
  4. 该字段是否随弹性伸缩动态变化? 是 → 强烈建议作为元数据;否 → 可考虑作为标签。
实战建议: 对于 Kubernetes 环境,推荐的最小标签集为 {namespace, app, container}。如果需要区分不同集群,可增加 cluster 标签。所有 Pod 级别的动态信息(名称、节点、IP)都应通过 Promtail 的 pipeline 阶段提取为元数据,或直接保留在原始日志行中。

通过严格区分标签与元数据,可以有效控制日志流数量,确保 Loki 在弹性伸缩场景下的写入和查询性能,为后续的告警降噪工程奠定坚实的基础。