Elastic-Stack日志采集系统搭建
背景
在分布式应用的服务部署时,每个服务都会以集群的方式部署在多台机器上来防止单点故障,这种情况下多台机器都会有同一个服务的业务日志,只是每台机器都记录着该服务不同请求链路的业务日志,即使我们在日志中加入了调用链traceId,在业务异常发送需要查看业务日志时,若没有一个统一查看日志的平台系统,我们基本上都需要登陆到每一台机器上去看一遍这个服务的日志。效率极低,所以行业发展了一套比较成熟的日志采集观察的方案,简称ELK方案
Elastic-Stack是什么?
“ELK”是三个开源项目的首字母缩写,这三个项目分别是:Elasticsearch、Logstash 和 Kibana。Elasticsearch 是一个搜索和分析引擎。Logstash 是服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到诸如 Elasticsearch 等“存储库”中。Kibana 则可以让用户在 Elasticsearch 中使用图形和图表对数据进行可视化。
随着社区生态的发展,在2015年,Elastic公司向 ELK体系中加入了一系列轻量型的单一功能数据采集器,并把它们叫做 Beats,这个时候ELK 这个名称又要变了,把它叫做 BELK?BLEK?ELKB?当时的确有过继续沿用首字母缩写的想法。然而,对于扩展速度如此之快的堆栈而言,一直采用首字母缩写的确不是长久之计,所以最终确定以Elastic-Stack命名ELK的生态。
Elastic-Stack和ELK的区别
传统ELK采集日志的方案,我们一般会配套加入Kafka来完成整个日志采集的过程,整个日志采集管理的流程为:应用日志通过Application SDK的嵌入,将logger打印的日志都发送到Kafka,然后Logstash消费Kafka中的日志消息数据,并完成自定义的一些过滤规则,然后output到ES,最终在Kibana中就可以做日志查询了
上面说到ELK生态加入了Beats后,整个生态重新命名了,Beats是集合了多种单一用途数据采集器。它们从成百上千或成千上万台机器和系统向 Logstash 或 Elasticsearch 发送数据,截至当前(2021年7月)为止,官方共有7中Beats,分别是FileBeat(日志文件)、Metricbeat(指标)、Packetbeat(网络数据)、Winlogbeat(Windows事件日志)、Auditbeat(审计数据)、Heartbeat(运行时间监控)、Functionbeat(无需服务器的采集器),本文章主要介绍的FileBeat+ELK组合的日志采集方案,对其它数据采集器感兴趣的同学可以到Elastic官网学习,官方的文档还是比较详细的
FileBeat+ELK组合的日志采集方案中,Application不主动上报日志到ELK日志系统体,开发人员只需关注正常的业务开发以及日志打印,不关心日志怎么上传到ELK系统,FileBeat采集器根据配置指定的业务日志路径,主动采集解析业务日志内容上传到ELK系统,这里FileBeat其实就是业务部署机器上的一个Agent程序,这种方式的好处是做到了ELK系统和业务系统的完全解耦,对业务代码0侵入。下面就以从0到1部署一套Elastic-Stack采集JAVA应用日志的系统,让大家比较直观的了解这套体系的流程
一、ES集群部署
关于ES的集群部署,这里我选择使用docker-compose的方式部署,因为容器化管理应用是行业的趋势,且容器化管理服务部署简单、易复用,其它的好处就不再一一赘述,对docker技术不了解的同学建议可以学习一下。
Step1:环境配置
在机器安装好Docker后,需要修改一下Linux机器的句柄数,默认情况下一个进程只能打开65530个句柄,需要修改为262144,否则使用docker启用ES时会报错:max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]
临时修改方式:sysctl -w vm.max_map_count=262144
永久修改方式:vim /etc/sysctl.conf,添加vm.max_map_count=262144,然后重启机器
开放机器端口:9200、9300
Step2:编写docker-compose.yml文件
version: '3.8'
services:
#集群其中一个节点的配置
es01:
#镜像来源
image: elasticsearch:7.13.3
container_name: es01
restart: always
environment:
- node.name=es01
- cluster.name=es-cluster
#集群中其它节点的信息配置
- discovery.seed_hosts=es02,es03
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
#挂载配置,将ES数据持久化到本地机器磁盘,可以避免容器重启而丢失ES数据
volumes:
- /root/docker-compose-deploys/es/cluster/es01/data:/usr/share/elasticsearch/data
ports:
- 9200:9200
#声明ES集群使用的容器网络
networks:
- elastic
es02:
image: elasticsearch:7.13.3
container_name: es02
restart: always
environment:
- node.name=es02
- cluster.name=es-cluster
- discovery.seed_hosts=es01,es03
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- /root/docker-compose-deploys/es/cluster/es02/data:/usr/share/elasticsearch/data
networks:
- elastic
es03:
image: elasticsearch:7.13.3
container_name: es03
restart: always
environment:
- node.name=es03
- cluster.name=es-cluster
- discovery.seed_hosts=es01,es02
- cluster.initial_master_nodes=es01,es02,es03
- bootstrap.memory_lock=true
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- /root/docker-compose-deploys/es/cluster/es03/data:/usr/share/elasticsearch/data
networks:
- elastic
#创建一个ES集群使用的容器网络
networks:
elastic:
driver: bridge
Step3:启动服务命令
docker-compose up -d,启动后可以通过docker ps查看到3个ES容器,并通过docker logs -f <container_id>可以查看具体的容器日志,如下图

到这里,ES集群已经部署完成,还可以通过浏览器访问http://ip:9200/的方式验证ES是否准备就绪
二、Logstash部署
同理,logstash我也使用docker-compose的方式部署
Step1:docker-compose.yml的配置
version: '3.8'
services:
logstash:
image: logstash:7.13.3
restart: always
container_name: logstash
#配置挂载,容器加载使用本地准备好的配置文件
volumes:
- /root/docker-compose-deploys/logstash/pipeline/:/usr/share/logstash/pipeline/
- /root/docker-compose-deploys/logstash/conf/logstash.yml:/usr/share/logstash/config/logstash.yml
ports:
- 5000:5000
- 5044:5044
- 9600:9600
networks:
- elknet
#使用步骤一中创建的ES容器网络,这里需要注意,网络名通过外部执行命令docker network ls获得
networks:
elknet:
external:
name: cluster_elastic
Step2:准备步骤1中加载的2个配置文件:
conf/logstash.yml,这个配置文件指定logstash输出的ES地址
http.host: "0.0.0.0"
path.config: /usr/share/logstash/pipeline
xpack.monitoring.elasticsearch.hosts: http://192.168.2.146:9200
pipeline/logstash.conf,这个配置文件配置了logstash的过滤规则、输入输出方向等,关于filter配置的作用是我的自定义配置,后面会说明为什么我需要这样配置,若不理解这里可以先不关注这个细节
input {
beats {
port => 5044
}
}
filter {
ruby {
code => "
log_path = event.get('log')['file']['path']
event.set('service_name', log_path.split('/')[-2])
"
}
mutate {
add_field => {
"serviceName" => "%{service_name}"
}
}
}
output {
elasticsearch {
hosts => ["192.168.2.146:9200"]
}
}
Step3:启动Logstash服务
docker-compose up -d,同样,可以通过docker logs -f 观察容器日志是否有异常判断服务是否正常启动
三、Kibana部署
Step1:准备docker-compose.yml配置文件,因为使用的是5601端口,所以也要开放机器的5601端口
version: '3.8'
services:
kibana:
image: kibana:7.13.3
container_name: kibana
restart: always
#挂载使用本地准备的配置文件
volumes:
- /root/docker-compose-deploys/kibana/config/kibana.yml:/usr/share/kibana/config/kibana.yml
ports:
- 5601:5601
networks:
- cluster_elastic
#使用步骤一中创建的ES容器网络,这里需要注意,网络名通过外部执行命令docker network ls获得
networks:
cluster_elastic:
external: true
Step2:准备kibana本地的配置文件kibana.yml,配置指定了数据来源ES的地址
server.name: "kibana"
server.host: "0.0.0.0"
server.port: "5601"
elasticsearch.hosts: "http://192.168.2.146:9200"
Step3:启动Kibana服务
docker-compose up -d,同样,可以通过docker logs -f 观察容器日志是否有异常判断服务是否正常启动,服务正常的情况下通过浏览器访问:http://ip:5601/app/kibana,可以跳转到kibana的使用界面
四、fileBeat部署
关于filBeat的部署,和前三者不太一样,虽然也可以通过docker的方式部署,不过这里我选择使用非容器化的形式部署,下载官方的tar包,然后修改filebeat.yml的配置文件进行部署,因为docker方式的部署对于MacOS系统的用户不太友好,而本篇文章的案例,我将通过本地idea的启动java应用程序,将应用日志写到MacOS本地,然后再使用fileBeat采集MacOS本地的java日志,所以才选择官方的安装包方式部署,关于docker方式的部署,也可以参考官网的文档进行部署,只需要将本片文章的配置文件换成容器启动时挂载的配置文件即可
Step1:下载解压官方的安装包后,主要是修改解压目录下的filebeat.yml这个配置文件,主要修改的配置如下
# ============================== Filebeat inputs ===============================
filebeat.inputs:
- type: log
enabled: true
#这里配置fileBeat读取日志文件的路径,这个路径也是java应用程序logback配置中日志输出的路径,/*/*.log中的第一个*是服务名
paths:
- /Users/hzk/Desktop/ideaTmpLogs/*/*.log
#这里配置是处理多行日志合并的规则,因为对于Java异常堆栈的日志,如果没有配置这里,则堆栈的每一行将作为一个消息发送到ES
#而这里因为我的java应用日志,每一条日志的开头都是时间加[的形式,如: 2021-07-25 10:20:31.316 [
#所以该配置的意思是,正常匹配日志文件的数据,每次遇到新匹配成功的数据时,则从新匹配成功的位置到上一次匹配成功的位置之间的数据作为1条数据。这样保证了异常堆栈日志采集的完整性
multiline.type: pattern
multiline.pattern: '^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2}.[0-9]{3} \['
multiline.negate: true
multiline.match: after
# ------------------------------ Logstash Output -------------------------------
output.logstash:
# The Logstash hosts
hosts: ["192.168.2.146:5044"]
关于multiline的更多配置用法,可参考官网链接:https://www.elastic.co/guide/en/beats/filebeat/7.13/multiline-examples.html
Step2:启动fileBeat服务
转到fileBeat的解压目录,以后台进程的形式启动:nohup ./filebeat -e &
启动后,可以通过查看nohup.out观察进程日志,到这里整个ELK+FileBeat就已经部署完成,准备就绪了!
五、使用验证
这里需要说明一下的是,ELK第一次安装完后,还没有数据,所有也没有建立任何ES索引,我们在第一次跑完程序采集到日志到ES后,需要手动在Kibana创建索引,后续才可以使用这个索引查询日志
Step1:启动应用程序,执行一个异常流,打印异常堆栈信息,观察本地日志文件如下

Step2:在Kibana中查看日志,展开详情可以看到完整的异常堆栈信息

至此,整个Elastic-Stack日志采集系统已经能够正常使用,并能够准确的采集我们所需要的日志信息
Step3:其它说明
在上面配置Logstash的Filter的时候,我自定义给条日志数据添加了一个serviceName的属性,这是因为我需要在kibana查看日志的时候区分当前这条日志是属于哪一个服务的,因为我的日志路径规范为/xxx/serviceName/*.log,所以我需要截取日志采集路径的serviceName这一层文件夹的名称作为服务名遍历添加到ES日志数据的属性中,这也是为什么logstash我的filter需要配置split切割日志路径
六、总结
关于Elastic-Stack的日志采集系统的部署以及使用暂时就介绍到这里,这里其实有个点时需要考虑的,就是按照目前的配置方式,所有服务只要日志打印规范,切部署机器中安装好fileBeat代理,都可以动态的接入到ELK日志采集系统中,而无需在每次接入时重启Logstash或者ES服务,但这也存在一个问题,就是所有服务的日志都将在一个ES索引上,随着日后服务的递增以及日志量的递增,单一ES索引的数据量将达到巨大的情况下,是否会影响ES的查询检索效率,如果会影响我们是否应该考虑按照某一维度拆分服务群,不同的服务群使用不同的ES索引,避免单一索引数据量过大的情况,如何拆分索引这个还得根据不同业务团队的实际业务场景来决定,感兴趣的同学可以研究一下!