隆重推出 OpenTelemetry Collector 的新容器日志解析器

博客文章在发布后不会更新。这篇文章已经发布一年多了,其内容可能已过时,部分链接可能无效。在依赖任何信息之前,请务必核实。

正如最近的调查所示,Filelog receiver是 OpenTelemetry Collector 中最常用的组件之一。根据同一项调查,Kubernetes 是 Collector 部署的首选平台(占 80.6%)也就不足为奇了。基于这两点,我们可以意识到在 Kubernetes 环境中无缝收集日志的重要性。

目前,filelog receiver能够解析 Kubernetes Pod 中的容器日志,但它需要大量的配置才能根据各种容器运行时格式正确解析日志。原因是容器日志可能以各种已知格式出现,具体取决于容器运行时,因此您需要执行特定的操作集才能正确解析它们。

  1. 在运行时检测传入日志的格式。
  2. 相应地解析每种格式,同时考虑其特定于格式的特性。例如,定义它是 JSON 还是纯文本,并考虑时间戳格式。
  3. 提取预定义的模式所依赖的已知元数据。

这种高级操作序列可以通过将适当的 stanza 操作符链接在一起进行处理。最终结果相当复杂。可以通过使用相应的 helm chart 预设来减轻这种配置复杂性。但是,即使有了预设,用户在维护和排除此类高级配置时仍然会遇到挑战。

社区过去曾提出过改善 Kubernetes 日志收集体验的问题。实现这一目标的一步是提供一种简化且健壮的容器日志解析选项,而无需手动指定或维护实现细节。随着新的 container parser 的提议和实现,所有这些实现细节都被封装并处理在解析器的实现中。此外,单元测试和各种故障转移逻辑能够覆盖实现,这表明容器日志解析有了显著的改进。

容器日志的样子

首先,让我们快速回顾一下我们可以遇到的不同容器日志格式。

  • Docker 容器日志

    {"log":"INFO: This is a docker log line","stream":"stdout","time":"2024-03-30T08:31:20.545192187Z"}

  • cri-o 日志

    2024-04-13T07:59:37.505201169-05:00 stdout F This is a cri-o log line!

  • Containerd 日志

    2024-04-22T10:27:25.813799277Z stdout F This is an awesome containerd log line!

我们可以注意到 cri-o 和 containerd 的日志格式非常相似(都遵循 CRI 日志格式),但在时间戳格式上略有不同。

要正确处理这 3 种不同的格式,您需要 3 条不同的 stanza 操作符路由,正如我们在 container parser operator issue 中看到的。

此外,CRI 格式可以提供部分日志,而您希望首先将它们合并到一个日志中。

2024-04-06T00:17:10.113242941Z stdout P This is a very very long line th
2024-04-06T00:17:10.113242941Z stdout P at is really, really, long and spa
2024-04-06T00:17:10.113242941Z stdout F ns across multiple log entries

理想情况下,您希望我们的解析器能够自动在运行时检测格式并正确解析日志行。我们稍后会看到 container parser 会为我们做到这一点。

属性处理

容器日志文件遵循特定的命名模式,您可以从中在解析过程中提取有用的元数据信息。例如,从 /var/log/pods/kube-system_kube-scheduler-kind-control-plane_49cc7c1fd3702c40b2686ea7486091d3/kube-scheduler/1.log 中,您可以提取命名空间、Pod 的名称和 UID 以及容器的名称。

提取此元数据后,您需要使用符合 Semantic Conventions 的适当属性来正确存储它。此处理也可以封装在解析器的实现中,从而无需用户手动定义。

使用新的容器解析器

考虑到所有这些,容器解析器可以这样配置:

receivers:
  filelog:
    include_file_path: true
    include:
      - /var/log/pods/*/*/*.log
    operators:
      - id: container-parser
        type: container

该配置足以正确解析日志行并提取所有有用的 Kubernetes 元数据。可以明显看出现在所需的配置要少得多。正如原始提案中指出的那样,使用操作符的组合将产生大约 69 行配置。

写入 /var/log/pods/kube-system_kube-controller-kind-control-plane_49cc7c1fd3702c40b2686ea7486091d6/kube-controller/1.log 的日志行 {"log":"INFO: This is a docker log line","stream":"stdout","time":"2024-03-30T08:31:20.545192187Z"} 将产生如下日志条目:

{
  "timestamp": "2024-03-30 08:31:20.545192187 +0000 UTC",
  "body": "INFO: This is a docker log line",
  "attributes": {
    "time": "2024-03-30T08:31:20.545192187Z",
    "log.iostream": "stdout",
    "log.file.path": "/var/log/pods/kube-system_kube-controller-kind-control-plane_49cc7c1fd3702c40b2686ea7486091d6/kube-controller/1.log"
  },
  "resource": {
    "attributes": {
      "k8s.pod.name": "kube-controller-kind-control-plane",
      "k8s.pod.uid": "49cc7c1fd3702c40b2686ea7486091d6",
      "k8s.container.name": "kube-controller",
      "k8s.container.restart_count": "1",
      "k8s.namespace.name": "kube-system"
    }
  }
}

您会注意到不必定义格式。解析器会自动检测格式并相应地解析日志。即使是 cri-o 或 containerd 运行时可能产生的局部日志,也能正确地重新组合,而无需任何特殊配置。

这真的非常方便,因为作为用户,您无需关心指定格式,甚至无需为不同环境维护不同的配置。

实现细节

为了实现这个解析器操作符,大部分代码都是从头开始编写的,但是我们能够重用内部的 recombine 操作符来进行局部日志解析。为了实现这一点,需要进行一些小的重构,但这给了我们机会重用一个已经存在且经过充分测试的组件。

在讨论此功能实现期间,出现了一个问题:*为什么将其实现为操作符而不是处理器?*

一个基本的原因是到达处理器的日志记录顺序不保证。但是我们需要确保这一点,以便正确处理局部日志解析。这就是为什么目前将其实现为操作符是可行的。此外,目前建议在收集过程中尽可能多地完成工作,而强大的解析能力可以实现这一点。

有关实现讨论的更多信息,请参阅相应的 GitHub issue 及其相关的 PR。

最后但同样重要的是,我们应该提到,通过这个具体的容器解析器示例,我们可以看到改进的空间,以及未来我们如何为具有已知日志格式的流行技术进行进一步优化。

结论:使用 filelog receiver,容器日志解析现在更加容易

渴望了解更多关于容器解析器的信息?请访问官方 文档,如果您尝试了,请告诉我们您的想法。不要犹豫,在官方 CNCF Slack 工作区,特别是 #otel-collector 频道联系我们。

致谢

感谢 Daniel Jaglowski 对解析器实现的审查并提供宝贵的反馈!