本文参考《告别笨重的 ELK,试试用 Loki 处理日志吧》的实践方向,但按当前 Loki 文档重写。需要特别更新两点:Promtail 已在 Loki 3.7.3 中移除,采集端应迁移到 Grafana Alloy;Simple Scalable Deployment 正在弃用,新系统不应再把它作为长期架构。

Loki 的优势不是“比 Elasticsearch 小”,而是采用了不同的索引策略:索引日志流的 labels,而不是为每个日志字段建立全文索引。

这让它在容器日志场景里更节省索引成本,也意味着标签设计一旦失控,系统同样会变慢、变贵。

先理解 stream

一组完全相同 labels 的日志构成一个 stream:

{service_name="checkout", deployment_environment="prod", cluster="hk-1"}

只要任意 label 值变化,就会创建新的 stream。Loki 查询时先通过 labels 找到候选 stream,再扫描其中的日志内容。

因此 labels 适合描述来源和稳定分组:

  • service_name
  • deployment_environment
  • clusterregion
  • namespace
  • 有界的业务域或日志级别。

不适合做 labels 的字段包括:

  • request_idtrace_id
  • user_id、订单号;
  • IP 地址;
  • 时间戳;
  • 完整 URL;
  • Pod UID、不断变化的实例 id。

这些值基数高且生命周期短,会制造大量小 stream 和小 chunk,增加索引、对象存储请求和查询开销。

高基数字段放进日志正文或 structured metadata

推荐应用输出结构化 JSON:

{
  "timestamp": "2026-07-15T09:00:00Z",
  "level": "ERROR",
  "service": "checkout",
  "message": "payment provider timeout",
  "trace_id": "4c9c...",
  "order_id": "o_123",
  "duration_ms": 2018
}

service、环境和集群可以在采集阶段提升为 labels;trace_idorder_id 和耗时保留在正文或 structured metadata 中,查询时再解析:

{service_name="checkout", deployment_environment="prod"}
  | json
  | trace_id="4c9c..."

先用低基数 labels 缩小范围,再解析正文。不要为了偶尔按订单号查询,就让每个订单生成一个 stream。

Grafana 当前文档建议从少量 labels 开始,并特别提醒新部署不要把 k8s.pod.nameservice.instance.id 默认提升为索引 labels;这类字段更适合 structured metadata。

日志格式比采集器更早决定质量

应用侧应先统一:

  • 机器可解析的 JSON;
  • 固定字段名和类型;
  • 标准时间戳与时区;
  • service、环境、版本和 trace id;
  • 异常类型与 stack trace;
  • 不记录密码、token、验证码和完整个人信息;
  • 对超长 payload、重复 stack trace 和 debug 日志设限。

如果应用输出不可预测的多行文本,采集器只能用脆弱正则补救。结构化日志还可以让指标和追踪通过 trace id 互相跳转。

采集端使用 Grafana Alloy

Promtail 曾是 Loki 的默认日志采集器,但当前已经移除,代码和能力并入 Grafana Alloy。新系统可以把链路理解为:

容器 stdout / 文件 / journald
  → Alloy 发现目标
  → 解析、重标记、脱敏和限流
  → Loki push API
  → 对象存储与索引

Alloy 侧主要负责:

  • Kubernetes 或主机目标发现;
  • 保留稳定 labels,删除高基数 labels;
  • 解析 JSON 与多行异常;
  • 丢弃无价值日志;
  • 在发送前做必要脱敏;
  • 暴露自身采集、丢弃和发送失败指标。

从 Promtail 迁移时,不要只转换配置语法。还应对比迁移前后的 label 集合、stream 数量、丢弃率和查询结果,避免一次 label 变化让历史与新日志无法使用同一条查询。

部署模式按规模和运维能力选择

Monolithic

所有 Loki 组件运行在同一进程中,最适合本地实验和小规模生产。官方当前把它定位为大约每日 20 GB 以内的简单场景;结合共享对象存储和多个实例也可做高可用。

优点是组件少、升级和排障简单。对大多数小团队,这往往比过早拆微服务更可靠。

HA Monolithic

两个或多个单体实例共享对象存储,并通过 ring 协调。它在复杂度和可用性之间比较平衡,适合单体容量足够、但不能接受单点故障的团队。

Microservices

distributor、ingester、querier、query-frontend、compactor 等组件独立部署和扩缩。它能分别扩展读写路径,但升级顺序、容量规划和元监控都更复杂。

只有日志规模或查询模式确实要求独立扩展时才值得使用。组件更多不等于天然更可靠。

不再为新系统选择 SSD

Simple Scalable Deployment 把 Loki 分为 read、write 和 backend 三组,但当前文档已标记弃用,并计划在 Loki 4.0 移除。现有部署应制定迁移计划,新部署直接在 HA monolithic 与 microservices 之间选择。

对象存储、保留和删除要一起设计

生产环境通常把 chunk 放进对象存储。需要提前确定:

  • schema 与索引周期;
  • retention 时间;
  • compactor 的职责与容量;
  • 租户配额;
  • 删除请求和合规流程;
  • 对象存储生命周期规则是否与 Loki retention 冲突;
  • 备份目标到底是配置、索引还是原始日志。

“对象存储会自动过期”不能替代 Loki 的一致性删除策略。过早删除 chunk 会让索引指向不存在的数据,保留过久则增加成本和合规风险。

查询与告警从固定入口开始

不要让每个团队从全局空查询开始。为常见场景提供模板:

# 某服务中的超时
{service_name="checkout", deployment_environment="prod"}
  |= "timeout"

# 解析 JSON 后统计错误
sum by (service_name) (
  count_over_time(
    {deployment_environment="prod"}
      | json
      | level="ERROR" [5m]
  )
)

日志告警适合发现明确错误模式,但服务错误率、延迟和容量仍应优先来自 metrics。不要为了一个可以直接计数的指标持续扫描大量日志。

Loki 自己也需要元监控

至少观察:

  • Alloy 发现的目标数与发送失败;
  • Loki 接收、拒绝和丢弃的日志量;
  • 活跃 stream 数和 label 基数;
  • ingester 内存、flush 失败与 WAL;
  • 查询延迟、并发和超时;
  • compactor 与 retention 状态;
  • 对象存储错误和请求成本;
  • 各租户的摄入量与限额命中。

最好让监控 Loki 的指标和关键告警不完全依赖被监控的同一个 Loki 实例,否则日志系统故障时会同时失去诊断证据。

上线前检查清单

  • labels 只包含稳定、低基数、经常用于筛选的字段;
  • request id、用户 id、IP 等放在正文或 structured metadata;
  • 应用输出结构化日志并完成敏感数据审查;
  • 新采集链路使用 Alloy,而不是继续建设 Promtail;
  • 新部署没有依赖正在弃用的 SSD 模式;
  • retention、compactor 与对象存储生命周期一致;
  • 为团队提供查询模板和日志到 trace 的关联;
  • Loki 与 Alloy 的元监控不依赖单一故障域。

参考资料