关于运维:Grafana-系列文章十一Loki-中的标签如何使日志查询更快更方便

8次阅读

共计 9651 个字符,预计需要花费 25 分钟才能阅读完成。

👉️URL: https://grafana.com/blog/2020…

📝Description:

对于标签在 Loki 中如何真正发挥作用,你须要晓得的所有。它可能与你设想的不同

在咱们从事 Loki 我的项目的第一年的大部分工夫里,问题和反馈仿佛都来自相熟 Prometheus 的人。毕竟,Loki 就像 Prometheus– 不过是针对日志的!”。

然而最近,咱们看到越来越多的人尝试应用 Loki,他们没有 Prometheus 的教训,而且许多人来自于具备不同策略的零碎,以解决日志。这就带来了很多对于 Loki 一个十分重要的概念的问题,即便是 Prometheus 专家也想理解更多:标签 (Labels)!

这篇文章将涵盖很多内容,以帮忙每一个刚接触 Loki 的人和想要温习的人。咱们将探讨以下主题。

什么是标签 (Label)?

标签是键值对,能够被定义为任何货色!咱们喜爱把它们称为元数据 (metadata),用来形容日志流。如果你相熟 Prometheus,你会习惯性地看到一些标签,比方 jobinstance,我将在接下来的例子中应用这些。

咱们用 Loki 提供的刮削 (scrape) 配置也定义了这些标签。如果你正在应用 Prometheus,在 Loki 和 Prometheus 之间领有统一的标签是 Loki 的超级劣势之一,使你 非常容易将你的应用程序指标 (Metrics) 与你的日志 (Logs) 数据分割起来。

Loki 如何应用标签

Loki 中的标签执行一个十分重要的工作。它们定义了一个流。更确切地说,每个标签的键和值的组合都定义了流。如果只有一个标签值发生变化,就会产生一个新的流。

如果你相熟 Prometheus,那里应用的术语是系列 (series);然而,Prometheus 有一个额定的维度:度量名称 (metric name)。Loki 简化了这一点,没有度量名称,只有标签,咱们决定应用流而不是系列。

让咱们举个例子:

scrape_configs:
 - job_name: system
   pipeline_stages:
   static_configs:
   - targets:
      - localhost
     labels:
      job: syslog
      __path__: /var/log/syslog

这个配置将跟踪一个文件并调配一个标签:job=syslog。你能够这样查问:

{job=”syslog”}

这将在 Loki 创立一个流。

当初让咱们把这个例子扩充一点:

scrape_configs:
 - job_name: system
   pipeline_stages:
   static_configs:
   - targets:
      - localhost
     labels:
      job: syslog
      __path__: /var/log/syslog
 - job_name: system
   pipeline_stages:
   static_configs:
   - targets:
      - localhost
     labels:
      job: apache
      __path__: /var/log/apache.log

当初咱们正在跟踪两个文件。每个文件只失去一个标签和一个值,所以 Loki 当初将存储两个数据流。

咱们能够用几种形式查问这些流:

{job=”apache”} <- 显示标签 job 是 apache 的日志
{job=”syslog”} <- 显示标签 job 是 syslog 的日志
{job=~”apache|syslog”} <- 显示标签 job 是 apache ** 或 ** syslog 的日志

在最初一个例子中,咱们应用了一个 regex 标签匹配器来记录应用标签 job 的两个值的流。当初考虑一下如何也应用一个额定的标签:

scrape_configs:
 - job_name: system
   pipeline_stages:
   static_configs:
   - targets:
      - localhost
     labels:
      job: syslog
      env: dev
      __path__: /var/log/syslog
 - job_name: system
   pipeline_stages:
   static_configs:
   - targets:
      - localhost
     labels:
      job: apache
      env: dev
      __path__: /var/log/apache.log

当初咱们能够这样做,而不是应用正则表达式:

{env=”dev”} <- 返回 env=dev 的所有日志,本例中包含两个日志流

心愿你当初开始看到标签的力量。通过应用一个标签,你能够查问许多数据流。通过联合几个不同的标签,你能够创立非常灵活的日志查问。

标签是 Loki 的日志数据的索引。它们被用来寻找压缩的日志内容,这些内容以块模式独自存储。每个独特的标签和值的组合都定义了一个流,一个流的日志被分批压缩,并作为块存储。

为了使 Loki 的效率和老本效益,咱们必须负责任地应用标签。下一节将更具体地探讨这个问题。

基数 (Cardinality)

后面的两个例子应用的是动态定义的标签,只有一个值;然而,有一些办法能够动静地定义标签。让咱们用 Apache 的日志和你能够用来解析这样的日志行的大量的重合词来看看。

11.11.11.11 - frank [25/Jan/2000:14:00:01 -0500] "GET /1986.js HTTP/1.1" 200 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
- job_name: system
   pipeline_stages:
      - regex:
        expression: "^(?P<ip>\\S+) (?P<identd>\\S+) (?P<user>\\S+) \\[(?P<timestamp>[\\w:/]+\\s[+\\-]\\d{4})\\] \"(?P<action>\\S+)\\s?(?P<path>\\S+)?\\s?(?P<protocol>\\S+)?\"(?P<status_code>\\d{3}|-) (?P<size>\\d+|-)\\s?\"?(?P<referer>[^\"]*)\"?\\s?\"?(?P<useragent>[^\"]*)?\"?$"
    - labels:
        action:
        status_code:
   static_configs:
   - targets:
      - localhost
     labels:
      job: apache
      env: dev
      __path__: /var/log/apache.log

这个词组匹配日志行的每一个组件,并将每个组件的值提取到一个捕捉组中。在管道代码中,这些数据被搁置在一个长期数据结构中,容许在解决该日志行时将其用于多种用处(此时,这些长期数据被抛弃)。对于这一点的更多细节能够在 这里 找到。

从该重合码中,咱们将应用两个捕捉组,依据日志行自身的内容动静地设置两个标签。

action(例如,action=”GET”,action=”POST”)status_code(例如,status_code=”200″,status_code=”400″)。

当初让咱们看几个例子行:

11.11.11.11 - frank [25/Jan/2000:14:00:01 -0500] "GET /1986.js HTTP/1.1" 200 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
11.11.11.12 - frank [25/Jan/2000:14:00:02 -0500] "POST /1986.js HTTP/1.1" 200 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
11.11.11.13 - frank [25/Jan/2000:14:00:03 -0500] "GET /1986.js HTTP/1.1" 400 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
11.11.11.14 - frank [25/Jan/2000:14:00:04 -0500] "POST /1986.js HTTP/1.1" 400 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"

在 Loki 中,将创立以下数据流:

{job=”apache”,env=”dev”,action=”GET”,status_code=”200”} 11.11.11.11 - frank [25/Jan/2000:14:00:01 -0500] "GET /1986.js HTTP/1.1" 200 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
{job=”apache”,env=”dev”,action=”POST”,status_code=”200”} 11.11.11.12 - frank [25/Jan/2000:14:00:02 -0500] "POST /1986.js HTTP/1.1" 200 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
{job=”apache”,env=”dev”,action=”GET”,status_code=”400”} 11.11.11.13 - frank [25/Jan/2000:14:00:03 -0500] "GET /1986.js HTTP/1.1" 400 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"
{job=”apache”,env=”dev”,action=”POST”,status_code=”400”} 11.11.11.14 - frank [25/Jan/2000:14:00:04 -0500] "POST /1986.js HTTP/1.1" 400 932 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; de; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7 GTB6"

这四条日志即将成为四个独立的流,并开始填充四个独立的块。

任何合乎这些标签 / 值组合的额定日志即将被增加到现有的流中。如果有另一个独特的标签组合进来(例如 status_code="500"),就会创立另一个新的流。

当初设想一下,如果你为 ip 设置一个标签。不仅每个来自用户的申请都成为一个独特的流。每个来自同一用户的具备不同动作或状态代码的申请都将失去它本人的流。

做一些简略的计算,如果有四个常见的动作(GET, PUT, POST, DELETE)和四个常见的状态代码(尽管可能不止四个!),这将是 16 个流和 16 个独立的块。当初,如果咱们用一个标签来示意 ip,就把这个数字乘以每个用户。你能够很快有几千或几万个流。

这会导致很高的 cardinality。会杀死 Loki。

当咱们议论 cardinality 时,咱们指的是标签和值的组合以及它们发明的流的数量。高 cardinality 是指应用具备大范畴可能值的标签,如 ip,或联合许多标签,即便它们有一个小而无限的值集,如应用 status_code 和 action。

高 cardinality 导致 Loki 建设一个微小的索引(读作:💲💲💲💲),并将成千上万的小块冲到对象存储中(读作:慢)。目前,Loki 在这种配置下体现很差,运行和应用起来将是最不划算和最没有乐趣的。

应用并行化 (parallelization) 的最佳 Loki 性能

当初你可能会问:如果应用大量的标签或有大量数值的标签是不好的,那么我应该如何查问我的日志呢?如果没有一个数据是有索引的,那查问岂不是很慢?

当咱们看到应用 Loki 的人习惯于应用其余索引反复的解决方案时,他们仿佛感觉有任务定义大量的标签,以便无效地查问他们的日志。毕竟,许多其余的日志解决方案都是对于索引的,这也是常见的思维形式。

在应用 Loki 时,你可能须要遗记你所晓得的货色,看看如何用并行化的形式来解决这个问题。Loki 的超能力是将查问分解成小块,并将其并行调度,这样你就能够在小工夫内查问大量的日志数据。

这种粗犷的办法听起来可能并不现实,但让我解释一下为什么会这样。

大型索引是简单而低廉的。通常状况下,你的日志数据的全文索引与日志数据自身的大小雷同或更大。为了查问你的日志数据,你须要加载这个索引,而且为了性能,它可能应该在内存中。这是很难扩大的,当你摄入更多的日志时,你的索引会很快变大。

当初让咱们来谈谈 Loki,它的索引通常比你摄入的日志量小一个数量级。因而,如果你能很好地放弃你的数据流和数据流的散失,那么与摄取的日志相比,索引的增长十分迟缓。

Loki 将无效地放弃你的动态老本尽可能低(索引大小和内存要求以及动态日志存储),并使查问性能成为你能够在运行时管制的程度扩大。

为了理解这一点,让咱们回过头来看看咱们查问特定 IP 地址的拜访日志数据的例子。咱们不想用一个标签来存储 IP。相同,咱们应用一个过滤器表达式来查问它。

{job=”apache”} |=“11.11.11.11”

在幕后,Loki 会将该查问分解成更小的片段(分片),并为标签所匹配的流关上每个分片,开始寻找这个 IP 地址。

这些分片的大小和并行化的数量是可配置的,并基于你提供的资源。如果你违心,你能够把分片的距离配置到 5m,部署 20 个查询器,在几秒钟内解决几十亿字节的日志。或者你能够疯狂地配置 200 个查询器,解决 TB 级的日志。

这种较小的索引和平行的暴力查问与较大 / 较快的全文索引之间的衡量,使得 Loki 可能比其余零碎节省成本。操作大型索引的老本和复杂性很高,而且通常是固定的 – 无论你是否查问它,你都要一天 24 小时为它付费。

这种设计的益处是,你能够决定你想领有多少查问能力,而且你能够按需扭转。查问性能成为你想在下面花多少钱的一个函数。同时,数据被大量压缩并存储在低成本的对象存储中,如 S3 和 GCS。这使固定的经营老本降到最低,同时还能实现令人难以置信的疾速查问能力

最佳实际

这里有一些 Loki 目前最无效的标签做法,能够给你带来 Loki 的最佳体验。

1. 举荐动态标签

像主机、应用程序和环境这些货色是很好的标签。它们对于一个给定的零碎 / 应用程序来说是固定的,并且有限定的值。应用动态标签能够使你更容易在逻辑上查问你的日志(例如,给我看一个给定的应用程序和特定环境的所有日志,或者给我看一个特定主机上的所有应用程序的所有日志)。

2. 审慎应用动静标签

太多的标签值组合会导致太多的数据流。在 Loki 中,这样做的惩办是一个大索引和存储中的小块,这反过来又会升高性能。

为了防止这些问题,在你晓得你须要它之前,不要为某样货色增加标签。应用过滤表达式 (|= "text", |~ "regex", …) 并对这些日志进行暴力解决。这很无效 – 而且速度很快。

从晚期开始,咱们就应用 promtail 管道为 level 动静地设置了一个标签。这对咱们来说仿佛很直观,因为咱们常常想只显示 level="error" 的日志;然而,咱们当初正在从新评估这一点,因为写一个查问。{app="loki"} |= "level=error"对咱们的许多利用来说,证实与 {app="loki",level="error"} 一样快。

这仿佛令人诧异,但如果应用程序有中等至低容量,该标签导致一个应用程序的日志被分成多达五个流,这意味着 5 倍的块被存储。而加载块有一个与之相干的开销。设想一下,如果这个查问是 {app="loki",level!="debug"}。这将不得不比{app="loki"} != "level=debug"} 加载多的多数据块。

下面,咱们提到在你 须要它们之前不要增加标签,那么你什么时候会 须要标签呢?再往下一点是对于 chunk_target_size 的局部。如果你把这个设置为 1MB(这是正当的),这将试图以 1MB 的压缩大小来切割块,这大概是 5MB 左右的未压缩的日志(可能多达 10MB,取决于压缩)。如果你的日志有足够的容量在比 max_chunk_age 更短的工夫内写入 5MB,或者在这个工夫范畴内有多的多的块,你可能要思考用动静标签把它分成独立的流。

你想防止的是将一个日志文件宰割成流,这将导致块被刷新,因为流是闲暇的或在满之前达到最大年龄。从 Loki 1.4.0 开始,有一个指标能够帮忙你理解为什么要刷新数据块sum by (reason) (rate(loki_ingester_chunks_flushed_total{cluster="dev"}[1m]))

每个块在刷新时都是满的,这并不要害,但它将改善许多方面的操作。因而,咱们目前的指导思想是尽可能防止动静标签,而偏向于过滤器表达式。例如,不要增加 level 的动静标签,而用 |= "level=debug" 代替。

3. 标签值必须始终是有界的

如果你要动静地设置标签,千万不要应用能够有无界值或有限值的标签。这总是会给 Loki 带来大问题。

尽量将值限度在尽可能小的范畴内。咱们对 Loki 能解决的数值没有完满的领导,但对于动静标签来说,要思考 个位数,或者10 个数值。这对动态标签来说就不那么重要了。例如,如果你的环境中有 1,000 台主机,那么有 1,000 个值的主机标签就会很好。

4. 留神客户端的动静标签

Loki 有几个客户端选项。Promtail(也反对 systemd 日志摄取和基于 TCP 的系统日志摄取),FluentD,Fluent Bit,一个 Docker 插件,以及更多!

每一个都有办法来配置用什么标签来创立日志流。但要留神可能会用哪些动静标签。应用 Loki 系列 API 来理解你的日志流是什么样子的,看看是否有方法缩小流和 cardinality。系列 API 的细节能够在 这里 找到,或者你能够应用 logcli 来查问 Loki 的系列信息。

5. 配置缓存

Loki 能够对数据进行多层次的缓存,这能够极大地提高性能。这方面的细节将在今后的文章中介绍。

6. 每条流的日志必须按工夫程序递增(新版本默认承受无序日志)

📝Notes:

新版本默认承受无序日志

许多人在应用 Loki 时遇到的一个问题是,他们的客户端收到了谬误的日志条目。这是因为 Loki 外部有一条硬性规定。

  • 对于任何繁多的日志流,日志必须总是以递增的工夫程序发送。如果收到的日志的工夫戳比该流收到的最新日志的工夫戳大,该日志将被放弃。

从这个申明中,有几件事须要分析。首先,这个限度是针对每个流的。让咱们看一个例子:

{job=”syslog”} 00:00:00 i’m a syslog!
{job=”syslog”} 00:00:01 i’m a syslog!

如果 Loki 收到这两行是针对同一流的,那么所有都会好起来。但这种状况呢?

{job=”syslog”} 00:00:00 i’m a syslog!
{job=”syslog”} 00:00:02 i’m a syslog!
{job=”syslog”} 00:00:01 i’m a syslog!  <- 回绝不合乎程序的!

嗯,额。….. 但咱们能做些什么呢?如果这是因为这些日志的起源是不同的零碎呢?咱们能够用一个额定的标签来解决这个问题,这个标签在每个零碎中是惟一的。

{job=”syslog”, instance=”host1”} 00:00:00 i’m a syslog!
{job=”syslog”, instance=”host1”} 00:00:02 i’m a syslog!
{job=”syslog”, instance=”host2”} 00:00:01 i’m a syslog!  <- 被承受,这是一个新的流!{job=”syslog”, instance=”host1”} 00:00:03 i’m a syslog!  <- 被承受,流 1 仍是有序的
{job=”syslog”, instance=”host2”} 00:00:02 i’m a syslog!  <- 被承受,流 2 仍是有序的

然而,如果应用程序自身产生的日志是不失常的呢?嗯,这恐怕是个问题。如果你用相似 promtail 管道阶段的货色从日志行中提取工夫戳,你反而能够不这样做,让 Promtail 给日志行调配一个工夫戳。或者你能够心愿在应用程序自身中修复它。

然而我想让 Loki 来解决这个问题!为什么你不能为我缓冲数据流并从新排序?说实话,因为这将给 Loki 减少大量的内存开销和复杂性,而正如这篇文章中的一个共同点,咱们心愿 Loki 简略而经济。现实状况下,咱们心愿改良咱们的客户端来做一些根本的缓冲和排序,因为这仿佛是解决这个问题的一个更好的中央。

另外值得注意的是,Loki 推送 API 的批处理性质可能会导致收到一些程序谬误的状况,这其实是误报。(兴许一个批处理局部胜利了,并呈现了;或者任何以前胜利的货色都会返回一个失序的条目;或者任何新的货色都会被承受)。

7. 应用 chunk_target_size

这是在 2020 年早些时候咱们 公布 Loki v1.3.0 时增加的,咱们曾经用它试验了几个月。当初咱们在所有的环境中都有 chunk_target_size: 1536000。这批示 Loki 尝试将所有的 chunks 填充到 1.5MB 的指标 压缩 大小。这些较大的块对 Loki 来说是更无效的解决。

其余几个配置变量会影响到一个块的大小。Loki 默认的 max_chunk_age 为 1 小时,chunk_idle_period 为 30 分钟,以限度所应用的内存量,以及在过程解体时失落日志的危险。

依据应用的压缩形式(咱们始终应用 snappy,它的可压缩性较低,但性能较快),你须要 5-10 倍或 7.5-10MB 的原始日志数据来填充 1.5MB 的块。记住,一个块是每一个流,你把你的日志文件分成的流越多,在内存中的块就越多,在它们被填满之前,它们被击中上述的超时的可能性就越大。

很多小的、未填充的块目前是 Loki 的顽石。咱们始终在致力改善这一点,并可能思考在某些状况下应用压缩器来改善这一点。然而,一般来说,领导准则应该放弃不变:尽力填充块。

如果你有一个应用程序,它的记录速度足以迅速填满这些块(远远小于max_chunk_age),那么应用动静标签将其分解成独立的数据流就变得更加正当。

总结

我最初再强调一次这个死马当活马医的主见吧!

为了性能而应用并行化,而不是标签和索引

对标签要严格要求。动态标签通常是好的,但动静标签应该少用。(如果你的日志流以每分钟 5-10MB 的速度写入,那么思考一个动静标签如何将其分成两到三个流,这能够进步查问性能。如果你的量比拟少,保持应用 过滤表达式。

索引不肯定是 Loki 的性能之路!首先要优先思考并行化和 LogQL 查问过滤。

请记住:与其余日志存储解决方案相比,Loki 须要一种不同的思维形式。咱们正在对 Loki 进行优化,以取得更少的数据流和更小的索引,这有助于填充更大的块,更容易通过并行化进行查问。

咱们正在踊跃改良 Loki,并钻研如何做到这一点。请务必持续关注 Loki 故事的开展,咱们都在推敲如何将这个真正无效的工具施展到极致!

Grafana 系列文章

Grafana 系列文章

三人行, 必有我师; 常识共享, 天下为公. 本文由东风微鸣技术博客 EWhisper.cn 编写.

正文完
 0