本文参考《告别笨重的 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;cluster、region;namespace;- 有界的业务域或日志级别。
不适合做 labels 的字段包括:
request_id、trace_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_id、order_id 和耗时保留在正文或 structured metadata 中,查询时再解析:
{service_name="checkout", deployment_environment="prod"}
| json
| trace_id="4c9c..."
先用低基数 labels 缩小范围,再解析正文。不要为了偶尔按订单号查询,就让每个订单生成一个 stream。
Grafana 当前文档建议从少量 labels 开始,并特别提醒新部署不要把 k8s.pod.name 和 service.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 的元监控不依赖单一故障域。