第一章:ELK日志收集系统介绍
日志收集重要性
没有日志收集系统之前,运维工作存在的痛点:
-
痛点:生产出现故障后,运维需要不停的查看各种不同的日志进行分析?是不是毫无头绪;
-
痛点:项目上线出现错误,如何快速定位问题?如果后端节点过多、日志分散怎么办;
-
痛点:开发人员需要实时查看日志但又不想给服务器的登陆权限,怎么办?难道每天帮开发取日志;
-
痛点:如何在海量的日志中快速的提取我们想要的业务数据?比如:IP、PV、UV、TOP10等;
使用日志分析系统之后
如上所有的痛点都可以使用日志分析系统解决,通过日志分析系统将所有的离散的服务器都收集到一个平台下;然后提取想要的内容,比如错误信息,警告信息等;当过滤到这种信息,就马上告警,告警后,运维人员就能马上定位是哪台机器、哪个业务系统出现了问题,出现了什么问题。
ELK介绍
ELK是一套开源免费、功能强大的日志分析管理系统,可以将系统日志、网站日志、应用日志等各种日志进行收集、过滤、清洗,然后进行集中存放并展示。
ELK不是一个单独的技术,而是由一套日志收集工具组合而成,包括:Elasticsearch、Logstash、Kibana组成而成。
- Elasticsearch(简称es):是由Java开发的一个非关系型数据库,提供日志数据的存储和搜索功能;
- Logstash:是一个日志采集工具,提供数据采集、数据清洗、数据过滤等功能;
- Kibana:是一个日志展示工具,提供web界面的数据图形分析、数据展示等功能;
- 官方地址:https://www.elastic.co/cn/
EFK介绍
简单来说就是将Logstash替换成了Filebeat,由于Logstash本身是基于Java开发,并且需要部署在每一台业务主机中运行,在收集日志时会占用业务系统大量内存资源,可能会影响到系统中运行的业务。
而替换成Filebeat这种轻量级的日志收集工具后,由于Filebeat基于Go语言开发,本身占用资源少,启动速度快,也能够满足日常的日志收集需求,从而就诞生了EFK结构。
ELFK介绍
由于Filebeat本身处理数据能力比较弱,如果日志内容过于复杂,例如:评论商品数量、加入购物车数量、提交订单数量、加入收藏的数量、使用优惠卷数量等,面对如此复杂的日志那么Filebeat无法处理的话,从而诞生了ELFK结构。
ES集群部署
ES集群为了实现高可用,通常至少由三台组成,在一个ES集群中,只能有一个Master节点(通过选举产生),Master节点用于控制整个集群。集群的数据在存储时,Master会将数据同步给其他的Node节点。
主机名 | IP地址 | 操作系统 | 硬件环境 |
---|---|---|---|
es01 | 192.168.0.91 | CentOS7.6 | 2Core/4G Mem/50G disk |
es02 | 192.168.0.92 | CentOS7.6 | 2Core/4G Mem/50G disk |
es03 | 192.168.0.93 | CentOS7.6 | 2Core/4G Mem/50G disk |
es软件包可以从清华大学下载rpm包:https://mirrors.tuna.tsinghua.edu.cn/elasticstack/yum/elastic-7.x/7.8.1/
由于es7.8版本已经内置Java JDK,无需安装
bash
rpm -ivh elasticsearch-7.8.1-x86_64.rpm
es01修改配置文件
sh
[root@es01 ~]# egrep -v '^#|^$' /etc/elasticsearch/elasticsearch.yml
...
#集群名称自定义,每个集群主机中该名称保持一致
cluster.name: my-es
#在集群中的节点名称(主机名称)
node.name: es01
#数据存储路径
path.data: /var/lib/elasticsearch
#日志存储路径
path.logs: /var/log/elasticsearch
#本机IP地址
network.host: 192.168.0.91
#http监听的端口号
http.port: 9200
#集群主机列表
discovery.seed_hosts: ["192.168.0.91", "192.168.0.92", "192.168.0.93"]
#仅第一次启动集群时参与选举的主机列表
cluster.initial_master_nodes: ["192.168.0.91", "192.168.0.92", "192.168.0.93"]
启动es并加入开机自启动
sh
[root@es01 ~]# systemctl start elasticsearch
[root@es01 ~]# systemctl enable elasticsearch
[root@es01 ~]# netstat -ntlp | grep java
tcp6 0 0 192.168.0.91:9200 :::* LISTEN 9656/java
tcp6 0 0 192.168.0.91:9300 :::* LISTEN 9656/java
ES端口:9200是对外部提供访问的端口,9300是对集群内部提供访问的端口。
可以使用curl或者浏览器访问IP:9200测试是否可以访问
sh
[root@es01 ~]# curl http://192.168.0.91:9200
{
"name" : "es01",
"cluster_name" : "my-es",
"cluster_uuid" : "B8BaoXbdSWKGMDt9AVmKyw",
"version" : {
"number" : "7.8.1",
"build_flavor" : "default",
"build_type" : "rpm",
"build_hash" : "b5ca9c58fb664ca8bf9e4057fc229b3396bf3a89",
"build_date" : "2020-07-21T16:40:44.668009Z",
"build_snapshot" : false,
"lucene_version" : "8.5.1",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
es02修改配置文件
sh
[root@es02 ~]# egrep -v '^#|^$' /etc/elasticsearch/elasticsearch.yml
...
#集群名称自定义,每个集群主机中该名称保持一致
cluster.name: my-es
#在集群中的节点名称(主机名称)
node.name: es02
#数据存储路径
path.data: /var/lib/elasticsearch
#日志存储路径
path.logs: /var/log/elasticsearch
#本机IP地址
network.host: 192.168.0.92
#http监听的端口号
http.port: 9200
#集群主机列表
discovery.seed_hosts: ["192.168.0.91", "192.168.0.92", "192.168.0.93"]
#仅第一次启动集群时参与选举的主机列表
cluster.initial_master_nodes: ["192.168.0.91", "192.168.0.92", "192.168.0.93"]
启动es并加入开机自启
sh
[root@es02 ~]# systemctl start elasticsearch
[root@es02 ~]# systemctl enable elasticsearch
[root@es02 ~]# netstat -ntlp | grep java
tcp6 0 0 192.168.0.92:9200 :::* LISTEN 9670/java
tcp6 0 0 192.168.0.92:9300 :::* LISTEN 9670/java
访问测试
sh
[root@es02 ~]# curl http://192.168.0.92:9200
{
"name" : "es02",
"cluster_name" : "my-es",
"cluster_uuid" : "B8BaoXbdSWKGMDt9AVmKyw",
"version" : {
"number" : "7.8.1",
"build_flavor" : "default",
"build_type" : "rpm",
"build_hash" : "b5ca9c58fb664ca8bf9e4057fc229b3396bf3a89",
"build_date" : "2020-07-21T16:40:44.668009Z",
"build_snapshot" : false,
"lucene_version" : "8.5.1",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
es03修改配置文件
sh
[root@es03 ~]# egrep -v '^#|^$' /etc/elasticsearch/elasticsearch.yml
...
#集群名称自定义,每个集群主机中该名称保持一致
cluster.name: my-es
#在集群中的节点名称(主机名称)
node.name: es03
#数据存储路径
path.data: /var/lib/elasticsearch
#日志存储路径
path.logs: /var/log/elasticsearch
#本机IP地址
network.host: 192.168.0.93
#http监听的端口号
http.port: 9200
#集群主机列表
discovery.seed_hosts: ["192.168.0.91", "192.168.0.92", "192.168.0.93"]
#仅第一次启动集群时参与选举的主机列表
cluster.initial_master_nodes: ["192.168.0.91", "192.168.0.92", "192.168.0.93"]
启动es并加入开机自启
sh
[root@es03 ~]# systemctl start elasticsearch
[root@es03 ~]# systemctl enable elasticsearch
[root@es03 ~]# netstat -ntlp | grep java
tcp6 0 0 192.168.0.93:9200 :::* LISTEN 9662/java
tcp6 0 0 192.168.0.93:9300 :::* LISTEN 9662/java
访问测试
sh
[root@es03 ~]# curl http://192.168.0.93:9200
{
"name" : "es03",
"cluster_name" : "my-es",
"cluster_uuid" : "B8BaoXbdSWKGMDt9AVmKyw",
"version" : {
"number" : "7.8.1",
"build_flavor" : "default",
"build_type" : "rpm",
"build_hash" : "b5ca9c58fb664ca8bf9e4057fc229b3396bf3a89",
"build_date" : "2020-07-21T16:40:44.668009Z",
"build_snapshot" : false,
"lucene_version" : "8.5.1",
"minimum_wire_compatibility_version" : "6.8.0",
"minimum_index_compatibility_version" : "6.0.0-beta1"
},
"tagline" : "You Know, for Search"
}
Kibana安装配置
Kibana是由HTML和Javascript编写的一个开源的可视化平台,设计出来用于和Elasticsearch一起使用的,用于搜索、查看存放在Elasticsearch索引里的数据,使用各种不同的图表来展示日志中数据。
主机名 | IP地址 | 操作系统 | 硬件环境 |
---|---|---|---|
kibana-cerebro | 192.168.0.96 | CentOS7.6 | 2Core/4G Mem/50G disk |
Kibana软件包与ES在同一个仓库中,可以从清华大学下载rpm包:https://mirrors.tuna.tsinghua.edu.cn/elasticstack/yum/elastic-7.x/7.8.1/
sh
[root@kibana-cerebor ~]# rpm -ivh kibana-7.8.1-x86_64.rpm
修改kibana配置文件
sh
[root@kibana-cerebor ~]# egrep -v '^#|^$' /etc/kibana/kibana.yml
...
server.port: 5601 #kibana默认监听的端口
server.host: "0.0.0.0" #kibana监听的地址段
server.name: "web.kibana.com" #kibana访问的域名,提前在windows配置域名解析
elasticsearch.hosts: ["http://192.168.0.91:9200"] #es01主机的地址及端口,可以指定多个es主机地址
i18n.locale: "zh-CN" #kibana汉化
启动kibana
sh
[root@kibana-cerebor ~]# systemctl start kibana
[root@kibana-cerebor ~]# systemctl enable kibana
[root@kibana-cerebor ~]# netstat -ntlp | grep 5601
tcp 0 0 0.0.0.0:5601 0.0.0.0:* LISTEN 9202/node
访问kibana(提前在windows配置域名解析): http://web.kibana.com:5601/
**索引:**索引是ElasticSearch存放数据的地方,可以理解为MySQL库中的一个表;
**文档:**文档是ElasticSearch中存储的实体,类比关系型数据库,每个文档相当于数据库表中的一行数据;
主分片: ElasticSearch把一个索引里面的数据分成多个主分片,并均匀的存储在集群所有节点上,来解决单个节点的存储容量上限问题;
**副本分片:**副本分片的数据是由主分片同步而来,当主分片所在的机器宕机时,Elasticsearch可以使用其副本分片进行数据恢复,从而避免数据丢失;
**JVM堆内存:**ElasticSearch的数据存储在磁盘中,而数据操作是在内存中,官方在内存和数据量之间有一个建议的比例:1G内存可以存储48GB~96GB的数据量,官方建议单个节点的JVM内存不要超过31GB,否则会影响性能;
ES集群监控工具Cerebor
Cerebro 是由Java开发的 elasticsearch web管理工具,与Kibana类似,与Kibana不同的是Cerebro可以通过图形界面查看分片分配和执行常见的索引管理操作。
项目地址:https://github.com/lmenezes/cerebro
安装Cerebor(与kibana安装同一台主机即可)
sh
#cerebro需要java JDK环境
[root@kibana-cerebro ~]# yum -y install java
[root@kibana-cerebro ~]# rpm -ivh cerebro-0.9.4-1.noarch.rpm
更改cerebro数据存储路径
sh
[root@kibana-cerebro ~]# vim /etc/cerebro/application.conf
...
data.path = "/var/lib/cerebro/cerebro.db"
启动cerebro服务
sh
[root@kibana-cerebro ~]# systemctl start cerebro
[root@kibana-cerebro ~]# systemctl status cerebro
[root@kibana-cerebro ~]# systemctl enable cerebro
[root@kibana-cerebro ~]# netstat -ntlp
...
tcp6 0 0 :::9000 :::* LISTEN 9525/java
访问cerebro:http://192.168.0.96:9000/
提示:ILM(索引生命周期管理策略)策略操作的历史记录到ilm-history-*中。
ES集群分片与副本
什么是分片shard?一台服务器上无法存储大量数据,ES把一个index里面的数据分成多个shard分布式的存储在多个服务器上(将一份完整的数据 拆分成多份,并均匀的存储在集群所有节点上),ES就是通过shard来解决节点的容量上限问题的,这也是es集群能够支持PB级数据量的基石。
分片有主分片(primary shard)和副分片(replaca shard)之分,主分片是由索引拆分而成,副本分片的数据是由主分片同步而来,当分片所在的机器宕机时,Elasticsearch可以使用其副本进行恢复,从而避免数据丢失。
创建索引,指定3个主分片和1个副本分片
提示:主分片在创建索引时指定且后续不允许修改。
为了实现数据的冗余,Elasticsearch 禁止同一个分片的主分片和副本分片在同一个节点上,所以如果是一个节点的集群是不能有副本的。
假设my_test索引中的数据量为10G,那么这10G的数据被拆分成了3份(3个主分片)均匀存储到集群的三个节点中,而每个主分片都有一个副本分片,从而达到了数据的高可用。那么此时该索引占用的集群存储容量为20G。
**问题1:**目前ES集群一共有3个节点,假设集群的总存储容量为150G,如果此时增加一个50G的节点加入到集群中,能否提高my_test索引的数据存储容量?
问题2: 副本分片是越多越好嘛?目前ES集群一共有三个节点,而my_test索引当前有3个主分片和1个副本分片,如果此时调整副本分片的数量为2,能否提高数据的读吞吐量?
提示:多个副本replica 可以提升搜索操作的吞吐量和性能,但是如果只是在相同节点上增加更多的副本分片并不能提高性能,反而会占用更多的存储资源。
如果想要增加ES集群的读吞吐量性能,可以通过增加集群节点数量来解决,例如在增加3个节点 es04、es05、es06,然后在创建索引时规划好副本的数量,或者将原来的副本迁移至新增的三个节点上,当有读请求进来时,就会被分配到es04、es05、es06中,这时候读的吞吐量才会得到提升,如下图:
ES集群故障转移
故障转移指的是当集群中有节点发生故障时,这个集群是如何自动修复的?自动修复大体分为三个步骤:
**1.重新选举:**如果es01发生故障,那么es02、es03发现es01无法响应,一段时间后会发起master选举,比如这里选择es02为新master节点,此时集群状态为Red;
**2.主分片调整:**es02发现主分片P0为分配,此时将es03上的R0提升为主分片,当所有的主分片都正常分配后,集群状态为Yellow状态;
**3.副本分片调整:**es02在重新将P0和P1主分片生成新的副本分片R0和R1,此时集群状态变为Green;
ES集群节点类型
ES集群节点根据功能大致分为master节点,client协调节点,data节点三种。
**master主节点:**主要负责管理集群状态,比如维护索引元数据(创建删除索引)、负责切换primary shard和replica shard身份等;默认情况下,每个节点都有可能成为主节点的资格,也会存储数据,还会处理客户端的请求。
data数据节点: 主要负责数据的写入与查询,也就是执行增删改查等操作,数据节点压力大,所以对cpu、内存、IO要求较高,生产环境建议单独部署;
**client协调节点:**主要负责处理客户端请求、处理搜索、分片索引操作等,客户端请求发送到client协调节点后,协调节点知道任意文档所处的位置,然后转发请求,收集数据并返回给客户端,这种处理客户端请求的节点称为协调节点;
默认情况下集群中的每个节点都属于协调节点。
组合方法:
默认情况下,每个节点都有成为主节点的资格,也会存储数据,还会处理客户端的请求,在一个生产集群中我们可以对这些节点的职责进行划分。
1)在生产环境下,建议master至少由3台组成,且只负责管理集群,不负责数据的写入和查询,内存可以相对小一些,但是机器一定要稳定。
在elasticsearch.yml
文件中定义
sh
...
node.master: true #当node.master被设置为true时,表示可以参与选举成为主节点
node.data: false #当node.date被设置为false时,不存储数据
node.ingest: false #当node.ingest被设置为false时,不处理客户端请求
2)再根据数据量设置一批 data节点,这些节点只负责存储数据,建立索引和查询索引的服务。
在elasticsearch.yml
文件中定义
sh
...
node.master: false #当node.master被设置为false时,不参与选举,没有成为主节点的资格
node.data: true #当node.date被设置为true时,作为数据节点存储数据
node.ingest: false #当node.ingest被设置为false时,不处理客户端请求
3)如果用户量大且请求比较频繁,在集群中建议再设置一批 ingest 节点也称之为 client 节点,独立的协调节点在大型集群中是非常有用的,它协调主节点和数据节点,这些节点只负责处理用户请求,实现请求转发,负载均衡等功能。
在elasticsearch.yml
文件中定义
sh
...
node.master: false #即不会成为主节点
node.data: false #也不会存储数据
node.ingest: true #只作为client协调节点
提示:ES集群没有办法手动指定 mater,你只能通过重启其他节点,达到改变主节点的目的;
ES集群节点扩容
主机名 | IP地址 | 操作系统 | 硬件环境 |
---|---|---|---|
es04-data | 192.168.0.84 | CentOS 7.6 | 2Core/4G Mem/50G disk |
es05-data | 192.168.0.85 | CentOS 7.6 | 2Core/4G Mem/50G disk |
es04-data主机配置
安装软件包
sh
[root@es04-data ~]# rpm -ivh elasticsearch-7.8.1-x86_64.rpm
修改elasticsearch.yml文件
sh
[root@es04-data ~]# egrep -v '^#|^$' /etc/elasticsearch/elasticsearch.yml
...
#集群名称
cluster.name: my-es
#在集群中的节点名称(主机名称)
node.name: es04-data
#数据存储路径
path.data: /var/lib/elasticsearch
#日志存储路径
path.logs: /var/log/elasticsearch
#本机IP地址
network.host: 192.168.0.94
#http监听的端口号
http.port: 9200
#集群主机列表(指定加入的集群主机列表,可以指定一个或多个)
discovery.seed_hosts: ["192.168.0.91", "192.168.0.92", "192.168.0.93"]
#不参与master选举
node.master: false
#只负责数据存储
node.data: true
#不做client协调节点
node.ingest: false
启动服务
sh
[root@es04-data ~]# systemctl start elasticsearch
[root@es04-data ~]# systemctl enable elasticsearch
es05-data主机配置
安装软件包
sh
[root@es05-data ~]# rpm -ivh elasticsearch-7.8.1-x86_64.rpm
修改elasticsearch.yml文件
sh
[root@es05-data ~]# egrep -v '^#|^$' /etc/elasticsearch/elasticsearch.yml
...
cluster.name: my-es
node.name: es05-data
path.data: /var/lib/elasticsearch
path.logs: /var/log/elasticsearch
network.host: 192.168.0.85
http.port: 9200
discovery.seed_hosts: ["192.168.0.91", "192.168.0.92", "192.168.0.93"]
node.master: false
node.data: true
node.ingest: false
启动服务
sh
[root@es05-data ~]# systemctl start elasticsearch
[root@es05-data ~]# systemctl enable elasticsearch
JVM堆内存优化
JVM内存大小需要根据集群存储节点数量来估算,为了保证性能,官方在内存和数据量之间有一个建议的比例:1G内存可以存储48GB~96GB的数据量;
假设每天写入到ES集群的总数据量为1TB左右,分别由集群的3个node节点去存储数据(不设定副本的情况)
每个节点存储的数据量计算方法:1TB数据量 / 3node = 350GB数据,每个节点还需要预留20%的空间,即每个节点要存储410GB左右的数据量;
所需内存计算方法:410GB总数据量 / 1GB内存可存储的48GB数据量 = 8GB内存
假设每天写入到ES集群的总数据量为1TB左右,1个副本,分别由集群的3个node节点去存储数据,那么实际要存储的数据为2TB左右(有一个副本)每个节点存储的数据量计算方法:2TB数据量 / 3node = 700GB数据,每个节点还需要预留20%的空间,即每个节点要存储850GB左右的数据量;
所需内存计算方法:850GB总数据量 / 1GB内存可存储的48GB数据量 = 17GB内存
假设每天写入到ES集群的总数据量为2TB左右,1个副本,分别由集群的3个node节点去存储数据,那么实际要存储的数据为4TB左右(有一个副本)每个节点存储的数据量计算方法:4TB数据量 / 3node = 1.4TB数据,每个节点还需要预留20%的空间,即每个节点要存储1.7TB左右的数据量;
所需内存计算方法:1.7TB总数据量 / 1GB内存可存储的48GB数据量 = 32GB内存
提示:官方建议单个节点的JVM内存不要超过31GB,此时需要通过扩容集群节点来解决。
调整JVM堆内存配置文件:/etc/elasticsearch/jvm.options
sh
vim /etc/elasticsearch/jvm.options
#提示:根据数据量来调整如下参数,修改为合适的值,一般设置为服务器物理内存的一半最佳,但最大不能超过31GB
#每天1TB左右的数据量服务器配置参考:3node 16Core 64GB 6T
-Xms31g #最小堆内存
-Xmx31g #最大堆内存
第二章:Filebeat日志收集
Filebeat介绍
Filebeat 基于 Go 语言开发的轻量型日志采集器(本质上是一个 agent ),也是 ELKStack 里面的一员,无其他依赖,可以安装在各个节点上,根据配置读取对应位置的日志,并将收集的日志内容转发到Elasticsearch、Logstash、Redis、Kafka中。
它最大的特点是配置简单、占用系统资源很少、安装使用也非常简单,早期的ELK架构中使用Logstash收集、解析日志,但是Logstash对内存、cpu、io等资源消耗比较高,而Filebeat所占系统的CPU和内存几乎可以忽略不计。
Filebeat主要组件
Filebeat由两个组件构成,分别是inputs(输入)和harvesters(收集器)组成。
- inputs输入:指定要读取的日志文件位置;
- harvesters收集器:负责读取日志文件的内容,并将内容发送到指定的存储;
Filebeat安装
主机名 | IP地址 | 操作系统 | 硬件环境 |
---|---|---|---|
webserver | 192.168.0.94 | CentOS 7.6 | 2Core/4G Mem/50G disk |
filebeat软件包下载地址:https://mirrors.tuna.tsinghua.edu.cn/elasticstack/7.x/yum/7.8.1/
sh
[root@webserver ~]# rpm -ivh filebeat-7.8.1-x86_64.rpm
[root@webserver ~]# systemctl enable filebeat
Filebeat采集日志
基本语法说明:
通过filebeat采集系统日志,并将日志内容输出到ES集群
sh
#备份主配置文件
[root@webserver ~]# cp /etc/filebeat/filebeat.yml{,.bak}
#收集messages日志(清空filebeat.yml文件,并将如下内容写入到文件中)
[root@webserver ~]# cat /etc/filebeat/filebeat.yml
filebeat.inputs: #采集日志
- type: log #日志类型
enabled: true #启用日志收集
paths: /var/log/messages #日志文件的路径
output.elasticsearch: #将日志输出至ES
hosts: ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
index: "messageslog-%{+yyyy.MM.dd}" #定义在ES中的索引名称,并附带索引生成时间
setup.ilm.enabled: false #关闭索引生命周期管理功能,否则自定义索引名称将会失效
setup.template.name: "messageslog" #定义索引的模板名称(索引基于模板创建)
setup.template.pattern: "messageslog-*" #定义模板匹配的索引名称
启动filebeat服务
sh
[root@webserver ~]# systemctl start filebeat
通过Kibana索引模式匹配索引
Filebeat采集Nginx日志
当我们想要获取Nginx的用户访问信息,例如:客户端的IP属地、网站的PV、UV、状态码、访问时间等等,需要通过Nginx的日志获取;
安装Nginx
sh
[root@webserver ~]# yum -y install nginx
[root@webserver ~]# systemctl start nginx && systemctl enable nginx
Filebeat收集Nginx访问日志
sh
[root@webserver ~]# cat /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths: /var/log/nginx/access.log
output.elasticsearch:
hosts: ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
index: "nginx-access-%{+yyyy.MM.dd}"
setup.ilm.enabled: false
setup.template.name: "nginx"
setup.template.pattern: "nginx-*"
重启filebeat服务
sh
[root@webserver ~]# systemctl restart filebeat
Kibana添加索引
通过左下角 Stack Magenment
--> 索引模式
创建索引匹配
提示:访问Nginx产生日志。
nginx的acces日志格式是message格式(普通的文本格式),日志内容都在一行显示,如果想分析某一个字段的数据时,例如:状态码、客户端IP、客户端使用的浏览器等等,这些字段数据不方便提取,所以无法满足分析的需求,如果将日志转为json格式(key-value)对于后期分析就很友好了。
修改nginx日志格式为json
sh
[root@webserver ~]# vim /etc/nginx/nginx.conf
...
#注释掉之前 log_format重新写一个json格式的log_format
http {
# log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
log_format json '{ "time_local": "$time_local", '
'"remote_addr": "$remote_addr", '
'"referer": "$http_referer", '
'"request": "$request", '
'"status": $status, '
'"bytes": $body_bytes_sent, '
'"agent": "$http_user_agent", '
'"x_forwarded": "$http_x_forwarded_for", '
'"lb_addr": "$upstream_addr",'
'"lb_host": "$upstream_http_host",'
'"upstream_time": "$upstream_response_time",'
'"request_time": "$request_time"'
'}';
access_log /var/log/nginx/access.log json; #名称改为json,与log_format后边定义的名称一致
字段 | 说明 |
---|---|
remote_addr | 远程客户端的IP地址。 |
referer | 从哪个链接访问过来的(请求头Referer的内容 ) |
request | 记录客户端请求的URI和HTTP协议 |
status | 记录请求返回的http状态码,比如成功是200。 |
bytes | 发送给客户端的文件内容的大小 |
agent | 客户端浏览器信息(请求头User-Agent的内容 ) |
x_forwarded | 客户端的真实ip,通常web服务器放在反向代理的后面,这样就不能获取到客户的IP地址了,通过$remote_add拿到的IP地址是反向代理服务器的iP地址。 |
lb_addr | 后端服务器IP地址,即真正提供服务的主机地址 |
upstream_time | 请求过程中,upstream的响应时间,以秒为单位 |
request_time | 整个请求的总时间,以秒为单位 |
nginx日志改为json后,清空原有message格式的日志内容
sh
[root@webserver ~]# > /var/log/nginx/access.log
删除es中的索引,否则索引中原有的数据格式依然是message格式
修改filebeat配置文件,指定日志格式为json
sh
[root@webserver ~]# cat /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths: /var/log/nginx/access.log
json.keys_under_root: true # 日志格式不存储为message格式
json.overwrite_keys: true # 存储为json格式
output.elasticsearch:
hosts: ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
index: "nginx-access-%{+yyyy.MM.dd}"
setup.ilm.enabled: false
setup.template.name: "nginx"
setup.template.pattern: "nginx-*"
重启nginx与filebeat
sh
[root@webserver ~]# systemctl restart nginx
[root@webserver ~]# systemctl restart filebeat
访问Nginx产生日志:curl 192.168.0.94
kibana查看索引数据
Filebeat采集Nginx多个日志
nginx有access.log访问日志与error.log错误日志,如何通过filebeat同时收集这两个日志呢?我们希望不同的日志存储到不同的索引中,例如:
- access.log -->存储到--> nginx-access 索引;
- error.log -->存储到--> nginx-error 索引;
修改filebeat配置文件,filebeat收集多个日志,需要通过tags标签进行区分;
sh
[root@webserver ~]# cat /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths: /var/log/nginx/access.log
json.keys_under_root: true
json.overwrite_keys: true
tags: ["nginx-access"] #定义日志标签
- type: log #日志类型
enabled: true #启用日志收集
paths: /var/log/nginx/error.log #日志文件路径
tags: ["nginx-error"] #定义日志标签
output.elasticsearch:
hosts: ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
indices: #配置多个索引
- index: "nginx-access-%{+yyyy.MM.dd}" #索引名称
when.contains: #增加条件判断
tags: "nginx-access" #日志的tags包含nginx-access的日志存储至nginx-access-* 索引中
- index: "nginx-error-%{+yyyy.MM.dd}" #索引名称
when.contains: #增加条件判断
tags: "nginx-error" #日志的tags包含nginx-error的日志存储至nginx-error-* 索引中
setup.ilm.enabled: false
setup.template.name: "nginx"
setup.template.pattern: "nginx-*"
重启filebeat
sh
[root@webserver ~]# systemctl restart filebeat
kibana添加索引
通过左下角 Stack Magenment
--> 索引模式
创建索引匹配(如果未发现索引,访问nginx产生日志信息)
模拟错误的访问:http://192.168.0.34/hello
Filebeat采集Tomcat日志
采集Tomcat日志与Nginx类似,只需要将日志格式修改为Json格式,在通过Filebeat进行采集即可;
安装tomcat
sh
[root@webserver ~]# tar -xf apache-tomcat-8.0.30.tar
[root@webserver ~]# mv apache-tomcat-8.0.30 /usr/local/tomcat
[root@webserver ~]# yum -y install java-1.8.0-openjdk.x86_64
将tomcat的访问日志转化为json格式 (以下配置在文件的135行)
sh
[root@webserver ~]# vim /usr/local/tomcat/conf/server.xml
#...删除默认的prefix与pattern行,替换如下内容
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="tomcat_access_log" suffix=".log"
pattern="{"clientip":"%h","ClientUser":"%l","authenticated":"%u","AccessTime":"%t","method":"%r","status":"%s","SendBytes":"%b","Query?string":"%q","partner":"%{Referer}i","AgentVersion":"%{User-Agent}i"}"/>
字段 | 说明 |
---|---|
%h | 客户端的IP |
%l | 客户端的逻辑用户名,总是返回'-' |
%u | 认证以后的客户端用户(如果不存在的话,为'-') |
%t | 请求的时间 |
%r | 请求的方法和URL |
%s | 响应的状态码 |
%b | 发送信息的字节数,不包含HTTP头,如果为0,使用"-" |
%q | 查询字符串(比如客户端访问的是a.jsp?bbb=ccc,那么这里就显示?bbb=ccc) |
启动tomcat
sh
[root@webserver ~]# /usr/local/tomcat/bin/startup.sh
[root@webserver ~]# netstat -ntlp | grep java
tcp6 0 0 :::8009 :::* LISTEN 10017/java
tcp6 0 0 :::8080 :::* LISTEN 10017/java
访问tomcat:http://192.168.0.94:8080/
检查访问日志格式是否为json
sh
[root@webserver ~]# tail -1 /usr/local/tomcat/logs/tomcat_access_log.2023-02-03.log
{"clientip":"192.168.0.1","ClientUser":"-","authenticated":"-","AccessTime":"[03/Feb/2023:13:27:58 +0800]","method":"GET / HTTP/1.1","status":"200","SendBytes":"11250","Query?string":"","partner":"-","AgentVersion":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36"}
修改Filebeat配置文件,采集Tomcat访问日志(删除filebeat.yml中原有的nginx配置)
sh
[root@webserver ~]# cat /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths: /usr/local/tomcat/logs/tomcat_access_log*.log
json.keys_under_root: true
json.overwrite_keys: true
tags: ["tomcat-access"]
output.elasticsearch:
hosts: ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
index: "tomcat-access-%{+yyyy.MM.dd}"
setup.ilm.enabled: false
setup.template.name: "tomcat"
setup.template.pattern: "tomcat-*"
重启filebeat
sh
[root@webserver ~]# systemctl restart filebeat
kibana添加索引
通过左下角 Stack Magenment
--> 索引模式
创建索引匹配
Filebeat采集Tomcat错误日志
关闭tomcat
sh
[root@webserver ~]# /usr/local/tomcat/bin/shutdown.sh
修改tomcat配置文件,改错一个地方
sh
[root@webserver ~]# vim /usr/local/tomcat/conf/server.xml
...
<abcdServer port="8005" shutdown="SHUTDOWN">
清空tomcat错误日志内容
sh
[root@webserver ~]# > /usr/local/tomcat/logs/catalina.out
启动tomcat就会有报错
sh
[root@webserver ~]# /usr/local/tomcat/bin/startup.sh
配置Filebeat采集tomcat错误日志
sh
[root@webserver ~]# vim /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths:
- /usr/local/tomcat/logs/tomcat_access_log*.log
json.keys_under_root: true
json.overwrite_keys: true
tags: ["tomcat-access"]
- type: log
enabled: true
paths: /usr/local/tomcat/logs/catalina.out
tags: ["tomcat-error"]
output.elasticsearch:
hosts: ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
indices:
- index: "tomcat-access-%{+yyyy.MM.dd}"
when.contains:
tags: "tomcat-access"
- index: "tomcat-error-%{+yyyy.MM.dd}"
when.contains:
tags: "tomcat-error"
setup.ilm.enabled: false
setup.template.name: "tomcat"
setup.template.pattern: "tomcat-*"
重启filebeat
sh
[root@webserver ~]# systemctl status filebeat
Kibana添加索引
通过左下角 Stack Magenment
--> 索引模式
创建索引匹配
Java错误日志的特点:一个java程序报错往往是一个事件,这个报错并不是一行就能展示完的,几乎需要几十行才能展示完这个报错内容,对于filebeat来说,filebeat每次都是把一行看成了一个日志,那么对于java多行报错就不是很友好了,即使收集过来也是将一个事件的报错日志分成很多行在kibana上展示,这样对于开发人员来看日志就很头疼了。
多行报错型日志收集方案:以tomcat为例,tomcat的一个报错事件是以日期开头,到下一个日期的时候结束,我们可以通过Filebeat的 "多行匹配" 方式,匹配以日期开头到下一个日期结束为一行日志。
配置Filebeat收集Tomcat错误日志
sh
[root@webserver ~]# vim /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths: /usr/local/tomcat/logs/tomcat_access_log*.log
json.keys_under_root: true
json.overwrite_keys: true
tags: ["tomcat-access"]
- type: log
enabled: true
paths: /usr/local/tomcat/logs/catalina.out #错误日志文件
tags: ["tomcat-error"]
multiline.pattern: '^\d{2}' #匹配以2个数字开头的行(日期开头)\d 匹配数字,等价于[0-9]
multiline.negate: true #将不符合规则的行合并在一起
multiline.match: after #合并到上一行的末尾
multiline.max_lines: 1000 #默认最大合并的行为500,可根据实际情况调整
output.elasticsearch:
hosts: ["192.168.0.81:9200","192.168.0.82:9200","192.168.0.83:9200"]
indices:
- index: "tomcat-access-%{+yyyy.MM.dd}"
when.contains:
tags: "tomcat-access"
- index: "tomcat-error-%{+yyyy.MM.dd}"
when.contains:
tags: "tomcat-error"
setup.ilm.enabled: false
setup.template.name: "tomcat"
setup.template.pattern: "tomcat-*"
重启filebeat
sh
[root@webserver ~]# systemctl restart filebeat
Kibana删除索引并重新匹配
通过左下角 Stack Magenment
--> 索引管理
删除 tomcat-error 索引,启动tomcat产生错误日志,在重新匹配索引
,
多行合并在了一起。
提示:如果需要收集多台主机的相同日志时,只需要在对应主机filebeat中配置相同的索引名称,将多台主机的同一个日志写入到同一个ES索引中,然后通过日志中的agent.name字段区分不同主机的日志即可。
第三章:Logstach日志收集
在实际的生产环境中,肯定会需要同时收集多个服务的不同日志文件的需求,比如:nginx的访问日志与错误日志,tomcat的访问日志与错误日志,MySQL相关日志等等,但如果完全靠filebeat去收集多个不同服务日志的话,很难实现!就需要Logstash来完成。
Logstash介绍
logstash是一款强大的数据处理工具,它可以实现数据传输,格式处理,格式化输出,还有强大的插件功能,常用于日志处理。 经常与ElasticSearch,Kibana结合使用,组成著名的ELKStack。
它可以单独出现,作为日志收集软件,也可以收集日志到多种存储系统或临时中转系统,如:ElasticSearch,MySQL,Redis,Kakfa等。
官方地址:https://www.elastic.co/cn/logstash/
Logstash安装
主机名 | IP地址 | 操作系统 | 硬件环境 |
---|---|---|---|
logstash-node01 | 192.168.0.95 | CentOS 7.6 | 2 Core/4G Mem/50G disk |
Logstash软件下载地址:https://mirrors.tuna.tsinghua.edu.cn/elasticstack/7.x/yum/7.8.1/
Logstash安装非常简单,下载rpm包安装即可!(需要先安装JDK)
sh
[root@logstash-node01 ~]# yum -y install java-1.8.0-openjdk
[root@logstash-node01 ~]# rpm -ivh logstash-7.8.1.rpm
#将logstash命令程序加入到PATH路径
[root@logstash-node01 ~]# ln -s /usr/share/logstash/bin/logstash /usr/bin
修改logstash.yml配置文件
sh
[root@logstash-node01 ~]# vim /etc/logstash/logstash.yml
...
#节点名称,在集群中具备唯一性,默认为logstash主机的主机名
node.name: logstash-node01
#logstash的工作进程数量,此工作进程默认为主机的cpu核心数量
pipeline.workers: 2
Logstash Input输入插件
input插件让logstash可以读取特定的事件源;
- stdin 标准输入( 屏幕输入读取为事件 )
- file 从文件中读入事件
- beats 从filebeat接收事件
- kafka 将 kafka 中的数据读取为事件
- HTTP 从http请求中读入事件
Logstash Filter过滤插件
数据从源传输到存储的过程中,Logstash的Filter过滤器能够解析各个事件,识别以命名的字段结构,并将他们转换为通用格式,以方便对数据进行快速分析;
- 利用Grok将非结构化数据转换为结构化数据格式;
- 利用geoip从IP地址分析出地理坐标;
- 利用useragent从请求中分析操作系统、设备类型、浏览器等;
- 利用date将日期字符串解析为日志类型;
- 利用mutate对字段进行、类型转换(convert)添加字段(add_field)、删除(remove_field)、取列(split)、重命名(rename)等;
Logstash Output输出插件
- stdout 将数据输出到屏幕(调试使用);
- file 将数据输出到文件;
- elastic 将数据输出到elasticsearch;
Input stdin
从标准输入读取数据,从标准输出中输出内容
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/stdin-test.conf
input {
stdin {
type => "stdin" #日志类型
}
}
output {
stdout {
codec => "rubydebug" #指定输出为rubydebug格式,主要用于在屏幕中调试日志
}
}
检测文件语法
sh
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/stdin-test.conf -t
...
Configuration OK
执行测试
sh
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/stdin-test.conf
hello #在屏幕输入一句hello,查看输出结果
#输出结果如下
{
"message" => "hello",
"type" => "stdin",
"tags" => [
[0] "stdin_type"
],
"@timestamp" => 2023-02-09T08:43:58.639Z,
"host" => "logstash-node01",
"@version" => "1"
}
Input file
从文件中读入事件,从标准输出中输出内容
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/file-test.conf
input {
file {
path => "/var/log/hello.log"
type => "log" #定义日志类型
start_position => "beginning" #从什么位置开始读取文件内容,beginning第一次读取文件时,从头开始读取,仅对第一次被监听的文件起作用,后续会从上一次结束位置读取
stat_interval => "3" #定时检查文件更新,默认为15s
}
}
output {
stdout {
codec => "rubydebug"
}
}
检测文件语法
sh
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/file-test.conf -t
...
Configuration OK
执行测试
sh
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/file-test.conf
向文件中写入一条nginx访问日志数据
sh
cat >> /var/log/hello.log<<EOF
10.0.16.138 - - [18/Aug/2020:16:21:05 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36" "-"
EOF
返回内容如下
shell
{
"path" => "/var/log/hello.log",
"host" => "logstash-node01",
"@version" => "1",
"message" => "221.198.204.180 - - [18/Aug/2020:16:21:05 +0800] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36\" \"-\"",
"type" => "log",
"@timestamp" => 2023-03-16T04:03:39.942Z
}
Filter Grok
Grok可以使蹩脚的、无结构,杂乱无章的日志内容解析成json形式的结构化数据格式;
grok语法生成器:http://grokdebug.herokuapp.com/
以一个完整的nginx的access非结构化日志为例,通过grok解析为json格式的效果如下:
sh
10.0.16.138 - - [18/Aug/2020:16:21:05 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36" "-"
使用grok分析Nginx access日志
sh
%{IP:clientip} - (%{USERNAME:user}|-) \[%{HTTPDATE:timestamp}\] \"%{WORD:request_verb} %{NOTSPACE:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:status:int} %{NUMBER:body_sent:int} \"-\" \"%{GREEDYDATA:agent}\" \"-\"
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/file-test.conf
input {
file {
path => "/var/log/hello.log"
type => "log"
start_position => "beginning"
stat_interval => "3"
}
}
filter {
#通过grok解析nginx日志中的message字段
grok {
#match定义解析的字段名,定义方式:字段名=>值匹配
match => {
message => "%{IP:clientip} - (%{USERNAME:user}|-) \[%{HTTPDATE:timestamp}\] \"%{WORD:request_verb} %{NOTSPACE:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:status:int} %{NUMBER:body_sent:int} \"-\" \"%{GREEDYDATA:agent}\" \"-\""
}
}
}
output {
stdout {
codec => "rubydebug"
}
}
检测文件语法
sh
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/file-test.conf -t
...
Configuration OK
执行测试,-r 配置文件发生变化时,自动加载配置,使新配置生效
sh
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/file-test.conf -r
写入日志
sh
cat >> /var/log/hello.log<<EOF
10.0.16.138 - - [18/Aug/2020:16:21:05 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36" "-"
EOF
logstash显示结果如下
sh
{
"clientip" => "10.0.16.138",
"httpversion" => "1.1",
"@version" => "1",
"request" => "/",
"host" => "logstash-node01",
"user" => "-",
"agent" => "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36",
"status" => 304,
"request_verb" => "GET",
"body_sent" => 0,
"type" => "log",
"message" => "10.0.16.138 - - [18/Aug/2020:16:21:05 +0800] \"GET / HTTP/1.1\" 304 0 \"-\" \"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36\" \"-\"",
"path" => "/var/log/hello.log",
"timestamp" => "18/Aug/2020:16:21:05 +0800",
"@timestamp" => 2023-02-10T14:40:58.807Z
}
Filter geoip
geoip功能可以根据IP地址提供对应的地域信息,比如:经纬度、城市名称等地理数据信息;
通过geoip提取nginx日志中clientip字段,并获取地域信息
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/file-test.conf
input {
file {
path => "/var/log/hello.log"
type => "log"
start_position => "beginning"
stat_interval => "3"
}
}
filter {
#通过grok解析nginx日志中的message字段
grok {
#match定义解析的字段名,定义方式:字段名=>值匹配
match => {
message => "%{IP:clientip} - (%{USERNAME:user}|-) \[%{HTTPDATE:timestamp}\] \"%{WORD:request_verb} %{NOTSPACE:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:status:int} %{NUMBER:body_sent:int} \"-\" \"%{GREEDYDATA:agent}\" \"-\""
}
}
#通过geoip解析IP地址的地理位置
geoip {
#source用于指定要解析的字段,结果将被保存到geoip字段里
source => "clientip"
}
}
output {
stdout {
codec => "rubydebug"
}
}
写入日志(私有地址不会显示)
sh
cat >> /var/log/hello.log<<EOF
221.198.204.180 - - [18/Aug/2020:16:21:05 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36" "-"
EOF
logstash显示结果如下,主要看geoip部分
sh
"geoip" => {
"country_code3" => "CN",
"timezone" => "Asia/Shanghai",
"country_code2" => "CN",
"longitude" => 117.1726,
"city_name" => "Tianjin",
"ip" => "221.198.204.180",
"region_code" => "TJ",
"country_name" => "China",
"continent_code" => "AS",
"latitude" => 39.1423,
"region_name" => "Tianjin",
"location" => {
"lon" => 117.1726,
"lat" => 39.1423
}
Filter geoip fields
有些输出内容我们并不需要,可以通过geoip fields过滤自己需要的字段信息
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/file-test.conf
input {
file {
path => "/var/log/hello.log"
type => "log"
start_position => "beginning"
stat_interval => "3"
}
}
filter {
#通过grok解析nginx日志中的message字段
grok {
#match定义解析的字段名,定义方式:字段名=>值匹配
match => {
message => "%{IP:clientip} - (%{USERNAME:user}|-) \[%{HTTPDATE:timestamp}\] \"%{WORD:request_verb} %{NOTSPACE:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:status:int} %{NUMBER:body_sent:int} \"-\" \"%{GREEDYDATA:agent}\" \"-\""
}
}
#通过geoip解析IP地址的地理位置
geoip {
#source用于指定要解析的字段,结果将被保存到geoip字段里
source => "clientip"
#通过fields提取国家、城市
fields => ["country_name","city_name"]
}
}
output {
stdout {
codec => "rubydebug"
}
}
写入日志(私有地址不会显示)
sh
cat >> /var/log/hello.log<<EOF
221.198.204.180 - - [18/Aug/2020:16:21:05 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36" "-"
EOF
logstash显示结果如下,主要看geoip部分
sh
"geoip" => {
"city_name" => "Tianjin",
"country_name" => "China"
},
Filter date
我们希望日志展示的时间就是日志生成的时间,这样方便根据时间查找问题,但在logstash中,默认使用@timestamp时间值来表示日志的时间,@timestamp值的获取是根据logstash本身什么时候处理这条日志数据而产生时间的,并不是日志产生的时间。
所以我们就需要使用date插件来匹配日志产生的时间并将覆盖掉@timestamp时间,这样最后展示的时间就是日志生成时间。
参考地址:https://www.elastic.co/guide/en/logstash/current/plugins-filters-date.html
通过date将nginx日志中的timestamp的值覆盖掉@timestamp的值
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/file-test.conf
input {
file {
path => "/var/log/hello.log"
type => "log"
start_position => "beginning"
stat_interval => "3"
}
}
filter {
#通过grok解析nginx日志中的message字段
grok {
#match定义解析的字段名,定义方式:字段名=>值匹配
match => {
message => "%{IP:clientip} - (%{USERNAME:user}|-) \[%{HTTPDATE:timestamp}\] \"%{WORD:request_verb} %{NOTSPACE:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:status:int} %{NUMBER:body_sent:int} \"-\" \"%{GREEDYDATA:agent}\" \"-\""
}
}
#通过geoip解析IP地址地理位置
geoip {
#source用于指定要解析的字段,结果将被保存到geoip字段里
source => "clientip"
#通过fields提取国家、城市
fields => ["country_name","city_name"]
}
#通过date将timestamp的值覆盖掉@timestamp的值
date {
#在date中match用于匹配日期型的字段,并进行转化
match => ["timestamp","dd/MMM/yyyy:HH:mm:ss Z"]
#target用于指定转化后的日期保存到那个字段中
target => "@timestamp"
#timezone用于指定日期中的时区
timezone => "Asia/Shanghai"
}
}
output {
stdout {
codec => "rubydebug"
}
}
写入日志(私有地址不会显示)
sh
cat >> /var/log/hello.log<<EOF
221.198.204.180 - - [18/Aug/2020:16:21:05 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36" "-"
EOF
logstash显示结果如下
sh
"@timestamp" => 2020-08-18T08:21:05.000Z,
"timestamp" => "18/Aug/2020:16:21:05 +0800",
提示:在"@timestamp"中我们看到的时间是TUC时间,比中国标准时间早8小时,但是这个时间在kibana中展示后,默认会以中国标准时间来显示。
Filter useragent
在上述日志中,agent字段显示的内容是用户的操作系统、浏览器等信息,但是都显示在了一行,不便于提取和分析,可以通过useragent来进行解析,并以json格式来显示。
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/file-test.conf
input {
file {
path => "/var/log/hello.log"
type => "log"
start_position => "beginning"
stat_interval => "3"
}
}
filter {
#通过grok解析nginx日志中的message字段
grok {
#match定义解析的字段名,定义方式:字段名=>值匹配
match => {
message => "%{IP:clientip} - (%{USERNAME:user}|-) \[%{HTTPDATE:timestamp}\] \"%{WORD:request_verb} %{NOTSPACE:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:status:int} %{NUMBER:body_sent:int} \"-\" \"%{GREEDYDATA:agent}\" \"-\""
}
}
#通过geoip解析IP地址地理位置
geoip {
#source用于指定要解析的字段,结果将被保存到geoip字段里
source => "clientip"
#通过fields提取国家、城市
fields => ["country_name","city_name"]
}
#通过date将timestamp的值覆盖掉@timestamp的值
date {
#在date中match用于匹配日期型的字段,并进行转化
match => ["timestamp","dd/MMM/yyyy:HH:mm:ss Z"]
#target用于指定转化后的日期保存到那个字段中
target => "@timestamp"
#timezone用于指定日期中的时区
timezone => "Asia/Shanghai"
}
#通过useragent解析日志中的操作系统、浏览器等
useragent {
#source用于指定要解析的字段
source => "agent"
#target用于指定转化后的值保存到那个字段中
target => "agent"
}
}
output {
stdout {
codec => "rubydebug"
}
}
写入日志(私有地址不会显示)
sh
cat >> /var/log/hello.log<<EOF
221.198.204.180 - - [18/Aug/2020:16:21:05 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36" "-"
EOF
logstash显示结果如下
sh
"agent" => {
"name" => "Chrome",
"os" => "Windows",
"os_name" => "Windows",
"major" => "76",
"build" => "",
"minor" => "0",
"device" => "Other",
"patch" => "3809"
},
Filter mutate
mutate主要对字段进行,类型转换、添加字段、删除字段、替换字段、取列、重命名等操作。
- remove_field:删除字段
- split:字符串切割(取列)
- add_field:添加字段
- convert:类型转换
- gsub:字段替换
- rename:字段重命名
remove_field示例
在上述返回的日志中,有很多无用的字段,可以通过remove_field删除不需要的字段
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/file-test.conf
input {
file {
path => "/var/log/hello.log"
type => "log"
start_position => "beginning"
stat_interval => "3"
}
}
filter {
#通过grok解析nginx日志中的message字段
grok {
#match定义解析的字段名,定义方式:字段名=>值匹配
match => {
message => "%{IP:clientip} - (%{USERNAME:user}|-) \[%{HTTPDATE:timestamp}\] \"%{WORD:request_verb} %{NOTSPACE:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:status:int} %{NUMBER:body_sent:int} \"-\" \"%{GREEDYDATA:agent}\" \"-\""
}
}
#通过geoip解析IP地址地理位置
geoip {
#source用于指定要解析的字段,结果将被保存到geoip字段里
source => "clientip"
#通过fields提取国家、城市
fields => ["country_name","city_name"]
}
#通过date将timestamp的值覆盖掉@timestamp的值
date {
#在date中match用于匹配日期型的字段,并进行转化
match => ["timestamp","dd/MMM/yyyy:HH:mm:ss Z"]
#target用于指定转化后的日期保存到那个字段中
target => "@timestamp"
#timezone用于指定日期中的时区
timezone => "Asia/Shanghai"
}
#通过useragent解析日志中的操作系统、浏览器等
useragent {
#source用于指定要解析的字段
source => "agent"
#target用于指定转化后的值保存到那个字段中
target => "agent"
}
#通过mutate对数据进行清洗
mutate {
#通过remove_field移除多余字段
remove_field => ["message"]
}
}
output {
stdout {
codec => "rubydebug"
}
}
写入日志(私有地址不会显示)
sh
cat >> /var/log/hello.log<<EOF
221.198.204.180 - - [18/Aug/2020:16:21:05 +0800] "GET / HTTP/1.1" 304 0 "-" "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.87 Safari/537.36" "-"
EOF
logstash显示结果如下
sh
{
"request_verb" => "GET",
"geoip" => {
"city_name" => "Tianjin",
"country_name" => "China"
},
"httpversion" => "1.1",
"request" => "/",
"type" => "log",
"@version" => "1",
"path" => "/var/log/hello.log",
"host" => "logstash-node01",
"@timestamp" => 2020-08-18T08:21:05.000Z,
"agent" => {
"name" => "Chrome",
"os" => "Windows",
"os_name" => "Windows",
"major" => "76",
"build" => "",
"minor" => "0",
"device" => "Other",
"patch" => "3809"
},
"status" => 304,
"clientip" => "221.198.204.180",
"body_sent" => 0,
"timestamp" => "18/Aug/2020:16:21:05 +0800",
"user" => "-"
}
split示例
例如遇到这样形式的日志时,我们该如何收集?
sh
[INFO] 2020-08-28 08:11:14 [cn.test.dashboard.Main] - DAU|2784|提交订单|2020-08-28 02:04:04
[INFO] 2020-08-28 08:11:15 [cn.test.dashboard.Main] - DAU|6642|加入收藏|2020-08-28 06:05:03
[INFO] 2020-08-28 08:11:28 [cn.test.dashboard.Main] - DAU|7662|搜索|2020-08-28 08:07:14
[INFO] 2020-08-28 08:11:32 [cn.test.dashboard.Main] - DAU|7890|加入购物车|2020-08-28 06:07:17
[INFO] 2020-08-28 08:10:56 [cn.test.dashboard.Main] - DAU|7026|评论商品|2020-08-28 05:09:21
从文件中读取数据,从标准输出中输出内容,查看内容的显示方式
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/app_log.conf
input {
file {
path => "/var/log/app.log"
type => "log"
start_position => "beginning"
stat_interval => "3"
}
}
output {
stdout {
codec => rubydebug
}
}
执行logstash
sh
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/app_log.conf -r
写入日志
sh
cat >> /var/log/app.log<<EOF
[INFO] 2020-08-28 08:11:14 [cn.test.dashboard.Main] - DAU|2784|提交订单|2020-08-28 02:04:04
EOF
logstash显示的结果如下
sh
{
"@timestamp" => 2023-02-14T07:05:12.417Z,
"host" => "logstash-node01",
"type" => "log",
"path" => "/var/log/app.log",
"message" => "[INFO] 2020-08-28 08:11:14 [cn.test.dashboard.Main] - DAU|2784|提交订单|2020-08-28 02:04:04",
"@version" => "1"
}
问题:如何将message中的整体数据拆分成独立的字段便于分析!
mutate中的split可以对字段进行切割,指定以|
作为分隔符,来分割每一部分
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/app_log.conf
input {
file {
path => "/var/log/app.log"
type => "log"
start_position => "beginning"
stat_interval => "3"
}
}
filter {
mutate {
#指定message字段以|为分隔符
split => {"message" => "|"}
}
}
output {
stdout {
codec => rubydebug
}
}
写入日志
sh
cat >> /var/log/app.log<<EOF
[INFO] 2020-08-28 08:11:14 [cn.test.dashboard.Main] - DAU|2784|提交订单|2020-08-28 02:04:04
EOF
logstash显示结果如下
sh
"message" => [
[0] "[INFO] 2020-08-28 08:11:14 [cn.test.dashboard.Main] - DAU",
[1] "2784",
[2] "提交订单",
[3] "2020-08-28 02:04:04"
],
add_field示例
mutate中的add_field 可以将分割后的数据在添加上新的字段名称,以key = value的形式展示,便于数据的统计分析
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/app_log.conf
input {
file {
path => "/var/log/app.log"
type => "log"
start_position => "beginning"
stat_interval => "3"
}
}
filter {
mutate {
#指定message字段以|为分隔符
split => {"message" => "|"}
#为分割后的message[1][2][3]添加新字段
add_field => {
"UserID" => "%{[message][1]}"
"Action" => "%{[message][2]}"
"Date" => "%{[message][3]}"
}
#移除不需要的字段
remove_field => ["message","@version","type","path"]
}
date {
#在date中match用于匹配日期型的字段,并进行转化(转换后时区不对)
match => ["Date","yyyy-MM-dd HH:mm:ss"]
#target用于指定转化后的日期保存到那个字段中
target => "@timestamp"
#timezone用于指定日期中的时区
timezone => "Asia/Shanghai"
}
}
output {
stdout {
codec => rubydebug
}
}
写入日志
sh
cat >> /var/log/app.log<<EOF
[INFO] 2020-08-28 08:10:56 [cn.test.dashboard.Main] - DAU|7026|评论商品|2020-08-28 05:09:21
EOF
logstash显示结果如下
sh
{
"@timestamp" => 2020-08-27T21:09:21.000Z,
"host" => "logstash-node01",
"Action" => "评论商品",
"UserID" => "7026",
"Date" => "2020-08-28 05:09:21"
}
在日志分析中,有些数据需要进行累加统计,例如:userID字段,如果该字段的值是字符串类型,是无法进行累加统计的。
convert示例
mutate中的convert可以对字段中的值进行类型转换,可转化的类型包括:
- integer 整数类型
- float 浮点类型
- string 字符串类型
通过convert对日志中的数据类型进行转换
sh
[root@logstash-node01 ~]# cat /etc/logstash/conf.d/app_log.conf
input {
file {
path => "/var/log/app.log"
type => "log"
start_position => "beginning"
stat_interval => "3"
}
}
filter {
mutate {
#指定message字段以|为分隔符
split => {"message" => "|"}
#为分割后的message[1][2][3]添加新字段
add_field => {
"UserID" => "%{[message][1]}"
"Action" => "%{[message][2]}"
"Date" => "%{[message][3]}"
}
#对字段进行格式转换
convert => {
"UserID" => "integer"
"Action" => "string"
"Date" => "string"
}
#移除不需要的字段
remove_field => ["message","@version","type","path"]
}
date {
#在date中match用于匹配日期型的字段,并进行转化
match => ["Date","yyyy-MM-dd HH:mm:ss"]
#target用于指定转化后的日期保存到那个字段中
target => "@timestamp"
#timezone用于指定日期中的时区
timezone => "Asia/Shanghai"
}
}
output {
stdout {
codec => rubydebug
}
}
写入日志
sh
cat >> /var/log/app.log<<EOF
[INFO] 2020-08-28 08:10:56 [cn.test.dashboard.Main] - DAU|7026|评论商品|2020-08-28 05:09:21
EOF
logstash显示结果如下
sh
{
"@timestamp" => 2020-08-27T21:09:21.000Z,
"host" => "logstash-node01",
"Action" => "评论商品",
"UserID" => "7026",
"Date" => "2020-08-28 05:09:21"
}
提示:表面上虽然看不出来效果,但实际上已经进行了类型转换。
第四章:ELK收集App日志实践
APP日志收集思路
1.首先通过Filebeat读取日志文件中的内容,并且将内容发送给Logstash;
2.Logstash接收到数据后,对数据进行处理,然后输出给Elasticsearch;
3.Kibana中添加ES索引,读取索引数据并分析展示;
配置filebeat收集日志
将app.log日志上传到webserver主机的/var/log/目录
sh
[root@webserver ~]# wc -l /var/log/app.log
166030 /var/log/app.log
[root@webserver ~]# head -1 /var/log/app.log
[INFO] 2020-08-28 08:08:12 [cn.test.dashboard.Main] - DAU|9136|加入收藏|2020-08-28 01:05:02
修改filebeat文件采集日志
sh
[root@webserver ~]# vim /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths: /var/log/app.log
#将日志输出给logstash的5045端口(该端口自定义)
output.logstash:
hosts: ["192.168.0.99:5045"]
重启filebeat
sh
[root@webserver ~]# systemctl restart filebeat
配置Logstash日志过滤
sh
[root@logstash-node01 ~]# cat /etc/logstash/conf.d/app_log.conf
#从filebeat接收事件,定义Logstash监听的端口为5045
input {
beats {
port => 5045
}
}
filter {
mutate {
#指定message字段以|为分隔符
split => {"message" => "|"}
#为分割后的message[1][2][3]添加新字段
add_field => {
"UserID" => "%{[message][1]}"
"Action" => "%{[message][2]}"
"Date" => "%{[message][3]}"
}
#对字段进行格式转换
convert => {
"UserID" => "integer"
"Action" => "string"
"Date" => "string"
}
#移除不需要的字段
remove_field => ["message","@version","type","path"]
}
date {
#在date中match用于匹配日期型的字段,并进行转化
match => ["Date","yyyy-MM-dd HH:mm:ss"]
#target用于指定转化后的日期保存到那个字段中
target => "@timestamp"
#timezone用于指定日期中的时区
timezone => "Asia/Shanghai"
}
}
output {
elasticsearch {
hosts => ["192.168.0.81:9200","192.168.0.82:9200","192.168.0.83:9200"]
#定义在ES中的索引名称
index => "app_log-%{+YYYY.MM.dd}"
#由于修改了字段类型,可能无法被默认模板识别,需要覆盖默认的索引模板
template_overwrite => true
}
}
启动logstash
启动logstash并放入后台运行
sh
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/app_log.conf &> /dev/null &
[root@logstash-node01 ~]# jobs -l
logstash启动多实例
logstash只启动一个进程不需要指定数据目录,如果想要启动多个进程,需要为每个进程指定不同的数据目录,需要加--path.data
参数,然后就可以启动多实例
sh
#创建数据目录
[root@logstash-node01 ~]# mkdir -p /etc/logstash/data/app
[root@logstash-node01 ~]# chown -R logstash:logstash /etc/logstash/data
#启动进程放入后台,并指定数据目录(提前检测好配置文件语法)
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/app_log.conf --path.data=/etc/logstash/data/app/ &> /dev/null &
[root@logstash-node01 ~]# jobs -l
kibana检查索引是否生成,通过 Stack Management
- 索引管理
查看
配置Kibana数据展示
通过左下角 Stack Magenment
--> 索引模式
创建索引匹配
由于日志的时间是2020.08,所以在Discover
中要根据具体的时间去查看日志内容
在侧边导航栏点击 Visualize
- 新建可视化
创建饼图来展示索引中Action的值
在侧边导航栏点击 Visualize
- 新建可视化
创建标签云图
在侧边导航栏点击 Visualize
- 新建可视化
创建数据表
在侧边导航栏点击 Dashboard
- 创建新的仪表板
第五章:ELK收集Nginx日志实践
Nginx日志收集思路
1.首先通过Filebeat读取日志文件中的内容,并且将内容发送给Logstash;
2.Logstash接收到数据后,对数据进行处理,然后输出给Elasticsearch;
3.Kibana中添加ES索引,读取索引数据并分析展示;
配置filebeat收集日志
将nginx_access.log日志上传到webserver主机的/var/log/目录
sh
[root@webserver ~]# wc -l /var/log/nginx_access.log
10000 /var/log/nginx_access.log
[root@webserver ~]# head -1 /var/log/nginx_access.log
183.162.52.7 - - [10/Nov/2020:00:01:02 +0800] "POST /api3/getadv HTTP/1.1" 200 813 "www.test.com" "-" cid=0×tamp=1478707261865&uid=2871142&marking=androidbanner&secrect=a6e8e14701ffe9f6063934780d9e2e6d&token=f51e97d1cb1a9caac669ea8acc162b96 "oldxu/5.0.0 (Android 5.1.1; Xiaomi Redmi 3 Build/LMY47V),Network 2G/3G" "-" 10.100.134.244:80 200 0.027 0.027
创建filebeat文件
sh
[root@webserver ~]# vim /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths: /var/log/nginx_access.log
tags: ["access"]
- type: log
enabled: true
paths: /var/log/nginx/error.log
tags: ["error"]
#将日志输出给logstash的5044端口
output.logstash:
hosts: ["192.168.0.95:5046"]
配置Logstash日志过滤
logstash文件配置如下
sh
[root@logstash-node01 ~]# cat /etc/logstash/conf.d/nginx.conf
input {
beats {
port => 5046
}
}
filter {
#通过if判断日志中标签字段包含access,该标签在filebeat中定义
if "access" in [tags][0] {
#通过grok将日志解析为json格式
grok {
match => { "message" => "%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] \"%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:response} (?:%{NUMBER:bytes}|-) %{QS:hostname} (?:%{QS:referrer}|-) (?:%{NOTSPACE:post_args}|-) %{QS:useragent} (?:%{QS:x_forward_for}|-) (?:%{URIHOST:upstream_host}|-) (?:%{NUMBER:upstream_response_code}|-) (?:%{NUMBER:upstream_response_time}|-) (?:%{NUMBER:response_time}|-)" }
}
#通过useragent获取客户端的操作系统、浏览器等
useragent {
source => "useragent"
target => "useragent"
}
#通过clientip获取客户端地理位置
geoip {
source => "clientip"
#只提取国家和城市
fields => ["country_name","region_name"]
}
#将日志中的timestamp的值赋给@timestamp
date {
match => ["timestamp","dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
timezone => "Asia/Shanghai"
}
#通过convert对日志中的数据类型进行转换
mutate {
#bytes字段是发送给客户端的文件内容的大小,转换为整数类型
convert => ["bytes","integer"]
#response_time字段是服务端的响应时间,转换为浮点类型
convert => ["response_time", "float"]
#upstream_response_time字段是负载均衡服务器响应时间,转换为浮点类型
convert => ["upstream_response_time", "float"]
#移除不需要的字段
remove_field => ["message","agent","tags","ecs","input","@version","log","agent","post_args","httpversion"]
#添加索引名称
add_field => { "target_index" => "nginx-access-%{+YYYY.MM.dd}"}
}
#提取referrer中具体的域名/^"http/
if [referrer] =~ /^"http/ {
#通过grok将该字段解析为json格式,获取客户端的来源地址
grok {
match => { "referrer" => '%{URIPROTO}://%{URIHOST:referrer_host}' }
}
}
}
#定义错误日志,在else结构中判断日志中标签字段包含error,该标签在filebeat中定义
else if "error" in [tags][0] {
date {
match => ["timestamp","dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
timezone => "Asia/Shanghai"
}
mutate {
add_field => { "target_index" => "nginx-error-%{+YYYY.MM.dd}" }
}
}
}
output {
elasticsearch {
hosts => ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
index => "%{[target_index]}"
}
}
启动Logstach进程
sh
#创建数据目录
[root@logstash-node01 ~]# mkdir -p /etc/logstash/data/nginx
[root@logstash-node01 ~]# chown -R logstash:logstash /etc/logstash/data/nginx
#启动进程放入后台,并指定数据目录(提前检测好配置文件语法)
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/nginx.conf --path.data=/etc/logstash/data/nginx/ &> /dev/null &
[root@logstash-node01 ~]# jobs -l
重启filebeat
sh
[root@webserver ~]# systemctl restart filebeat
配置Kibana数据展示
1.总访问次数
2.独立IP数量
3.共产生的流量
4.访问的时间趋势
5.客户端设备、浏览器
6.客户端分布的地区
7.访问状态码
通过左下角 Stack Magenment
--> 索引模式
创建索引匹配
由于日志的时间是2020.08,所以在Discover
中要根据具体的时间去查看日志内容
在侧边导航栏点击 Visualize
- 新建可视化
通过 指标图
展示网站总访问次数
在侧边导航栏点击 Visualize
- 新建可视化
通过 指标图
展示网站独立IP数量
在侧边导航栏点击 Visualize
- 新建可视化
通过 指标图
展示网站访问共产生的流量Bytes
通过左下角 Stack Magenment
--> 索引模式
在app-logstash-nginx-access*
索引模式中调整字段类型
再次回到 Visualize
中查看流量数据
在侧边导航栏点击 Visualize
- 新建可视化
通过 垂直条形图
展示网站访问时间趋势图
在侧边导航栏点击 Visualize
- 新建可视化
通过 饼图
展示网站访问状态码
在侧边导航栏点击 Visualize
- 新建可视化
通过 饼图
展示网站客户端设备
在侧边导航栏点击 Visualize
- 新建可视化
通过 饼图
展示客户端使用的浏览器
在侧边导航栏点击 Visualize
- 新建可视化
通过 数据表
展示访问次数最高的前20客户端IP
创建Dashboard面板,在侧边导航栏点击 Dashboard
- 创建新的仪表板
第六章:ELK收集MySQL慢日志
什么是MySQL慢日志?
MySQL的慢查询日志是MySQL提供的一种日志记录,它用来记录在MySQL中响应时间超过阀值的语句,则会被记录到慢查询日志中,当我们通过ELK收集到慢查询日志中响应超时的SQL语句时,把这些语句提交给对应的人员进行优化处理。
MySQL慢日志收集思路
1.安装MySQL并开启慢查询日志;
2.通过Filebeat读取日志文件中的内容,并且将内容发送给Logstash;
2.Logstash接收到数据后,对数据进行处理,然后输出给Elasticsearch;
3.Kibana中添加ES索引,读取索引数据并分析展示;
配置MySQL
安装mariadb,并开启慢查询日志(实验环境安装在nginx同一个主机)
sh
[root@webserver ~]# yum install mariadb-server
修改my.cnf文件开启慢查询日志, 默认情况下,Mysql数据库并不启动慢查询日志,需要我们手动来设置这个参数
sh
[root@webserver ~]# vim /etc/my.cnf
[mysqld]
#开启慢查询日志
slow_query_log=ON
#定义慢查询日志文件的路径
slow_query_log_file=/var/log/mariadb/slow.log
#定义SQL语句的超时时间,默认为10s,意思是运行10s以上的语句
long_query_time=3
...
启动mariadb
sh
[root@webserver ~]# systemctl start mariadb
进入数据库,执行一条查询超时语句,产生慢日志
sh
select sleep(1) user,host from mysql.user;
检查慢日志是否生成
sh
[root@webserver ~]# ls /var/log/mariadb/
slow.log
[root@webserver ~]# cat /var/log/mariadb/slow.log
Time Id Command Argument
# Time: 230320 22:21:18 //命令执行时间
# User@Host: root[root] @ localhost []
# Thread_id: 2 Schema: QC_hit: No
# Query_time: 6.029917 //命令执行时消耗的时间
SET timestamp=1679322078; //命令的执行时间,Unix时间
select sleep(1) user,host from mysql.user; #执行的命令
配置Filebeat收集日志
通过filebeat把日志中的多行数据进行合并成一行,在交给logstash过滤
sh
[root@webserver ~]# vim /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths: /var/log/mariadb/slow.log
tags: ["slow"]
exclude_lines: ['^\# Time'] #排除匹配的行
multiline.pattern: '^\# User' #匹配以#User开头的行
multiline.negate: true #将不符合规则的行合并在一起
multiline.match: after #合并到上一行的末尾
multiline.max_lines: 1000 #默认最大合并的行为500,可根据实际情况调整
#输出到console(控制台)终端
output.console:
pretty: true
停止filebeat
sh
[root@webserver ~]# systemctl stop filebeat
启动filebeat在前台运行:-e 日志输出到前台,-c 指定配置文件
效果如下:将多行合数据并成一条日志
sh
[root@webserver ~]# filebeat -e -c filebeat.yml
...
"message": "# User@Host: root[root] @ localhost []\n# Thread_id: 2 Schema: QC_hit: No\n# Query_time: 6.015497 Lock_time: 0.000046 Rows_sent: 6 Rows_examined: 6\nSET timestamp=1679378633;\nselect sleep(1) host,user from mysql.user;",
...
验证效果后,将日志输出到logstach
sh
[root@webserver ~]# vim /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths: /var/log/mariadb/slow.log
tags: ["slow"]
exclude_lines: ['^\# Time'] #排除匹配的行
multiline.pattern: '^\# User' #匹配以#User开头的行
multiline.negate: true #将不符合规则的行合并在一起
multiline.match: after #合并到上一行的末尾
multiline.max_lines: 1000 #默认最大合并的行为500,可根据实际情况调整
#输出到logstash
output.logstash:
hosts: ["192.168.0.95:5047"]
启动filebeat
sh
[root@webserver ~]# systemctl start filebeat
配置Logstash过滤日志
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/mysql-slow.conf
input {
beats {
port => 5047
}
}
output {
stdout {
codec => rubydebug
}
}
启动logstash
sh
#创建数据目录
[root@logstash-node01 ~]# mkdir -p /etc/logstash/data/mysql
[root@logstash-node01 ~]# chown -R logstash:logstash /etc/logstash/data/mysql
#启动进程,并指定数据目录(提前检测好配置文件语法)
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/mysql-slow.conf -r --path.data=/etc/logstash/data/mysql/
在数据库执行慢语句,logstash显示结果如下,主要看message字段:
sh
{
"host" => {
"name" => "webserver"
},
"ecs" => {
"version" => "1.5.0"
},
"message" => "# User@Host: root[root] @ localhost []\n# Thread_id: 2 Schema: QC_hit: No\n# Query_time: 6.012626 Lock_time: 0.000048 Rows_sent: 6 Rows_examined: 6\nSET timestamp=1679381735;\nselect sleep(1) host,user from mysql.user;",
"@timestamp" => 2023-03-21T06:55:37.679Z,
"agent" => {
"name" => "webserver",
"hostname" => "webserver",
"ephemeral_id" => "bfafd9ab-ff79-43a9-b5cb-c680bb167df3",
"version" => "7.8.1",
"type" => "filebeat",
"id" => "0601e86d-e04d-49a7-833a-40f1776ffb2d"
},
"log" => {
"file" => {
"path" => "/var/log/mariadb/slow.log"
},
"offset" => 2821,
"flags" => [
[0] "multiline"
]
},
"tags" => [
[0] "slow",
[1] "beats_input_codec_plain_applied"
],
"@version" => "1",
"input" => {
"type" => "log"
}
}
该日志过滤思路:
- 该日志中最重要的数据在message字段中,需要通过grok进行格式转换;
- 转换Json格式后删除无用字段;
- 将日志中 timestamp时间覆盖掉 @timestamp时间;
Logstash过滤日志
sh
[root@logstash-node01 ~]# vim /etc/logstash/conf.d/mysql-slow.conf
input {
beats {
port => 5047
}
}
filter {
mutate {
#gsub替换:将合并后日志中的\n转换为空格作为每一部分的分隔符
gsub => [ "message", "\n", " "]
}
grok {
match => { "message" => "(?m)^# User@Host: %{USER:User}\[%{USER-2:User}\] @ (?:(?<Clienthost>\S*) )?\[(?:%{IP:Client_IP})?\] # Thread_id: %{NUMBER:Thread_id:integer}\s+ Schema: (?:(?<DBname>\S*) )\s+QC_hit: (?:(?<QC_hit>\S*) )# Query_time: %{NUMBER:Query_Time}\s+ Lock_time: %{NUMBER:Lock_Time}\s+ Rows_sent: %{NUMBER:Rows_Sent:integer}\s+Rows_examined: %{NUMBER:Rows_Examined:integer} SET timestamp=%{NUMBER:timestamp}; \s*(?<Query>(?<Action>\w+)\s+.*)" }
}
#将日志中 timestamp时间覆盖掉 @timestamp时间;
date {
match => ["timestamp","UNIX","YYYY-MM-dd HH:mm:ss"]
target => "@timestamp"
timezone => "Asia/Shanghai"
}
mutate {
#移除message等字段
remove_field => ["message","ecs","Thread_id","Rows_Sent","Clienthost","@version","QC_hit","Lock_Time","input","timestamp","log","offset","Rows_Examined","agent","tags"]
#对Query_time格式转换为浮点数
convert => ["Query_Time","float"]
#添加索引名称
add_field => { "target_index" => "mysql-slow-%{+YYYY.MM.dd}" }
}
}
output {
stdout {
codec => rubydebug
}
}
grok解析MySQL不同版本日志,解析语法不同的,详细请参考:https://blog.csdn.net/weixin_34356555/article/details/92513002
在数据库执行慢语句,logstash显示结果如下:
sh
{
"Action" => "select",
"target_index" => "mysql-slow-2023.04.11",
"User" => "root",
"Query_Time" => 6.007203,
"host" => {
"name" => "webserver"
},
"Query" => "select sleep(1) user,host from mysql.user;",
"@timestamp" => 2023-04-11T12:36:16.000Z
}
Logstash将过滤后日志写入到es
sh
input {
beats {
port => 5047
}
}
filter {
mutate {
#gsub替换:将合并后日志中的\n转换为空格作为每一列的分隔符,方便grok解析
gsub => [ "message", "\n", " "]
}
grok {
match => { "message" => "(?m)^# User@Host: %{USER:User}\[%{USER-2:User}\] @ (?:(?<Clienthost>\S*) )?\[(?:%{IP:Client_IP})?\] # Thread_id: %{NUMBER:Thread_id:integer}\s+ Schema: (?:(?<DBname>\S*) )\s+QC_hit: (?:(?<QC_hit>\S*) )# Query_time: %{NUMBER:Query_Time}\s+ Lock_time: %{NUMBER:Lock_Time}\s+ Rows_sent: %{NUMBER:Rows_Sent:integer}\s+Rows_examined: %{NUMBER:Rows_Examined:integer} SET timestamp=%{NUMBER:timestamp}; \s*(?<Query>(?<Action>\w+)\s+.*)" }
}
#将日志中 timestamp时间覆盖掉 @timestamp时间;
date {
match => ["timestamp","UNIX","YYYY-MM-dd HH:mm:ss"]
target => "@timestamp"
timezone => "Asia/Shanghai"
}
mutate {
#移除message等字段
remove_field => ["message","input","timestamp"]
#对Query_time Lock_time 格式转换为浮点数
convert => ["Lock_Time","float"]
convert => ["Query_Time","float"]
#添加索引名称
add_field => { "target_index" => "mysql-slow-%{+YYYY.MM.dd}" }
}
}
output {
elasticsearch {
hosts => ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
index => "%{[target_index]}"
}
}
启动进程放入后台,并指定数据目录(提前检测好配置文件语法)
sh
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/mysql-slow.conf --path.data=/etc/logstash/data/mysql/ &> /dev/null &
[root@logstash-node01 ~]# jobs -l
配置Kibana数据展示
通过左下角 Stack Magenment
--> 索引管理
追加mysql-slow文件中的慢语句到/var/log/mariadb/mysql-slow.log中
sh
[root@webserver ~]# > /var/log/mariadb/slow.log
[root@webserver ~]# cat slow-2020-01.log >> /var/log/mariadb/slow.log
kibana对日志进行分析与展示
在侧边导航栏点击 Visualize
- 新建可视化
通过 数据表
展示慢语句
第七章:ELKStack 集群安全
默认情况下,当我们搭建好es集群后,es的9200、9300及Kibana的5601可任意访问,这显然是不安全的。
而且在同一个局域网中,如果我们启动了多个es节点,且集群的名字相同,那么他们可能会自动加入集群,导致集群匹配和数据异常问题,这显然也是不安全的。
针对es集群常规使用中的多种安全问题,Elasticsearch安全特性提供了独立的身份验证机制,能够使用户快速地对集群进行密码保护。
Elasticsearch集群安全功能,主要包括:
- 通过密码保护、基于角色的访问控制防止未经授权的访问;
- 使用
SSL/TLS
加密保护数据的完整性; - 维护审计跟踪;
集群安全TLS
1.启用Elastic TLS功能,集群之间通讯采用TLS加密通讯;
用elasticsearch-certutil工具来生成节点证书文件 (文件名称:elastic-certificates.p12) ,证书密码为空
只需在一台es01节点生成即可
sh
[root@es01 ~]# /usr/share/elasticsearch/bin/elasticsearch-certutil \
cert -out /etc/elasticsearch/elastic-certificates.p12 -pass ""
#默认证书权限600,运行elasticsearch程序的用户没有权限读取,会造成elasticsearch启动失败
[root@es01 ~]# chmod 660 /etc/elasticsearch/elastic-certificates.p12
拷贝TLS证书文件到其他es节点
sh
[root@es01 ~]# scp /etc/elasticsearch/elastic-certificates.p12 192.168.0.92:/etc/elasticsearch/
[root@es01 ~]# scp /etc/elasticsearch/elastic-certificates.p12 192.168.0.93:/etc/elasticsearch/
在对应节点修改文件权限为660
sh
[root@es02 ~]# chmod 660 /etc/elasticsearch/elastic-certificates.p12
[root@es03 ~]# chmod 660 /etc/elasticsearch/elastic-certificates.p12
修改elasticsearch.yml配置文件,启动TLS功能
es01、es02、es03所有es节点都要配置
sh
#用vi打开,vim语法检测容易出现错误
vi /etc/elasticsearch/elasticsearch.yml
...
#添加到文件最后
#启用安全功能
xpack.security.enabled: true
#启用传输层的认证设置
xpack.security.transport.ssl.enabled: true
#验证证书是否由受信任的机构(CA)签名
xpack.security.transport.ssl.verification_mode: certificate
#证书文件的存放位置
xpack.security.transport.ssl.keystore.path: elastic-certificates.p12
#密钥文件的存放位置
xpack.security.transport.ssl.truststore.path: elastic-certificates.p12
所有es节点重启elasticsearch
sh
systemctl restart elasticsearch
集群安全Basic Auth
es的基础安全认证,主要涉及角色和用户的访问控制,在es的基础安全认证框架中,用户可以自主生成es的系统默认角色用户,并使用es默认生成的超级管理员用户elastic用户进行es集群的相关监控和管理操作,包括创建用户组和普通用户并进行认证授权,后续可以使用创建的普通用户进行es集群数据的访问和操作。
es提供六个带有系统默认角色的默认账号:
-
elastic:为es超级管理员,拥有所有权限;
-
kibana_system:用于Kibana服务和es集群的通信和信息交互;
-
logstash_system:用于Logstash服务和es集群的通信和信息交互;
-
beats_system:用于Beats服务和es集群的通信和信息交互;
-
apm_system:用于APM(程序性能指标收集)服务和es集群的通信和信息交互;
-
remote_monitoring_user:Metricbeat用户在es集群中收集和存储监控信息时使用,它具有远程监视代理和远程监视收集器内置角色;
生成集群账号
只需要在一个节点生成即可
sh
[root@es01 ~]# /usr/share/elasticsearch/bin/elasticsearch-setup-passwords auto
...
Please confirm that you would like to continue [y/N]y #输入y
#请记住这些用户名和密码
Changed password for user apm_system
PASSWORD apm_system = q4GDcPwgmmieTYBUN6zy
Changed password for user kibana_system
PASSWORD kibana_system = rphcraTeJD5NxcGFpn3H
Changed password for user kibana #kibana这个用户已经废弃,不需要记住
PASSWORD kibana = rphcraTeJD5NxcGFpn3H
Changed password for user logstash_system
PASSWORD logstash_system = etRtgMNASa4rzI2Ogg6f
Changed password for user beats_system
PASSWORD beats_system = kQVW1QJDP1DvNVUf4gkn
Changed password for user remote_monitoring_user
PASSWORD remote_monitoring_user = COpqlD7BWLB9tUvZMjoJ
Changed password for user elastic
PASSWORD elastic = 0hYfB32A7v5Y0CClmsbO
配置Kibana认证
将上边生成的kibana_system用户和密码写入到kibana.yml文件中,应为此时集群已经设置用户密码访问
sh
[root@kibana ~]# vim /etc/kibana/kibana.yml
...
#大约在文件的46、47行左右
elasticsearch.username: "kibana_system"
elasticsearch.password: "rphcraTeJD5NxcGFpn3H"
重启kibana服务
sh
[root@kibana ~]# systemctl restart kibana
登录kibana(或Cerebor)时需要使用集群超级管理员elastic登录(其他账号没有权限),而前边kibana_system账号是kibana用于访问es的账号,并不是登录kibana的账号
重新修改kibana配置文件指定用户密码
sh
[root@kibana ~]# vim /etc/kibana/kibana.yml
...
elasticsearch.username: "kibana_system"
elasticsearch.password: "123456"
重启服务
sh
[root@kibana ~]# systemctl restart kibana
配置Logstash认证
将logstash_system用户和密码写入到对应的logstash文件中,否则logstash无法将数据写入到es中
以mysql慢日志为例:
sh
output {
elasticsearch {
hosts => ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
index => "%{[target_index]}"
user => "elastic" #注意,这里使用的是管理员,默认logstash_system没有权限在es中创建索引
password => "123456"
}
}
重新启动进程放入后台,并指定数据目录(提前检测好配置文件语法)
sh
[root@logstash-node01 ~]# logstash -f /etc/logstash/conf.d/mysql-slow.conf --path.data=/etc/logstash/data/mysql/ &> /dev/null &
[root@logstash-node01 ~]# jobs -l
生成慢SQL语句(执行了三次)
sh
select sleep(1) user,host from mysql.user;
验证认证
kibana验证,注意语句生成的时间,按照当前语句产生时间查看数据
第八章:ELKStack架构引入消息队列
什么是消息队列?
消息message:互联网中传输的任何数据,都可以称为消息;
队列Queue:是一种先进先出的机制,类似于排队买票;
消息队列MQ:是用来保存消息的一个容器,消息队列提供两个功能接口供外部调用;
- 生产者Producer:把数据放到消息队列容器中即为生产者;
- 消费者Consumer:从消息队列容器中取走数据即为消费者;
ELKStack引入消息队列说明
场景说明:
使用filebeat 或logstash 直接将日志写入ES ,如果采集的日志节点数量过多,日志量比较大,那么日志频繁的被写入ES 的情况下,可能会造成ES 出现超时、丢失等情况。因为ES 需要处理数据,存储数据,搜索数据,所以性能会变的很缓慢;
解决方法:
让filebeat 或Logstash 直接将日志写入到消息队列中,因为消息队列可以起到一个缓冲作用,然后logstash在根据ES的处理能力进行数据消费,匀速写入ES 集群,这样能有效缓解ES 写入性能的瓶颈的问题;
Kafka介绍
消息队列软件kafka、RocketMQ、RabbitMQ等,kafka是一个MQ消息队列系统,在大数据场景下使用较多,kafka优势主要体现在吞吐量上,可以满足每秒百万级的消息的生产和消费;并且自身有一套完善的消息存储机制,确保数据的高效安全的持久化;基于分布式的扩展和容错机制,当某一台发生故障失效时,可以实现故障自动转移。
Kafka基础架构
-
Broker:kafka集群中包含多个kafka节点,一个kafka节点就称为一个broker;
-
Topic:主题,kafka将消息分门别类,例如:nginx日志与tomcat日志,每一类消息称为一个Topic;
-
Partition:分区,真正存储消息的地方,每个Topic可以在一个Partition中,也可以在多个Partition中;
-
Replication:副本,每个Partition可以有一个或多个副本,分布在不同的broker节点,其目的是实现数据的分布存储与备份;
副本又分为Leader与Follower之分:
Leader副本:选出一个副本作为Leader,所有的读写请求都会通过Leader完成(类似ES中的主分片);
Follower副本:其余的副本作为Follower,负责备份数据,实现数据高可用,所有的Follower会自动从Leader中复制数据(类似ES中的副本分片);
-
Producer:消息的生产者,向kafaka指定的topic发布消息;
-
Consumer:消息的消费者,订阅指定的topic并读取其发布的消息;
-
Consumer group:每个consumer属于一个特定的consumer group中,例如:一个Logstash在单个组中消费一个topic中的消息,而多个consumer也可以属于同一个consumer group中,例如:多个Logstash在同一个组中消费一个topic中的消息,如果一个topic中有10条消息,每个消费者平均消费5条(实现负载均衡),如果其中一个Logstash故障,另外一个Logstash消费剩余的全部消息,可避免消息没有备消费;
-
zookeeper:主要用来存储kafka的元数据信息,例如:有多少个集群节点,主题名称,以及协调kafaka的正常运行,topic中的消息数据并不存储在zk中,而是存储在kafaka磁盘文件中;
最新消息,即将发布的 Kafka 2.8 版本会移除 ZooKeeper,也就是说可以在没有 ZooKeeper 的情况下运行Kafka。
ZooKeeper集群部署
安装kafka之前,先部署ZK集群,ZooKeeper是一个分布式协调服务,就是为用户的分布式应用程序提供协调服务,而kafka依赖zookeeper来存储kafka的元数据信息,例如:有多少个集群节点,主题名称,以及协调kafaka的正常运行。
环境准备
主机名 | IP地址 | 操作系统 | 硬件配置 |
---|---|---|---|
kafka-node01 | 192.168.0.97 | CentOS 7.6 | 2Core/4G mem/50G disk |
kafka-node02 | 192.168.0.98 | CentOS 7.6 | 2Core/4G mem/50G disk |
kafka-node03 | 192.168.0.99 | CentOS 7.6 | 2Core/4G mem/50G disk |
zookeeper是通过java开发,所以运行需要依赖JAVA环境,安装zookeeper之前,先安装JDK,需要依赖jdk1.8以上版本,且需要maven作为zookeeper依赖
三台节点配置相同
sh
]# yum -y install java maven
安装zookeeper
软件包下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/
sh
]# tar -xf apache-zookeeper-3.5.6-bin.tar.gz -C /opt/
]# cd /opt/apache-zookeeper-3.5.6-bin/conf/
创建zookeeper配置文件zoo.cfg
(默认不存在)
sh
]# cat zoo.cfg
# 服务器之间或客户端与服务器之间维持心跳的时间间隔tickTime以毫秒为单位。
tickTime=2000
# 集群中的follower服务器(F)与leader服务器(L)之间的初始连接心跳数10*tickTime(20秒)
initLimit=10
# 集群中的follower服务器与leader服务器之间请求和应答之间能容忍的最多心跳数5*tickTime(10秒)
syncLimit=5
# 数据保存目录:即当前目录的上一级目录/opt/apache-zookeeper-3.5.6-bin
dataDir=../data
# 日志保存目录:即当前目录的上一级目录/opt/apache-zookeeper-3.5.6-bin
dataLogDir=../logs
# 客户端连接端口
clientPort=2181
# 客户端最大连接数,根据自己实际情况设置,默认为60个
maxClientCnxns=60
# 三个节点配置,格式为:server.节点编号=节点地址:leader和follower通信端口:集群选举端口
server.1=192.168.0.97:2888:3888
server.2=192.168.0.98:2888:3888
server.3=192.168.0.99:2888:3888
创建zoo.cfg文件中定义的数据目录、日志保存目录
sh
]# pwd
/opt/apache-zookeeper-3.5.6-bin/conf
]# mkdir ../data
]# mkdir ../logs
kafka-node01节点
根据zoo.cfg文件中定义的节点编号,创建节点编号文件,来声明本机在集群中的ID
sh
]# pwd
/opt/apache-zookeeper-3.5.6-bin/conf
]# echo 1 > ../data/myid
]# cat ../data/myid
1
kafka-node02节点
根据zoo.cfg文件中定义的节点编号,创建节点编号文件,来声明本机在集群中的ID
sh
]# pwd
/opt/apache-zookeeper-3.5.6-bin/conf
]# echo 2 > ../data/myid
]# cat ../data/myid
2
kafka-node03节点
根据zoo.cfg文件中定义的节点编号,创建节点编号文件,来声明本机在集群中的ID
sh
]# pwd
/opt/apache-zookeeper-3.5.6-bin/conf
]# echo 3 > ../data/myid
]# cat ../data/myid
3
三台节点启动zookeeper服务
sh
]# pwd
/opt/apache-zookeeper-3.5.6-bin/bin
]# ./zkServer.sh start
每个节点查看在集群中的角色
sh
[root@kafka-node01 bin]# ./zkServer.sh status
...
Mode: follower
sh
[root@kafka-node02 bin]# ./zkServer.sh status
...
Mode: leader
sh
[root@kafka-node03 bin]# ./zkServer.sh status
...
Mode: follower
Kafka集群部署
kafka是通过java开发,所以运行需要依赖JAVA环境,安装kafka之前,先安装JDK,需要依赖jdk1.8以上版本,且需要maven作为kafka依赖(但是kafka和zk都在同一套环境,就不需要重复安装)
三台节点配置相同
安装kafka
软件包下载地址:https://mirrors.tuna.tsinghua.edu.cn/apache/kafka/
sh
]# tar -xf kafka_2.12-2.3.0.tgz -C /opt/
]# cd /opt/kafka_2.12-2.3.0/config/
kafka-node01节点
修改kafka配置文件server.properties
使用下边内容替换默认配置文件内容
sh
[root@kafka-node01 config]# cat server.properties
############################# Server Basics #############################
# broker的id,值为整数,且必须唯一,在一个集群中不能重复,与zk的ID没有任何关系
broker.id=1
############################# Socket Server Settings #############################
# kafka当前节点监听端口,默认9092
listeners=PLAINTEXT://192.168.0.97:9092
# 处理网络请求的线程数量,通常与cpu核心数量保持一致
num.network.threads=2
# 执行磁盘IO操作的线程数量,默认为8个
num.io.threads=8
# socket发送数据的缓冲区大小,默认100KB(数据从磁盘加载到缓冲区,在提供给消费者)
socket.send.buffer.bytes=102400
# socket接收数据的缓冲区大小,默认100KB(缓冲区达到100KB后,将数据写入到磁盘)
socket.receive.buffer.bytes=102400
# socket所能接收的一个请求的最大大小,默认为100M(超出该上限无法接受)
socket.request.max.bytes=104857600
############################# Log Basics #############################
# kafka存储消息数据的目录,即当前目录的上一级目录/opt/kafka_2.12-2.3.0/,该目录应该有充足的空间
log.dirs=../data
# 每个topic默认的partition(分区)
num.partitions=1
# 分区副本数量,更高的副本数量会带来更高的可用性和存储成本
# 比如指定3副本,代表每个分区会有3个副本,1个作为首领Leader提供读写,另外2个跟随者Follower向主同步数据来保证高可用,当Leader的Replication故障,会进行故障自动转移,副本的数量最好与节点数量保持一致,保证每个节点都有一个副本,这样就算集群中只剩下一个节点,该节点都可以通过副本保证集群的可用性
default.replication.factor=3
# 在启动时恢复数据和关闭时刷新数据时每个数据目录的线程数量
num.recovery.threads.per.data.dir=1
############################# Log Flush Policy #############################
# 消息刷新到磁盘中的消息条数
log.flush.interval.messages=10000
# 消息刷新到磁盘中的最大时间间隔,1s
log.flush.interval.ms=1000
############################# Log Retention Policy ############################
# 日志保留小时数,超时会自动删除,默认为7天
log.retention.hours=10
# 日志保留大小,超出大小会自动删除,默认为1G
#log.retention.bytes=1073741824
# 单个日志文件的大小最大为1G,超出后则创建一个新的日志文件(类似与日志切割中的转储机制)
log.segment.bytes=1073741824
# 每隔多长时间检测数据是否达到删除条件,300s
log.retention.check.interval.ms=300000
############################# Zookeeper #############################
# Zookeeper连接信息,如果是zookeeper集群,则以逗号隔开
zookeeper.connect=192.168.0.97:2181,192.168.0.98:2181,192.168.0.99:2181
# 连接zookeeper的超时时间,6s
zookeeper.connection.timeout.ms=6000
# 支持彻底删除Topic,默认不会彻底删除,仅为标记删除
delete.topic.enable=true
创建server.properties
文件中定义的数据目录
sh
[root@kafka-node01 config]# pwd
/opt/kafka_2.12-2.3.0/config
[root@kafka-node01 config]# mkdir ../data
kafka-node02节点
将件server.properties
文件拷贝到该节点,只需要修改以下部分即可
sh
[root@kafka-node02 config]# vim server.properties
# broker的id,值为整数,且必须唯一,在一个集群中不能重复,与zk的ID没有任何关系
broker.id=2
# kafka当前节点IP监听端口,默认9092
listeners=PLAINTEXT://192.168.0.98:9092
创建server.properties
文件中定义的数据目录
sh
[root@kafka-node02 config]# pwd
/opt/kafka_2.12-2.3.0/config
[root@kafka-node02 config]# mkdir ../data
kafka-node03节点
将件server.properties
文件拷贝到该节点,只需要修改以下部分即可
sh
[root@kafka-node03 config]# vim server.properties
# broker的id,值为整数,且必须唯一,在一个集群中不能重复,与zk的ID没有任何关系
broker.id=3
# kafka当前节点IP监听端口,默认9092
listeners=PLAINTEXT://192.168.0.99:9092
创建server.properties
文件中定义的数据目录
sh
[root@kafka-node03 config]# pwd
/opt/kafka_2.12-2.3.0/config
[root@kafka-node03 config]# mkdir ../data
启动kafka集群
所有节点操作,可以根据每个节点的内存情况调整kafka的JVM内存使用情况
sh
]# pwd
/opt/kafka_2.12-2.3.0/bin
]# vim kafka-server-start.sh
...
if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
fi
#Kafka本身规避掉了使用JVM来保存数据,但是kafka的垃圾回收会用到JVM,所以业界比较公认的一个合理的值6G(实验环境可以调整为512M)
所有节点启动kafka
sh
]# pwd
/opt/kafka_2.12-2.3.0/bin
#前台启动(用于测试):启动kafka需要指定配置文件的位置(路径根据自己当前所在位置指定配置文件)
]# ./kafka-server-start.sh ../config/server.properties
#后台启动:启动kafka需要指定配置文件的位置
]# ./kafka-server-start.sh -daemon ../config/server.properties
查看kafaka进程,jps命令是Java提供的一个查看当前用户启动的java进程。
sh
]# jps
9138 QuorumPeerMain
11524 Kafka
11608 Jps
kafka集群验证
使用kafka创建一个Topic
sh
[root@kafka-node01 bin]# ./kafka-topics.sh --create \
--zookeeper 192.168.0.97:2181,192.168.0.98:2181,192.168.0.99:2181 \
--partitions 1 \
--replication-factor 3 \
--topic test
#参数说明:
--create #创建
--zookeeper #zk集群信息,应为zk存储kafka元数据信息,包含主题名称
--partitions #分区数量
--replication-factor #分区副本数量,尽量与节点数量保持一致
--topic #主题名称
查看集群所有Topic
sh
[root@kafka-node01 bin]# ./kafka-topics.sh \
--zookeeper 192.168.0.97:2181,192.168.0.98:2181,192.168.0.99:2181 \
--list
test
查看Topic详细信息
sh
[root@kafka-node01 bin]# ./kafka-topics.sh --describe \
--zookeeper 192.168.0.97:2181,192.168.0.98:2181,192.168.0.99:2181 \
--topic test
Topic:test PartitionCount:1 ReplicationFactor:3 Configs:
Topic: test Partition: 0 Leader: 3 Replicas: 3,2,1 Isr: 3,2,1
#详细信息说明
Topic:test #主题名称
PartitionCount:1 #分区数量
ReplicationFactor:3 #分区副本数量
Partition: 0 #分区编号从0起始,0即第一个分区
Leader: 3 #Leader分区是brokerID为3的kafka节点
Replicas: 3,2,1 #副本分区存储在brokerID(3,2,1)节点
Isr: 3,2,1 #可用的副本分区为brokerID(3,2,1)节点
模拟生产者向主题中发布消息
sh
[root@kafka-node01 bin]# ./kafka-console-producer.sh \
--broker-list 192.168.0.97:9092,192.168.0.98:9092,192.168.0.99:9092 \
--topic test
#...在命令行发布消息
hello kafka
hello zookeeper
模拟消费者订阅主题中的消息
sh
[root@kafka-node02 bin]# ./kafka-console-consumer.sh \
--bootstrap-server 192.168.0.97:9092,192.168.0.98:9092,192.168.0.99:9092 \
--topic test \
--from-beginning
helo kafka
hello zookeeper
kafka的容错机制
模拟其中一个Broker节点故障,检查是否会影响生产者和消费者使用
sh
#随便关掉一台kafka节点(任何节点都可以)
[root@kafka-node03 bin]# jps
10914 Kafka
9098 QuorumPeerMain
12510 Jps
[root@kafka-node03 bin]# kill 10914
查看Topic详细信息
sh
[root@kafka-node03 bin]# ./kafka-topics.sh --describe \
--zookeeper 192.168.0.97:2181,192.168.0.98:2181,192.168.0.99:2181 \
--topic test
Topic:test PartitionCount:1 ReplicationFactor:3 Configs:
Topic: test Partition: 0 Leader: 2 Replicas: 3,2,1 Isr: 2,1
#详细信息说明
Topic:test #主题名称
PartitionCount:1 #分区数量
ReplicationFactor:3 #分区副本数量
Partition: 0 #分区编号从0起始,0即第一个分区
Leader: 2 #Leader分区是brokerID为2的kafka节点
Replicas: 3,2,1 #副本分区存储在brokerID(3,2,1)节点
Isr: 2,1 #可用的副本分区为brokerID(2,1)节点
模拟生产者向主题中发布消息,检查消费者是否可以正常消费
sh
[root@kafka-node01 bin]# ./kafka-console-producer.sh \
--broker-list 192.168.0.97:9092,192.168.0.98:9092,192.168.0.99:9092 \
--topic test
#...在命令行发布消息
hello test
继续模拟其中一个Broker节点故障,检查是否会影响生产者和消费者使用
sh
[root@kafka-node02 ~]# jps
14465 Jps
12389 Kafka
13784 ConsoleConsumer
9099 QuorumPeerMain
[root@kafka-node02 ~]# kill 12389
查看Topic详细信息
sh
[root@kafka-node03 bin]# ./kafka-topics.sh --describe \
--zookeeper 192.168.0.97:2181,192.168.0.98:2181,192.168.0.99:2181 \
--topic test
Topic:test PartitionCount:1 ReplicationFactor:3 Configs:
Topic: test Partition: 0 Leader: 1 Replicas: 3,2,1 Isr: 1
#详细信息说明
Topic:test #主题名称
PartitionCount:1 #分区数量
ReplicationFactor:3 #分区副本数量
Partition: 0 #分区编号从0起始,0即第一个分区
Leader: 1 #Leader分区是brokerID为1的kafka节点
Replicas: 3,2,1 #副本分区存储在brokerID(3,2,1)节点
Isr: 1 #可用的副本分区为brokerID(1)节点
模拟生产者向主题中发布消息,检查消费者是否可以正常消费
sh
[root@kafka-node01 bin]# ./kafka-console-producer.sh \
--broker-list 192.168.0.97:9092,192.168.0.98:9092,192.168.0.99:9092 \
--topic test
#...在命令行发布消息
hello test
启动kafka其他节点
sh
./kafka-server-start.sh -daemon ../config/server.properties
删除Topic
sh
[root@kafka-node01 bin]# ./kafka-topics.sh --delete \
--zookeeper 192.168.0.97:2181,192.168.0.98:2181,192.168.0.99:2181 \
--topic test
查看集群所有Topic
sh
[root@kafka-node01 bin]# ./kafka-topics.sh \
--zookeeper 192.168.0.97:2181,192.168.0.98:2181,192.168.0.99:2181 \
--list
安装kafka的web管理界面
EFAK(Eagle For Apache Kafka,以前称为 Kafka Eagle)EFAK是开源可视化和管理软件,可以查询、可视化、监控kafka集群,是将 kafka 的集群数据转换为图形可视化的工具;
官方地址:http://www.kafka-eagle.org/
软件下载地址:https://github.com/smartloli/kafka-eagle-bin
可以安装在kibana节点
sh
#需要安装JDK环境
[root@kibana-cerebro ~]# tar -xf jdk-8u60-linux-x64.tar.gz -C /usr/local
[root@kibana-cerebro ~]# mv /usr/local/jdk1.8.0_60/ /usr/local/jdk
#配置JDK环境,定义jdk路径,与程序路径
[root@kibana-cerebro ~]# vim /etc/profile
#...在文件最后添加
export JAVA_HOME=/usr/local/jdk
export PATH=$PATH:$JAVA_HOME/bin
安装efak
sh
[root@kibana-cerebro ~]# tar -xf efak-web-2.0.8-bin.tar.gz -C /opt
[root@kibana-cerebro ~]# vim /etc/profile
#配置efak环境,定义efak路径,与程序路径
export KE_HOME=/opt/efak-web-2.0.8
export PATH=$PATH:$KE_HOME/bin
#加载文件
[root@kibana-cerebro ~]# source /etc/profile
修改efak配置文件,定义集群信息
sh
[root@kibana-cerebro ~]# vim /opt/efak-web-2.0.8/conf/system-config.properties
######################################
# 指定zk集群列表,环境只有一个集群,注释掉cluster2.zk.list集群列表
######################################
efak.zk.cluster.alias=cluster1,cluster2
cluster1.zk.list=192.168.0.97:2181,192.168.0.98:2181,192.168.0.99:2181
#cluster2.zk.list=xdn10:2181,xdn11:2181,xdn12:2181
######################################
# zk的acl访问策略,默认没有启用,不需要修改
######################################
cluster1.zk.acl.enable=false
cluster1.zk.acl.schema=digest
cluster1.zk.acl.username=test
cluster1.zk.acl.password=test123
######################################
# broker最大规模数量,不需要修改
######################################
cluster1.efak.broker.size=20
######################################
# zk 客户端线程数,不需要修改
######################################
kafka.zk.limit.size=16
######################################
# EFAK webui端口,不需要修改
######################################
efak.webui.port=8048
#...中间省略
######################################
# efak 数据库地址,需要修改存储路径,其他不需要修改
######################################
efak.driver=org.sqlite.JDBC
efak.url=jdbc:sqlite:/opt/efak-web-2.0.8/db/ke.db
efak.username=root
efak.password=www.kafka-eagle.org
######################################
# 也可以使用mysql数据库存储,需要提前准备,本环境不使用MySQL
######################################
#efak.driver=com.mysql.cj.jdbc.Driver
#efak.url=jdbc:mysql://127.0.0.1:3306/ke?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull
#efak.username=root
#efak.password=123456
启动efak
sh
[root@kibana-cerebro ~]# /opt/efak-web-3.0.2/bin/ke.sh start
______ ______ ___ __ __
/ ____/ / ____/ / | / //_/
/ __/ / /_ / /| | / ,<
/ /___ / __/ / ___ | / /| |
/_____/ /_/ /_/ |_|/_/ |_|
( Eagle For Apache Kafka® )
Version v3.0.2 -- Copyright 2016-2022
*******************************************************************
* EFAK Service has started success.
* Welcome, Now you can visit 'http://192.168.0.96:8048'
* Account:admin ,Password:123456
*******************************************************************
* <Usage> ke.sh [start|status|stop|restart|stats] </Usage>
* <Usage> https://www.kafka-eagle.org/ </Usage>
*******************************************************************
访问efak web:http://192.168.0.96:8048
efak监控kafka,监控kafka客户端、生产者、消息数、请求数、处理时间等
sh
#开启kafka的JMX端口(所有集群节点都需要开启)
]# cd /opt/kafka_2.12-2.3.0/bin/
]# vim kafka-server-start.sh
...
if [ "x$KAFKA_HEAP_OPTS" = "x" ]; then
export KAFKA_HEAP_OPTS="-Xmx1G -Xms1G"
export JMX_PORT="9999" #根据官方参考,开启的端口是9999
fi
重启kafka(所有集群节点)
sh
#通过jps查看当前kafka进程
]# jps
9473 Jps
9448 Kafka
9100 QuorumPeerMain
#kill掉当前的进程
]# kill 9448
#重新启动kafka
]# ./kafka-server-start.sh -daemon ../config/server.properties
查看kafka节点
ELK对接kafka日志收集思路
1.首先通过Filebeat读取日志文件中的内容,并且将内容发送给kafka的Topic中;
2.kafka接收到数据后,由Logstash消费kafka中的Topic消息;
3.Logstash对消息进行过滤处理,并输出到ES;
4.Kibana中添加ES索引,读取索引数据并分析展示;
配置Filebeat读取日志
首先通过Filebeat读取nginx日志文件中的内容,并且将内容发送给kafka的Topic中;
sh
[root@webserver ~]# cat /etc/filebeat/filebeat.yml
filebeat.inputs:
- type: log
enabled: true
paths: /var/log/nginx/access*.log
tags: ["nginx-access"]
- type: log
enabled: true
paths: /var/log/nginx/error*.log
tags: ["nginx-error"]
#写入到kafka集群
output.kafka:
hosts: ["192.168.0.97:9092","192.168.0.98:9092","192.168.0.99:9092"]
topic: nginx #topic名称
required_acks: 1 #保证消息可靠,0不保证,1等待写入主分区(默认),-1等待写入副本分区
compression: gzip #启动压缩功能(默认)
max_message_bytes: 10000 #每条消息最大长度(行数),多余的将被删除
重启Filebeat
sh
[root@webserver ~]# systemctl restart filebeat
[root@webserver ~]# systemctl status filebeat
写入日志测试
sh
[root@webserver ~]# > /var/log/nginx/access.log
[root@webserver ~]# tail -1 nginx_access.log >> /var/log/nginx/access.log
[root@webserver ~]# tail -1 nginx_access.log >> /var/log/nginx/access.log
通过EFAK查看是否生成Topic
| ![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?ori
Topic:主题
Partition:分区编号
LogSize:分区中的消息数量
Leader:Leader分区是brokerID为2的kafka节点
Replicas:副本分区存储在brokerID(2,3,1)的kafka节点
In Sync Replicas:可用的副本分区为brokerID(2,3,1)的kafka节点
配置Logstash日志过滤
kafka接收到数据后,由Logstash消费kafka中的Topic消息,由Logstash对消息进行过滤处理,并输出到ES;
sh
[root@logstash ~]# cat /etc/logstash/conf.d/nginx.conf
input {
kafka {
#kafka节点信息
bootstrap_servers => "192.168.0.97:9092,192.168.0.98:9092,192.168.0.99:9092"
#消费kafka中的主题名称
topics => ["nginx"]
#消费者组名称,名称自定义,每个消费者都应该属于一个组中,不同的组消费同一个主题不冲突
group_id => "nginx_log"
#消费者在组内的名称,名称自定义,同一个组中多个用户消费同一个主题可以实现负载均衡,例如:一个Topic中有10条消息,分别由两个用户消费,则每个用户平均消费5条
client_id => "logstash-node01"
#启用的线程数量,与topic中的分区数量保持一致,线程大于分区,意味着多余的线程处于闲置状态
consumer_threads => "1"
#日志格式以json形式存储
codec => "json"
}
}
filter {
#通过if判断日志中标签字段包含access,该标签在filebeat中定义
if "nginx-access" in [tags][0] {
#通过grok将日志解析为json格式
grok {
match => { "message" => "%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] \"%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:response} (?:%{NUMBER:bytes}|-) %{QS:hostname} (?:%{QS:referrer}|-) (?:%{NOTSPACE:post_args}|-) %{QS:useragent} (?:%{QS:x_forward_for}|-) (?:%{URIHOST:upstream_host}|-) (?:%{NUMBER:upstream_response_code}|-) (?:%{NUMBER:upstream_response_time}|-) (?:%{NUMBER:response_time}|-)" }
}
#通过useragent获取客户端的操作系统、浏览器等
useragent {
source => "useragent"
target => "useragent"
}
#通过clientip获取客户端地理位置
geoip {
source => "clientip"
fields => ["country_name","region_name"]
}
#将日志中的timestamp的值赋给@timestamp
date {
match => ["timestamp","dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
}
#通过convert对日志中的数据类型进行转换
mutate {
#bytes字段是发送给客户端的文件内容的大小,转换为整数类型
convert => ["bytes","integer"]
#response_time字段是服务端的响应时间,转换为浮点类型
convert => ["response_time", "float"]
#upstream_response_time字段是负载均衡服务器响应时间,转换为浮点类型
convert => ["upstream_response_time", "float"]
#移除不需要的字段
remove_field => ["message","agent","tags","ecs","input","@version","log","agent","post_args","httpversion"]
#添加索引名称
add_field => { "target_index" => "nginx-access-%{+YYYY.MM.dd}"}
}
#提取referrer中具体的域名/^"http/
if [referrer] =~ /^"http/ {
#通过grok将该字段解析为json格式,获取客户端的来源地址
grok {
match => { "referrer" => '%{URIPROTO}://%{URIHOST:referrer_host}' }
}
}
}
}
output {
stdout {
codec => "rubydebug"
}
}
#output {
# elasticsearch {
# hosts => ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
# index => "%{[target_index]}"
# user => "elastic"
# password => "123456"
# }
#}
启动Logstash
sh
[root@logstash ~]# logstash -f /etc/logstash/conf.d/nginx.conf -r
Filebeat写入日志
sh
[root@webserver ~]# tail -1 nginx_access.log >> /var/log/nginx/access.log
Logstash验证
sh
{
"response" => "301",
"timestamp" => "10/Nov/2020:14:05:04 +0800",
"host" => {
"name" => "webserver"
},
"@timestamp" => 2020-11-10T06:05:04.000Z,
"auth" => "-",
"target_index" => "nginx-access-2020.11.10",
"bytes" => 0,
"hostname" => "\"117.121.101.40\"",
"x_forward_for" => "\"-\"",
"ident" => "-",
"method" => "HEAD",
"useragent" => {
"minor" => "19",
"os" => "Red Hat",
"build" => "",
"name" => "curl",
"device" => "Other",
"patch" => "7",
"os_name" => "Red Hat",
"major" => "7"
},
"referrer" => "\"-\"",
"geoip" => {},
"clientip" => "10.100.0.1",
"request" => "/",
"response_time" => 0.0
}
Logstash输出到ES
sh
[root@logstash ~]# cat /etc/logstash/conf.d/nginx.conf
...
#output {
# stdout {
# codec => "rubydebug"
# }
#}
output {
elasticsearch {
hosts => ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
index => "%{[target_index]}"
user => "elastic"
password => "123456"
}
}
启动Logstash并指定数据目录
sh
[root@logstash ~]# logstash -f /etc/logstash/conf.d/nginx.conf --path.data=/etc/logstash/data/nginx/ &> /dev/null &
kibana验证
------------------------------------ |
Topic:主题
Partition:分区编号
LogSize:分区中的消息数量
Leader:Leader分区是brokerID为2的kafka节点
Replicas:副本分区存储在brokerID(2,3,1)的kafka节点
In Sync Replicas:可用的副本分区为brokerID(2,3,1)的kafka节点
配置Logstash日志过滤
kafka接收到数据后,由Logstash消费kafka中的Topic消息,由Logstash对消息进行过滤处理,并输出到ES;
sh
[root@logstash ~]# cat /etc/logstash/conf.d/nginx.conf
input {
kafka {
#kafka节点信息
bootstrap_servers => "192.168.0.97:9092,192.168.0.98:9092,192.168.0.99:9092"
#消费kafka中的主题名称
topics => ["nginx"]
#消费者组名称,名称自定义,每个消费者都应该属于一个组中,不同的组消费同一个主题不冲突
group_id => "nginx_log"
#消费者在组内的名称,名称自定义,同一个组中多个用户消费同一个主题可以实现负载均衡,例如:一个Topic中有10条消息,分别由两个用户消费,则每个用户平均消费5条
client_id => "logstash-node01"
#启用的线程数量,与topic中的分区数量保持一致,线程大于分区,意味着多余的线程处于闲置状态
consumer_threads => "1"
#日志格式以json形式存储
codec => "json"
}
}
filter {
#通过if判断日志中标签字段包含access,该标签在filebeat中定义
if "nginx-access" in [tags][0] {
#通过grok将日志解析为json格式
grok {
match => { "message" => "%{IPORHOST:clientip} %{USER:ident} %{USER:auth} \[%{HTTPDATE:timestamp}\] \"%{WORD:method} %{URIPATHPARAM:request} HTTP/%{NUMBER:httpversion}\" %{NUMBER:response} (?:%{NUMBER:bytes}|-) %{QS:hostname} (?:%{QS:referrer}|-) (?:%{NOTSPACE:post_args}|-) %{QS:useragent} (?:%{QS:x_forward_for}|-) (?:%{URIHOST:upstream_host}|-) (?:%{NUMBER:upstream_response_code}|-) (?:%{NUMBER:upstream_response_time}|-) (?:%{NUMBER:response_time}|-)" }
}
#通过useragent获取客户端的操作系统、浏览器等
useragent {
source => "useragent"
target => "useragent"
}
#通过clientip获取客户端地理位置
geoip {
source => "clientip"
fields => ["country_name","region_name"]
}
#将日志中的timestamp的值赋给@timestamp
date {
match => ["timestamp","dd/MMM/yyyy:HH:mm:ss Z"]
target => "@timestamp"
}
#通过convert对日志中的数据类型进行转换
mutate {
#bytes字段是发送给客户端的文件内容的大小,转换为整数类型
convert => ["bytes","integer"]
#response_time字段是服务端的响应时间,转换为浮点类型
convert => ["response_time", "float"]
#upstream_response_time字段是负载均衡服务器响应时间,转换为浮点类型
convert => ["upstream_response_time", "float"]
#移除不需要的字段
remove_field => ["message","agent","tags","ecs","input","@version","log","agent","post_args","httpversion"]
#添加索引名称
add_field => { "target_index" => "nginx-access-%{+YYYY.MM.dd}"}
}
#提取referrer中具体的域名/^"http/
if [referrer] =~ /^"http/ {
#通过grok将该字段解析为json格式,获取客户端的来源地址
grok {
match => { "referrer" => '%{URIPROTO}://%{URIHOST:referrer_host}' }
}
}
}
}
output {
stdout {
codec => "rubydebug"
}
}
#output {
# elasticsearch {
# hosts => ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
# index => "%{[target_index]}"
# user => "elastic"
# password => "123456"
# }
#}
启动Logstash
sh
[root@logstash ~]# logstash -f /etc/logstash/conf.d/nginx.conf -r
Filebeat写入日志
sh
[root@webserver ~]# tail -1 nginx_access.log >> /var/log/nginx/access.log
Logstash验证
sh
{
"response" => "301",
"timestamp" => "10/Nov/2020:14:05:04 +0800",
"host" => {
"name" => "webserver"
},
"@timestamp" => 2020-11-10T06:05:04.000Z,
"auth" => "-",
"target_index" => "nginx-access-2020.11.10",
"bytes" => 0,
"hostname" => "\"117.121.101.40\"",
"x_forward_for" => "\"-\"",
"ident" => "-",
"method" => "HEAD",
"useragent" => {
"minor" => "19",
"os" => "Red Hat",
"build" => "",
"name" => "curl",
"device" => "Other",
"patch" => "7",
"os_name" => "Red Hat",
"major" => "7"
},
"referrer" => "\"-\"",
"geoip" => {},
"clientip" => "10.100.0.1",
"request" => "/",
"response_time" => 0.0
}
Logstash输出到ES
sh
[root@logstash ~]# cat /etc/logstash/conf.d/nginx.conf
...
#output {
# stdout {
# codec => "rubydebug"
# }
#}
output {
elasticsearch {
hosts => ["192.168.0.91:9200","192.168.0.92:9200","192.168.0.93:9200"]
index => "%{[target_index]}"
user => "elastic"
password => "123456"
}
}
启动Logstash并指定数据目录
sh
[root@logstash ~]# logstash -f /etc/logstash/conf.d/nginx.conf --path.data=/etc/logstash/data/nginx/ &> /dev/null &