为什么需要集中式日志:Fluentd 解决的问题
当日志散落在几十个文件和格式中时会出什么问题,以及 Fluentd 的收集-转换-发送管线如何解决。
单个应用写入一个日志文件很容易阅读。而一个真实的系统 —— 若干服务、一个数据库、一个消息代理、一个 ingress 代理,各自以自己的格式写入各自的位置 —— 则不然。本指南介绍为什么这在规模增长时会变成一个真正的问题,以及 Fluentd 用来解决它的管线模型。
日志究竟是用来做什么的
日志不只是调试时的事后补救。同一份日志数据通常同时服务于几种不同的目的:
- 调试,追踪错误发生前后的情况。
- 安全,检测可疑活动,例如来自同一地址的反复登录失败,这只有通过跨时间或跨来源关联事件才能发现。
- 合规,某些行业要求在规定期限内保留特定记录。
- 审计,事后回答”谁在何时访问了什么”。
这些目的都需要数据集中在一个地方、可搜索,而不是散落在各台机器上。
朴素的日志方式在哪里崩溃
两种常见的起点都会在系统增长时撞上同一堵墙:
写入本地文件。 简单,而且正是大多数软件默认的日志方式,但原始文本文件并不是为分析而构建的。要通过比较五个不同应用日志文件中的时间戳来重建一个请求在系统中的路径,每个文件的格式都不同,手工做这件事往往繁琐至极,有时甚至不可行。
直接记录到数据库。 把所有内容直接发送到 Elasticsearch 或类似的存储,现在每个应用都需要自己的客户端库、自己的连接处理,以及对字段和格式的自己的约定。每个开发者实际上都在重新解决同一个集成问题,并且无法保证他们两次解决的方式相同。
这两种方式都无法处理你无法控制其代码的第三方组件 —— 数据库、消息代理、ingress 控制器,或普通的系统日志,它们产生日志所用的格式是它们被构建时的格式,而不是你自己的应用所选定的格式。
管线模型:收集、转换、发送
Fluentd 的做法是作为它自己的一层,位于日志的产生处与最终去处之间,结构为一条三阶段的管线:
sources Fluentd destinations
┌─────────────┐ ┌───────────────────┐ ┌──────────────────┐
│ app logs │ │ │ │ Elasticsearch │
│ system logs │ ───────▶ │ collect → parse │ ───────▶ │ S3 │
│ container │ │ → filter/enrich │ │ Kafka │
│ stdout │ │ │ │ a database, etc. │
└─────────────┘ └───────────────────┘ └──────────────────┘
Fluentd 从日志产生的任何地方收集日志,无论来源或原始格式。它解析并转换这些日志为一致的结构,把无结构的文本变成键值数据。在此过程中,它可以用有用的上下文丰富条目(哪个 pod、哪台主机、哪个容器产生了这一行),或者过滤它们,丢弃不需要的内容或对敏感字段脱敏。最后,它把结果发送到一个或多个目的地 —— 分析平台、数据存储、冷存储 —— 而无需每个上游应用知道或关心它们的日志最终落在哪里。
标签:Fluentd 如何在管线中路由数据
Fluentd 收集的每个日志条目都会获得一个标签(tag),这是你在配置条目来源时指定的字符串。标签是管线后续阶段用来决定什么适用于什么的依据:“用 Apache 日志格式解析带有 apache.access 标签的条目”、“过滤 app.* 标签的条目以剥离某个字段”、“将 *.errors 标签的条目发送到这个目的地,其余的发送到那个目的地”。编写这类配置的具体方法是本系列的下一篇指南;这里重要的是概念:标签是将一条数据与其来源连接起来的线索,一直贯穿到它最终被发送到的地方。
为什么值得有一个专门的层
不使用这样一条管线,并不意味着”不用做工作”,而是意味着同样的解析、丰富和路由逻辑,在每个记录日志的应用内部被重复实现,不一致,每个都略有不同。将其集中在一个可配置的层中,意味着这些逻辑只需编写一次,而每个新应用只需以对它最容易的格式产生日志 —— 纯文本、JSON、第三方工具已经在写的任何格式 —— 其余的由 Fluentd 负责使之保持一致。