最近,我收到一位读者朋友的私信,问我 ELK 为什么没有下篇,道德感极强的我不得不坦诚相告,显然这一篇鸽了。这就是说,鸽子不单单会出现在吴宇森的电影里,只要你试图拖延或者逃避,你一样有鸽子可以放飞。话说回来,新冠疫情已然持续了三年,而这篇文章其实是我在新冠元年写下的。某年某月,彼时彼刻,立春过后紧接着是上元节,阳光已透过玻璃宣示着春天的到来,可在这一墙之隔的里里外外,仿佛是两个气候迥异的世界。记忆里那种每天都和消毒水、口罩打交道的日子,后来就变成了一种习以为常、甚至有一点唏嘘的常态化生活。在这过去的三年里,恍惚中已经发生太多的事情,譬如 ELK 早已变成了 EFK,譬如前女友有了新的男朋友,在一切的物是人非背后,在一切的断壁残垣下面,我想,我还是用这个旧题目来讲一个新的故事罢!
从 Logstash 到 Filebeat
当初准备写这个系列的时候,ELK 还是经典的 Elastaicsearch
、 Logstash
和 Kibana
组合,如下图所示,Logstash
从各种不同的数据源收集数据,通过内置的管道对输入的数据进行加工。最终,这些数据会被存储到 Elastaicsearch
中供 Kibana
完成数据可视化。 即使放到三年后的今天来看,这张图依然是非常经典的一幅图。为什么这么说呢?因为自此以后,可视化日志分析平台的搭建,基本都是围绕这三个方面展开,甚至 Logstash
的继任者 Filebeat
、Fluentd
、Fluent-Bit
等等无一不沿用了 Logstash
的这套管道设计,足可见其对后来者的影响之深远。不过,作为先驱出现的 Logstash
,其本身是采用 Java
语言开发的,其插件则是采用 Ruby
语言开发的,特别是第一点,一直让 Logstash
在性能问题上遭人垢病。在实际使用中,你常常需要在每一台服务器上安装 Logstash
,这意味着它在 CPU 和内存上的占用会比较高。
为了解决这个问题,Elastic 官方推出了被称为 Beats
的下一代日志收集方案, 这是一种基于 Go 语言开发、更加轻量级的、资源占用更少的日志收集方案,可以认为是 Logstash
的替代品, 而 Filebeat
正好是其中一种实现。关于这两者的区别,我想,使用下面的比喻或许会更恰当一点, Logstash
就像一个轰鸣声不断的垃圾转运车,虽然可以让你直接把垃圾丢车上拉走,可你不得不忍受一整天的噪音;Filebeat
则像一个拎着扫帚和簸萁的环卫工人,那里有需要就去哪里清扫,不单单效率高而且不会让你感觉扰民,下面是一张来自 Elastic 官方文档 中的示意图:
从这里我们可以看出, Filebeat
由两个主要的组件 Inputs
和 Harvester
组成。其中, Harvester
是一个负责读取单个文件内容的采集器,它可以打开和关闭一个文件,并将内容发送到指定的输出;Inputs
顾名思义就是输入,对于 Filebeat
而言,其实就是指各种不同类型的日志文件,譬如 Filebeat
可以支持 Kafka、Redis、MQTT、TCP、UDP、Stdin、Syslog 等等的输入。从某种意义上讲,你可以把 Filebeat
理解为一个文件扫描服务。例如,下面的配置表示 Filebeat
将会从一个指定的路径读取日志文件:
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/logs/*.log
我们都知道,通常情况下,Docker
会将其日志存储在以下位置:/var/lib/docker/containers/*/*.log
,并且其日志格式为 JSON
。考虑到 Filebeat
本身就支持 JSON
格式,所以,你还可以通过下面类似的方式来收集 Docker
容器的日志:
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/lib/docker/containers/*/*.log
除此以外,我们提到 Filebeat
内置了大量的模块,使其可以支持 Apache、Kakfa、Envoy、MySQL 等等第三方系统的日志采集,通常位于 module.d
这个文件夹中。如下图所示,禁用的模块将会以 .disable
结尾:
接下来,为了使用这些模块。首先,我们要在 filebeat.yml
这个配置文件中增加下列内容,这相当于指定了模块所在的目录:
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: true
此时,我们进入容器并执行下列命令就可以启用 envoyproxy
这个模块:
./filebeat modules enable envoyproxy
实际上,这个命令的作用就是移除 .disable
这个后缀,同时重新载入配置。同理,如果我们将 enbale
换成 disable
则可以禁用一个模块。模块可以理解为官方帮你提前写好了各种 Input
配置,当然,如果光是启用一个模块是没有用的,你还需要对这些模块进行简单的配置,最基础的一点,就是你需要告诉这些模块你的日志文件存储在哪里,以博主这里的 Envoy
为例,我们执行下面的命令:
vi ./modules.d/envoyproxy.yml
此时,我们可以看到下面的内容,我们只需要覆盖 var.paths
这个参数即可:
# Module: envoyproxy
# Docs: https://www.elastic.co/guide/en/beats/filebeat/7.5/filebeat-module-envoyproxy.html
- module: envoyproxy
# Fileset for native deployment
log:
enabled: true
# Set custom paths for the log files. If left empty,
# Filebeat will choose the paths depending on your OS.
var.paths: ["/var/logs/request_log.log"]
对于 Filebeat
而言,其最基础的输出是 Logstatsh
和 Elasticsearch
,下面是相应的配置,当然,这里给出的都是最最基础的、没有任何套路的配置,如果你还需要密码、证书这些更细节的东西,请以官方文档为准,谢谢。
# 输出日志到 Logstash
output.logstash:
hosts: ["logstash:5044"]
# 输出日志到 Elasticsearch
output.elasticsearch:
hosts: ["192.168.50.162:9200"]
这里,我们以输出到 Elasticsearch
为例,默认情况下,Filebeat
会生成类似于 filebeat-6.3.2-2017.04.26
这样的索引文件,如果你不满足于这个索引名称,你可以在 output.elasticsearch
配置项下设置 index
字段,博主这里依然采用默认配置,如图所示,我们在 Kibana
中可以找到下面的索引:
更进一步地,我们可以看到每个经过 Envoy
代理的请求日志。这样,我们就达到了通过 Filebeat
收集日志的目的。和你们一样,在看到这个结果前,我已经在心里演练了无数遍的当当当当:
坦白地讲,如果所有的日志格式都能做到统一规范,譬如,全部采用 JSON
格式来存储,此时,通过 Filebeat
来收集日志还是挺惬意的一件事情,因为不需要操心日志解析或者说 Grok
这种东西。其实 Logstash
在性能上的瓶颈我还没有触及,真正劝退我的正是这套基于 Ruby
的 DSL
,曾经我选择 Hexo
而不是 Jekyll
,某种程度上是因为我抵触 Ruby
这门语言。总而言之,你可以自行决定是否跳过 Logstash
这个环节,因为据官方说后来的 Eleasticsearch
已经具备数据加工和过滤的能力,从这个角度看起来, Logstash
并不是唯一的选择,你觉得呢?更详细的代码和配置请参考我的 Github。
从 Filebeat 到 Fluentd
当然,Filebeat
的轻量级是以牺牲了一部分功能为代价的,譬如最典型的缺陷是,它不具备像 Logstash
那样强大的日志解析能力,在 codec
和 filter
这两个方面存在短板,虽然官方内置了大量的模块使其可以支持 Nginx、Envoy、Kafka、Redis 等等日志收集,可实际上,Filebeat
更为普遍的使用场景还是从一个文件里读出日志,然后将其丢给 Logstash
或者 Elasticsearch
,这意味着缓冲的压力只会出现在输出这个阶段。所以,你会注意到,现在人们开始考虑把日志转发到 Kafka 或者 Redis,以期达到“削峰”的目的。此时,Filebeat
的轻量级仿佛就成为了一种罪过,人们基于 Filebeat
开发了 Fluntd
,一种支持多个输入/输出,插件和功能更为丰富的日志收集工具。
不过,身处在一个技术更新换代非常快的时代,“过期”才是这个时代永恒的话题,一如王家卫电影里的那句经典台词:不知道从什么时候开始,在什么东西上都有个日期,秋刀鱼会过期,肉罐头也会过期,连保鲜纸都会过期 ,我开始怀疑,在这个世界上,还有什么东西是不会过期的?我不知道这是不是一种悲哀,因为等到博主打算写这篇文章的时候,Fluentd
已然有了新的挑战者 Fluent-Bit
,更神奇的是这两个产品都是来自同一家公司的,难道国外一样喜欢赛马的吗?总而言之,虽然这里的标题是 Fluentd,但我实际上用的是 Fluent-Bit,关于这两者间的关系,官方曾给出过一个对比:
从这张图中,我们可以看出,两者在性能上的差异并不显著,区别只是在实现的语言、插件数量上面,因为 Fluentd
需要依赖 Ruby
环境,我果断选择了放弃,好吧,我承认是因为现在的公司在用 Fluent-Bit
,我客观上受到了这一影响。因此,我接下来分享的内容,始终都是以 Fluent-Bit 为准,我将会分享两种容器下的日志收集策略,即 Tail
模式 和 Forward
模式。
Tail 模式
Tail
模式, 这里指 Fluent-Bit
中的一种输入模式,你可以认为它就是 Filebeat
里最经典的基于日志文件的收集模式,如图所示,假设我们我们有多个应用,它们内部已经通过 NLog 或者 Serilog 输出了 JSON
格式的日志文件,此时,我们只需要通过配置 Fluent-Bit
中的输入/输出就可以实现日志的收集:
我们前面提到过, Fluent-Bit
支持多个输入和输出,下面是一个简单的示例,它可以将当前 CPU 的使用情况输出到控制台中,可以注意到,输入和输出是通过标签来进行匹配的:
[INPUT]
Name cpu
Tag cpu_logs
[OUTPUT]
Name stdout
Match cpu_logs
更进一步地,你可以考虑将其输出到 Elasticsearch
,为此,我们需要新增加一个 OUTPUT
节点:
[INPUT]
Name cpu
Tag cpu_logs
[OUTPUT]
Name stdout
Match cpu_logs
[OUTPUT]
Name es
Match cpu_logs
Host efk_es
Port 9200
Index cpu_logs
Logstash_Format On
Logstash_Prefix cpu_logs
Logstash_DateFormat %Y-%m
Include_Tag_Key On
此时,如果不出意外的话,你可以在 Kinana
中看到下面的信息,这就是我们通过 Fluent-Bit
采集到的日志信息,这相当于一个简单的热身,目的是帮助大家快速地掌握 Fluent-Bit
配置文件的结构 :
类似地,对于一般的日志文件,我们可以按下面这种方式来进行配置,它表示从 /var/log/*.log
这个路径采集日志,按 JSON
格式解析并将其输出到 Elasticsearch
中:
[INPUT]
Name tail
Path /var/log/*.log
Path_Key On
Parser json
Tag app_logs
Mem_Buf_Limit 5MB
[OUTPUT]
Name es
Match app_logs
Host efk_es
Port 9200
Index app_logs
Logstash_Format On
Logstash_Prefix app_logs
Logstash_DateFormat %Y.%m
Include_Tag_Key On
需要说明的是,在 Fluent-Bit
中,每个输入或者过滤器的 Parser
是通过全局的 Parser_File
来指定的,该配置项位于 [SERVICE]
节点下:
[SERVICE]
Flush 1
Daemon Off
Log_Level info
Parsers_File parser.conf
HTTP_Server On
HTTP_Listen 0.0.0.0
HTTP_Port 2020
相对应地,我们需要准备一个 parser.conf
文件,这里我们定义了 json
和 docker
两种解析器,你还可以基于正则表达式定义更多的解析器:
[PARSER]
Name json
Format json
Time_Key time
Time_Format %Y-%m-%d %H:%M:%S %z
[PARSER]
Name docker
Format json
Time_Key time
Time_Format %Y-%m-%d %H:%M:%S.%L
Time_Keep On
一切准备就绪后,你就可以在 Kibana
中看到对应的日志啦,眼尖的朋友会发现这是一个典型的 ASP.NET Core
应用。还是那句话,如果能从日志源头上进行规范化,那么采集日志的时候就会相对轻松一点:
如果希望了解更多细节,请参考: https://github.com/Regularly-Archive/2022/tree/main/src/ELK/EFK。
Forward 模式
Forward
模式需要借助 Docker
的 logging-driver ,我们前面提到过,Docker
产生的日志文件,默认是存储在 /var/lib/docker/containers/*/*.log
这个路径下,并且这些日志文件是 JSON
格式的,这里面隐含的信息,其实就是 Docker
的 logging-driver
,简单来说,正因为 Docker
的 logging-driver
默认值是 json-file
,所以,它才会有产生 JSON
格式的日志文件这样一种行为。事实上,除了 json-file
以外,Docker
还支持下面的这些“驱动”:
在这里,我们会发现一个熟悉的身影 Fluentd
,所以,如果你问我 Fluentd
相比 Filebeat
还有什么优点,现在我或许会说,它天然地被 Docker
的日志驱动支持着。因为 Fluentd
和 Fluent-Bit
师出同门,所以,后者完全支持前者的协议。此时,我们的日志收集策略就不再是读取日志文件,而是直接通过 Docker
转发到 Fluent-Bit
,这种模式就被成为 Forward
模式,它是 Fluent-Bit
中的一种新的输入。
如图所示,在编排服务的环节,我们将应用的 logging-driver
设置为 fluentd
,同时指定 fluentd
或者 fluent-bit
的地址。此时,我们只需要在 Fluent-Bit
的配置文件中处理输入/输出即可完成日志的收集:
version: '3'
service:
fluent_bit:
image: cr.fluentbit.io/fluent/fluent-bit
container_name: efk_flb
volumes:
- ./config/fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf:rw
- ./config/fluent-bit/parser.conf:/fluent-bit/etc/parser.conf:rw
ports:
- "2020:2020"
- 24224:24224
- 24224:24224/udp
- 5140:5140/udp
app:
build: app/
container_name: efk_app
ports:
- 2333:80
logging:
driver: fluentd
options:
tag: docker-app
fluentd-address: fluent-bit:24224
depends_on:
- fluent_bit
其中 24224
这个端口是 Forward
模式需要用到的一个端口,它是在哪里用到了呢?其实是在 Fluent-Bit
的配置文件 fluent-bit.conf
里面:
[INPUT]
Name forward
Listen 0.0.0.0
Port 24224
buffer_chunk_size 1M
buffer_max_size 5M
tag forward_logs
[OUTPUT]
Name es
Match forward_logs
Host efk_es
Port 9200
Index forward_logs
Logstash_Format On
Logstash_Prefix forward_logs
Logstash_DateFormat %Y.%m
Include_Tag_Key On
简单来说,Fluent-Bit
会接收经由 Docker
转发的日志,我们再将其输出到 Elasticsearch
中,这种情况下我们不用关心应用的日志存储在什么地方,只要是输出到控制台的日志,我们转发到 Fluent-Bit
即可,显而易见,这是一种容器级别的日志收集方案。以前用 Logstash
和 Filebeat
的时候,我曾纠结过这个这个问题,而直到此时此刻,我才真正找到答案。所以,这是否说明,时间和地点更重要,如果晚一点遇见某个人,你是不是会做得稍微好一点?
如果希望了解更多细节,请参考: https://github.com/Regularly-Archive/2022/tree/main/src/ELK/EFK。
本文小结
准备这篇文章的这几天,有时会在脑海中重复说着一句话,大意是说,你可以怀念过去,可你不能一直都期待别人永远保持最初的样子。对我而言,这篇兴起于新冠元年的系列文章,一切都早已与当时相去甚远,因此,怀着一种物是人非、沧海桑田的心态来写这篇文章,我大抵是有一点从头再来的觉悟在里面。顺着 Logstash
-> Filebeat
-> Fluent-Bit
这样的脉络一点点探索,这三年间陆陆续续地经历了从 Wndows
到 Linux
再到 Docker
这种平台上的变化,日志收集的关注点更是从单个日志文件扩展到微服务、集群。整体而言,非侵入式的日志收集始终要比侵入式的日志收集要好,在源头上控制日志格式要比通 Grok
或者正则来加工要好。也许。日志收集的工具不止 ELK
,可一番探索下来你会发现它们是殊途同归,最难的工作其实是如何组合或者协调这些工具,这篇文章的内容相对琐碎,对于那些我没有写出来的内容,欢迎大家在评论区积极留言、参与讨论,好了,以上就是这篇文章的全部内容啦!