Skip to content

Loki 笔记

一、Loki 简介

Loki 是 Grafana Labs 开源的 云原生日志聚合系统,由 Prometheus 团队(同款作者)打造。它的设计哲学与 Elasticsearch 等传统日志系统截然不同:

Loki: Like Prometheus, but for logs.

核心设计理念

  • 不索引日志内容,只索引标签:Loki 不像 ELK 那样对全文建倒排索引,而是只对日志的 labels(标签)建立索引。日志内容则被压缩后按块(chunk)存储。
  • 成本低:因为不索引文本,存储和计算成本都远低于 ES。
  • 与 Prometheus 统一:同一套标签体系,可以在 Grafana 中无缝关联指标和日志。
  • 水平扩展:无状态组件 + 对象存储 + 一致性哈希,天然适合云原生环境。

Loki vs ELK

维度 Loki ELK(Elasticsearch)
索引方式 只索引 label 全文倒排索引
存储成本 低(对象存储 + 压缩) 高(本地 SSD + 副本)
全文检索 弱(需要 grep 风格过滤)
适合场景 已结构化的日志(JSON、按行 k=v) 非结构化全文检索
复杂度 较低(组件少) 较高(JVM、索引调优)

二、Loki 架构

Loki 采用 读写分离 的架构,主要由两大部分组成:

整体架构图(简化)

┌──────────────┐    ┌──────────────────────────────────────────┐
│  Log Sources │    │               Loki Cluster               │
│ (Promtail /  │    │                                          │
│  Grafana     │──▶│  ┌────────────┐    ┌──────────────┐     │
│  Agent /     │    │  │ Distributor│───▶│   Ingester    │     │
│  Fluent Bit) │    │  └────────────┘    └──────────────┘     │
└──────────────┘    │                          │               │
                    │                          ▼               │
                    │                  ┌──────────────┐        │
                    │                  │ Object Store │        │
                    │                  │ (S3/GCS/...) │        │
                    │                  └──────────────┘        │
                    │                          ▲               │
                    │  ┌────────────┐    ┌──────────────┐     │
                    │  │ Querier /  │───▶│   Query      │     │
                    │  │ Query Frontend    │  Frontend    │     │
                    │  └────────────┘    └──────────────┘     │
                    └──────────────────────────────────────────┘
                                          │
                                          ▼
                                    ┌──────────┐
                                    │ Grafana  │
                                    └──────────┘

写路径(Write Path)

  1. 客户端(Promtail/Fluentbit/Agent)通过 HTTP/POST 把日志推送到 Loki。
  2. Distributor(无状态):接收日志,根据一致性哈希 + 租户信息,把它路由到正确的 Ingester。
  3. Ingester(有状态):把日志流 组装成 chunk(默认 4h 一个),写入临时存储(内存 + 后台 flush),并定期把 chunk 写入对象存储(S3/GCS/Azure Blob/本地文件系统)。
  4. Compactor:后台任务,负责把 chunk 合并、去重、清理过期数据。

读路径(Read Path)

  1. Query Frontend(可选):查询调度、并行切分、缓存、限流。
  2. Querier(无状态):收到 LogQL 查询后,根据标签索引找到对应的 chunk,从对象存储拉取 chunk,执行 LogQL 表达式。
  3. 结果合并:多 Querier 节点的结果由 Query Frontend 合并后返回。

三、核心组件

组件 作用 是否无状态 是否必须
Distributor 写入路由、租户限流
Ingester 日志流聚合、chunk 写入 否(有 WAL)
Querier LogQL 查询执行
Query Frontend 查询调度、并行、缓存 否(但强烈推荐)
Compactor 索引合并、过期数据清理 否(单实例) 否(单机部署可省略)
Index Gateway 索引查询(在 simple scalable 中)
Query Scheduler 查询队列调度
Ruler Loki 自身的告警/Recording 规则 否(可由 Grafana 替代)
Store Gateway 读取 TSDB 索引 否(微服务模式)

关键概念

  • Chunk:Loki 存储日志的最小物理单位,默认 4 小时一个,采用 gzip 压缩。
  • Stream:一组具有相同 label 集合的日志序列。
  • Tenant(租户):多租户隔离的基础,每个请求 header X-Scope-OrgID 区分。
  • Index:Loki 用 BoltDB/TSDB 来索引 label→chunk 的映射关系。
  • WAL(Write-Ahead Log):Ingester 在 chunk 持久化前,本地 WAL 用于崩溃恢复。

四、部署模式

1. Single Binary(单机模式)

适合开发测试,所有组件跑在一个进程里:

./loki -config.file=loki-config.yaml

2. Simple Scalable(推荐入门)

读写分离,可水平扩展:

  • Read 路径:Query Frontend + Querier + Index Gateway
  • Write 路径:Distributor + Ingester
  • 后端服务:Compactor(单实例)+ Object Storage

3. Microservices(生产大规模)

按组件分别部署,适合百万级日志/秒的场景:

# 伪 helm values
ingester:
  replicas: 3
querier:
  replicas: 5
distributor:
  replicas: 3
queryFrontend:
  replicas: 2
compactor:
  replicas: 1

五、存储后端

1. 对象存储(Chunks)

Loki 把压缩后的 chunk 写入对象存储,支持:

  • AWS S3 / S3-compatible(MinIO、Ceph)
  • Google Cloud Storage
  • Azure Blob Storage
  • 阿里云 OSS / 腾讯云 COS
  • 本地文件系统(仅测试)

2. 索引存储

  • BoltDB(单机模式):索引存本地文件
  • TSDB(微服务模式):Loki 2.0+ 推荐,把索引也写入对象存储,Scalability 大幅提升

3. 推荐生产配置

storage_config:
  aws:
    s3: s3://<region>.amazonaws.com/<bucket>
    s3forcepathstyle: true
  boltdb_shipper:
    active_index_directory: /data/loki/index
    cache_location: /data/loki/index_cache
    shared_store: s3
schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: s3
      schema: v13
      index:
        prefix: index_
        period: 24h

六、关键配置详解

1. 限制并发与速率

limits_config:
  # 每租户写入速率(B/s)
  ingestion_rate_mb: 10
  # 每租户最大并行查询数
  max_query_parallelism: 32
  # 单条日志最大长度
  max_line_size: 256KB
  # 单次查询返回最大行数
  max_entries_limit_per_query: 5000
  # 拒绝采样
  reject_old_samples: true
  reject_old_samples_max_age: 168h

2. 标签校验

limits_config:
  # 强制 label 名称规范,避免 cardinality 爆炸
  allow_structured_metadata: true
  # 限制每条日志的结构化元数据 key 数量
  max_structured_metadata_entries_count: 128

3. 缓存(Redis/Memcached)

query_range:
  results_cache:
    cache:
      redis:
        endpoint: redis:6379
        expiration: 1h

七、与采集端集成

1. Promtail(已弃用,推荐 Grafana Agent)

server:
  http_listen_port: 9080

positions:
  filename: /tmp/positions.yaml

clients:
  - url: http://loki:3100/loki/api/v1/push

scrape_configs:
  - job_name: system
    static_configs:
      - targets: [localhost]
        labels:
          job: syslog
          __path__: /var/log/*.log

2. Grafana Agent(Flow 模式)

river 配置:

loki.source.file "app" {
  targets = [
    {__path__ = "/var/log/app/*.log", job = "app"},
  ]
  forward_to = [loki.write.endpoint.receiver]
}

loki.write "endpoint" {
  endpoint {
    url = "http://loki:3100/loki/api/v1/push"
  }
}

3. Kubernetes 自动采集

用 Loki Helm Chart 自带的 promtail DaemonSet,自动打上 pod/container/node 标签:

# 自动生成的 label 示例
{app="myapp", container="nginx", namespace="prod", pod="myapp-7d8f", instance="node-1"}

八、LogQL 查询示例

详见同目录下 LogQL.md,下面给一些高级用法:

1. 计算 QPS + 错误率

sum(rate({job="nginx"}[5m]))
sum(rate({job="nginx"} | json | status=~"5.." [5m])) by (status)

2. 计算 P99 响应时间

quantile_over_time(0.99,
  {job="api"} | json | unwrap duration [5m]
) by (path)

3. 关联 TraceID 与日志

{app="checkout"} | json | trace_id="abc123"

九、运维与最佳实践

1. Label 规范(极其重要)

  • ✅ 推荐的 label:appenvclusterregionpodcontainer
  • ❌ 避免的 label:用户 ID、IP、URL、TraceID(应放结构化元数据或日志内容)
  • 经验法则:每条流每分钟写入日志 < 10 条

2. Cardinality 控制

Loki 对标签 cardinality 极其敏感:

# 限制每条流的标签 cardinality
limits_config:
  max_label_names_per_series: 30
  max_label_name_length: 64
  max_label_value_length: 2048

3. Retention(数据保留)

Loki 通过 Compactor 删除过期数据:

compactor:
  working_directory: /data/loki/compactor
  compaction_interval: 10m
  retention_enabled: true
  retention_delete_delay: 2h
  delete_request_store: filesystem

limits_config:
  retention_period: 744h  # 31 天

4. 监控 Loki 自身

Loki 通过 /metrics 暴露 Prometheus 指标,关键指标:

指标 含义
loki_distributor_lines_received_total 接收日志行数
loki_ingester_chunks_created_total 生成的 chunk 数
loki_ingester_memory_streams 内存中的活跃流
loki_ingester_wal_bytes_flushed WAL flush 数据量
loki_request_duration_seconds_bucket 请求耗时分布
loki_query_frontend_queue_duration_seconds_bucket 查询队列等待时间

推荐使用 Grafana 官方 Dashboard Loki / 编写日志 / 1.0

5. 故障排查

现象 可能原因 排查方向
写入 429 超过 ingestion_rate_mb 提高 limits,或优化日志量
查询超时 chunk 过多或范围过大 缩小时间窗口,加大 max_query_parallelism
内存 OOM 流数过多 检查 label cardinality
索引损坏 不正常关机 重启 Compactor 重建索引
时间戳错乱 客户端时间与服务器不一致 统一 NTP 同步

十、与 OpenTelemetry 集成

Loki 2.0+ 支持通过 OTLP 协议接收日志:

# loki 配置
server:
  http_listen_port: 3100
  grpc_listen_port: 9096

# OpenTelemetry Collector 配置
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317

exporters:
  otlphttp/loki:
    endpoint: http://loki:3100/otlp
    tls:
      insecure: true

Loki 会自动把 OTLP resource.attributes 转成 label,把 log.record.attributes 转成结构化元数据。


十一、常见架构方案

1. 中小规模(< 50GB/天)

Promtail → Loki(Simple Scalable) → Grafana
                ↓
            MinIO / S3

2. 大规模(> 500GB/天)

应用/Pod
   ↓
Grafana Agent(Cluster 模式)
   ↓
Loki(Microservices)
 ├─ Distributor × N
 ├─ Ingester × N(挂载 NVMe WAL)
 ├─ Querier × N
 ├─ Query Frontend × N
 └─ Compactor × 1
       ↓
   S3/GCS + TSDB 索引

3. 混合云/多区域

  • 每个区域部署独立 Loki
  • 通过 Grafana 数据源 Federation 或 Mimir 统一展示
  • 或使用 Loki 的 multi-tenancy 模式把不同业务当作不同租户

十二、参考资料