2. Loki 基础回顾:Loki 日志存储模型、LogQL 查询语言核心概念

在深入变更窗口告警路由与模板通信之前,必须重新夯实 Loki 的两大基石:日志存储模型与LogQL 查询语言。Loki 的设计哲学与 Prometheus 一脉相承——通过标签索引代替全文检索,从而在成本与性能之间取得极致平衡。本节将拆解其存储架构的核心抽象,并梳理 LogQL 中与告警路由最相关的查询模式。

2.1 Loki 日志存储模型:从流到块

Loki 不解析日志内容,而是将日志视为不可变的字节流,按标签(Labels)组织成日志流(Log Stream)。每个日志流由一组唯一的标签键值对标识,同一流内的日志按时间戳递增排列。

核心存储层次:
日志流(Stream) → Chunk(块) → Index(索引)
标签决定流归属,Chunk 是压缩后的日志数据块,Index 仅存储标签与 Chunk 的映射关系。

下表总结了存储模型中各层级的职责与关键属性:

层级 职责 关键属性 告警关联
日志流 标签组合的唯一标识 标签基数、流数量 告警规则按流聚合
Chunk 压缩存储日志行 压缩算法、大小、时间范围 影响查询扫描量
Index 标签 → Chunk 映射 倒排索引、布隆过滤器 决定查询速度
Ingester 内存中构建 Chunk Flush 策略、副本因子 影响实时告警延迟
SRE 视角: 告警路由的标签选择器必须与日志流的标签设计对齐。如果变更窗口的日志缺少 deploy_id 或 change_window 标签,LogQL 将无法高效过滤,导致查询扫描大量无关 Chunk。
Loki 日志存储模型:从应用到 Chunk 应用 / Pod 日志流 (Stream) Chunk 块 标签示例:{job="api-server", env="prod", deploy_id="v2.3.1", change_window="true"} 标签组合唯一确定一个日志流,所有日志行按时间顺序追加 Index(倒排索引:标签 → Chunk 列表) 查询路径:LogQL → 标签匹配 → Index 定位 Chunk → 解压扫描 → 返回结果

2.2 LogQL 查询语言核心概念

LogQL 是 Loki 的查询语言,语法上兼容 PromQL 的标签选择器,并扩展了日志行过滤与指标聚合能力。对于变更窗口告警,我们最关注两类查询:日志流选择器和指标查询(Metric Queries)。

2.2.1 日志流选择器(Log Stream Selector)

所有 LogQL 查询必须以日志流选择器开头,用于限定查询的日志流范围。语法与 Prometheus 标签选择器完全一致:

{job="api-server", env="prod", change_window="true"}

支持的操作符包括 =、!=、=~(正则匹配)、!~(正则排除)。

告警路由陷阱: 如果变更窗口的标签值包含特殊字符(如 deploy_id="v2.3.1-rc1"),正则选择器必须正确转义。建议在日志采集侧使用 结构化标签,避免在 LogQL 中做复杂正则匹配。

2.2.2 日志行过滤(Line Filter Expression)

在流选择器之后,可以使用管道操作符 | 对日志内容进行过滤:

{job="api-server", change_window="true"} |= "ERROR" | json

常用过滤操作符:

  • |= "pattern" — 包含子串
  • != "pattern" — 不包含子串
  • |~ "regex" — 正则匹配
  • !~ "regex" — 正则排除
  • | json — 解析 JSON 日志行,提取字段
  • | logfmt — 解析 logfmt 格式日志

2.2.3 指标查询(Metric Queries)—— 告警的核心

告警规则必须使用指标查询将日志事件转化为可聚合的时序数据。Loki 提供两类指标函数:

函数类别 函数 含义 告警场景示例
计数 rate() 每秒日志行速率 错误率突增
count_over_time() 指定时间窗口内的日志行总数 变更后错误总数阈值
标签值 avg_over_time() 提取数值字段的平均值 平均响应时间
max_over_time() 提取数值字段的最大值 峰值延迟告警
存在性 absent_over_time() 时间窗口内无日志则返回 1 变更后服务静默

一个完整的告警 LogQL 示例:

# 变更窗口内 5 分钟错误日志速率超过 10/s 触发告警
rate(
  {job="api-server", change_window="true"}
  |= "ERROR"
  | json
  [5m]
) > 10
性能建议: 在 rate() 或 count_over_time() 之前,尽量使用 |= 或 | json 提前过滤掉无关日志行。这能显著减少 Loki 需要扫描的 Chunk 数量,降低告警延迟。
LogQL 查询流程:从选择器到告警 1. 流选择器 {job="api", change="true"} 2. 行过滤 |= "ERROR" | json 3. 指标函数 rate(... [5m]) 4. 比较阈值 > 10 5. 告警触发 发送到 Alertmanager 告警路由关键点:标签在步骤 1 中确定流范围,步骤 3 的指标函数决定聚合方式 变更窗口标签(如 change_window="true")必须在步骤 1 中显式选择,否则告警会扫描全量日志

2.3 标签设计对告警路由的影响

Loki 的标签设计直接决定了告警路由的灵活性与性能。以下是变更窗口场景下的标签设计原则:

  • 基数控制: 避免将高基数字段(如 request_id、user_ip)设为标签,应放在日志内容中通过 | json 提取。
  • 窗口标识: 必须包含 change_window 或 deploy_id 等标签,使告警规则能精确限定变更范围。
  • 环境与服务: job、env、service 是标准标签,用于告警路由的粗粒度分组。
  • 结构化解析: 推荐在采集端使用 json 或 logfmt 解析,避免在 LogQL 中重复解析开销。
最佳实践: 在变更窗口期间,为所有相关 Pod 注入一个临时标签 change_window="true",并在变更结束后移除。告警规则只需在 LogQL 流选择器中加入该标签,即可实现变更期间告警的自动隔离。