今天(而且不仅仅是)Kubernetes 中的日志:期望与现实

今天(而且不仅仅是)Kubernetes 中的日志:期望与现实

现在已经是 2019 年了,我们在 Kubernetes 中仍然没有一个标准的日志聚合解决方案。 在这篇文章中,我们希望通过实际实践中的例子来分享我们的探索、遇到的问题和解决方案。

不过,首先我要保留一点,不同的客户通过收集日志理解的东西是非常不同的:

  • 有人想查看安全和审核日志;
  • 有人 - 整个基础设施的集中日志记录;
  • 对于某些人来说,仅收集应用程序日志就足够了,不包括平衡器等。

以下是我们如何实现各种“愿望清单”以及遇到的困难的剪辑。

理论:关于日志记录工具

日志系统组件的背景知识

日志记录已经取得了长足的进步,因此开发了收集和分析日志的方法,这就是我们今天使用的方法。 早在 1950 世纪 XNUMX 年代,Fortran 就引入了标准输入/输出流的模拟,这有助于程序员调试程序。 这些是第一批计算机日志,使当时的程序员的生活变得更加轻松。 今天我们在它们中看到了日志系统的第一个组件 - 日志的来源或“生产者”.

计算机科学并没有停滞不前:计算机网络出现了,第一个集群......由多台计算机组成的复杂系统开始工作。 现在,系统管理员被迫从多台计算机收集日志,并且在特殊情况下,他们可以添加操作系统内核消息,以便需要调查系统故障。 为了描述集中式日志收集系统,在 2000 年代初发布了 RFC 3164,标准化了remote_syslog。 这就是另一个重要组件的出现方式: 日志收集器 以及它们的存储。

随着日志量的增加和Web技术的广泛引入,出现了需要方便地向用户展示哪些日志的问题。 简单的控制台工具(awk/sed/grep)已被更高级的工具取代 记录浏览者 - 第三部分。

由于日志量的增加,其他事情变得清晰:日志是需要的,但不是全部。 而且不同的日志需要不同程度的保存:有些可能一天之内就丢失,而另一些则需要保存5年。 因此,日志系统中添加了一个用于过滤和路由数据流的组件——我们称之为 筛选.

存储也实现了重大飞跃:从常规文件到关系数据库,再到面向文档的存储(例如Elasticsearch)。 所以存储与收集器是分开的。

最终,日志的概念已经扩展为一种我们想要保存为历史的抽象事件流。 或者更确切地说,如果您需要进行调查或起草分析报告......

因此,在较短的时间内,日志采集已经发展成为一个重要的子系统,堪称大数据的子系统之一。

今天(而且不仅仅是)Kubernetes 中的日志:期望与现实
如果以前普通的印刷品足以满足“记录系统”的需要,那么现在情况已经发生了很大变化。

Kubernetes 和日志

当 Kubernetes 来到基础设施时,已经存在的日志收集问题也没有绕开。 在某些方面,它变得更加痛苦:基础设施平台的管理不仅被简化,同时也变得复杂。 许多旧服务已经开始迁移到微服务。 在日志方面,这体现在越来越多的日志源、其特殊的生命周期以及需要通过日志跟踪所有系统组件的关系……

展望未来,我可以说,不幸的是,现在 Kubernetes 还没有一个可以与其他所有日志选项相媲美的标准化日志选项。 社区中最流行的方案如下:

  • 有人展开堆栈 SFAO (Elasticsearch、Fluentd、Kibana);
  • 有人正在尝试最近发布的 洛基 或使用 记录操作员;
  • 我们 (也许不仅仅是我们?..) 我对自己的发展非常满意—— 木屋...

通常,我们在 K8s 集群中使用以下捆绑包(用于自托管解决方案):

不过,我不会详细介绍它们的安装和配置说明。 相反,我将重点关注它们的缺点以及关于日志总体情况的更全面的结论。

在 K8s 中使用日志进行练习

今天(而且不仅仅是)Kubernetes 中的日志:期望与现实

“日常日志”,你们有多少人?

从相当大的基础设施中集中收集日志需要大量资源,这些资源将用于收集、存储和处理日志。 在各个项目的运营过程中,我们面临着各种各样的需求以及由此产生的运营问题。

让我们尝试一下 ClickHouse

让我们看一下一个项目的集中式存储,该项目的应用程序非常活跃地生成日志:每秒超过 5000 行。 让我们开始处理他的日志,将它们添加到 ClickHouse 中。

一旦需要最大实时性,带有 ClickHouse 的 4 核服务器的磁盘子系统就已经过载:

今天(而且不仅仅是)Kubernetes 中的日志:期望与现实

这种类型的加载是因为我们试图尽快在 ClickHouse 中编写。 数据库会通过增加磁盘负载来对此作出反应,这可能会导致以下错误:

DB::Exception: Too many parts (300). Merges are processing significantly slower than inserts

事实是, 合并树表 ClickHouse中的(它们包含日志数据)在写操作时有自己的困难。 插入其中的数据会生成一个临时分区,然后与主表合并。 这样一来,记录对磁盘的要求非常高,而且还受到上面通知的限制:1秒内不能合并超过300个子分区(实际上这是300个插入)每秒)。

为了避免这种行为, 应该写给 ClickHouse 尽可能大的片段,并且每 1 秒不超过 2 次。 然而,大量的写入表明我们应该减少在 ClickHouse 中的写入频率。 这反过来又可能导致缓冲区溢出和日志丢失。 解决方案是增加Fluentd缓冲区,但这样内存消耗也会增加。

注意:我们的 ClickHouse 解决方案的另一个问题与我们的案例(木屋)中的分区是通过连接的外部表实现的这一事实有关 合并表。 这导致了这样一个事实:当对大时间间隔进行采样时,需要过多的 RAM,因为元表会迭代所有分区 - 即使是那些显然不包含必要数据的分区。 然而,现在可以安全地宣布这种方法对于当前版本的 ClickHouse 来说已过时(c 18.16).

由此可见,并非每个项目都有足够的资源在 ClickHouse 中实时收集日志(更准确地说,它们的分布并不合适)。 此外,您还需要使用 电池,我们稍后会返回。 上述案例是真实存在的。 当时我们无法提供适合客户并允许我们以最小延迟收集日志的可靠且稳定的解决方案......

弹性搜索怎么样?

众所周知,Elasticsearch 可以处理繁重的工作负载。 让我们在同一个项目中尝试一下。 现在负载看起来像这样:

今天(而且不仅仅是)Kubernetes 中的日志:期望与现实

Elasticsearch 能够消化数据流,但是向其中写入此类卷会极大地利用 CPU。 这是通过组织集群来决定的。 从技术上讲,这不是问题,但事实证明,仅仅为了操作日志收集系统,我们就已经使用了大约 8 个核心,并且系统中还有一个额外的高负载组件......

底线:此选项是合理的,但前提是项目很大并且其管理层准备在集中式日志记录系统上花费大量资源。

那么一个自然的问题就出现了:

真正需要什么日志?

今天(而且不仅仅是)Kubernetes 中的日志:期望与现实 让我们尝试改变方法本身:日志应该同时提供信息而不是覆盖 系统中的事件。

假设我们有一家成功的在线商店。 哪些日志很重要? 收集尽可能多的信息(例如从支付网关收集信息)是个好主意。 但并非产品目录中图像切片服务的所有日志对我们都至关重要:只有错误和高级监控就足够了(例如,该组件生成的 500 个错误的百分比)。

所以我们得出的结论是 集中式日志记录并不总是合理的。 很多时候,客户端希望将所有日志收集到一个地方,尽管事实上,从整个日志中,只需要有条件的 5% 对业务至关重要的消息:

  • 有时,仅配置容器日志和错误收集器(例如 Sentry)的大小就足够了。
  • 错误通知和大型本地日志本身通常足以调查事件。
  • 我们的项目仅使用功能测试和错误收集系统。 开发人员不需要这样的日志 - 他们可以从错误跟踪中看到一切。

来自生活的插画

另一个故事可以作为一个很好的例子。 我们收到了一位客户安全团队的请求,该客户已经在使用早在 Kubernetes 推出之前就开发的商业解决方案。

有必要将集中式日志收集系统与企业问题检测传感器QRadar“交朋友”。 该系统可以通过 syslog 协议接收日志并从 FTP 检索它们。 但是,无法立即将其与 Fluentd 的 Remote_syslog 插件集成 (事实证明, 我们并不孤单)。 事实证明,设置 QRadar 时出现的问题出在客户安全团队这边。

结果,部分业务关键日志被上传到 FTP QRadar,另一部分通过远程系统日志直接从节点重定向。 为此我们甚至写了 简单的图表 - 也许它将帮助某人解决类似的问题...由于最终的方案,客户自己接收并分析了关键日志(使用他最喜欢的工具),我们能够降低日志系统的成本,仅节省上个月。

另一个例子很能说明什么是不该做的。 我们的一位加工客户 来自用户的事件,做成多行 非结构化输出 日志中的信息。 正如您可能猜到的那样,此类日志的读取和存储都极其不方便。

日志标准

这样的例子得出的结论是,除了选择日志收集系统之外,您还需要 还自己设计日志! 这里有什么要求?

  • 日志必须采用机器可读的格式(例如 JSON)。
  • 日志应该紧凑,并且能够更改日志记录的程度,以便调试可能的问题。 同时,在生产环境中,您应该运行具有如下日志级别的系统 警告 или 误差.
  • 日志必须规范化,即在一个日志对象中,所有行必须具有相同的字段类型。

非结构化日志可能会导致将日志加载到存储中时出现问题并导致其处理完全停止。 作为说明,下面是一个错误 400 的示例,许多人在 fluidd 日志中肯定遇到过该错误:

2019-10-29 13:10:43 +0000 [warn]: dump an error event: error_class=Fluent::Plugin::ElasticsearchErrorHandler::ElasticsearchError error="400 - Rejected by Elasticsearch"

该错误意味着您正在使用现成的映射向索引发送类型不稳定的字段。 最简单的例子是 nginx 日志中带有变量的字段 $upstream_status。 它可以包含数字或字符串。 例如:

{ "ip": "1.2.3.4", "http_user": "-", "request_id": "17ee8a579e833b5ab9843a0aca10b941", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staffs/265.png", "protocol": "HTTP/1.1", "status": "200", "body_size": "906", "referrer": "https://example.com/staff", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.001", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "127.0.0.1:9000", "upstream_status": "200", "upstream_response_length": "906", "location": "staff"}
{ "ip": "1.2.3.4", "http_user": "-", "request_id": "47fe42807f2a7d8d5467511d7d553a1b", "time": "29/Oct/2019:16:18:57 +0300", "method": "GET", "uri": "/staff", "protocol": "HTTP/1.1", "status": "200", "body_size": "2984", "referrer": "-", "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.70 Safari/537.36", "request_time": "0.010", "cache_status": "-", "upstream_response_time": "0.001, 0.007", "upstream_addr": "10.100.0.10:9000, 10.100.0.11:9000", "upstream_status": "404, 200", "upstream_response_length": "0, 2984", "location": "staff"}

日志显示服务器 10.100.0.10 响应了 404 错误,并且请求被发送到另一个内容存储。 结果,日志中的值变成了这样:

"upstream_response_time": "0.001, 0.007"

这种情况非常普遍,甚至值得单独讨论 文档中的引用.

可靠性怎么样?

有时,所有日志无一例外都至关重要。 因此,上面提出/讨论的典型的 K8 日志收集方案存在问题。

例如,Fluentd 无法从短期容器中收集日志。 在我们的一个项目中,数据库迁移容器存活了不到 4 秒,然后被删除 - 根据相应的注释:

"helm.sh/hook-delete-policy": hook-succeeded

因此,迁移执行日志未包含在存储中。 在这种情况下,政治可以提供帮助。 before-hook-creation.

另一个例子是 Docker 日志轮转。 假设有一个应用程序主动写入日志。 在正常情况下,我们设法处理所有日志,但一旦出现问题(例如,如上所述,格式不正确),处理就会停止,Docker 会轮换文件。 结果是关键业务日志可能会丢失。

这就是为什么 分离日志流很重要,将最有价值的邮件直接发送到应用程序中以确保其安全。 此外,创建一些也不是多余的 日志的“累加器”,它可以在短暂的存储不可用的情况下生存,同时保存关键消息。

最后,我们一定不要忘记 正确监控任何子系统非常重要。 否则很容易遇到 fluidd 处于这种状态的情况 CrashLoopBackOff 并且不发送任何内容,这可能会丢失重要信息。

发现

在本文中,我们不会讨论 Datadog 等 SaaS 解决方案。 这里描述的许多问题已经被专门收集日志的商业公司以这种或那种方式解决了,但由于各种原因并不是每个人都可以使用SaaS (主要是成本和遵守152-FZ).

集中式日志收集乍一看似乎是一项简单的任务,但事实并非如此。 重要的是要记住:

  • 仅需要详细记录关键组件,而可以为其他系统配置监控和错误收集。
  • 生产中的日志应保持最少,以免增加不必要的负载。
  • 日志必须是机器可读的、规范化的并且具有严格的格式。
  • 真正关键的日志应该在单独的流中发送,该流应该与主要日志分开。
  • 值得考虑日志累加器,它可以使您免于突发的高负载并使存储上的负载更加均匀。

今天(而且不仅仅是)Kubernetes 中的日志:期望与现实
这些简单的规则如果应用于各处,将使上述电路正常工作 - 即使它们缺少重要组件(电池)。 如果您不遵守这些原则,该任务将很容易将您和基础架构引导到系统的另一个高负载(同时无效)的组件。

PS

另请阅读我们的博客:

来源: habr.com

添加评论