0.开篇
0.1.环境
本篇所有的案例使用的环境基于以下版本:
- Linux操作系统:CentOS-7-x86_64
- jdk:jdk-8u211-linux-x64.tar.gz
- Elastic Stack:7.2.0
1.认识Elastic Stack
1.1.ELK & ELK Stack & Elastic Stack
在当前互联网时代,每天会有超大数量的日志产生,在很多情况下,我们需要收集这些杂乱无章的日志进行处理并保存起来,最终以可视化的方式展示出来,让它们变成有价值的数据,这个过程也可以称之为数据探索。
现如今比较大的互联网应用可能会部署到成千上万台服务器上运行,我们要想将庞大的日志数据变为有价值的数据,通常面临几个问题:
- 如此多的服务器日志,如何去实时采集?
- 各个服务的日志格式复杂且可能互不相同,如何将它们转化成结构化数据?
- 日志数据过于庞大,应当使用什么去存储?又应当如何去在庞大的数据中进行搜索?
- 如何将存储好的数据人性化的展示出来?
- …
ELK
的出现就是为了解决以上问题。
ELK
是由三个开源项目组成:ElasticSearch
、Logstash
、Kibana
按照 Elastic 官方的说法,ELK
的一切都从ElasticSearch
开始。ElasticSearch
是Shay Banon( Elastic 公司的创始人)开发的,第一个版本叫Compass
。后来ElasticSearch
的影响不断扩大,催生了Logstash
和Kibana
两个开源项目,这三个开源项目组成了ELK
,其中Logstash
负责日志的采集、过滤,ElasticSearch
负责日志数据的存储以及检索,Kibana
负责日志数据的图形化展示。
在2015年,一个名为Packbeat
的开源项目引起了Elastic公司的重视,它是一个以一种轻量级方式将网络数据发送到ElasticSearch
的开源项目。Elastic团队由此引申,开发出了一组专门用于数据传送的轻量级组件,可以将网络、日志、指标、审计等各种数据从不同的数据源头发送到Logstash
或Kibana
。Elastic公司给这种组件起了一个统一的名字,这就是Beats
组件。
有了Beats
组件的加入,使得ELK
这个名称不能再概括Elastic的所有开源项目了,于是ELK就自然而然的更名为ELK Stack。但是这个名称本身又是缩写含义,依然容易引起误会,所以最终它们被统称为Elastic Stack
。
1.2.版本演变
早期,由于Elastic Stack
包含的开源项目“各自为政”,每个项目都有一套自己管理版本的颁发,使得用户必须要了解并处理不同版本之间的兼容问题。
2015年ELK 2.0作为一个整体同时发布,解决了版本协调同步与兼容问题。自此以后,用户不管使用哪个版本的Elastic Stack
,只要使所有组件的版本统一即可。
1.3.经典架构
在Beats
组件加入后,日志系统的架构发生了改变。大多数情况下,可以使用Beats
中的filebeat
对应用服务日志进行采集,而logstash则只负责日志的传输和过滤。
如上图,filebeat
采集服务日志,发送给logstash
进行处理,然后将处理好的数据存入ElasticSearch
中,最终由Kibana
进行展示出来。
这个架构并不是完美的架构,logstash
如果处理日志的速度没有filebeat
采集日志的速度快,那么可能会发生采集卡顿的现象,为了解决这一问题,我们可以在filebeat
和logstash
中加一层缓冲,这个缓冲可以用Redis
来做,但是更推荐使用Kafka
消息队列。
有了消息队列的存在,我们就不用担心filebeat
和logstash
速率不一致的问题了,但是在生产环境,往往还会遇到其它问题导致采集卡顿,并且这个问题大多是因为在logstash
处理日志时候处理慢造成的,遇到这种情况,就需要开发者去耐心的调试处理日志的逻辑了。
2.ElasticSearch
2.1.ElasticSearch简介
ElasticSearch
是一个分布式、可扩展、近实时的高性能搜索与数据分析引擎。
ElasticSearch
提供了搜集、分析、存储数据三大功能,其主要特点有:分布式、零配置、易装易用、自动发现、索引自动分片、索引副本机制、RESTful风格接口、多数据源和自动搜索负载等。
ElasticSearch
基于Java
编写,其内部使用Lucene
做索引与搜索。它将Lucene
的复杂性封装了起来,对外暴露了简单易操作的接口。
2.2.Lucene简介
Lucene
是一个免费、开源、高性能、纯Java
编写的全文检索引擎。
在业务开发场景中,Lucene
几乎适用任何需要全文检索的场景。
2005年,Lucene
升级成为Apache顶级项目。
Lucene
仅仅是一个工具包,主要提供倒排索引的查询结构,以方便软件开发人员在其业务系统中实现全文检索的功能。
Lucene
作为一个全文检索引擎工具包,具有如下突出优点:
- 索引文件格式独立于应用平台:
Lucene
定义了一套以8位字节为基础的索引文件格式,使得兼容系统或者不同平台的应用能够共享建立的索引文件。 - 索引速度快:在传统全文检索引擎的倒排索引的基础上,实现了分块索引,能够针对新的文件建立小文件索引,提升索引速度。然后通过与原有索引的合并,达到优化的目的。
- 简单易学:优秀的面向对象的系统架构,降低了
Lucene
扩展的学习难度,方便扩充新功能。 - 跨语言:设计了独立于语言和文件格式的文本分析接口,索引器通过接收Token流完成索引文件的创立,用户扩展新的语言和文件格式,只需实现文本分析的接口即可。
- 强大的查询引擎:
Lucene
默认实现了一套强大的查询引擎,用户无需自己编写代码即可通过系统获得强大的查询能力。Lucene
默认实现了布尔操作、模糊查询、分组查询等。Lucene
的主要模块有Analysis模块、Index模块、Store模块、QueryParser模块、Search模块和Similarity模块,各模块的功能分别汇总如下:- Analysis模块:主要负责词法分析及语言处理,也就是我们常说的分词,通过该模块可最终形成存储或者搜索的最小单元Term。
- Index模块:主要负责索引的创建工作。
- Store模块:主要负责索引的读和写,主要是对文件的一些操作,其主要目的是抽象出和平台文件系统无关的存储。
- QueryParser模块:主要负责语法分析,把查询语句生成Lucene底层可以识别的条件。
- Search模块:主要负责对索引的搜索工作。
- Similarity模块:主要负责相关性打分和排序实现。
在Lucene
中,还有一些核心术语,主要涉及Term、词典(Term Dictionary,也叫做字典)、倒排表(Posting List)、正向信息和段(Segment),这些属于的含义汇总如下:
- Term:索引中最小的存储和查询单元。对于英文语境而言,一般是指一个单词;对于中文词境而言,一般是指一个分词后的词。
- 词典:是Term的集合。词典的数据结构有很多种,各有优缺点。如可以通过排序数据(通过二分查找来检索数据)、HashMap(哈希表,检索速度更快,属于空间换时间的模式)、FST(FiniteState Transducer,有很好的压缩率)等来实现。
- 倒排表:一篇文章通常由多个词组成,倒排表记录的是某个词在哪些文章中出现过。
- 正向信息:原始的文档信息,可以用来做排序、聚合、展示等。
- 段:索引中最小的独立存储单元。一个索引文件由一个或者多个段组成。在Lucence中,段有不变性,段一旦生成,在段上只能读取、不可写入。
2.3安装ElasticSearch
我们可以使用docker-compose部署ElasticSearch
集群 在这之前需要先安装docker环境,参考附录2 安装docker环境。
为了方便操作ElasticSearch
我们在安装ElasticSearch
集群的同时安装Kibana
, 这里只是简单的使用Kibana
操作ElasticSearch
, 并未对Kibana
进行详细讲解
2.3.1.集群规划
node1 | node2 | node3 | |
---|---|---|---|
ElasticSearch | ok | ok | ok |
Kibana | ok |
2.3.2.修改开辟虚拟内存
ElasticSearch
需要开辟一个65536字节空间以上的虚拟内存, 而Linux默认不存续任何用户和应用开启虚拟内存
修改/etc/sysctl.conf
vim /etc/sysctl.conf
# 在文件中追加一行
vm.max_map_count=262144
# 使用此命令使配置生效
sysctl -p
2.3.3.编写docker-compose.yml
2.3.3.1.node1
version: '3'
services:
elasticsearch_n0:
image: elasticsearch:7.2.0
container_name: elasticsearch_n0
privileged: true
environment:
- cluster.name=elasticsearch-cluster
- node.name=node0
- node.master=true
- search.max_buckets=100000000
- node.data=true
- bootstrap.memory_lock=true
- http.cors.enabled=true
- http.cors.allow-origin=*
- cluster.initial_master_nodes=node0
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.zen.ping.unicast.hosts=192.168.133.11,192.168.133.12,192.168.133.13
- discovery.zen.minimum_master_nodes=2
- discovery.zen.ping_timeout=120s
- client.transport.ping_timeout=60s
- network.publish_host=192.168.133.11
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- /etc/localtime:/etc/localtime
- ./data:/usr/share/elasticsearch/data
- ./logs:/usr/share/elasticsearch/logs
ports:
- 9200:9200
- 9300:9300
restart: always
networks:
- es_cluster_net
kibana:
image: kibana:7.2.0
container_name: kibana
ports:
- 5601:5601
volumes:
- /etc/localtime:/etc/localtime
- ./kibana/kibana.yml:/usr/share/kibana/config/kibana.yml:rw
depends_on:
- elasticsearch_n0
restart: always
networks:
- es_cluster_net
networks:
es_cluster_net:
external: true
在docker-compose.yml的同级目录创建一个kibana
文件夹, 进入这个文件夹, 创建kibana.yml
配置文件:
vim kibana.yml
server.name: kibana
server.host: "0"
elasticsearch.hosts: [ "http://192.168.144.11:9200" ]
xpack.monitoring.ui.container.elasticsearch.enabled: true
i18n.locale: zh-CN
使用普通用户创建好data
目录和logs
目录
mkdir data
mkdir logs
2.3.3.2.node2
version: '3'
services:
elasticsearch_n1:
image: elasticsearch:7.2.0
container_name: elasticsearch_n1
privileged: true
environment:
- cluster.name=elasticsearch-cluster
- node.name=node1
- node.master=true
- search.max_buckets=100000000
- node.data=true
- bootstrap.memory_lock=true
- http.cors.enabled=true
- http.cors.allow-origin=*
- cluster.initial_master_nodes=node0
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.zen.ping.unicast.hosts=192.168.133.11,192.168.133.12,192.168.133.13
- discovery.zen.minimum_master_nodes=2
- discovery.zen.ping_timeout=120s
- client.transport.ping_timeout=60s
- network.publish_host=192.168.133.12
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- /etc/localtime:/etc/localtime
- ./data:/usr/share/elasticsearch/data
- ./logs:/usr/share/elasticsearch/logs
ports:
- 9200:9200
- 9300:9300
restart: always
networks:
- es_cluster_net
networks:
es_cluster_net:
external: true
使用普通用户创建好data
目录和logs
目录
mkdir data
mkdir logs
2.3.3.3.node3
version: '3'
services:
elasticsearch_n2:
image: elasticsearch:7.2.0
container_name: elasticsearch_n2
privileged: true
environment:
- cluster.name=elasticsearch-cluster
- node.name=node2
- node.master=true
- search.max_buckets=100000000
- node.data=true
- bootstrap.memory_lock=true
- http.cors.enabled=true
- http.cors.allow-origin=*
- cluster.initial_master_nodes=node0
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
- discovery.zen.ping.unicast.hosts=192.168.133.11,192.168.133.12,192.168.133.13
- discovery.zen.minimum_master_nodes=2
- discovery.zen.ping_timeout=120s
- client.transport.ping_timeout=60s
- network.publish_host=192.168.133.13
ulimits:
memlock:
soft: -1
hard: -1
volumes:
- /etc/localtime:/etc/localtime
- ./data:/usr/share/elasticsearch/data
- ./logs:/usr/share/elasticsearch/logs
ports:
- 9200:9200
- 9300:9300
restart: always
networks:
- es_cluster_net
networks:
es_cluster_net:
external: true
使用普通用户创建好data
目录和logs
目录
mkdir data
mkdir logs
2.3.4.启动 & 访问
在三个节点使用docker-compose up -d
运行即可
然后可以访问: http://{node1}:5601 可以看到Kibana
页面
访问: http://{node1}:9200 可以获取ElasticSearch
集群信息
接下来我们在Kibana的工作台进行操作
2.4.索引操作
2.4.1.最基本的操作索引
2.4.1.1.添加索引
添加一个名为test
的索引
PUT test
{
“acknowledged” : true,
“shards_acknowledged” : true,
“index” : “test”
}
2.4.1.2.获取索引
获取名为test
的索引
GET test
{
“test” : {
“aliases” : { },
“mappings” : { },
“settings” : {
“index” : {
“creation_date” : “1608192737117”,
“number_of_shards” : “1”,
“number_of_replicas” : “1”,
“uuid” : “lU6bLKm4RQGZ6Os-i8_Wsw”,
“version” : {
“created” : “7020099”
},
“provided_name” : “test”
}
}
}
}
2.4.1.3.查看索引是否存在
查看test
索引是否存在
HEAD test
200 - OK
说明: 返回200说明索引存在, 如果索引不存在会返回404
2.4.1.4.删除索引
删除test
索引
DELETE test
{
“acknowledged” : true
}
2.4.2.索引配置
2.4.2.1.创建索引的同时指定分片和副本数
PUT test1
{
"settings": {
"index": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
}
{
“acknowledged” : true,
“shards_acknowledged” : true,
“index” : “test1”
}
也可以使用下面的方式, 效果一样
PUT test2
{
"settings": {
"index.number_of_shards": 3,
"index.number_of_replicas": 2
}
}
{
“acknowledged” : true,
“shards_acknowledged” : true,
“index” : “test2”
}
索引配置包括静态配置和动态配置两种, 静态配置只能在索引创建时或索引关闭时设置, 而动态配置没有这个限制
2.4.2.2.索引关闭与打开
索引可以被关闭,关闭后的索引除了维护自身元数据信息以外,基本上不会再占用集群资源,同时也不能再被用户读写。索引关闭后可以再次打开,所以通过关闭索引可以实现索引存档的目的。
关闭
POST /test/_close
{
“acknowledged” : true,
“shards_acknowledged” : true
}
打开
POST /test/_open
{
“acknowledged” : true,
“shards_acknowledged” : true
}
2.4.2.3.索引静态配置
索引静态配置只能在创建索引时设置,一旦索引创建完成就不能再修改静态配置。
参数名 | 默认值 | 说明 |
---|---|---|
number_of_shards | 5 | 主分片数量,范围1~1024,es.index.max_number_of_shards可设置上限 |
shard.check_on_startup | false | 是否打开分片前校验,校验错误会阻止分片打开。可选值: false:默认值,在打开分片前不做数据校验; checksum:使用checksum做数据完整性校验; true:做checksum完整性校验和数据逻辑校验 |
codec | default | 索引保存时采用的压缩编码算法,可选值含义: default:默认编码为LZ4,速度快但压缩率不高; best_compresses:DEFLATE编码,速度慢但压缩率高 |
routing_partition_size | 1 | 自定义路由中分区数量,应该大于1且小于number_of_shards,默认值1代表不使用 |
load_fixed_bitset_filter_eagerly | true | 是否加载bitset过滤器,可选值true/false |
2.4.2.4.索引动态配置
ElasticSearch
为查询和修改索引配置提供_settings
接口。
参数名 | 默认值 | 说明 |
---|---|---|
number_of_replicas | 1 | 每个主分片的副本数量 |
auto_expand_replicas | false | 根据集群节点数量自动扩展副本数量的范围 |
search.idle.after | 30s | 分片被视为空闲的时间 |
refresh_interval | 1s | 索引刷新的时间间隔为-1则代表永不刷新 |
max_result_window | 10000 | 检索文档时返回的最大数量 |
max_inner_result_window | 100 | inner和聚焦最大数量 |
max_rescore_window | 10000 | rescore请求中window_size的上限,默认与index.max_result_window which defaults相同 |
max_docvalue_fields_search | 100 | docvalue_fields查询上限 |
max_script_fields | 32 | script_fields查询上限 |
max_ngram_diff | 1 | NGramTokenizer与NGramTokenFilter最大差值 |
max_shingle_diff | 3 | ShingleTokenFilter最大差值 |
blocks.read_only | |索引和索引源数据是否只读,true/false | |
blocks.read_only_allow_delete | |与上一个参数相同,但允许删除以释放资源 | |
blocks.read | |是否禁止读,true/false | |
blocks.write | |是否禁止写,true/false | |
block.metadata | |禁止对源数据读写,true/false | |
max_refresh_listeners | |每个分片上刷新监听器的最大数量 | |
analyze.max_token_count | 10000 | _analyze接口最大词项数量 |
highlight.max_analyzed_offset | 1000000 | 高亮请求中被分析的最大字符数量 |
max_terms_count | 65536 | term查询中可以使用词项的最大值 |
max_regex_length | 1000 | regex查询中正则表达式的最大值 |
routing.allocation.enable | all | 控制索引分片分配,可选值all(所有分片)、primaries(主分片)、new_primaries(新创建分片)、none(都不分配) |
routing.rebanlance.enable | all | 重平衡时规则,可选值同上 |
gc_deletes | 60s | 删除文档版本号的时长 |
default_pipeline | |索引默认ingest节点管道 |
下面看几个例子:
1.查看所有的动态配置:
GET _settings
2.查看某个索引的动态配置:
加flat_settings
可以将返回的JSON对象平铺展示。
GET /test1/_settings?flat_settings
{
“test1” : {
“settings” : {
“index.creation_date” : “1608210151608”,
“index.number_of_replicas” : “2”,
“index.number_of_shards” : “3”,
“index.provided_name” : “test1”,
“index.uuid” : “f3EI4o98QaWrisRTGd3ewQ”,
“index.version.created” : “7020099”
}
}
}
3.修改所有的索引的副本数为1:
PUT _settings
{
"number_of_replicas": 1
}
{
“acknowledged” : true
}
4.可以同时修改多个索引:
PUT /test1,test2/_settings
{
"blocks.write": false
}
{
“acknowledged” : true
}
3.Logstash
Logstash
能够动态的采集、转换和传输数据,不受格式或复杂度的影响。利用Grok
从非结构化数据中派生出结构,从 IP 地址解码出地理坐标,匿名化或排除敏感字段,并简化整体处理过程。
Logstash
是基于数据事件的,一个数据事件可能有多行数据,从数据源传输到存储库的过程中,Logstash
过滤器能够解析各个数据事件,识别已命名的字段、构建对应的数据结构,并将它们转换成通用格式,以便更轻松、更快速地进行分析,实现商业价值。
简单来说,Logstash
可以对数据进行传输和处理,它给用户提供了很多的插件来协助完成这些操作。
官方提供的插件分为三类:
- 输入:https://www.elastic.co/guide/en/logstash/current/input-plugins.html
- 过滤器:https://www.elastic.co/guide/en/logstash/current/filter-plugins.html
- 输出:https://www.elastic.co/guide/en/logstash/current/output-plugins.html
这些插件使用起来非常简单,只需要启用插件,就可以在配置文件中使用。
3.1.Logstash初体验
目标:使用Logstash
从控制台采集到“hello logstash”字符串,并将采集信息输出到控制台。
说明:只用到了输入、输出,并未用到过滤器。
在生产环境,我们可以直接使用docker
容器技术部署Logstash
,但在学习过程中,为了体验原汁原味,我们暂时使用原生方式安装。
因为Logstash
是java
编写的,所以在安装Logstash
之前,我们需要先准备好java
运行环境:参考附录1 安装jdk8
安装好jdk之后,就可以开始安装Logstash
了。
步骤一:下载
https://artifacts.elastic.co/downloads/logstash/logstash-7.2.0.tar.gz
步骤二:上传至Linux系统
将下载好的安装包上传至Linux系统的/home/
目录下:
步骤三:解压
tar -zxvf /home/logstash-7.2.0.tar.gz
步骤四:运行
bin/logstash -e "input { stdin { } } output { stdout { } }"
耐心等待启动
这样子就是启动成功了,接下来输入“hello logstash”:
采集日志成功。
3.2.logstash命令
bin/logstash -h
列出参数:
-n, --node.name
:logstash实例的名称,如果不设置默认为当前的主机名(比如我的主机名为logstash)。-f, --path.config
:配置文件,我们可以指定一个特定的文件,也可以指定一个特定的目录,如果指定的是特定的目录,则logstash会读取该目录下的所有文本文件,将其在内存中拼接成完整的大配置文件,再去执行。-e, --config.string
:给定的可直接执行的配置内容,也就是说我们可以不指定-f参数,直接把配置文件中的内容作为字符串放到-e参数后面。-w, --pipeline.workers
:指定工作线程的个数。-p, --path.plugins
:logstash用来加载插件的目录。-l, --path.logs
:日志输出文件,如果不设置,logstash将会把日志发送至标准的output。-t, --config.test_and_exit
:检查配置的语法是否正确并退出。-r, --config.reload.automatic
:监视配置文件的变化,并且自动重新加载修改后的配置文件。--config.reload.interval
:为了检查配置文件是否改变,而去拉取配置文件的频率。--http.host
:Web API绑定的主机,默认为“127.0.0.1”。--http.port
:Web API绑定的端口,默认为9600-9700之间。--log.format
:logstash写它自身日志的时候使用json还是文本格式,默认是“plain”。--path.settings
:设置包含logstash.yml配置文件的目录,比如log4j日志配置。也可以设置LS_SETTINGS_DIR环境变量。
3.3.Logstash配置文件格式
Logstash
的配置有三部分,如下:
input { # 输入
stdin{ ... } # 标准输入
}
filter { # 过滤,对数据进行分割、截取等处理
...
}
output { # 输出
stdout { ... } # 标准输出
}
在这里只说一下过滤器,输入和输出留到后面案例中体现。
3.4.Logstash过滤器
Filter是Logstash
功能强大的主要原因,它可以对Logstash Event
进行丰富的处理,比如说解析数据、删除字段、类型转换等等。
常见的过滤器插件有:
- date:日志解析
- grok:正则匹配解析
- dissect:分隔符解析
- mutate:对字段做处理,比如重命名、删除、替换等
- json:按照 json 解析字段内容到指定字段中
- geoip:增加地理位置数据
- ruby:利用ruby代码来动态修改Logstash Event
- csv:该插件用于将逗号分隔的值数据解析为单个字段
- split:该插件用于将多行消息拆分为不同的事件
3.4.1.所有过滤器通用的配置选项
设置 | 输入类型 | 需要 |
---|---|---|
add_field | hash | No |
add_tag | array | No |
enable_metric | boolearn | No |
id | string | No |
periodic_flush | boolean | No |
remove_field | array | No |
remove_tag | array | No |
3.4.2.date插件
3.4.2.1.日期过滤器配置选项
设置 | 输入类型 | 需要 |
---|---|---|
local | string | No |
match | array | No |
tag_on_failure | array | No |
target | string | No |
timezone | string | No |
按指定的时间格式读取事件的指定字段值后,赋值给指定的字段(默认为@timestamp
)。
创建logstash
的配置文件:
vim config/logstash.conf
# 在文件中输入以下内容
input {
stdin { }
}
filter {
date {
match => ["logdate", "yyyy-MM-dd HH:mm:ss"]
}
}
output {
stdout { }
}
运行logstash
:
# -f 指定加载的配置文件
bin/logstash -f config/logstash.conf
耐心等待完全启动成功后,在控制台输入如下内容:
2020-12-17 22:27:30
发现"@timestamp"
字段的值就是我们输入的时间
其他的过滤器的使用就不在这里赘述了,用到了直接去官网看,官网写的非常详细
比如date过滤器的官网地址:https://www.elastic.co/guide/en/logstash/current/plugins-filters-date.html
4.Beats
官方目前提供了7种Beats供用户使用,而对于大多数用户来说,常用的有Filebeat
、Metricbeat
、Heartbeat
,当然其它的也可能会使用到,根据具体的业务需求而定。
4.1.Filebeat
4.1.1.简介
Filebeat
是用于转发和集中日志数据的轻量级传送程序。作为服务器上的代理安装,Filebeat
监视您指定的日志文件或配置,收集日志时间,并将它们转发到ElasticSearch
或Logstash
进行索引。
Filebeat
的工作方式如下:启动Filebeat
时,它将启动一个或多个输入,这些输入将在为日志数据指定的位置中查找。对于Filebeat
所找到的每个日志,Filebeat
都会启动收集器。收集器也可以叫做收割机(Harvester)。每个收割机都读取单个日志以获取新内容,并将新日志数据发送到libbeat,libbeat(libbeat是一个Go库,其中包含所有Beats的通用软件包)将聚焦数据发送到为Filebeat配置的输出。
Filebeat原理请参考官网:https://www.elastic.co/guide/en/beats/filebeat/current/how-filebeat-works.html
4.1.2.Filebeat安装
步骤一:下载
https://artifacts.elastic.co/downloads/beats/filebeat/filebeat-7.2.0-linux-x86_64.tar.gz
步骤二:上传至Linux系统
将下载好的压缩包上传到Linux操作系统的/home/beats/
目录下:
步骤三:解压
tar -zxvf filebeat-7.2.0-linux-x86_64.tar.gz
4.1.3.Filebeat运行
4.1.3.1.控制台输入-控制台输出
编写配置文件:
mkdir config
vim config/filebeat-console.yml
# 在配置文件中加入以下配置
# 输入,是一个数组,可以指定多个
filebeat.inputs:
- type: stdin
# 开启这个输入, 只有是true的时候,这个输入才生效
enabled: true
# 输出,指定输出到控制台
output.console:
pretty: true
enable: true
启动filebeat:
./filebeat -e -c config/filebeat-console.yml
在控制台输入"hello filebeat":
4.1.3.2.文件输入-控制台输出
编写配置文件
mkdir logs/
vim config/filebeat-file-console.yml
# 在文件中输入以下内容
filebeat.inputs:
- type: log
enabled: true
paths:
- ./logs/*.log
output.console:
pretty: true
enable: true
启动filebeat
./filebeat -e -c config/filebeat-file-console.yml
新打开一个终端,在logs文件夹下随便创建文件,然后输入内容,观察变化:
后面会有例子从日志文件读取,输出到kafka。
5.Kibana
前面在安装ElasticSearch的时候已经将Kibana安装好了, 接下来主要看一下如何使用它
Kibana的功能列表:https://www.elastic.co/cn/kibana/features
官方有非常详细的使用方式, 接下来就以几个常用的操作作为示例:
5.1.创建索引模式
要想将ElasticSearch中的索引数据可视化, 就必须给它创建索引模式,索引创建为索引模式后,才可以进行Kibana的可视化工作。
如下图所示,左侧红框中是已经创建好的索引模式,我们可以点击右上角的“创建索引模式”按钮跳转到创建索引模式页面。
创建索引模式分为两步,第一步是选择要创建的索引模式包括的索引,换句话说就是你要对哪些索引创建索引模式,要将哪些索引可视化。
第二部是配置,如下图,直接选择时间字段即可,点击创建索引模式。
如下图,索引模式创建成功,如果想要删除这个索引模式,可以点击右上角的删除按钮。
5.2.发现索引模式
选择菜单栏的“Discover”选择,进入到如下图界面,选择我们刚刚创建好的索引模式,可以看到关于这个索引模式的一些信息。
左侧栏是我们在收集日志过程中定义的字段,在这里可以通过添加和删除的方式指定某个字段是否显示。
索引模式创建成功后,索引中的数据还是会不断的更新,所以我们可以通过时间选项来控制要展示的数据。
可以对字段进行筛选,如下图所示。
修改筛选值为ERROR,展示出来的数据的日志级别就都是ERROR的了
5.3.创建可视化
接下来我们创建一个可视化。
可视化有如下图所示的多种方式创建,我们先来创建一个条形图。
学案则刚刚创建建的索引模式。
配置可视化选项,然后点击保存。
为这个可视化指定一个名字。
5.4.创建仪表盘
接下来创建一个仪表盘。
5.5.将可视化添加到仪表盘
点击添加按钮,添加可视化到仪表盘。
选择刚刚创建的可视化,条形图。
添加后就可以在仪表盘中看到。如果想要保存仪表盘,点击左上角的保存按钮即可。
创建第二个可视化,值得说明的是,我们想要新建第二个可视化,就要先将可视化界面中的元素先清空,如下图所示。
全部删除之后,再次点击可视化菜单。
点击创建新的可视化。
这次我们创建一个饼图。
选择的还是之前创建的索引模式
创建后,填写饼图的配置参数,如下图所示。
配置成功后点击运行,即可展示结果,可以点击左上角的保存按钮进行保存。
给饼图可视化起一个名字。
然后和上面一样的流程, 将饼图添加到仪表盘:
同样的方式, 我们创建一个标签云图
然后将它添加到仪表盘.
最后将仪表盘保存即可
6.日志收集案例
6.1.产生日志的app
首先我们要编写一个不停产生日志的应用程序,为了测试方便,就使用循环随机时间去产生日志。
6.1.1.pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.unionman</groupId>
<artifactId>filebeats-springboot</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>filebeats-springboot</name>
<properties>
<skipTests>true</skipTests>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<springfox-swagger.version>2.8.0</springfox-swagger.version>
<hibernate-validator.version>6.0.10.Final</hibernate-validator.version>
<mysql.version>5.1.47</mysql.version>
<commons-collections4>4.1</commons-collections4>
<ant.version>1.9.7</ant.version>
<pinyin4j.version>2.5.0</pinyin4j.version>
<ouath.version>2.3.3.RELEASE</ouath.version>
<redis.version>1.4.7.RELEASE</redis.version>
<httpclient.version>4.5.6</httpclient.version>
<excel.version>2.6.10</excel.version>
<maven-surefire.version>2.18.1</maven-surefire.version>
<jsoup.version>1.9.2</jsoup.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<includeSystemScope>true</includeSystemScope>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>${maven-surefire.version}</version>
<configuration>
<skip>${skipTests}</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
6.1.2.application.yml
server:
port: 20201
6.1.3.logback.xml
<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">
<!--定义日志文件的存储地址 勿在 LogBack 的配置中使用相对路径-->
<property name="LOG_HOME" value="logs"/>
<!-- 彩色日志 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter"/>
<conversionRule conversionWord="wex"
converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter"/>
<conversionRule conversionWord="wEx"
converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter"/>
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN"
value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!-- Console 输出设置 -->
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${CONSOLE_LOG_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<!-- 按照每天生成日志文件 -->
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!--日志文件输出的文件名-->
<fileNamePattern>${LOG_HOME}/%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!--日志文件保留天数-->
<maxHistory>365</maxHistory>
<!--日志文件最大的大小-->
<!--用来指定日志文件的上限大小,那么到了这个值,就会删除旧的日志-->
<totalSizeCap>1GB</totalSizeCap>
<maxFileSize>10MB</maxFileSize>
</rollingPolicy>
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
<charset>UTF-8</charset>
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
<pattern>%d{yyyy-MM-dd HH:mm:ss} %level [%thread] %class => %method 行:%line ==> %msg%n</pattern>
</encoder>
</appender>
<!-- 日志输出级别 -->
<root level="INFO">
<appender-ref ref="STDOUT"/>
<appender-ref ref="FILE"/>
</root>
</configuration>
6.1.4.主启动类
package com.unionman.filebeats;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class FileBeatsApplication {
public static void main(String[] args) {
SpringApplication.run(FileBeatsApplication.class, args);
}
}
6.1.5.controller
package com.unionman.filebeats.controller;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.Random;
import java.util.concurrent.TimeUnit;
@Slf4j
@RestController
public class TestController {
public static String[] logArr = {
"有一辆车过来了,它的车牌号码是:粤LAAAAA",
"程序出现异常",
"consumerTopicOne(ConsumerRecord<String, String> record) Consumer news topic,1."
};
@RequestMapping(value = "/sendlog", method = RequestMethod.GET)
public String info() {
Random random = new Random();
boolean flag = true;
while (flag) {
try {
TimeUnit.SECONDS.sleep(random.nextInt(5) + 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
int index = random.nextInt(3);
String logStr = logArr[index];
switch (index){
case 0:
log.info(logStr);
break;
case 1:
log.error(logStr);
break;
case 2:
log.warn(logStr);
break;
default:break;
}
}
return "success";
}
}
6.1.6.启动
将编写好的app使用maven打成jar包,上传到Linux系统node1节点的/home/unionman/elk/jars/
目录下:
运行之前,请确保node1节点安装了jdk,如果没有安装,请安装jdk8,参考附录1。
将程序跑起来
nohup java -jar filebeats-springboot-0.0.1-SNAPSHOT.jar &
看一下启动日志,如果没有报错,说明一切正常:
tail -f nohup.out
接下来我们要监控的就是logs文件下的内容。
调用生成日志的接口:http://192.168.133.11:20201/sendlog
6.2.在node1安装kafka
mkdir -p /home/unionman/kafka/
cd /home/unionman/kafka
touch docker-compose.yml
cat >>/home/unionman/kafka/docker-compose.yml<<EOF
version: '3'
services:
zoo1:
image: wurstmeister/zookeeper
restart: always
hostname: zoo1
container_name: zookeeper
networks:
- base_net
kafka:
image: wurstmeister/kafka
ports:
- "9092:9092"
environment:
KAFKA_ADVERTISED_HOST_NAME: "192.168.133.11"
KAFKA_ADVERTISED_PORT: "9092"
KAFKA_ZOOKEEPER_CONNECT: "zoo1:2181"
KAFKA_BROKER_ID: 1
KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1
KAFKA_CREATE_TOPICS: "storage-producer-topic:3:1"
KAFKA_LOG_CLEANUP_POLICT: delete
KAFKA_LOG_RETENTION_HOURS: 4464
KAFKA_LOG_RETENTION_BYTES: 53687091200
KAFKA_DELETE_TOPIC_ENABLE: "true"
KAFKA_PARTITION_ASSIGNMENT_STRATEGY: "roundrobin"
depends_on:
- zoo1
container_name: kafka
networks:
- base_net
networks:
base_net:
external: true
EOF
启动kafka
docker-compose up -d
6.3.配置并启动filebeat
在节点:node1
安装filebeat在前面已经说过了,这里重点列出filebeat的配置:
mkdir -p /home/unionman/elk/beats/filebeat/filebeat-7.2.0-linux-x86_64/config/
touch /home/unionman/elk/beats/filebeat/filebeat-7.2.0-linux-x86_64/config/filebeat-file-kafka.yml
cat >>/home/unionman/elk/beats/filebeat/filebeat-7.2.0-linux-x86_64/config/filebeat-file-kafka.yml<<EOF
filebeat.inputs:
- type: log
enabled: true
fields:
server_id: spring-app
paths:
- /home/unionman/elk/jars/logs/*.log
multiline:
pattern: '^(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})'
negate: true
match: after
setup.template.settings:
index.number_of_shards: 3
output.kafka:
hosts: ["192.168.133.11:9092"]
topic: spring-boot-app-logs-topic
EOF
cd /home/unionman/elk/beats/filebeat/filebeat-7.2.0-linux-x86_64/
启动filebeat
nohup ./filebeat -e -c config/filebeat-file-kafka.yml -strict.perms=false &
6.4.配置并启动logstash
本次使用unionman用户(普通用户)安装Logstash , 安装过程没有什么区别,安装在/home/unionman/elk/logstash-7.2.0/
目录即可。
安装Logstash在前面已经说过了,这里重点列出Logstash的配置:
touch /home/unionman/elk/logstash-7.2.0/config/logstash-kafka-elasticsearch.conf
cat >>/home/unionman/elk/logstash-7.2.0/config/logstash-kafka-elasticsearch.conf<<EOF
input {
kafka {
bootstrap_servers => "192.168.130.211:9092"
topics => ["spring-boot-app-logs-topic"]
codec => "json"
group_id => "logstash-consumer-group"
consumer_threads => 1
decorate_events => true
}
}
filter {
grok {
match => {
"message" => "(?<dateTime>20%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{HOUR}:?%{MINUTE}(?::?%{SECOND}))\s%{DATA:javaLogLevel}\s%{DATA:threadName}\s%{DATA:className}\s=>\s%{DATA:methodName}\s%{DATA:codeLineNum}\s==>\s%{GREEDYDATA:logMessage}"
}
}
mutate {
rename => {
"[fields][server_id]" => "serverId"
"[host][name]" => "hostname"
"[log][file][path]" => "logFilePath"
}
}
mutate {
remove_field => ["agent","ecs","log","input","fields","host"]
}
date {
match => ["dateTime","YYYY-MM-dd HH:mm:ss"]
target => "dateTime"
}
ruby{
code => "event.set('dateTime',event.get('dateTime').to_i*1000)"
}
}
output {
elasticsearch {
hosts => ["192.168.130.211:9200","192.168.130.212:9200","192.168.130.213:9200"]
index => "boluo-%{+YYYY.MM.dd}"
}
}
EOF
cd /home/unionman/elk/logstash-7.2.0/
启动logstash
nohup ./bin/logstash -f config/logstash-kafka-elasticsearch.conf &
6.5.查看结果
进入Kibana可以看到,生成了一个索引:
我们给这个索引创建索引模式,创建索引模式的方式在5.1章节说过了,这里直接说结果:
附1安装jdk8
准备一台干净的虚拟机,配置好网络,安装基础的软件包如:vim, net-tools等。
将jdk压缩包上传至Linux操作系统的/usr/local/lib64/
目录下:
解压缩:
tar -zxvf /usr/local/lib64/jdk-8u211-linux-x64.tar.gz -C /usr/local/src/
配置环境变量:
cat >>/etc/profile<<EOF
# JAVA_HOME
export JAVA_HOME=/usr/local/src/jdk1.8.0_211
export CLASSPATH=.:\$JAVA_HOME/lib:\$JAVA_HOME/lib/tools.jar
export PATH=\$JAVA_HOME/bin:\$PATH
EOF
刷新配置:
source /etc/profile
检验是否配置成功:
java -version
javac -version
附2 安装docker环境
在线安装
Centos7安装docker官方地址 :https://docs.docker.com/install/linux/docker-ce/centos/
安装所需要的的软件包:
yum install -y yum-utils device-mapper-persistent-data lvm2
设置稳定的存储库:
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
配置启用一些参数:
yum-config-manager --enable docker-ce-nightly
yum-config-manager --enable docker-ce-test
yum-config-manager --disable docker-ce-nightly
安装最新版docker-ce:
yum -y install docker-ce docker-ce-cli containerd.io
启动docker:
systemctl start docker
helloworld:
docker run hello-world
将普通用户加入到docker组:
usermod -a -G docker username
设置docker开机自启动
systemctl enable docker
卸载
yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
安装docker-compose:
curl \
-L \
"https://github.com/docker/compose/releases/download/1.25.0/docker-compose-$(uname -s)-$(uname -m)" \
-o\
/usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
附3 docker-compose 部署filebeat
docker-compose.yml
version: '3'
services:
filebeat:
image: elastic/filebeat:7.2.0
container_name: filebeat
command: --strict.perms=false
volumes:
- ./filebeat.yml:/usr/share/filebeat/filebeat.yml
- /var/log/laravel/:/var/log/laravel/
- /var/lib/docker/containers:/var/lib/docker/containers:ro
- /var/run/docker.sock:/var/run/docker.sock
networks:
- es_cluster_net
networks:
es_cluster_net:
external: true
filebeat.yml
filebeat.inputs:
- type: log
enabled: true
tags: ["platformserver"]
fields:
server_id: platformserver
paths:
- /home/unionman/apps/boluomonitor-2.1.1/bin/common/logs/platformserver/*.log
multiline:
pattern: '^(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})'
negate: true
match: after
- type: log
enabled: true
tags: ["statistical-analysis"]
fields:
server_id: statistical-analysis
paths:
- /home/unionman/apps/boluomonitor-2.1.1/bin/common/logs/statistical-analysis/*.log
multiline:
pattern: '^(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})'
negate: true
match: after
- type: log
enabled: true
tags: ["storageserver"]
fields:
server_id: storageserver
paths:
- /home/unionman/apps/boluomonitor-2.1.1/bin/common/logs/storageserver/*.log
multiline:
pattern: '^(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})'
negate: true
match: after
- type: log
enabled: true
tags: ["structuredserver"]
fields:
server_id: structuredserver
paths:
- /home/unionman/apps/boluomonitor-2.1.1/bin/common/logs/structuredserver/*.log
multiline:
pattern: '^(\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2})'
negate: true
match: after
setup.template.settings:
index.number_of_shards: 3
output.kafka:
hosts: ["192.168.130.211:9092"]
topic: spring-boot-app-logs-topic
附4 docker-compose 部署logstash
docker-compose.yml
version: '3'
services:
logstash:
image: logstash:7.2.0
container_name: logstash
ports:
- 5044:5044
volumes:
- ./logstash.yml:/usr/share/logstash/config/logstash.yml
- ./pipeline/:/usr/share/logstash/pipeline
networks:
- es_cluster_net
networks:
es_cluster_net:
external: true
logstash.yml
http.host: "0.0.0.0"
pipeline/logstash.conf
input {
kafka {
bootstrap_servers => "192.168.130.211:9092"
topics => ["spring-boot-app-logs-topic"]
codec => "json"
group_id => "logstash-consumer-group"
consumer_threads => 1
decorate_events => true
}
}
filter {
grok {
match => {
"message" => "(?<dateTime>20%{YEAR}-%{MONTHNUM}-%{MONTHDAY} %{HOUR}:?%{MINUTE}(?::?%{SECOND}))\s%{DATA:javaLogLevel}\s%{DATA:threadName}\s%{DATA:className}\s=>\s%{DATA:methodName}\s%{DATA:codeLineNum}\s==>\s%{GREEDYDATA:logMessage}"
}
}
mutate {
rename => {
"[fields][server_id]" => "serverId"
"[host][name]" => "hostname"
"[log][file][path]" => "logFilePath"
}
}
mutate {
remove_field => ["agent","ecs","log","input","fields","host"]
}
date {
match => ["dateTime","YYYY-MM-dd HH:mm:ss"]
target => "dateTime"
}
ruby{
code => "event.set('dateTime',event.get('dateTime').to_i*1000)"
}
}
output {
elasticsearch {
hosts => ["192.168.130.211:9200","192.168.130.212:9200","192.168.130.213:9200"]
index => "boluo-%{+YYYY.MM.dd}"
}
}