3. Loki 基础:日志聚合系统架构、LogQL 查询语言与标签索引机制
Loki 是 Grafana Labs 受 Prometheus 启发设计的一款水平可扩展、高可用、多租户的日志聚合系统。与 Elasticsearch 等全文索引方案不同,Loki 的核心设计哲学是仅对日志元数据(标签)建立索引,而不对日志内容建立全文索引。这一设计大幅降低了存储成本和运维复杂度,使其在云原生环境中成为日志可观测性的首选方案。
3.1 Loki 核心架构与组件拓扑
Loki 采用微服务架构,各组件可独立扩展。其数据流遵循 写入路径 与 查询路径 两条主线。
架构要点:Loki 将日志数据压缩存储于对象存储(如 S3、GCS、MinIO)中,内存中仅维护索引与最近写入的日志块(Chunk),以此实现低成本与高性能的平衡。
3.1.1 核心组件职责
| 组件 | 职责 | 扩展性 |
|---|---|---|
| Distributor | 接收日志写入请求,验证数据合法性,基于哈希环将日志流分发至对应 Ingester | 无状态,可水平扩展 |
| Ingester | 接收日志并构建 Chunk(压缩块),将 Chunk 刷入对象存储;维护内存中的最近日志 | 有状态,通过一致性哈希分片 |
| Querier | 处理 LogQL 查询,从 Ingester(热数据)和对象存储(冷数据)中获取日志 | 无状态,可水平扩展 |
| Query Frontend | 查询缓存、拆分与合并;将大查询拆分为小查询并行执行 | 无状态,可水平扩展 |
| Index Gateway | 管理标签索引,加速查询时的标签匹配 | 有状态,可分区 |
| Compactor | 合并与重排对象存储中的索引和 Chunk,提升查询效率 | 无状态,单实例运行 |
3.2 LogQL 查询语言
LogQL 是 Loki 的查询语言,语法受 PromQL 启发,但专门针对日志数据设计。LogQL 查询由 日志流选择器 和 日志管道 两部分组成。
3.2.1 基本查询结构
{label="value"} | logfmt | line_format "{{.message}}" | unwrap duration | rate()
查询从左至右执行:
- 日志流选择器(花括号内):通过标签匹配筛选日志流
- 管道操作符(竖线后):对匹配的日志行进行过滤、解析、格式化、计算
3.2.2 常用管道操作符
| 操作符 | 用途 | 示例 |
|---|---|---|
|= |
行包含(字符串匹配) | {job="api"} |= "error" |
!= |
行不包含 | {job="api"} != "debug" |
|~ |
行匹配正则表达式 | {job="api"} |~ "5[0-9]{2}" |
logfmt |
解析 logfmt 格式日志为结构化字段 | {job="api"} | logfmt |
json |
解析 JSON 格式日志为结构化字段 | {job="api"} | json |
line_format |
重写日志行内容(支持模板语法) | | line_format "{{.status}}" |
unwrap |
提取数值字段用于指标计算 | | unwrap duration |
rate() |
计算日志速率(每秒条数) | rate({job="api"}[5m]) |
实战案例:查询最近 1 小时内,API 服务中状态码为 500 的日志,并提取响应时间字段计算 P99 延迟。
quantile_over_time(0.99,
{job="api"} |= "500" | logfmt | unwrap response_time_ms
[1h]
)
3.3 标签索引机制
Loki 的索引机制是其性能与成本优势的核心。与 Elasticsearch 对日志全文建立倒排索引不同,Loki 仅对标签(Labels)建立索引,日志内容以压缩块形式存储,不参与索引。
3.3.1 标签设计原则
- 高基数标签需谨慎:如
user_id、request_id、ip等取值过多的标签会导致索引膨胀,影响性能。建议将高基数信息放入日志内容而非标签。 - 推荐的低基数标签:
job、namespace、pod、container、level、service。 - 标签命名规范:使用小写字母、下划线,避免特殊字符。推荐遵循 Prometheus 标签命名惯例。
3.3.2 索引查询流程
- 标签匹配:查询中的标签选择器(如
{job="api", level="error"})在倒排索引中查找匹配的 Chunk 列表 - Chunk 定位:取多个标签匹配结果的交集,得到候选 Chunk 集合
- Chunk 扫描:从对象存储中读取候选 Chunk,在解压后的日志内容中执行管道操作符过滤
- 结果聚合:Querier 合并来自多个 Ingester 和对象存储的结果,返回给客户端
性能陷阱:如果查询未指定时间范围,Loki 将扫描所有匹配标签的 Chunk,导致查询缓慢。始终在查询中显式指定时间范围(如
[1h] 或通过 Grafana 时间选择器),以限制扫描范围。
3.4 标签与 LogQL 协同优化
在 OPA 与 Loki 协同的场景中,标签设计直接影响安全策略的生效粒度与查询性能。以下为推荐的最佳实践:
| 场景 | 推荐标签 | 说明 |
|---|---|---|
| 多租户隔离 | tenant_id |
每个租户使用独立标签,OPA 策略可基于此标签限制查询范围 |
| 服务拓扑 | service、namespace |
快速定位特定服务的日志,配合 OPA 实现服务级访问控制 |
| 安全审计 | user、action |
低基数用户标签(如角色),避免使用高基数的 user_id |
| 异常检测 | level、status_code |
配合 LogQL 的 rate() 和 quantile_over_time() 实现告警 |
OPA 集成提示:在 OPA 策略中,可以通过
input.labels 获取查询中的标签选择器,从而动态判断用户是否有权访问特定标签组合。例如,限制非管理员用户只能查询 namespace="dev" 的日志。