预写日志
Ingester 临时将数据存储在内存中。如果发生崩溃,可能会丢失数据。预写日志(WAL)有助于填补此可靠性空白。
Grafana Loki 中的 WAL 记录传入数据并将其存储在本地文件系统中,以确保在进程崩溃时已确认数据的持久性。重启后,Loki 将在注册自身准备好接受后续写入之前,“重放”日志中的所有数据。这使得 Loki 能够保留在内存中缓冲数据的性能和成本优势,以及持久性优势(一旦写入被确认,数据就不会丢失)。
本节将使用 Kubernetes 作为示例中的参考部署范例。
免责声明和 WAL 细微之处
与其他您可能熟悉的 WAL 相比,Loki 中的预写日志做出了一些特定的权衡。WAL 的目标是增加额外的持久性保证,但不以牺牲可用性为代价。特别是,在以下两种场景下,WAL 会牺牲这些保证。
重放 WAL 之前 WAL 损坏/删除
如果 WAL 损坏/部分删除,Loki 将无法恢复所有数据。在这种情况下,Loki 会尝试恢复它能恢复的任何数据,但不会阻止 Loki 启动。
您可以使用 Prometheus 指标
loki_ingester_wal_corruptions_total
来跟踪并告警此情况发生时。磁盘空间不足
如果底层 WAL 磁盘已满,Loki 不会使传入写入失败,但也不会将其记录到 WAL 中。在这种情况下,跨进程重启的持久性保证将失效。
您可以使用 Prometheus 指标
loki_ingester_wal_disk_full_failures_total
来跟踪并告警此情况发生时。
反压
WAL 还包含一种反压机制,允许在较小的内存限制内重放大型 WAL。这在 WAL 增长到可能无法在内存中恢复的程度后(例如,发生中断)的糟糕场景下很有帮助。在这种情况下,Ingester 会跟踪正在重放的数据量,一旦超过 ingester.wal-replay-memory-ceiling
阈值,就会刷新到存储。发生这种情况时,Loki 尝试通过内容可寻址存储进行块去重的效率可能会受到影响。考虑到它简化了操作并且在正常操作(滚动更新、重新调度)中不会触发此阈值,我们认为这种效率损失是可以接受的权衡。
指标
以下指标可用于监控 WAL
- 遇到的 WAL 损坏总数
- 磁盘满失败总数
- 记录到 WAL 的记录计数器
- 写入 WAL 的总字节数
部署变更
由于 Ingester 在重启/滚动更新时需要使用相同的持久卷,因此所有 Ingester 都应在具有固定卷的 StatefulSet 上运行。
需要设置以下标志
- 将
--ingester.wal-enabled
设置为true
,以便在摄取过程中启用写入 WAL。 - 将
--ingester.wal-dir
设置为存储和/或恢复 WAL 数据的目录。请注意,这应该在挂载的卷上。 - 将
--ingester.checkpoint-duration
设置为创建检查点的时间间隔。 --ingester.wal-replay-memory-ceiling
(默认为 4GB)可以根据您的资源设置进行调高/调低。它处理 WAL 重放期间的内存压力,允许重放比可用内存大得多倍的 WAL。这是为了最大程度地减少在非常糟糕的情况(例如中断)后的协调时间,并且完全不会影响常规操作/滚动更新。我们建议将其设置为可用内存的较高百分比(约 75%)。
- 将
启用 WAL 后生命周期中的变更
在滚动更新或缩容期间,将数据刷新到块存储的功能被禁用。这是因为在有状态集(StatefulSet)的滚动更新期间,没有 Ingester 同时离开和加入,而是同一个 Ingester 被关闭然后使用更新的配置重新启动。因此,跳过刷新,数据从 WAL 中恢复。如果您需要确保在 Pod 关闭时始终将数据刷新到块存储,可以将 --ingester.flush-on-shutdown
标志设置为 true
。
磁盘空间要求
基于实际测试
- 来自一个拥有 5000 个系列且摄取速率约为 5MB/秒的 Ingester 的数据。
- 检查点周期为 5 分钟。
- 仅用于 WAL 的磁盘的磁盘利用率稳定在约 10-15GB。
不应将磁盘利用率目标设置为 100%。
从无状态部署迁移
没有 WAL 的 Ingester 部署(Deployment) 和带有 WAL 的 有状态集(StatefulSet) 应同步分别缩容和扩容,期间不进行数据传输,以确保迁移后立即实现可靠的摄取。
以 4 个 Ingester 为例。迁移步骤大致如下:
- 启动一个有状态 Ingester
ingester-0
并等待其就绪(接受读写请求)。 - 将旧的 Ingester 部署缩容到 3 个,并等待正在离开的 Ingester 将所有数据刷新到块存储。
- 一旦该 Ingester 从
kc get pods ...
中消失,再添加另一个有状态 Ingester 并等待其就绪。现在您将拥有ingester-0
和ingester-1
。 - 重复步骤 2,从旧部署中再移除一个 Ingester。
- 重复步骤 3,再添加一个有状态 Ingester。现在您将拥有
ingester-0 ingester-1 ingester-2
。 - 重复步骤 4 和 5,最终您将拥有
ingester-0 ingester-1 ingester-2 ingester-3
。
如何扩容/缩容
扩容
扩容与没有 WAL 或 StatefulSets 时一样。这里无需更改。
缩容
缩容时,我们必须确保正在离开的 Ingester 上的现有数据被刷新到存储中,而不仅仅是 WAL。这是因为我们不会在即将不存在的 Ingester 上重放 WAL,我们需要确保数据不丢失。
假设您有 4 个 Ingester ingester-0 ingester-1 ingester-2 ingester-3
,并且您想缩容到 2 个 Ingester,根据 StatefulSet 规则将关闭的 Ingester 是 ingester-3
,然后是 ingester-2
。
因此,在 Kubernetes 中实际缩容之前,请将这些 Ingester 的端口转发出来,并访问 /ingester/shutdown?flush=true
端点。这将刷新块并将自身从环中移除,之后它将注册为未就绪并可能被删除。
在访问 ingester-2 ingester-3
的端点后,将 Ingester 缩容到 2 个。
此外,您还可以将 --ingester.flush-on-shutdown
标志设置为 true
。这将在 Ingester 关闭时将块刷新到长期存储。
补充说明
Kubernetes 技巧
StatefulSet 的使用、升级等操作要繁琐得多。这很大程度上源于其规范中不可变的字段。例如,如果想开始将 WAL 与单存储 Loki 一起使用,并且希望为 WAL 和 boltdb-shipper 分别设置卷挂载,那么在尝试更新 Kubernetes StatefulSet 时可能会看到不可变性错误。
在这种情况下,可以尝试使用 kubectl -n <namespace> delete sts ingester --cascade=false
。这将保留 Pod 运行,但删除 StatefulSet。然后您可以重新创建(已更新的)StatefulSet,并按顺序逐个删除 ingester-0
到 ingester-n
的 Pod,让 StatefulSet 启动新的 Pod 来替换它们。
使用 /flush_shutdown
端点和生命周期钩子进行缩容
用于有序缩容的 StatefulSets:Loki Ingester 应逐个缩容,这可以由 Kubernetes StatefulSets 有效地处理。这确保了有序且可靠的缩容过程,如 部署和缩容保证 文档所述。
使用 PreStop 生命周期钩子:在 Pod 缩容过程中,PreStop 生命周期钩子 会触发 Ingester 上的
/flush_shutdown
端点。此操作将刷新块并将 Ingester 从环中移除,使其注册为未就绪并可以被删除。使用 terminationGracePeriodSeconds:为 Ingester 在被删除之前刷新数据提供时间;如果刷新数据需要超过 30 分钟,您可能需要增加此时间。
清理持久卷:通过利用 Kubernetes 中的 enableStatefulSetAutoDeletePVC 功能,持久卷会自动清理。
通过遵循上述步骤,您可以确保 Loki Ingester 的平滑缩容过程,同时保持数据完整性并最大程度地减少潜在中断。
非 Kubernetes 或裸机部署
- 当 Ingester 因任何原因(升级、崩溃等)重启时,它应该能够附加到同一个卷上,以便恢复 WAL 和令牌。
- 两个 Ingester 不应使用同一个卷/目录作为 WAL。
- 滚动更新应该完全关闭一个 Ingester,然后启动新的 Ingester,而不是反过来。