Loki 日志系统基础:架构、查询与标签设计

在构建日志驱动的弹性伸缩健康检查之前,必须先理解 Loki 的核心设计哲学。Loki 与 Prometheus 师出同门,其核心理念是 仅索引元数据(标签),不全文索引日志内容。这种设计使得 Loki 在存储成本和查询性能上远低于 Elasticsearch 等传统全文搜索引擎,特别适合 Kubernetes 环境下的海量日志处理。

2.1 Loki 架构简介

Loki 采用微服务架构,主要组件包括 Distributor、Ingester、Querier 和 Compactor。日志数据以 流(Stream) 为单位组织,每个流由一组唯一的标签(Labels)标识。下图展示了 Loki 的核心写入与查询路径:

Loki 核心架构与数据流 Loki 核心架构:写入与查询路径 Promtail / Agent Distributor Ingester 对象存储 Querier Grafana 关键设计原则: • 写入路径:Agent → Distributor(校验/哈希)→ Ingester(内存缓存/Batch)→ 对象存储 • 查询路径:Grafana → Querier(合并结果)→ Ingester(热数据)+ 对象存储(冷数据) • 标签是数据分片和查询的唯一入口,日志内容不建索引

架构要点:

  • Distributor:接收日志流,验证标签合法性,通过一致性哈希将流分发给对应的 Ingester。
  • Ingester:负责将日志数据缓存在内存中,并定期刷写到对象存储(如 S3、GCS、MinIO)。
  • Querier:处理 LogQL 查询,从 Ingester(热数据)和对象存储(冷数据)中拉取日志并去重合并。
  • Compactor:后台合并小文件、清理过期数据、执行索引重排,优化查询性能。

2.2 LogQL 查询语言入门

LogQL 是 Loki 的查询语言,语法与 PromQL 高度相似。它分为两个核心部分:日志流选择器 和 日志管道。流选择器用于过滤出特定的日志流,管道则对日志内容进行解析、过滤和计算。

2.2.1 日志流选择器

流选择器使用花括号 {} 包裹标签键值对,支持 =、!=、=~(正则匹配)、!~(正则排除)四种操作符。

{app="nginx", environment="production"}
{job=~"api-server.*", namespace!="default"}

2.2.2 日志管道操作

管道操作符以 | 连接,对选中的日志行进行逐行处理。常用操作符包括:

操作符 语法示例 说明
line_format | line_format "{{.status}}" 重新格式化日志行输出
logfmt | logfmt 解析 logfmt 格式的日志为字段
json | json 解析 JSON 格式的日志为字段
regexp | regexp "(?P<ip>\S+)" 使用正则提取命名捕获组
pattern | pattern "<ip> - -" 基于模式匹配快速提取字段
unwrap | unwrap duration 提取数值字段用于指标计算

2.2.3 指标查询(Metric Queries)

LogQL 支持将日志行转换为 Prometheus 风格的指标,用于告警和可视化。常用函数包括 rate、count_over_time、sum by 等。

# 计算过去5分钟内,每秒钟的 5xx 错误日志数
rate({app="nginx"} |= "5xx" [5m])

# 按 pod 分组统计过去1小时内的错误日志总量
sum by (pod) (count_over_time({namespace="production"} |= "ERROR" [1h]))

实战提示:在弹性伸缩场景中,rate 和 count_over_time 是最常用的指标函数。例如,通过 rate({app="api-server"} |= "timeout" [5m]) > 0.1 可以触发基于超时日志的自动扩容。

2.3 日志标签设计原则

标签是 Loki 性能的命脉。标签基数(Cardinality)过高会导致 Ingester 内存暴涨、查询变慢。设计标签时需遵循以下原则:

日志标签设计原则 日志标签设计原则:高基数 vs 低基数 ✅ 低基数标签(推荐) • namespace:固定值(如 production) • app:服务名(如 api-server) • job:采集任务名 • level:日志级别(info/error) • cluster:集群标识 ❌ 高基数标签(避免) • pod:Pod 名称(频繁重建) • ip:客户端 IP(百万级) • user_id:用户 ID(无限增长) • trace_id:请求追踪 ID • request_id:每次请求唯一 💡 高基数信息应放在日志行内容中,通过 LogQL 的 | json 或 | regexp 解析,而非作为标签。

2.3.1 标签设计黄金法则

原则 说明 示例
低基数优先 标签值总数应控制在 10,000 以内,理想情况 < 1,000 namespace、app、cluster
可枚举性 标签值应来自一个可预见的有限集合 level(info/warn/error)
稳定性 标签值不应随 Pod 重建或请求变化 避免使用 pod、instance
查询驱动 只将频繁用于过滤的字段设为标签 environment、service
避免动态标签 不要在日志采集时动态生成标签 禁止 user_id、session_id

2.3.2 弹性伸缩场景的标签推荐

在健康检查和弹性伸缩场景中,建议使用以下标签结构:

{namespace="production", app="api-server", cluster="us-east-1"}

将 pod、container 等信息保留在日志行内,通过 LogQL 的 | json 解析。例如:

{namespace="production", app="api-server"} | json | pod_name = "api-server-7d8f9c"

⚠️ 常见陷阱:不要将 pod 作为标签!Kubernetes 中 Pod 名称频繁变化,会导致标签基数爆炸。正确做法是将 Pod 名称放在日志 JSON 字段中,需要时通过 | json 过滤。

2.4 本章小结