BoltDB Shipper使您能够在不依赖于NoSQL存储的情况下运行Loki,用来存储索引。它将索引以BoltDB文件的形式在本地存储,并将这些文件发送到共享对象存储(即用于存储数据块chunks的相同对象存储)。它还通过从共享对象存储同步BoltDB文件到配置的本地目录,获取由同一Loki集群的其他服务创建的索引。这样可以在运行Loki时减少一个依赖项,并且在存储方面节省成本,因为对象存储很可能比托管或运行自托管的NoSQL存储实例的成本更低。

注意:BoltDB Shipper最适合使用24小时周期的索引文件。无论是当前正在使用还是即将使用boltdb-shipper,都需要将索引周期设置为24小时。如果boltdb-shipper已经创建了7天周期的索引文件,并且您希望保留先前的数据,则只需使用boltdb-shipper添加一个新的模式配置,设置一个未来日期,并将索引文件周期设置为24小时。

示例配置

使用GCS的示例配置:

 1schema_config:
 2  configs:
 3    - from: 2018-04-15
 4      store: boltdb-shipper
 5      object_store: gcs
 6      schema: v11
 7      index:
 8        prefix: loki_index_
 9        period: 24h
10
11storage_config:
12  gcs:
13    bucket_name: GCS_BUCKET_NAME
14
15  boltdb_shipper:
16    active_index_directory: /loki/index
17    shared_store: gcs
18    cache_location: /loki/boltdb-cache

这将在/loki/index本地存储BoltDB文件的情况下运行Loki,并将数据块存储在配置的GCS_BUCKET_NAME中。它还会定期将BoltDB文件发送到相同配置的存储桶。它还会从共享存储桶中下载由其他ingesters上传的BoltDB文件到本地的/loki/boltdb-cache文件夹中。

Operational Details

Loki可以配置为仅作为单个垂直扩展实例运行,或作为水平扩展的单一二进制(运行所有Loki服务)实例集群,或以微服务模式在每个实例中仅运行一个服务。在读取和写入方面,Ingesters负责将索引和数据块写入存储中,而Queriers负责从存储中读取索引和数据块以提供请求服务。

在进一步了解细节之前,了解Loki如何在存储中管理索引非常重要。Loki根据配置的周期对索引进行分片,默认为七天,即对于基于表(table)的存储(如Bigtable/Cassandra/DynamoDB),每周都会有一个单独的表,其中包含该周的索引。在BoltDB Shipper的情况下,一个表由许多较小的BoltDB文件集合定义,每个文件仅存储15分钟的索引。每天创建的表由配置的前缀_ + <自Unix纪元以来的周期编号>标识。在boltdb-shipper的情况下,<自纪Unix纪元来的周期编号>将是自1970年1月1日00:00:00UTC以来的天数。例如,如果您将前缀设置为loki_index_,并且在2020年4月20日收到写入请求,则会将其存储在名为loki_index_18372的表中,因为自纪元以来已经过了18371天,并且我们处于第18372天。由于使用BoltDB时索引的分片创建多个文件,BoltDB Shipper会为每天创建一个文件夹,并将当天的文件添加到该文件夹中,并将这些文件命名为创建它们的Ingesters的名称。

为了减小文件的大小,以实现更快的传输速度和降低存储成本,它们在存储之前经过gzip压缩。

为了展示共享对象存储中的BoltDB文件的样子,让我们考虑一个运行在Loki集群中的名为ingester-0ingester-1的2个Ingesters,并且它们都使用前缀loki_index_发送了1837118372两天的文件。以下是这些文件的样子:

1└── index
2    ├── loki_index_18371
3    │   ├── ingester-0-1587254400.gz
4    │   └── ingester-1-1587255300.gz
5    |   ...
6    └── loki_index_18372
7        ├── ingester-0-1587254400.gz
8        └── ingester-1-1587254400.gz
9        ...

注意:我们还会向文件名添加时间戳以随机化名称,以避免在使用相同名称且没有持久化存储的Ingesters时覆盖文件。为了简化,这里不显示时间戳。

让我们更深入地了解在使用BoltDB Shipper时Ingesters和Queriers是如何工作的。

Ingesters

Ingesters是数据写入者,将索引写入到active_index_directory中的BoltDB文件中,而BoltDB Shipper则在每1分钟的间隔内查找该目录中的新文件和更新文件,并将它们上传到共享对象存储。当以微服务模式运行Loki时,可能会有多个数据写入者用于处理写入请求。每个数据写入者都在本地生成BoltDB文件。

注意:为了避免数据写入者崩溃时造成索引的任何丢失,在使用Kubernetes时建议使用具有持久化存储的StatefulSet来运行数据写入者,用于存储索引文件。

当数据块被刷新时,它们会立即在对象存储中供读取。索引不会立即可用,因为我们使用BoltDB Shipper每15分钟上传一次。数据写入者会提供一个新的RPC接口,让查询者可以查询数据写入者的本地索引以获取最近刷新的数据块,但是这些索引在查询者那里可能还不可用。对于所有需要从存储中读取数据块的查询,查询者还会通过RPC查询数据写入者以获取最近刷新的数据块的ID。这样可以避免在查询中丢失任何日志。

Queriers

为了避免将Queriers作为带有持久化存储的StatefulSet运行,我们建议运行一个索引网关(Index Gateway)。索引网关将下载并同步索引,并通过gRPC提供给Queriers和Rulers。

查询者会从共享对象存储中延迟加载BoltDB文件到配置的缓存位置(cache_location)。当查询者接收到读取请求时,请求中的查询范围将解析为周期编号,并下载该周期编号下的所有文件下载到缓存位置(如果尚未下载)。一旦我们下载了一个周期的文件,我们会继续监视共享对象存储中的更新,并默认每5分钟下载一次更新。可以使用resync_interval配置来配置检查更新的频率。

为了避免永久保留已下载的索引文件,这里设置了一个过期时间(ttl),默认为24小时。这意味着如果一个周期的索引文件在24小时内没有使用,它们将从缓存位置中删除。可以使用cache_ttl配置来设置ttl。

在Kubernetes环境中,如果不使用索引网关,我们建议将查询者作为带有持久化存储的StatefulSet运行,用于下载和查询索引文件。这将获得更好的读取性能,并避免使用节点磁盘。

Index Gateway

索引网关从对象存储下载并同步BoltDB索引,以便通过gRPC为Queriers和Rulers提供索引查询服务。这避免了在磁盘上运行Queriers和Ruler以实现持久化。在大规模集群中,磁盘可能会成为昂贵的资源。

要运行索引网关,请配置StorageConfig并将-targetCLI标志设置为index-gateway。要将Queriers和Rulers连接到索引网关,请使用-boltdb.shipper.index-gateway-client.server-address CLI标志或其在StorageConfig下的等效YAML值设置索引网关的地址(带有gRPC端口)。

在Kubernetes中使用索引网关时,建议使用带有持久化存储的StatefulSet来下载和查询索引文件。这样可以获得更好的读取性能,避免使用节点磁盘导致的干扰问题,并且避免在重新调度到新节点后启动时耗时的索引下载步骤。

去重写入是禁用的

Loki通过使用Chunks和WriteDedupe缓存来分别对块和索引进行去重写入,配置在ChunkStoreConfig中。然而,当使用boltdb-shipper时,去重写入存在一个问题,即Ingesters仅定期上传boltdb文件以使其对所有其他服务可用,这意味着在某个短暂的时间段内,一些服务可能尚未接收到更新的索引。由此引起的问题是,如果首先写入了块和索引的Ingesters发生故障,并且由于去重写入,所有其他参与复制方案的Ingesters都跳过了写入这些块和索引,那么我们将错过来自查询响应的那些日志,因为只有具有索引的Ingesters发生了故障。即使在常见的发布过程中,也会面临这个问题。

为了避免这个问题,在复制因子大于1且boltdb-shipper是一种当前正在使用或即将使用的索引类型时,Loki会禁用索引的去重写入。在使用boltdb-shipper时,请避免配置WriteDedupe缓存,因为它仅用于索引的去重写入,因此无论如何都不会使用它。

Compactor

压缩器(Compactor)是一种特定于BoltDB Shipper的服务,通过去重索引并将所有文件合并到每个表的单个文件中,从而减小索引的大小。我们建议运行一个压缩器,因为每个Ingesters每天会创建96个文件,其中包含大量重复的索引条目,而对每个表进行多文件查询会增加整体查询延迟。

注意:一次只能运行一个压缩器实例,否则可能会引发问题,并可能导致数据丢失。

以下是使用GCS的压缩器配置示例:

压缩器(Compactor)是一个可选但建议使用的组件,它可以合并和去重boltdb-shipper的索引文件。在压缩索引文件时,压缩器会写入一个新文件并删除未优化的文件。确保压缩器具有删除文件的适当权限,例如,对于AWS S3,需要具有s3:DeleteObject权限。

1compactor:
2  working_directory: /loki/compactor
3  shared_store: gcs
4
5storage_config:
6  gcs:
7    bucket_name: GCS_BUCKET_NAME

参考