ELK日志收集

第一章: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="{&quot;clientip&quot;:&quot;%h&quot;,&quot;ClientUser&quot;:&quot;%l&quot;,&quot;authenticated&quot;:&quot;%u&quot;,&quot;AccessTime&quot;:&quot;%t&quot;,&quot;method&quot;:&quot;%r&quot;,&quot;status&quot;:&quot;%s&quot;,&quot;SendBytes&quot;:&quot;%b&quot;,&quot;Query?string&quot;:&quot;%q&quot;,&quot;partner&quot;:&quot;%{Referer}i&quot;,&quot;AgentVersion&quot;:&quot;%{User-Agent}i&quot;}"/>
字段 说明
%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&timestamp=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"
    }
}

该日志过滤思路:

  1. 该日志中最重要的数据在message字段中,需要通过grok进行格式转换;
  2. 转换Json格式后删除无用字段;
  3. 将日志中 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集群安全功能,主要包括:

  1. 通过密码保护、基于角色的访问控制防止未经授权的访问;
  2. 使用SSL/TLS加密保护数据的完整性;
  3. 维护审计跟踪;
集群安全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 &
kibana验证
相关推荐
Yana.nice1 小时前
fs.nr_open=65535与fs.file-max=65535两者区别
linux
码农小菜袅1 小时前
【TCP】SYN、ACK、FIN、RST、PSH、URG的全称
服务器·网络·tcp/ip
SANGEDZ_YYDS1 小时前
三格电子新品上市——IEC103 转 ModbusTCP 网关
服务器·网络·tcp/ip
合方圆~小文1 小时前
高清监控视频的管理与展示:从摄像头到平台的联接过程
linux·网络·人工智能·云计算·智能家居
大霞上仙1 小时前
jenkins入门2
运维·jenkins
小屁不止是运维1 小时前
麒麟操作系统服务架构保姆级教程(八)数据库拆分静态业务拆分和负载均衡
运维·服务器·数据库·架构·负载均衡
wq54wq2 小时前
优化现金流:CRM回款自动化的关键步骤
大数据·运维·人工智能·自动化
Linux运维老纪2 小时前
Nginx常用配置之详解(Detailed Explanation of Common Nginx Configurations)
计算机网络·nginx·微服务·云原生·架构·云计算·运维开发
十子木2 小时前
Emacs 中的缓冲区(Buffer)介绍
服务器·网络·emacs
A charmer2 小时前
告别编程困惑:GDB、冯诺依曼、操作系统速通指南
linux·运维·服务器