This commit is contained in:
baim0
2024-03-09 13:38:35 +08:00
parent d20f09f953
commit 17977970c4
229 changed files with 10417 additions and 0 deletions
BIN
View File
Binary file not shown.
@@ -0,0 +1,135 @@
## 序:ELK应用架构介绍(附专栏目录)
# 1.ELK架构介绍
## 1.1 核心组成
ELK是一个应用套件,由Elasticsearch、 和Kibana三部分组件组成,简称ELK;它是一套开源免费、功能强大的日志分析管理系统。ELK可以将我们的系统日志、网站日志、应用系统日志等各种日志进行收集、过滤、清洗,然后进行集中存放并可用于实时检索、分析。
这三款软件都是开源软件,通常是配合使用,而且又先后归于Elastic.co公司名下,故又被简称为ELK Stack。下图是ELK Stack的基础组成。
![](assets/1593327060-988d312eef7333de60ae9962f2741dfc.png)
本专栏将重点介绍这三款软件所实现的功能以及具体的应用实例。
## 1.2 Elasticsearch介绍
Elasticsearch是一个实时的分布式搜索和分析引擎,它可以用于全文搜索,结构化搜索以及分析,采用Java语言编写。目前,最新的版本是Elasticsearch
6.3.2,它的主要特点如下:
> 实时搜索,实时分析
> 分布式架构、实时文件存储,并将每一个字段都编入索引
> 文档导向,所有的对象全部是文档
> 高可用性,易扩展,支持集群(Cluster)、分片和复制(Shards和Replicas)
> 接口友好,支持JSON
Elasticsearch支持集群架构,典型的集群架构如下图所示:
![](assets/1593327060-95a4eaf0308c881457bee2329a9ec33a.png)
从图中可以看出,Elasticsearch集群中有Master Node和Slave
Node两种角色,其实还有一种角色Client Node,这在后面会做深入介绍。
## 1.3、Logstash介绍
Logstash是一款轻量级的、开源的日志收集处理框架,它可以方便的把分散的、多样化的日志搜集起来,并进行自定义过滤分析处理,然后传输到指定的位置,比如某个服务器或者文件。Logstash采用JRuby语言编写,目前最新的版本是Logstash6.3.2,它的主要特点如下:
> 几乎可以访问任何数据
> 可以和多种外部应用整合
> 支持动态、弹性扩展
Logstash的理念很简单,从功能上来讲,它只做三件事情:
> input:数据收集
> filter:数据加工,如过滤,改写等
> output:数据输出
别看它只做三件事,但通过组合输入和输出,可以变幻出多种架构实现多种需求。Logstash内部运行逻辑如下图所示:
![](assets/1593327060-63f0106ceb8e7c70b7ce18ebf01f95ac.png)
其中,每个部分含义如下:
> Shipper:主要用来收集日志数据,负责监控本地日志文件的变化,及时把日志文件的最新内容收集起来,然后经过加工、过滤,输出到Broker。
>
> Broker:相当于日志Hub,用来连接多个Shipper和多个Indexer。
>
> Indexer:从Broker读取文本,经过加工、过滤,输出到指定的介质(可以是文件、网络、elasticsearch等)中。
Redis服务器是logstash官方推荐的broker,这个broker起数据缓存的作用,通过这个缓存器可以提高Logstash
shipper发送日志到Logstash indexer的速度,同时避免由于突然断电等导致的数据丢失。可以实现broker功能的还有很多软件,例如kafka等。
这里需要说明的是,在实际应用中,LogStash自身并没有什么角色,只是根据不同的功能、不同的配置给出不同的称呼而已,无论是Shipper还是Indexer,始终只做前面提到的三件事。
这里需要重点掌握的是logstash中Shipper和Indexer的作用,因为这两个部分是logstash功能的核心,在下面的介绍中,会陆续介绍到这两个部分实现的功能细节。
## 1.4 kibana介绍
Kibana是一个开源的数据分析可视化平台。使用Kibana可以为Logstash和ElasticSearch提供的日志数据进行高效的搜索、可视化汇总和多维度分析,还可以与Elasticsearch搜索引擎之中的数据进行交互。它基于浏览器的界面操作可以快速创建动态仪表板,实时监控ElasticSearch的数据状态与更改。
## 1.5 ELK工作流程
一般都是在需要收集日志的所有服务上部署logstash,作为logstash shipper用于监控并收集、过滤日志,接着,将过滤后的日志发送给Broker,然后,Logstash Indexer将存放在Broker中的数据再写入Elasticsearch,Elasticsearch对这些数据创建索引,最后由Kibana对其进行各种分析并以图表的形式展示。
ELK工作流程如下图所示:
![](assets/1593327060-51139aeb3592a44ac81c0d71a766595f.png)
有些时候,如果收集的日志量较大,为了保证日志收集的性能和数据的完整性,logstash shipper和logstash indexer之间的缓冲器(Broker)也经常采用kafka来实现。
在这个图中,要重点掌握的是ELK架构的数据流向,以及logstash、Elasticsearch和Kibana组合实现的功能细节。
# 2.ELK常见应用架构
## 2.1 最简单的ELK架构
ELK套件在大数据运维应用中是一套必不可少的、方便的、易用的开源解决方案,它提供搜集、过滤、传输、储存等机制,对应用系统和海量日志进行集中管理和准实时搜索、分析,并通过搜索、监控、事件消息和报表等简单易用的功能,帮助运维人员进行线上业务系统的准实时监控、业务异常时及时定位原因、排除故障等,还可以跟踪分析程序Bug、分析业务趋势、安全与合规审计,深度挖掘日志的大数据应用价值。
下图是最简单的ELK应用架构:
![](assets/1593327060-1b6252e586b387ab95d86f874463a03f.png)
此架构主要是将Logstash部署在各个节点上搜集相关日志、数据,并经过分析、过滤后发送给远端服务器上的Elasticsearch进行存储。Elasticsearch再将数据以分片的形式压缩存储,并提供多种API供用户查询、操作。用户可以通过Kibana Web直观的对日志进行查询,并根据需求生成数据报表。
此架构的优点是搭建简单,易于上手。缺点是Logstash消耗系统资源比较大,运行时占用CPU和内存资源较高。另外,由于没有消息队列缓存,可能存在数据丢失的风险。此架构建议供初学者或数据量小的环境使用。
## 2.2 典型ELK架构
为保证ELK收集日志数据的安全性和稳定性,此架构引入了消息队列机制,下图是典型的ELK应用架构:
![](assets/1593327060-4d597212a5fd6222a4600846181761eb.png)
此架构主要特点是引入了消息队列机制,位于各个节点上的Logstash Agent(一级Logstash,主要用来传输数据)先将数据传递给消息队列(常见的有Kafka、Redis等),接着,Logstash server(二级Logstash,主要用来拉取消息队列数据,过滤并分析数据)将格式化的数据传递给Elasticsearch进行存储。最后,由Kibana将日志和数据呈现给用户。由于引入了Kafka(或者Redis)缓存机制,即使远端Logstash server因故障停止运行,数据也不会丢失,因为数据已经被存储下来了。
这种架构适合于较大集群、数据量一般的应用环境,但由于二级Logstash要分析处理大量数据,同时Elasticsearch也要存储和索引大量数据,因此它们的负荷会比较重,解决的方法是将它们配置为集群模式,以分担负载。
此架构的优点在于引入了消息队列机制,均衡了网络传输,从而降低了网络闭塞尤其是丢失数据的可能性,但依然存在Logstash占用系统资源过多的问题,在海量数据应用场景下,可能会出现性能瓶颈。
## 2.3 ELK集群架构
这个架构是在上面第二个架构基础上改进而来的,主要是将前端收集数据的Logstash Agent换成了filebeat,消息队列使用了kafka集群,然后将Logstash和Elasticsearch都通过集群模式进行构建,完整架构如下图所示:
![](assets/1593327060-a7127d16dd6a76c0f66906b508754eab.png)
此架构适合大型集群、海量数据的业务场景,它通过将前端Logstash
Agent替换成filebeat,有效降低了收集日志对业务系统资源的消耗。同时,消息队列使用kafka集群架构,有效保障了收集数据的安全性和稳定性,而后端Logstash和Elasticsearch均采用集群模式搭建,从整体上提高了ELK系统的高效性、扩展性和吞吐量。
本专栏内容我们就以此架构为主介绍如何安装、配置、构建和使用ELK大数据日志分析系统。
同时,专栏大纲明细如下:
序:ELK(Elasticsearch、Logstash、kibana)应用架构介绍
1.企业应用中各种常见ELK应用架构以及优劣
2.ZooKeeper是如何工作的以及集群应用架构
3.kafka的工作机制以及Producer生产机制与Consumer消费机制
4.Kafka Broker集群与elasticsearch的高效互联机制
5.如何通过Filebeat实现日志的稳定、高效传输
6.通过Logstash、Kibana服务实现日志的分析、清洗和可视化 
7.Logstash的输入插件(Input)与编码插件(Codec)的应用案例 
8.Logstash过滤器插件(Filter)与输出插件(output)实战详解 
9.ELK收集Apache访问日志实战案例 
10.ELK收集Nginx访问日志实战案例 
11.ELK收集tomcat日志应用案例
12.ELK通过filebeat收集mysql慢查询日志案例
13.使用ELK处理Docker日志应用案例
14.ELK实现日志监控告警功能以及高并发环境下的相关调优
@@ -0,0 +1,117 @@
## 1.ZooKeeper与kafka的工作机制以及集群应用架构
# 1.ZooKeeper基础与入门
## 1.1 ZooKeeper概念介绍
在介绍ZooKeeper之前,先来介绍一下分布式协调技术,所谓分布式协调技术主要是用来解决分布式环境当中多个进程之间的同步控制,让他们有序的去访问某种共享资源,防止造成资源竞争(脑裂)的后果。
这里首先介绍下什么是分布式系统,所谓分布式系统就是在不同地域分布的多个服务器,共同组成的一个应用系统来为用户提供服务,在分布式系统中最重要的是进程的调度,这里假设有一个分布在三个地域的服务器组成的一个应用系统,在第一台机器上挂载了一个资源,然后这三个地域分布的应用进程都要竞争这个资源,但我们又不希望多个进程同时进行访问,这个时候就需要一个协调器,来让它们有序的来访问这个资源。这个协调器就是分布式系统中经常提到的那个“锁”,例如"进程1"在使用该资源的时候,会先去获得这把锁,"进程1"获得锁以后会对该资源保持独占,此时其它进程就无法访问该资源,"进程1"在用完该资源以后会将该锁释放掉,以便让其它进程来获得锁。由此可见,通过这个“锁”机制,就可以保证分布式系统中多个进程能够有序的访问该共享资源。这里把这个分布式环境下的这个“锁”叫作分布式锁。这个分布式锁就是分布式协调技术实现的核心内容。
目前,在分布式协调技术方面做得比较好的有Google的Chubby,还有Apache的ZooKeeper,它们都是分布式锁的实现者。ZooKeeper所提供锁服务在分布式领域久经考验,它的可靠性、可用性都是经过理论和实践验证的。
综上所述,ZooKeeper是一种为分布式应用所设计的高可用、高性能的开源协调服务,它提供了一项基本服务:分布式锁服务,同时,也提供了数据的维护和管理机制,如:统一命名服务、状态同步服务、集群管理、分布式消息队列、分布式应用配置项的管理等等。
## 1.2 ZooKeeper应用举例 
为了方便读者理解ZooKeeper,这里通过一个例子,看看ZooKeeper是如何实现分布式协调技术的,这里以ZooKeeper提供的基本服务分布式锁为例进行介绍。
在分布式锁服务中,有一种最典型应用场景,就是通过对集群进行Master角色的选举,来解决分布式系统中的单点故障问题。所谓单点故障,就是在一个主从的分布式系统中,主节点负责任务调度分发,从节点负责任务的处理,而当主节点发生故障时,整个应用系统也就瘫痪了,那么这种故障就称为单点故障。
解决单点故障,传统的方式是采用一个备用节点,这个备用节点定期向主节点发送ping包,主节点收到ping包以后向备用节点发送回复Ack信息,当备用节点收到回复的时候就会认为当前主节点运行正常,让它继续提供服务。而当主节点故障时,备用节点就无法收到回复信息了,此时,备用节点就认为主节点宕机,然后接替它成为新的主节点继续提供服务。
这种传统解决单点故障的方法,虽然在一定程度上解决了问题,但是有一个隐患,就是网络问题,可能会存在这样一种情况:主节点并没有出现故障,只是在回复ack响应的时候网络发生了故障,这样备用节点就无法收到回复,那么它就会认为主节点出现了故障,接着,备用节点将接管主节点的服务,并成为新的主节点,此时,分布式系统中就出现了两个主节点(双Master节点)的情况,双Master节点的出现,会导致分布式系统的服务发生混乱。这样的话,整个分布式系统将变得不可用。为了防止出现这种情况,就需要引入ZooKeeper来解决这种问题。
## 1.3 ZooKeeper工作原理
下面通过三种情形,介绍下Zookeeper是如何进行工作的。
(1) Master启动
在分布式系统中引入Zookeeper以后,就可以配置多个主节点,这里以配置两个主节点为例,假定它们是"主节点A"和"主节点B",当两个主节点都启动后,它们都会向ZooKeeper中注册节点信息。我们假设"主节点A"锁注册的节点信息是"master00001","主节点B"注册的节点信息是"master00002",注册完以后会进行选举,选举有多种算法,这里以编号最小作为选举算法,那么编号最小的节点将在选举中获胜并获得锁成为主节点,也就是"主节点A"将会获得锁成为主节点,然后"主节点B"将被阻塞成为一个备用节点。这样,通过这种方式Zookeeper就完成了对两个Master进程的调度。完成了主、备节点的分配和协作。
(2) Master故障
如果"主节点A"发生了故障,这时候它在ZooKeeper所注册的节点信息会被自动删除,而ZooKeeper会自动感知节点的变化,发现"主节点A"故障后,会再次发出选举,这时候"主节点B"将在选举中获胜,替代"主节点A"成为新的主节点,这样就完成了主、被节点的重新选举。
(3)Master恢复
如果主节点恢复了,它会再次向ZooKeeper注册自身的节点信息,只不过这时候它注册的节点信息将会变成"master00003",而不是原来的信息。ZooKeeper会感知节点的变化再次发动选举,这时候"主节点B"在选举中会再次获胜继续担任"主节点","主节点A"会担任备用节点。
Zookeeper就是通过这样的协调、调度机制如此反复的对集群进行管理和状态同步的。
## 1.4 Zookeeper集群架构
Zookeeper一般是通过集群架构来提供服务的,下图是Zookeeper的基本架构图。
![](assets/1593327167-c445a22c608fc358485130dc04d09ca0.png)
Zookeeper集群主要角色有Server和client,其中,Server又分为Leader、Follower和Observer三个角色,每个角色的含义如下:
> Leader:领导者角色,主要负责投票的发起和决议,以及更新系统状态。
> Follower:跟随者角色,用于接收客户端的请求并返回结果给客户端,在选举过程中参与投票。
> Observer:观察者角色,用户接收客户端的请求,并将写请求转发给leader,同时同步leader状态,但不参与投票。Observer目的是扩展系统,提高伸缩性。
> Client:客户端角色,用于向Zookeeper发起请求。
Zookeeper集群中每个Server在内存中存储了一份数据,在Zookeeper启动时,将从实例中选举一个Server作为leader,Leader负责处理数据更新等操作,当且仅当大多数Server在内存中成功修改数据,才认为数据修改成功。
Zookeeper写的流程为:客户端Client首先和一个Server或者Observe通信,发起写请求,然后Server将写请求转发给Leader,Leader再将写请求转发给其它Server,其它Server在接收到写请求后写入数据并响应Leader,Leader在接收到大多数写成功回应后,认为数据写成功,最后响应Client,完成一次写操作过程。
# 2.kafka基础与入门
## 2.1 kafka基本概念
Kafka是一种高吞吐量的分布式发布/订阅消息系统,这是官方对kafka的定义,这样说起来,可能不太好理解,这里简单举个例子:现在是个大数据时代,各种商业、社交、搜索、浏览都会产生大量的数据。那么如何快速收集这些数据,如何实时的分析这些数据,是一个必须要解决的问题,同时,这也形成了一个业务需求模型,即生产者生产(produce)各种数据,消费者(consume)消费(分析、处理)这些数据。那么面对这些需求,如何高效、稳定的完成数据的生产和消费呢?这就需要在生产者与消费者之间,建立一个通信的桥梁,这个桥梁就是消息系统。从微观层面来说,这种业务需求也可理解为不同的系统之间如何传递消息。
kafka是Apache组织下的一个开源系统,它的最大的特性就是可以实时的处理大量数据以满足各种需求场景:比如基于hadoop平台的数据分析、低时延的实时系统、storm/spark流式处理引擎等。kafka现在它已被多家大型公司作为多种类型的数据管道和消息系统使用。
## 2.2 kafka角色术语
在介绍架构之前,先了解下kafka中一些核心概念和各种角色。
> Broker:Kafka集群包含一个或多个服务器,每个服务器被称为broker。
> Topic:每条发布到Kafka集群的消息都有一个分类,这个类别被称为Topic(主题)。
> Producer:指消息的生产者,负责发布消息到Kafka broker。
> Consumer :指消息的消费者,从Kafka broker拉取数据,并消费这些已发布的消息。
> Partition:Parition是物理上的概念,每个Topic包含一个或多个Partition,每个partition都是一个有序的队列。partition
> 中的每条消息都会被分配一个有序的id(称为offset)。
> Consumer:Group:消费者组,可以给每个Consumer指定消费者组,若不指定消费者组,则属于默认的group。
> Message:消息,通信的基本单位,每个producer可以向一个topic发布一些消息。
这些概念和基本术语对于理解kafka架构和运行原理非常重要,一定要牢记每个概念。
## 2.3 Kafka拓扑架构
一个典型的Kafka集群包含若干Producer,若干broker、若干Consumer Group,以及一个Zookeeper集群。Kafka通过Zookeeper管理集群配置,选举leader,以及在Consumer Group发生变化时进行rebalance。Producer使用push模式将消息发布到broker,Consumer使用pull模式从broker订阅并消费消息。典型架构如下图所示:
![](assets/1593327167-77d2458c5c44df20e07ae0cdd6479d53.png)
从图中可以看出,典型的消息系统有生产者(Producer),存储系统(broker)和消费者(Consumer)组成,Kafka作为分布式的消息系统支持多个生产者和多个消费者,生产者可以将消息分布到集群中不同节点的不同Partition上,消费者也可以消费集群中多个节点上的多个Partition。在写消息时允许多个生产者写到同一个Partition中,但是读消息时一个Partition只允许被一个消费组中的一个消费者所消费,而一个消费者可以消费多个Partition。也就是说同一个消费组下消费者对Partition是互斥的,而不同消费组之间是共享的。
kafka支持消息持久化存储,持久化数据保存在kafka的日志文件中,在生产者生产消息后,kafka不会直接把消息传递给消费者,而是先要在broker中进行存储,为了减少磁盘写入的次数,broker会将消息暂时缓存起来,当消息的个数或尺寸、大小达到一定阀值时,再统一写到磁盘上,这样不但提高了kafka的执行效率,也减少了磁盘IO调用次数。
kafka中每条消息写到partition中,是顺序写入磁盘的,这个很重要,因为在机械盘中如果是随机写入的话,效率将是很低的,但是如果是顺序写入,那么效率却是非常高,这种顺序写入磁盘机制是kafka高吞吐率的一个很重要的保证。
## 2.4 Topic与Partition
Kafka中的topic是以partition的形式存放的,每一个topic都可以设置它的partition数量,Partition的数量决定了组成topic的log的数量。推荐partition的数量一定要大于同时运行的consumer的数量。另外,建议partition的数量大于集群broker的数量,这样消息数据就可以均匀的分布在各个broker中。
那么,Topic为什么要设置多个Partition呢,这是因为kafka是基于文件存储的,通过配置多个partition可以将消息内容分散存储到多个broker上,这样可以避免文件尺寸达到单机磁盘的上限。同时,将一个topic切分成任意多个partitions,可以保证消息存储、消息消费的效率,因为越多的partitions可以容纳更多的consumer,可有效提升Kafka的吞吐率。因此,将Topic切分成多个partitions的好处是可以将大量的消息分成多批数据同时写到不同节点上,将写请求分担负载到各个集群节点。
在存储结构上,每个partition在物理上对应一个文件夹,该文件夹下存储这个partition的所有消息和索引文件。partiton命名规则为topic名称+序号,第一个partiton序号从0开始,序号最大值为partitions数量减1。
在每个partition(文件夹)中有多个大小相等的segment(段)数据文件,每个segment的大小是相同的,但是每条消息的大小可能不相同,因此segment
数据文件中消息数量不一定相等。segment数据文件有两个部分组成,分别为index file和data file,此两个文件是一一对应,成对出现,后缀".index"和“.log”分别表示为segment索引文件和数据文件。
## 2.5 Producer生产机制
Producer是消息和数据的生产者,它发送消息到broker时,会根据Paritition机制选择将其存储到哪一个Partition。如果Partition机制设置的合理,所有消息都可以均匀分布到不同的Partition里,这样就实现了数据的负载均衡。如果一个Topic对应一个文件,那这个文件所在的机器I/O将会成为这个Topic的性能瓶颈,而有了Partition后,不同的消息可以并行写入不同broker的不同Partition里,极大的提高了吞吐率。
## 2.6 Consumer消费机制
Kafka发布消息通常有两种模式:队列模式(queuing)和发布/订阅模式(publish-subscribe)。在队列模式下,只有一个消费组,而这个消费组有多个消费者,一条消息只能被这个消费组中的一个消费者所消费;而在发布/订阅模式下,可有多个消费组,每个消费组只有一个消费者,同一条消息可被多个消费组消费。
Kafka中的Producer和consumer采用的是push(推送)、pull(拉取)的模式,即Producer只是向broker
> push消息,consumer只是从broker
> pull消息,push和pull对于消息的生产和消费是异步进行的。pull模式的一个好处是Consumer可自主控制消费消息的速率,同时Consumer还可以自己控制消费消息的方式是批量的从broker拉取数据还是逐条消费数据。
@@ -0,0 +1,182 @@
## 10.【案例】ELK收集Nginx访问日志实战案例
## 1 、ELK收集Nginx访问日志应用架构
这里我们仍以ELK+Filebeat+Kafka+ZooKeeper构建大数据日志分析平台一节的架构进行讲述,完整的拓扑结构如下图所示:
![](assets/1593327458-aedc2217c6fa163641590c42f97bdefa.png)
此架构需要8台服务器完成,每台服务器的作用和对应的IP信息都已经在图上进行了标注。最前面的一台是nginx服务器,用于产生日志,然后由filebeat来收集nginx产生的日志,filebeat将收集到的日志推送(push)到kafka集群中,完成日志的收集工作。接着,logstash去kafka集群中拉取(pull)日志并进行日志过滤、分析,之后将日志发送到elasticsearch集群中进行索引和存储,最后由Kibana完成日志的可视化查询。
在下面的介绍中,我们设定kafka集群和elasticsearch集群已经部署完成,在此基础上介绍如何通过filebeat和logstash收集与处理nginx日志。
ELK收集Nginx日志的方法与配置流程与收集Apache日志的方式完成相同,只不过,收集Nginx日志更简单一些。如果我们已经掌握了ELK收集Apache日志的方法,那么学习ELK收集Nginx日志将会非常简单。
## 2、Nginx的日志格式与日志变量
Nginx是目前最流行的web服务器,通过ELK收集Nginx日志也是必须要掌握的内容。Nginx跟Apache一样,都支持自定义输出日志格式,在进行Nginx日志格式定义前,先来了解一下关于多层代理获取用户真实IP的几个概念。
>  remote\_addr:表示客户端地址,但有个条件,如果没有使用代理,这个地址就是客户端的真实IP,如果使用了代理,这个地址就是上层代理的IP。
>
>  X-Forwarded-For:简称XFF,这是一个HTTP扩展头,格式为 X-Forwarded-For: client, proxy1, proxy2,如果一个HTTP请求到达服务器之前,经过了三个代理 Proxy1、Proxy2、Proxy3,IP 分别为 IP1、IP2、IP3,用户真实IP为 IP0,那么按照 XFF标准,服务端最终会收到以下信息:
> X-Forwarded-For: IP0, IP1, IP2
由此可知,IP3这个地址X-Forwarded-For并没有获取到,而remote\_addr刚好获取的就是IP3的地址。
还要几个容易混淆的变量,这里也列出来做下说明:
>  $remote\_addr:此变量如果走代理访问,那么将获取上层代理的IP,如果不走代理,那么就是客户端真实IP地址。
>  $http\_x\_forwarded\_for:此变量获取的就是X-Forwarded-For的值。
>  $proxy\_add\_x\_forwarded\_for:此变量是$http\_x\_forwarded\_for和$remote\_addr两个变量之和。
除了上面介绍的三个nginx日志变量之外,Nginx还有很多日志变量可供使用,下面列出一些常用的日志变量以及表示的含义,如下表所示:
![](assets/1593327458-6fb6755e57ff79e1f96c15f52b705525.png)
## 3、自定义Nginx日志格式
Nginx的安装与配置这里不再介绍,仅仅介绍下Nginx配置文件中日志格式的定义方式。在掌握了Nginx日志变量的含义后,接着开始对它输出的日志格式进行改造,这里我们仍将Nginx日志输出设置为json格式,下面仅列出Nginx配置文件nginx.conf中日志格式和日志文件定义部分,定义好的日志格式与日志文件如下:
```makefile
map $http_x_forwarded_for $clientRealIp {
"" $remote_addr;
~^(?P<firstAddr>[0-9\.]+),?.*$ $firstAddr;
}
log_format nginx_log_json '{"accessip_list":"$proxy_add_x_forwarded_for","client_ip":"$clientRealIp","http_host":"$host","@timestamp":"$time_iso8601","method":"$request_method","url":"$request_uri","status":"$status","http_referer":"$http_referer","body_bytes_sent":"$body_bytes_sent","request_time":"$request_time","http_user_agent":"$http_user_agent","total_bytes_sent":"$bytes_sent","server_ip":"$server_addr"}';
access_log /var/log/nginx/access.log nginx_log_json;
```
下面介绍下这段配置的含义:
上面这段配置是在nginx配置文件中http段添加的配置,这里用到了nginx的map指令,通过map定义了一个变量$clientRealIp,这个就是获取客户端真实IP的变量, map指令由ngx\_http\_map\_module模块提供,并且默认加载。
map这段配置的含义是:首选定义了一个$clientRealIp变量,然后,如果$http\_x\_forwarded\_for为" "(即为空),那么就会将$remote\_addr变量的值赋给$clientRealIp变量。而如果$http\_x\_forwarded\_for为非空时,通过一个“~^(?P<firstAddr>\[0-9.\]+),?.\*$”正则匹配,将第一个IP地址提取出来,并赋值给firstAddr,其实也就是将firstAddr的值赋给$clientRealIp变量。
接着,通过 log\_format指令自定义了nginx的日志输出格式,这里定义了13个字段,每个字段的含义在上个部分已经做了介绍。最后,通过access\_log指令指定了日志文件的存放路径。
## 4、验证日志输出
Nginx的日志格式配置完成后,重启Nginx,然后查看输出日志是否正常,如果能看到类似如下内容,表示自定义日志格式输出正常:
```json
{"accessip_list":"172.16.213.132","client_ip":"172.16.213.132","http_host":"172.16.213.157","@timestamp":"2018-02-28T12:26:26+08:00","method":"GET","url":"/img/guonian.png","status":"304","http_referer":"-","body_bytes_sent":"1699956","request_time":"0.000","http_user_agent":"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36","total_bytes_sent":"1700201","server_ip":"172.16.213.157"}
{"accessip_list":"172.16.213.132, 172.16.213.120","client_ip":"172.16.213.132","http_host":"172.16.213.157","@timestamp":"2018-02-28T12:26:35+08:00","method":"GET","url":"/img/guonian.png","status":"304","http_referer":"-","body_bytes_sent":"1699956","request_time":"0.000","http_user_agent":"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36","total_bytes_sent":"1700201","server_ip":"172.16.213.157"}
{"accessip_list":"172.16.213.132, 172.16.213.84, 172.16.213.120","client_ip":"172.16.213.132","http_host":"172.16.213.157","@timestamp":"2018-02-28T12:26:44+08:00","method":"GET","url":"/img/guonian.png","status":"304","http_referer":"-","body_bytes_sent":"1699956","request_time":"0.000","http_user_agent":"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.140 Safari/537.36","total_bytes_sent":"1700201","server_ip":"172.16.213.157"}
```
在这个输出中,可以看到,client\_ip和accessip\_list输出的异同,client\_ip字段输出的就是真实的客户端IP地址,而accessip\_list输出是代理叠加而成的IP列表,第一条日志,是直接访问http://172.16.213.157/img/guonian.png 不经过任何代理得到的输出日志,第二条日志,是经过一层代理访问http://172.16.213.120/img/guonian.png 而输出的日志,第三条日志,是经过二层代理访问http://172.16.213.84/img/guonian.png 得到的日志输出,在三条日志的输出中,观察accessip\_list的输出结果,可以看出$proxy\_add\_x\_forwarded变量的功能,而如果要想测试$http\_x\_forwarded\_for变量和$proxy\_add\_x\_forwarded变量的异同,可以在定义日志输出格式中,将$proxy\_add\_x\_forwarded变量替换为$http\_x\_forwarded\_for变量,查看日志输出结果即可。
Nginx中获取客户端真实IP的方法很简单,无需做特殊处理,这也给后面编写logstash的事件配置文件减少了很多工作量。
## 5、配置filebeat
filebeat是安装在Nginx服务器上的,关于filebeat的安装与基础应用,在前面已经做过详细介绍了,这里不再说明,仅给出配置好的filebeat.yml文件的内容:
```plain
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/nginx/access.log
fields:
log_topic: nginxlogs
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: false
name: 172.16.213.157
output.kafka:
enabled: true
hosts: ["172.16.213.51:9092", "172.16.213.75:9092", "172.16.213.109:9092"]
version: "0.10"
topic: '%{[fields.log_topic]}'
partition.round_robin:
reachable_only: true
worker: 2
required_acks: 1
compression: gzip
max_message_bytes: 10000000
logging.level: debug
```
这个配置文件中,是将Nginx的访问日志/var/log/nginx/access.log内容实时的发送到kafka集群topic为nginxlogs中。需要注意的是filebeat输出日志到kafka中配置文件的写法。
配置完成后,启动filebeat即可:
```coffeescript
[root@filebeatserver ~]# cd /usr/local/filebeat
[root@filebeatserver filebeat]# nohup ./filebeat -e -c filebeat.yml &
```
启动完成后,可查看filebeat的启动日志,观察启动是否正常。
## 6、配置logstash
由于在Nginx输出日志中已经定义好了日志格式,因此在logstash中就不需要对日志进行过滤和分析操作了,下面直接给出logstash事件配置文件kafka\_nginx\_into\_es.conf的内容:
```php
input {
kafka {
bootstrap_servers => "172.16.213.51:9092,172.16.213.75:9092,172.16.213.109:9092"
topics => "nginxlogs" #指定输入源中需要从哪个topic中读取数据,这里会自动新建一个名为nginxlogs的topic
group_id => "logstash"
codec => json {
charset => "UTF-8"
}
add_field => { "[@metadata][myid]" => "nginxaccess-log" } #增加一个字段,用于标识和判断,在output输出中会用到。
}
}
filter {
if [@metadata][myid] == "nginxaccess-log" {
mutate {
gsub => ["message", "\\x", "\\\x"] #这里的message就是message字段,也就是日志的内容。这个插件的作用是将message字段内容中UTF-8单字节编码做替换处理,这是为了应对URL有中文出现的情况。
}
if ( 'method":"HEAD' in [message] ) { #如果message字段中有HEAD请求,就删除此条信息。
drop {}
}
json {
source => "message"
remove_field => "prospector"
remove_field => "beat"
remove_field => "source"
remove_field => "input"
remove_field => "offset"
remove_field => "fields"
remove_field => "host"
remove_field => "@version“
remove_field => "message"
}
}
}
output {
if [@metadata][myid] == "nginxaccess-log" {
elasticsearch {
hosts => ["172.16.213.37:9200","172.16.213.77:9200","172.16.213.78:9200"]
index => "logstash_nginxlogs-%{+YYYY.MM.dd}" #指定Nginx日志在elasticsearch中索引的名称,这个名称会在Kibana中用到。索引的名称推荐以logstash开头,后面跟上索引标识和时间。
}
}
}
```
这个logstash事件配置文件非常简单,没对日志格式或逻辑做任何特殊处理,由于整个配置文件跟elk收集apache日志的配置文件基本相同,因此不再做过多介绍。所有配置完成后,就可以启动logstash了,执行如下命令:
```coffeescript
[root@logstashserver ~]# cd /usr/local/logstash
[root@logstashserver logstash]# nohup bin/logstash -f kafka_nginx_into_es.conf &
```
logstash启动后,可以通过查看logstash日志,观察是否启动正常,如果启动失败,会在日志中有启动失败提示。
## 7、配置Kibana
Filebeat从nginx上收集数据到kafka,然后logstash从kafka拉取数据,如果数据能够正确发送到elasticsearch,我们就可以在Kibana中配置索引了。
登录Kibana,首先配置一个index\_pattern,点击kibana左侧导航中的Management菜单,然后选择右侧的Index Patterns按钮,最后点击左上角的Create index pattern,开始创建一个index pattern,如下图所示:
![](assets/1593327458-3b5770533ab3ff5527a5db8e021e5490.png)
![](assets/1593327458-436bb018d3958197eab110451fe2d6f2.png)
这里填写Nginx日志对应的索引名称logstash\_nginxlogs-\*,然后选择时间过滤器字段名为“@timestamp”。最后点击“Create index pattern”创建索引即可。索引创建完成,点击kibana左侧导航中的Discover导航栏,即可展示已经收集到的日志信息,如下图所示:
![](assets/1593327458-5cec3914614e9e2622b481b6d392a750.png)
至此,ELK收集Nginx日志的配置工作完成。
@@ -0,0 +1,249 @@
## 11.Filebeat+Logstash通过Zabbix实现日志监控告警
## 1、Logstash与插件的使用
Logstash支持多种输出介质,比如syslog、HTTP、TCP、elasticsearch、kafka等,而有时候我们想将收集到的日志中一些错误信息输出,并告警时,就用到了logstash-output-zabbix这个插件,此插件可以将Logstash与zabbix进行整合,也就是将Logstash收集到的数据进行过滤,将有错误标识的日志输出到zabbix中,最后通过zabbix的告警机制进行触发、告警。
logstash-output-zabbix是一个社区维护的插件,它默认没有在Logstash中安装,但是安装起来也很容易,直接在logstash中运行如下命令即可:
```sql
/usr/local/logstash/bin/logstash-plugin install logstash-output-zabbix
```
其中,/usr/local/logstash是Logstash的安装目录。
此外,logstash-plugin命令还有多种用法,我们来看一下:
### 1.1、列出目前已经安装的插件
将列出所有已安装的插件
> /usr/local/logstash/bin/logstash-plugin list
#将列出已安装的插件及版本信息
> /usr/local/logstash/bin/logstash-plugin list --verbose
#将列出包含namefragment的所有已安装插件
> /usr/local/logstash/bin/logstash-plugin list "http"
#将列出特定组的所有已安装插件( input,filter,codec,output)
> /usr/local/logstash/bin/logstash-plugin list --group input
### 1.2、安装插件
要安装某个插件,例如安装kafka插件,可执行如下命令:
> /usr/local/logstash/bin/logstash-plugin install logstash-output-kafka
要使用此命令安装插件,需要你的电脑可以访问互联网。此插件安装方法,会检索托管在公共存储库(RubyGems.org)上的插件,然后下载到本地机器并在Logstash安装之上进行自动安装。
### 1.3、更新插件
每个插件有自己的发布周期和版本更新,这些更新通常是独立于Logstash的发布周期的。因此,有时候需要单独更新插件,可以使用update子命令获得最新版本的插件。
将更新所有已安装的插件
> /usr/local/logstash/bin/logstash-plugin update
将仅更新指定的插件
> /usr/local/logstash/bin/logstash-plugin update logstash-output-kafka
### 1.4、删除插件
如果需要从Logstash插件中删除插件,可执行如下命令:
> /usr/local/logstash/bin/logstash-plugin remove logstash-output-kafka
这样就删除了logstash-output-kafka插件。
## 2、logstash-output-zabbix插件的使用
logstash-output-zabbix安装好之后,就可以在logstash配置文件中使用了,
下面是一个logstash-output-zabbix使用的例子:
```php
zabbix {
zabbix_host => "[@metadata][zabbix_host]"
zabbix_key => "[@metadata][zabbix_key]"
zabbix_server_host => "x.x.x.x"
zabbix_server_port => "xxxx"
zabbix_value => "xxxx"
}
```
其中:
> zabbix\_host:表示Zabbix主机名字段名称, 可以是单独的一个字段, 也可以是 @metadata 字段的子字段, 是必需的设置,没有默认值。
>
> zabbix\_key:表示Zabbix项目键的值,也就是zabbix中的item,此字段可以是单独的一个字段, 也可以是 @metadata 字段的子字段,没有默认值。
>
> zabbix\_server\_host:表示Zabbix服务器的IP或可解析主机名,默认值是 "localhost",需要设置为zabbix server服务器所在的地址。
>
> zabbix\_server\_port:表示Zabbix服务器开启的监听端口,默认值是10051。
>
> zabbix\_value:表示要发送给zabbix item监控项的值对应的字段名称,默认值是 "message",也就是将"message"字段的内容发送给上面zabbix\_key定义的zabbix item监控项,当然也可以指定一个具体的字段内容发送给zabbix item监控项。
## 3、将logstash与zabbix进行整合
这里我们仍以ELK+Filebeat+Kafka+ZooKeeper构建大数据日志分析平台一节的架构进行讲述,由于日志已经全部进入kafka集群中,所以接下来对日志的过滤,然后选择关键字进行告警的工作,就可以在logstash上完成。
先说明一下我们的应用需求:通过对系统日志文件的监控,然后去过滤日志信息中的一些关键字,例如ERR、error、ERROR、Failed、WARNING等,将日志中这些信息过滤出来,然后发送到zabbix上,最后借助zabbix的报警功能实现对系统日志中有上述关键字的告警。
> 对于过滤关键字,进行告警,不同的业务系统,可能关键字不尽相同,例如对http系统,可能需要过滤500、403、503等这些错误码,对于java相关的系统,可能需要过滤OutOfMemoryError、PermGen、Java heap等关键字。在某些业务系统的日志输出中,可能还有一些自定义的错误信息,那么这些也需要作为过滤关键字来使用。
### 3.1、配置filebeat
这里我们是通过filebeat收集操作系统上的系统日志信息的,因此需要配置filebeat,直接给出filebeat配置文件的内容:
```plain
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/secure
- /var/log/messages
- /var/log/cron
fields:
log_topic: osmessages
processors:
- drop_fields:
fields: ["beat", "input", "source", "offset", "prospector"] #这里在filebeat中直接去掉不需要的字段。
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: false
name: 172.16.213.157 #这是日志输出标识,表明日志来自哪个主机,后面再logstash会用到。
output.kafka:
enabled: true
hosts: ["172.16.213.51:9092", "172.16.213.75:9092", "172.16.213.109:9092"] #日志输出到kafka集群
version: "0.10"
topic: '%{[fields.log_topic]}'
partition.round_robin:
reachable_only: true
worker: 2
required_acks: 1
compression: gzip
max_message_bytes: 10000000
logging.level: debug
```
这里通过filebeat监控了三个系统日志文件:/var/log/secure、/var/log/messages和/var/log/cron,这三个文件的格式是一致的。因此可以放到一起。
### 3.2、配置logstash事件配置文件
接下来就是创建一个logstash事件配置文件,这里将配置文件分成三个部分来介绍,首先是input部分,内容如下:
```php
input {
kafka {
bootstrap_servers => "172.16.213.51:9092,172.16.213.75:9092,172.16.213.109:9092"
topics => ["osmessages"]
codec => "json"
}
}
```
input部分是从kafka集群中消费日志数据,这里消费的是topic是osmessages,由于数据在kafka中的存储格式是json,因此读取的时候也需要用json格式。
接着是filter部分,内容如下:
```php
filter {
if [fields][log_topic] == "osmessages" {
mutate {
add_field => [ "[zabbix_key]", "oslogs" ] #新增的字段,字段名是zabbix_key,值为oslogs。
add_field => [ "[zabbix_host]", "%{[host][name]}" ] #新增的字段,字段名是zabbix_host,值可以在这里直接定义,也可以引用字段变量来获取。这里的%{[host][name]获取的就是日志数据的来源IP,这个来源IP在filebeat配置中的name选项进行定义。
}
}
grok {
match => { "message" => "%{SYSLOGTIMESTAMP:message_timestamp} %{SYSLOGHOST:hostname} %{DATA:message_program}(?:\[%{POSINT:messag
e_pid}\])?: %{GREEDYDATA:message_content}" } #这里通过grok对message字段的数据进行字段划分,这里将message字段划分了5个子字段。其中,message_content字段会在output中用到。
}
mutate { #这里是删除不需要的字段
remove_field => "@version"
remove_field => "message"
}
date { #这里是对日志输出中的日期字段进行转换,其中message_timestamp字段是默认输出的时间日期字段,将这个字段的值传给 @timestamp字段。
match => [ "message_timestamp","MMM d HH:mm:ss", "MMM dd HH:mm:ss", "ISO8601"]
}
}
```
filter部分是个重点,首先有个if判断,注意这里对字段的应用方式,\[fields\]\[log\_topic\]这个字段是在filebeat中定义的,使用if判断可以对多个日志类别进行过滤、分析,根据不同字段的标识区分不同的任务。
在这个部分中,重点关注的是message\_timestamp字段、message\_content字段。
最后是output部分,内容如下:
```plain
output {
if [message_content] =~ /(ERR|error|ERROR|Failed)/ { #定义在message_content字段中,需要过滤的关键字信息,也就是在message_content字段中出现给出的这些关键字,那么就将这些信息发送给zabbix。
zabbix {
zabbix_host => "[zabbix_host]" #这个zabbix_host将获取上面filter部分定义的字段变量%{[host][name]的值
zabbix_key => "[zabbix_key]" #这个zabbix_key将获取上面filter部分中给出的值
zabbix_server_host => "172.16.213.140" #这是指定zabbix server的IP地址
zabbix_server_port => "10051" #这是指定zabbix server的监听端口
zabbix_value => "message_content" #这个很重要,指定要传给zabbix监控项item(oslogs)的值, zabbix_value默认的值是"message"字段,因为上面我们已经删除了"message"字段,因此,这里需要重新指定,根据上面filter部分对"message"字段的内容划分,这里指定为"message_content"字段,其实,"message_content"字段输出的就是服务器上具体的日志内容。
}
}
#stdout { codec => rubydebug } #这里是开启调试模式,当第一次配置的时候,建议开启,这样过滤后的日志信息直接输出的屏幕,方便进行调试,调试成功后,即可关闭。
}
```
将上面三部分内容合并到一个文件kafka\_os\_into\_es.conf中,然后启动logstash服务:
```coffeescript
[root@logstashserver ~]#cd /usr/local/logstash
[root@logstashserver logstash]#nohup bin/logstash -f config/kafka_os_into_es.conf --path.data /data/osdata &
```
这里的--path.data是指定此logstash进程的数据存储目录,用于在一个服务器上启动多个logstash进程的环境中。
### 3.3、zabbix平台配置日志告警
登录zabbix web平台,选择配置--->模板--->创建模板,名称定为logstash-output-zabbix,如下图所示:
![](assets/1593327499-c6ae268cbd7410ed7a486583cbfecf4b.png)
接着,在此模块下创建一个应用集,点击应用集----->创建应用集,如下图所示:
![](assets/1593327499-0f21e506c266e33040000db56745e2db.png)
然后,在此模块下创建一个监控项,点击监控项----->创建监控项,如下图所示:
![](assets/1593327499-bb44a804152072f1f88a6551d4956875.png)
到此为止,zabbix监控logstash的日志数据配置完成。
这里我们模拟一个故障,在任意主机通过ssh登录172.16.213.157主机,然后输入一个错误密码,让系统的/var/log/secure文件产生错误日志,然后看看logstash是否能够过滤到,是否能够发送到zabbix中。
首先让系统文件/var/log/secure产生类似如下内容:
```coffeescript
Sep 5 16:01:04 localhost sshd[27159]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=172.16.213.127 user=root
Sep 5 16:01:06 localhost sshd[27159]: Failed password for root from 172.16.213.127 port 59913 ssh2
```
这里面有我们要过滤的关键字Failed,因此logstash会将此内容过滤出来,发送到zabbix上。
接着,登录zabbix web平台,点击监测中----->最新数据,如果zabbix能够接收到日志,就可以看到下图的最新数据:
![](assets/1593327499-d7781592cb88d9f6a32228f9cbf9e994.png)
点击历史记录,可以查看详细内容,如下图所示:
![](assets/1593327499-162a40a067ce975a6f4449ccb546b00b.png)
可以看到,红框中的内容就是在logstash中定义的message\_content字段的内容。
到这里为止,zabbix已经可以收到logstash的发送过来的数据了,但是要实现报警,还需要在zabbix中创建一个触发器,进入配置----->模板,选择logstash-output-zabbix这个模板,然后点击上面的触发器,继续点击右上角的创建触发器,如下图所示:
![](assets/1593327499-7466821c16a771257cbc04a42309206c.png)
![](assets/1593327499-3e037132e4f5031925e4d7348c2356da.png)
这里注意触发器创建中,表达式的写法,这里触发器的触发条件是:如果接收到logstash发送过来的数据,就进行告警,也就是说接收到的数据,如果长度大于0就告警。
触发器配置完成后,如果配置正常,就会进行告警了,告警内容如下图所示:
![](assets/1593327499-85b24394fce699ae8b4e27b90c578be1.png)
@@ -0,0 +1,352 @@
## 12.【案例】通过ELK收集mysql慢查询日志数据
# 1、开启慢查询日志
## 1.1、什么是慢查询日志
在应用程序连接mysql过程中,某些sql语句由于写的不是很标准,会导致执行完毕所花费的时间特别长,SQL语句执行时间过长,势必影响业务系统的使用,这个时候可能就会出现开发人员和运维人员相互扯皮的问题,那么如何帮助运维人员解决这样的困境呢,其实通过ELK工具,就可以轻松解决:开启mysql慢查询日志,然后将日志收集到统一展示平台,每个SQL语句的写法、执行时间、读取时间等等指标可以尽收眼底,孰是孰非,一目了然。
先介绍一下mysql中的慢查询日志,首先不要被"慢查询日志"的名字误导,不要错误的以为慢查询日志只会记录执行比较慢的SELECT语句,其实完全不是这样的,除了SELECT语句,INSERT、DELETE、UPDATE、CALL等DML操作,只要是超过了指定的时间,都可以称之为"慢查询"。
要将慢查询的SQL记录到慢查询日志中,需要一个超时时间,这个时间在mysql中进行设置,所有进入mysql的语句,只要执行时间超过设置的这个时间阀值,都会被记录在慢查询日志中。默认设置下,执行超过10秒的语句才会被记录到慢查询日志中,当然,对于"慢"的定义,每个业务系统,每个环境各不相同,超过多长时间才是我们认为的"慢",可以自定义。
在默认情况下,mysql慢查询日志功能是关闭的,如果需要,可以手动开启,具体如何开启,下面我们将进行介绍。
## 1.2、mysql与慢查询日志相关的参数
在介绍开启mysql慢查询日志功能之前,首先介绍下跟慢查询日志相关的几个mysql参数:
> (1)log\_slow\_queries :表示是否开启慢查询日志,mysql5.6以前的版本使用此参数指定是否开启慢查询日志,mysql5.6以后的版本使用slow\_query\_log取代了此参数,如果你使用的mysql版本刚好是mysql5.5,那么可以看到这两个参数是同时存在的,此时不用同时设置它们,我们只需设置这两个参数中的任何一个即可,另一个也会自动保持一致。
>
> (2)slow\_query\_log :表示是否开启慢查询日志,此参数与log\_slow\_queries的作用没有区别,mysql5.6以后的版本使用此参数替代log\_slow\_queries。
>
> (3)log\_output : 表示当慢查询日志开启以后,以哪种方式存放慢查询日志,log\_output有4个值可以选择,分别是"FILE"、"TABLE"、"FILE,TABLE"和"NONE"。此值为"FILE"表示慢查询日志存放于指定的文件中,此值为"TABLE"表示慢查询日志存放于mysql库的slow\_log表中,此值为"FILE,TABLE"表示将慢查询日志同时存放于指定的文件与slow\_log表中,一般不会进行这样的设置,因为这样会增加很多IO压力,如果开启,建议设置为"table"或“file”,此值为"NONE"时表示不记录查询日志,即使slow\_query\_log设置为ON,如果log\_output设置为NONE,也不会记录慢查询日志,其实,log\_output不止用于控制慢查询日志的输出,查询日志的输出也是由此参数进行控制,也就是说,log\_output设置为file,就表示查询日志和慢查询日志都存放到对应的文件中,设置为table,查询日志和慢查询日志就都存放在对应的数据库表中。
>
> (4)slow\_query\_log\_file :当使用文件存储慢查询日志时(log\_output设置为"FILE"或者"FILE,TABLE"时),指定慢查询日志存储于哪个日志文件中,默认的慢查询日志文件名为"主机名-slow.log",慢查询日志的位置为datadir参数所对应的目录位置,默认情况下为 /var/lib/mysql目录。
>
> (5)long\_query\_time :表示"多长时间的查询"被认定为"慢查询",这就是慢查询的时间阀值,此值默认值为10秒,表示超过10秒的查询会被认定为慢查询。
>
> (6)log\_queries\_not\_using\_indexes :表示如果运行的sql语句没有使用到索引,是否也被当做慢查询语句记录到慢查询日志中,OFF表示不记录,ON表示记录。
>
> (7)log\_throttle\_queries\_not\_using\_indexes :这是mysql5.6.5版本新引入的参数,当log\_queries\_not\_using\_inde设置为ON时,没有使用索引的查询语句也会被当做慢查询语句记录到慢查询日志中,通过使用log\_throttle\_queries\_not\_using\_indexes可以限制这种语句每分钟记录到慢查询日志中的次数,因为在生产环境中,有可能有很多没有使用索引的语句,此类语句频繁的被记录到慢查询日志中,可能会导致慢查询日志快速不断的增长,dba可以通过此参数进行控制。
## 1.3、开启mysql的慢查询日志
首先,开启慢查询日志功能,由于当前使用的mysql版本为5.7,先查询下上面各个参数的默认配置信息:
```ruby
mysql> show variables like "%log_output%";
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_output | FILE |
+---------------+-------+
1 row in set (0.00 sec)
mysql> show variables like "%slow%";
+---------------------------+--------------------------------+
| Variable_name | Value |
+---------------------------+--------------------------------+
| log_slow_admin_statements | OFF |
| log_slow_slave_statements | OFF |
| slow_launch_time | 2 |
| slow_query_log | OFF |
| slow_query_log_file | /db/data/SparkWorker1-slow.log |
+---------------------------+--------------------------------+
5 rows in set (0.00 sec)
mysql> show variables like "%long_query_time%";
+-----------------+-----------+
| Variable_name | Value |
+-----------------+-----------+
| long_query_time | 10.000000 |
+-----------------+-----------+
1 row in set (0.01 sec)
```
从输出可知,log\_output是file, slow\_query\_log是off状态,slow\_query\_log\_file 的路径为/db/data/SparkWorker1-slow.log,而long\_query\_time就是默认的10秒。
下面在SQL命令行global级别动态修改这几个参数,执行如下:
```shell
mysql> set global slow_query_log=ON;
mysql> set global long_query_time=1;
```
第一个命令是动态开启慢查询日志功能,第二个命令是设置慢查询的超时时间为1秒(这里为测试方便,所以设置超时时间比较短,生产环境可根据需要自定义设置)
开启慢查询日志以后,将慢查询的时间界限设置为1秒,需要注意,在当前会话中查询设置是否生效时,需要加上global关键字,或者在一个新的数据库连接中进行查询,否则可能无法查看到最新的更改,在当前会话中,虽然全局变量已经发生改变,但是当前会话的变量值仍然没有被改变。
```plain
mysql> select @@global.long_query_time;
+--------------------------+
| @@global.long_query_time |
+--------------------------+
| 1.000000 |
+--------------------------+
1 row in set (0.00 sec)
```
为了让设置永久生效,可将上面配置写入my.cnf文件中,编辑/etc/my.cnf,添加如下内容:
```plain
slow_query_log=ON
slow_query_log_file=/db/data/SparkWorker1-slow.log
long_query_time=1
```
等下次需要重启mysql服务时,重启后,就永久生效了。
接下来,执行下面一个语句:
```perl
mysql> select sleep(5);
+----------+
| sleep(5) |
+----------+
| 0 |
```
此处我们故意使语句的执行时间超过1秒,然后查看慢查询日志中是否会记录这些语句。
打开/db/data/SparkWorker1-slow.log文件,发现有如下内容产生:
```shell
# Time: 2018-09-10T09:03:20.413647Z
# User@Host: root[root] @ [172.16.213.120] Id: 484
# Query_time: 5.010390 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0
SET timestamp=1536570200;
select sleep(5);
```
我们也可以使用如下语句,查看从mysql服务启动以后到现在,一共记录了多少条慢查询语句:
```shell
mysql> show global status like '%slow_queries%';
```
但是需要注意,这个查询只是本次mysql服务启动后到当前时间点的慢查询次数统计,当mysql重启以后,如下值将清零后重新计算,而慢查询日志与slow\_log表中的慢查询日志则不会被清除。
# 2、慢查询日志分析
下面首先给出各个mysql版本慢查询日志的格式:
mysql5.5版本慢查询日志格式:
```shell
# Time: 180911 10:50:31
# User@Host: osdb[osdb] @ [172.25.14.78]
# Query_time: 12.597483 Lock_time: 0.000137 Rows_sent: 451 Rows_examined: 2637425
SET timestamp=1536634231;
SELECT id,name,contenet from cs_tables;
```
mysql5.6版本慢查询日志格式:
```shell
# Time: 180911 11:36:20
# User@Host: root[root] @ localhost [] Id: 1688
# Query_time: 3.006539 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0
SET timestamp=1536550580;
SELECT id,name,contenet from cs_tables;
```
mysql5.7版本慢查询日志:
```plain
# Time: 2018-09-10T06:26:40.895801Z
# User@Host: root[root] @ [172.16.213.120] Id: 208
# Query_time: 3.032884 Lock_time: 0.000139 Rows_sent: 46389 Rows_examined: 46389
use cmsdb;
SET timestamp=1536560800;
select * from cstable;
```
通过分析上面三个mysql版本的慢查询日志,得出如下结论:
> (1)、 每个Mysql版本的慢查询日志中Time字段格式都不一样
> (2)、在mysql5.6、5.7版本中有一个id字段,而在mysql5.5版本中是没有Id字段的。
> (3)、 每个慢查询语句是分多行完成的,并且每行中有多少不等的空格、回车等字符。
> (4)、use db语句可能出现在慢查询中,也可以不出现。
> (5)、每个慢查询语句的最后一部分是具体执行的sql,这个sql可能跨多行,也可能是多条sql语句。
根据对不同mysql版本慢查询日志的分析,得出如下日志收集处理思路:
> (1)、合并多行慢查询日志:mysql的慢查询日志多行构成了一条完整的日志,日志收集时需要把这些行拼装成一条日志传输与存储。
> (2)、慢查询日志的开始行“# Time:”由于不同版本格式不相同,所以选择过滤丢弃此行即可,要获取sql的执行时间,可以通过SET timestamp这个值来确定。
> (3)、慢查询完整的日志应该是以"# User@Host:"开始,以最后一条SQL结束,并且需要将多行合并为一行,组成一条完整的慢查询日志语句。
> (4)、还需要确定SQL对应的主机,这个在慢查询日志中并没有输出,但是可以通过其他办法实现:可以通过在filebeat中的name字段来解决,也就是通过给filebeat的name字段设置为服务器IP,这样filebeat最终通过host.name这个字段就可以确定SQL对应的主机了。
# 3、配置Filebeat收集mysql慢查询日志
这里我们仍以ELK+Filebeat+Kafka+ZooKeeper构建大数据日志分析平台一节的架构进行讲述,这里选择mysql服务器的ip为172.16.213.232,logstash服务器的ip为172.16.213.120,首先在172.16.213.232上安装、配置filebeat,安装过程省略,配置好的filebeat.yml文件内容如下:
```plain
filebeat.inputs:
- type: log
enabled: true
paths:
- /db/data/SparkWorker1-slow.log #这是指定mysql慢查询日志文件的路径
fields:
log_topic: mysqlslowlogs #定义一个新字段log_topic,值为mysqlslowlogs,下面要进行引用。
exclude_lines: ['^\# Time'] #过滤掉# Time开头的行
multiline.pattern: '^\# Time|^\# User' #匹配多行时指定正则表达式,这里匹配以# Time或者# User开头的行,Time行要先匹配再过滤
multiline.negate: true #开启多行合并功能
multiline.match: after #定义如何将匹配行组合成时间,在之前或者之后,有 "after"、 "before"两个值。
processors:
- drop_fields:
fields: ["beat", "input", "source", "offset", "prospector"]
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: false
name: 172.16.213.232
output.kafka:
enabled: true
hosts: ["172.16.213.51:9092", "172.16.213.75:9092", "172.16.213.109:9092"]
version: "0.10"
topic: '%{[fields.log_topic]}'
partition.round_robin:
reachable_only: true
worker: 2
required_acks: 1
compression: gzip
max_message_bytes: 10000000
logging.level: debug
```
> filebeat的配置,重点是multiline.negate选项,通过此选项,将mysql慢查询日志多行合并到了一起,输出为一条日志。
配置文件编写好后,启动filebeat服务即可:
```coffeescript
[root@filebeat232 ~]# cd /usr/local/filebeat
[root@filebeat232 filebeat]#nohup ./filebeat -e -c filebeat.yml &
```
输出日志可从nohup.out文件中查看。
# 4、通过Logstash的grok插件过滤、分析配置mysql日志
Logstash服务部署在172.16.213.120服务器上,下面给出Logstash事件配置文件,命名为kafka\_mysql\_into\_es.conf,内容如下:
```php
input {
kafka {
bootstrap_servers => "172.16.213.51:9092,172.16.213.75:9092,172.16.213.109:9092"
topics => ["mysqlslowlogs"]
}
}
filter {
json {
source => "message"
}
grok {
# 有ID有use
match => [ "message", "^#\s+User@Host:\s+%{USER:user}\[[^\]]+\]\s+@\s+(?:(?<clienthost>\S*) )?\[(?:%{IP:clientip})?\]\s+Id:\s+%{NUMBER:id}\n# Query_time: %{NUMBER:query_time}\s+Lock_time: %{NUMBER:lock_time}\s+Rows_sent: %{NUMBER:rows_sent}\s+Rows_examined: %{NUMBER:rows_examined}\nuse\s(?<dbname>\w+);\nSET\s+timestamp=%{NUMBER:timestamp_mysql};\n(?<query>[\s\S]*)" ]
# 有ID无use
match => [ "message", "^#\s+User@Host:\s+%{USER:user}\[[^\]]+\]\s+@\s+(?:(?<clienthost>\S*) )?\[(?:%{IP:clientip})?\]\s+Id:\s+%{NUMBER:id}\n# Query_time: %{NUMBER:query_time}\s+Lock_time: %{NUMBER:lock_time}\s+Rows_sent: %{NUMBER:rows_sent}\s+Rows_examined: %{NUMBER:rows_examined}\nSET\s+timestamp=%{NUMBER:timestamp_mysql};\n(?<query>[\s\S]*)" ]
# 无ID有use
match => [ "message", "^#\s+User@Host:\s+%{USER:user}\[[^\]]+\]\s+@\s+(?:(?<clienthost>\S*) )?\[(?:%{IP:clientip})?\]\n# Query_time: %{NUMBER:query_time}\s+Lock_time: %{NUMBER:lock_time}\s+Rows_sent: %{NUMBER:rows_sent}\s+Rows_examined: %{NUMBER:rows_examined}\nuse\s(?<dbname>\w+);\nSET\s+timestamp=%{NUMBER:timestamp_mysql};\n(?<query>[\s\S]*)" ]
# 无ID无use
match => [ "message", "^#\s+User@Host:\s+%{USER:user}\[[^\]]+\]\s+@\s+(?:(?<clienthost>\S*) )?\[(?:%{IP:clientip})?\]\n# Query_time: %{NUMBER:query_time}\s+Lock_time: %{NUMBER:lock_time}\s+Rows_sent: %{NUMBER:rows_sent}\s+Rows_examined: %{NUMBER:rows_examined}\nSET\s+timestamp=%{NUMBER:timestamp_mysql};\n(?<query>[\s\S]*)" ]
}
date {
match => ["timestamp_mysql","UNIX"] #这个是对慢查询日志中的时间字段进行格式转换,默认timestamp_mysql字段是Unix时间戳格式,将转换后的时间值赋给 @timestamp字段。
target => "@timestamp"
}
mutate {
remove_field => "@version" #删除不需要的字段
remove_field => "message" #上面对"message"字段的内容已经分割成了多个小字段,因此"message"字段就不需要了,所以选择删除。
}
output {
elasticsearch {
hosts => ["172.16.213.37:9200","172.16.213.77:9200","172.16.213.78:9200"]
index => "mysql-slowlog-%{+YYYY.MM.dd}" #索引的名称
}
}
```
此配置文件的难点在对mysql慢查询日志的过滤出来上,上面在filter的grok插件中,有四个match,其实是对慢查询日志的格式分成了四种情况,当有多条匹配规则存在时,logstash会从上到下依次匹配,只要匹配到一条后,下面的将不再进行匹配。
所有配置完成后,启动logstash服务:
```coffeescript
[root@logstashserver ~]#cd /usr/local/logstash
[root@logstashserver logstash]#nohup bin/logstash -f config/kafka_mysql_into_es.conf --path.data /data/mysqldata &
```
接着,让慢查询产生日志,执行如下操作:
在远程主机172.16.213.120上登录172.16.213.232数据库,执行一个insert写入操作:
```shell
mysql> use cmsdb;
mysql> insert into cstable select * from cstable;
Query OK, 46812 rows affected (1 min 11.24 sec)
Records: 46812 Duplicates: 0 Warnings: 0
```
此insert操作耗费了1分钟以上,因此会记录到慢查询日志中,下面看看慢查询日志的输出内容:
```plain
# Time: 2018-09-10T09:09:28.697351Z
# User@Host: root[root] @ [172.16.213.120] Id: 484
# Query_time: 71.251202 Lock_time: 0.000261 Rows_sent: 0 Rows_examined: 93624
SET timestamp=1536570568;
insert into cstable select * from cstable;
```
然后在Logstash配置文件中kafka\_mysql\_into\_es.conf进行调试,通过输出为rubydebug格式后,看到的对应内容如下:
```php
{
"timestamp_mysql" => "1536570568",
"fields" => {
"log_topic" => "mysqlslowlogs"
},
"query_time" => "71.251202",
"rows_examined" => "93624",
"@timestamp" => 2018-09-10T09:09:28.000Z,
"clientip" => "172.16.213.120",
"rows_sent" => "0",
"lock_time" => "0.000261",
"id" => "484",
"host" => {
"name" => "172.16.213.232"
},
"user" => "root",
"query" => "insert into cstable select * from cstable;"
}
```
这就是我们要的输出结果。
5、Kibana平台创建mysql慢查询日志索引
登录Kibana平台,创建一个mysql慢查询日志索引,如下图所示:
![](assets/1593327525-7e214829746ccddc7e06379fa5b4cabb.png)
只要数据能正常写入Elasticsearch,索引就可以查看到,这里写入mysql-slowlog-\*即可。
然后继续,选择日志排序方式,选择按照@timestamp进行排序,如下图所示:
![](assets/1593327525-f24a7ac559977992d32d28ee9f8520f4.png)
最后,点击创建索引,即可完成。
索引创建完成后,点击左侧导航的discover,即可查看慢查询日志,如下图所示:
![](assets/1593327525-df0fa280217af16ed6177e435ed5b61d.png)
还可以根据需要,添加具体的多维度、可视化图形,下面是我做好的一个Dashboard,如下图所示:
![](assets/1593327525-c63463d953076e6ca33e6791c157177b.png)
至此,ELK收集mysql慢查询日志数据工作完成。
@@ -0,0 +1,268 @@
## 13【案例】通过ELK收集Tomcat访问日志和状态日志
## 1、tomcat日志解析
对tomcat的日志监控和分析主要有有两种类型,分别是访问日志(localhost\_access\_log.Y-M-D.txt)和运行状态日志(catalina.out),其中,访问日志记录访问的时间,IP,访问的资料等相关信息,catalina.out其实记录了tomcat的标准输出(stdout)和标准出错(stderr),这是在tomcat的启动脚本里指定的,默认情况下stdout和stderr都会重定向到这里。所以可以在应用里使用System.out打印的东西都会输出到这里来。此文件对监控tomcat的运行状态非常重要。
在企业具体的运维监控中,对访问日志的监控主要是看访问量,访问来源等信息,因此,这部分日志输出可以入到elasticsearch中,然后在Kibana中展示分析,而对catalina.out文件的监控,主要是监控日志中是否有错误、告警等信息,如果监控到有类似信息,那么就进行告警,因此对catalina.out文件的日志收集,需要过滤关键字,然后输出到zabbix进行告警。
下面就开始对tomcat两种不同类型的日志进行收集、过滤和告警设置。
## 2、配置tomcat的访问日志和catalina.out文件
### 2.1、配置访问日志(localhost\_access\_log.Y-M-D.txt)输出为JSON格式
要配置访问日志文件的输出格式,需要修改tomcat的server.xml文件,这里以tomcat8.5.30为例,打开server.xml,找到如下内容:
```python
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
```
将此内容修改为如下配置:
```perl
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".log"
pattern="{"client":"%h", "client user":"%l", "authenticated":"%u", "access time":"%t", "method":"%r", "status":"%s", "send bytes":"%b", "Query?string":"%q", "partner":"%{Referer}i", "Agent version":"%{User-Agent}i"}"/>
```
介绍下出现的一些参数的含义:
> directory:日志文件存放的目录。通常设置为tomcat下已有的那个logs文件。
> prefix:日志文件的名称前缀。
> suffix :日志文件的名称后缀。
> pattern:比较重要的一个参数。下面列出这个参数常用的选项和含义:
>
> %a   这是记录访问者的IP,在日志里是127.0.0.1
> %A   这是记录本地服务器的IP。
> %b   发送信息的字节数,不包括http头,如果字节数为0的话,显示为“-”
> %B   发送信息的字节数,不包括http头。
> %h   服务器的名称。如果resolveHosts为false的话,这里就是IP地址
> %H   访问者的协议
> %l   记录浏览者进行身份验证时提供的名称
> %m   访问的方式,是GET还是POST
> %p   本地接收访问的端口 
> %q   比如你访问的是aaa.jsp?bbb=ccc,那么这里就显示?bbb=ccc,表示query string的意思
> %r   请求的方法和URL
> %s   http的响应状态码 
> %S   用户的session ID,每次都会生成不同的session ID
> %t   请求时间
> %u   得到了验证的访问者,否则就是"-"
> %U   访问的URI地址
> %v   服务器名称
> %D  请求消耗的时间,以毫秒记
> %T   请求消耗的时间,以秒记
配置完成后,重启tomcat,可以查看tomcat的访问日志是否输出了json格式,如下所示:
```json
{"client":"172.16.213.132", "client user":"-", "authenticated":"-", "access time":"[13/Sep/2018:19:01:09 +0800]", "method":"GET /docs/jndi-datasource-examples-howto.html HTTP/1.1", "status":"200", "send bytes":"-", "Query?string":"", "partner":"http://172.16.213.233:8080/", "Agent version":"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"}
{"client":"172.16.213.132", "client user":"-", "authenticated":"-", "access time":"[13/Sep/2018:19:01:09 +0800]", "method":"GET /docs/jndi-datasource-examples-howto.html HTTP/1.1", "status":"200", "send bytes":"35230", "Query?string":"", "partner":"http://172.16.213.233:8080/", "Agent version":"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"}
{"client":"172.16.213.132", "client user":"-", "authenticated":"-", "access time":"[13/Sep/2018:19:01:10 +0800]", "method":"GET /favicon.ico HTTP/1.1", "status":"200", "send bytes":"21630", "Query?string":"", "partner":"http://172.16.213.233:8080/docs/jndi-datasource-examples-howto.html", "Agent version":"Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"}
```
简单整理一下,输出的json格式如下:
```json
{
"client": "172.16.213.132",
"client user": "-",
"authenticated": "-",
"access time": "[13/Sep/2018:19:01:09 +0800]",
"method": "GET /docs/jndi-datasource-examples-howto.html HTTP/1.1",
"status": "200",
"send bytes": "-",
"Query?string": "",
"partner": "http://172.16.213.233:8080/",
"Agent version": "Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36"
}
```
### 2.2、配置tomcat的catalina.out文件
默认情况下,tomcat8.5版本中catalina.out文件的输出格式如下:
```css
17-Sep-2018 15:57:10.387 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
17-Sep-2018 15:57:10.425 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
17-Sep-2018 15:57:10.443 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 31377 ms
```
在这个日志中,时间字段输出不是很友好,我们需要修改下时间自动的输出为类似“2018-09-17 15:57:10.387”这种格式,要修改catalina.out的时间输出字段,需要修改logging.properties文件,此文件位于tomcat配置文件目录conf下,打开此文件,添加如下内容:
```perl
1catalina.org.apache.juli.AsyncFileHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.SimpleFormatter.format = %1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL [%4$s] [%3$s] %2$s %5$s %6$s%n
```
同时删除如下行:
```plain
java.util.logging.ConsoleHandler.formatter = org.apache.juli.OneLineFormatter
```
这样,catalina.out文件输出的时间字段就变成比较友好的格式了,如下图所示:
```css
2018-09-17 16:03:56.540 [INFO] [org.apache.coyote.http11.Http11NioProtocol] org.apache.coyote.AbstractProtocol start Starting ProtocolHandler ["http-nio-8080"]
2018-09-17 16:03:56.582 [INFO] [org.apache.coyote.ajp.AjpNioProtocol] org.apache.coyote.AbstractProtocol start Starting ProtocolHandler ["ajp-nio-8009"]
2018-09-17 16:03:56.602 [INFO] [org.apache.catalina.startup.Catalina] org.apache.catalina.startup.Catalina start Server startup in 41372 ms
```
对于catalina.out文件的内容,我们只需要过滤出来四个字段即可,分别是时间字段、日志输出级别字段、异常信息字段以及运行状态内容字段,对上面的日志信息,通过logstash的grok插件进行过滤,可以很方便的取出这四个字段,登录http://grokdebug.herokuapp.com/ ,然后今天在线调试,可以很轻松得出过滤规则:
```markdown
%{TIMESTAMP_ISO8601:access_time}\s+\[%{LOGLEVEL:loglevel}\]\s+\[%{DATA:exception_info}\](?<tomcatcontent>[\s\S]*)
```
其中,access\_time取的是时间字段、loglevel取的是日志输出级别字段、exception\_info取的是异常信息字段,tomcatcontent取的是运行状态字段。
## 3、配置filebeat
在tomcat所在的服务器(172.16.213.233)上安装filebeat,然后配置filebeat,配置好的filebeat.yml文件内容如下:
```plain
filebeat.inputs:
- type: log
enabled: true
paths:
- /usr/local/tomcat/logs/localhost_access_log.2018-09*.txt #这是tomcat的访问日志文件
fields:
log_topic: tomcatlogs #这是新增的字段,用于后面的kafka的topic,其实是对tomcat访问日志文件分类
- type: log
enabled: true
paths:
- /usr/local/tomcat/logs/catalina.out #这是定义的第二个tomcat日志文件catalina.out,注意写的格式。
fields:
log_topic: tomcatlogs_catalina #这是新增的字段,用于后面的kafka的topic,专门用于存储catalina.out日志文件
processors:
- drop_fields:
fields: ["beat", "input", "source", "offset", "prospector"]
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: false
name: 172.16.213.233
output.kafka:
enabled: true
hosts: ["172.16.213.51:9092", "172.16.213.75:9092", "172.16.213.109:9092"]
version: "0.10"
topic: '%{[fields.log_topic]}' #这是指定topic,注意写法,上面新增了两个字段,两个对应的日志文件会分别写入不同的topic
partition.round_robin:
reachable_only: true
worker: 2
required_acks: 1
compression: gzip
max_message_bytes: 10000000
logging.level: debug
```
配置文件编写好后,启动filebeat服务即可:
```coffeescript
[root@filebeat232 ~]# cd /usr/local/filebeat
[root@filebeat232 filebeat]#nohup ./filebeat -e -c filebeat.yml &
```
## 4、通过Logstash的grok插件过滤、分析配置tomcat日志
Logstash服务部署在172.16.213.120服务器上,下面给出Logstash事件配置文件,命名为kafka\_tomcat\_into\_es.conf,内容如下:
```php
input { #这里定义了两个消费topic,分别读取的是tomcat的访问日志和catalina.out文件
kafka {
bootstrap_servers => "172.16.213.51:9092,172.16.213.75:9092,172.16.213.109:9092"
topics => ["tomcatlogs"]
codec => "json"
}
kafka {
bootstrap_servers => "172.16.213.51:9092,172.16.213.75:9092,172.16.213.109:9092"
topics => ["tomcatlogs_catalina"]
codec => "json"
}
}
filter {
if [fields][log_topic] == "tomcatlogs_catalina" { #判断语句,根据topic不同,对日志做不同的过滤、分析,先分析的是catalina.out文件
mutate {
add_field => [ "[zabbix_key]", "tomcatlogs_catalina" ]
add_field => [ "[zabbix_host]", "%{[host][name]}" ]
}
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:access_time}\s+\[(?<loglevel>[\s\S]*)\]\s+\[%{DATA:exception_info}\](?<tomcatcontent>[\s\S]*)" }
}
date {
match => [ "access_time","MMM d HH:mm:ss", "MMM dd HH:mm:ss", "ISO8601"]
}
mutate {
remove_field => "@version"
remove_field => "message"
#remove_field => "[fields][log_topic]"
#remove_field => "fields"
remove_field => "access_time"
}
}
if [fields][log_topic] == "tomcatlogs" { #判断语句,根据topic不同,对日志做不同的过滤、分析,这里分析的是访问日志文件
json {
source => "message" #由于访问日志文件已经是json格式,所以这里解码出来即可
}
date {
match => [ "access time" , "[dd/MMM/yyyy:HH:mm:ss Z]" ] #时间字段转换,然后赋值给@timestamp字段
}
mutate {
remove_field => "@version" #删除不需要的字段
remove_field => "message"
}
}
}
output {
if [fields][log_topic] == "tomcatlogs_catalina" { #输出判断,根据不同的topic,做不同的输出设置
if ([loglevel] =~ "INFO" or [tomcatcontent] =~ /(Exception|error|ERROR|Failed)/ ) { #对catalina.out文件出现前面指定的关键字,就过滤出来,然后告警
zabbix {
zabbix_host => "[zabbix_host]"
zabbix_key => "[zabbix_key]"
zabbix_server_host => "172.16.213.140"
zabbix_server_port => "10051"
zabbix_value => "tomcatcontent" #这是输出到zabbix的内容配置
}
}
}
if [fields][log_topic] == "tomcatlogs" { #输出判断,根据不同的topic,做不同的输出设置,这是将访问日志输出到elasticsearch集群
elasticsearch {
hosts => ["172.16.213.37:9200","172.16.213.77:9200","172.16.213.78:9200"]
index => "tomcatlogs-%{+YYYY.MM.dd}"
}
}
stdout { codec => rubydebug } #调试模式,可以方便观看日志输出是否正常,调试完成后,删除即可
}
```
这个配置文件稍微复杂,将两个topic的日志分别做了不同处理,将catalina.out文件首先进行grok分割,然后进行关键字过滤,如果输出指定关键字,那么将和zabbix进行联动,发出告警;
接着,将tomcat的访问日志进行简单过滤后,直接输出到 elasticsearch集群,最后在Kibana进行展示。
## 5、配置zabbix输出并告警
登录zabbix web平台,首先创建一个模板logstash-output-zabbix,然后在此模块下创建一个监控项,如下图所示:
![](assets/1593327564-ace4c1a45f184861dfa221cfde7bb92f.png)
最后,要实现监控、告警,还需要创建一个触发器,进入刚刚创建好的模板中,创建一个触发器,如下图所示:
![](assets/1593327564-a72ed742fffa5f73fd7b666e5668d455.png)
这个触发器的含义是:如果收到logstash发送过来的数据就进行告警,或者说接收到的数据大于0就告警。
可以模拟产生一些包含上面关键字的日志信息,然后观察是否会进行告警。
## 6、Kibana平台创建tomcat访问日志索引
tomcat日志的索引名称为tomcatlogs-%{+YYYY.MM.dd},登录Kibana,选择创建索引,然后添加索引名tomcatlogs-\*,然后按照时间添加排序规则,即可完成访问日志索引创建,然后到discover查看日志,如果日志输出正常,即可看到访问日志,如下图所示:
![](assets/1593327564-4d31b4387b2e974836b19fceb35e7c74.png)
至此,ELK收集tomcat日志完成。
@@ -0,0 +1,376 @@
# 14.ELK订阅日志到HDFS分布式文件系统
## 14.ELK订阅日志到HDFS分布式文件系统
## 一、ELK在大数据运维时代中的应用
随着大数据时代的到来,传统运维时代也将转向数据运维时代,数据运维时代的一个典型特征是海量数据,ELK的出现解决了这个问题,有人把运维比作医生给病人看病,那么日志则是病人对自己的陈述,运维人员要从海量数据中有效的提取关键信息,才能对症下药,通过对日志的集中管理,并提供全文检索功能,不仅可以提高运维诊断问题的效率,同时可以起到实时系统监测、网络安全、事件管理和发现bug等功能。
> 而基于ELK的日志分析,通常比较常用的是实时分析,日志存储的时间不会太长,一般存储一周,最长也就是一个月,更长时间的数据都会删除,但是在有些情况下,我们需要长期保存数据做离线分析的话,就需要将数据保存到hadoop平台的HDFS上,下面就来介绍下如何通过logstash将kafka中的数据订阅一份到HDFS中长期保存。
## 二、hadoop发行版介绍
要实现将数据存储到HDFS上,就需要一个基础的hadoop环境,这里介绍下如何快速构建一个hadoop大数据平台环境。作为hadoop入门基础,首先介绍下关于hadoop发行版的知识以及如何选择适合自己的hadoop发行。
目前Hadoop发行版非常多,有Intel发行版,华为发行版、Cloudera发行版(CDH)、Hortonworks版本等,所有这些发行版均是基于Apache Hadoop衍生出来的,之所以有这么多的版本,是由于Apache Hadoop的开源协议决定的:任何人可以对其进行修改,并作为开源或商业产品发布/销售。就目前而言,不收费的Hadoop版本主要有三个,都是国外厂商,分别是:
> (1) Apache(最原始的版本,所有发行版均基于这个版本进行改进)
> (2)Cloudera版本(Cloudera’s Distribution Including Apache Hadoop,简称CDH)
> (3)Hortonworks版本(Hortonworks Data Platform,简称“HDP”)
对于国内用户而言,绝大多数选择CDH版本,Cloudera的CDH和Apache的Hadoop的区别如下:
> (1)、 CDH对Hadoop版本的划分非常清晰,截止目前为止,CDH共有5个版本,其中,前三个已经不再更新,最近的两个,分别是CDH4和CDH5,CDH4基于Hadoop2.0,CDH5基于hadoop2.2/2.3/2.5/2.6.相比而言,Apache版本则混乱得多;同时,CDH发行版比Apache hadoop在兼容性,安全性,稳定性上有很大增强。
> (2)、CDH3是CDH第三个版本,基于Apache hadoop0.20.2改进而来,并融入了最新的patch,CDH4版本是基于Apache hadoop2.0.0改进的,CDH总是并应用了最新Bug修复或者Feature的Patch,并比Apache hadoop同功能版本提早发布,更新速度比Apache官方快。
> (3)、CDH支持Kerberos安全认证,apache hadoop则使用简陋的用户名匹配认证.
> (4)、CDH文档完善清晰,很多采用Apache版本的用户都会阅读CDH提供的文档,包括安装文档、升级文档等。
> (5)、CDH支持yum/apt包,RPM包,tar包,Cloudera Manager三种方式安装,Apache hadoop只支持Tar包安装。
CDH首先是100%开源,基于Apache协议。基于Apache Hadoop和相关projiect开发。可以做批量处理,交互式sql查询和及时查询,基于角色的权限控制。在企业中使用最广的Hadoop分发版本。
Cloudera完善了CDH的版本,并提供了对hadoop的发布、配置和管理,监控,诊断工具,在官网提供了多种集成发行版。单纯CDH版本下载,目前最新版本为CDH5.15.1,可自由下载并免费无限制使用。
## 三、CDH的获取与操作系统、JDK的依赖
要使用CDH发行版本,可以从CDH官方下载,地址为http://www.cloudera.com/downloads.html, 也可以到下面地址下载不同版本:
> ```coffeescript
> http://archive.cloudera.com/cdh4/
> http://archive.cloudera.com/cdh5/
> ```
CDH支持yum/apt包,RPM包,tarball包多种安装方式,根据hadoop运维需要,我们选择tarball的安装方式(开箱即用)。
这里我们下载最新二进制包版本:
[http://archive.cloudera.com/cdh5/cdh/5/hadoop-2.6.0-cdh5.15.1.tar.gz](http://archive.cloudera.com/cdh5/cdh/5/hadoop-2.6.0-cdh5.15.1.tar.gz) 。
除了下载hadoop的二进制包,还需要JDK的支持,CDH发行版本对JDK是有要求的,仅支持JDK1.7和JDK1.8,相关依赖关系如下:
![](assets/1593327584-8a8c387e344171be9cfcd693f16773c9.png)
![](assets/1593327584-9888433aa1ea29c04dab17a072a2c072.png)
更详细的依赖可参考:[https://www.cloudera.com/documentation/enterprise/release-notes/topics/rn\_consolidated\_pcm.html#pcm\_jdk](https://www.cloudera.com/documentation/enterprise/release-notes/topics/rn_consolidated_pcm.html#pcm_jdk)
最后,CDH对操作系统也有要求,推荐大家使用RHEL/CentOS7.5, 7.4, 7.3, 7.2, 7.1系列版本,尽量不要使用RHEL/CentOS6.x版本,因为不支持RHEL/CentOS6.x某些小版本。
更详细的操作系统依赖可参考: [https://www.cloudera.com/documentation/enterprise/release-notes/topics/rn\_consolidated\_pcm.html#concept\_jpd\_hpz\_jdb](https://www.cloudera.com/documentation/enterprise/release-notes/topics/rn_consolidated_pcm.html#concept_jpd_hpz_jdb)
## 四、伪分布式安装hadoop
为了让大家快速了解hadoop的使用和原理,我们先通过伪分布式来迅速安装一个hadoop集群,所谓的伪分布式hadoop,其实就是在一台机器上部署hadoop的所有角色和进程,主要有Namenode、datanode、resourcemanager、nodemanager等进程。伪分布式hadoop主要用于学习、开发、功能测试等方面。
### 4.1、安装规划
这里以centos7.5为例,IP地址为172.16.213.235,以下所有操作都在此服务器上完成。
jdk需要oracle JDK1.7.0\_55以上,或者JDK1.8.0\_31以上。这里选择jdk1.8.0\_162。
对于hadoop程序安装路径,这里安装到/opt/hadoop目录下,hadoop配置文件放到/etc/hadoop目录下。
> 这样规划将配置文件和主程序分开,有利于后期的程序升级和自动化运维。
### 4.2、安装过程
首先,从oracle官网下载jdk1.8.0\_162,将jdk-8u162-linux-x64.tar.gz解压到/usr/java目录下。
```coffeescript
[root@cdh5namenode hadoop]#mkdir /usr/java
[root@cdh5namenode hadoop]#tar zxvf jdk-8u162-linux-x64.tar.gz -C /usr/java
[root@cdh5namenode hadoop]#cd /usr/java
[root@cdh5namenode hadoop]#ln -s jdk1.8.0_162 default
```
接着,需要创建一个hadoop用户,然后设置hadoop用户的环境变量,配置如下:
```plain
[root@cdh5namenode hadoop]#useradd hadoop
[root@cdh5namenode hadoop]# more /home/hadoop/.bashrc
# .bashrc
# Source global definitions
if [ -f /etc/bashrc ]; then
. /etc/bashrc
fi
# User specific aliases and functions
export JAVA_HOME=/usr/java/default
export CLASSPATH=.:$JAVA_HOME/jre/lib/rt.jar:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export PATH=$PATH:$JAVA_HOME/bin
export HADOOP_PREFIX=/opt/hadoop/current
export HADOOP_MAPRED_HOME=${HADOOP_PREFIX}
export HADOOP_COMMON_HOME=${HADOOP_PREFIX}
export HADOOP_HDFS_HOME=${HADOOP_PREFIX}
export HADOOP_YARN_HOME=${HADOOP_PREFIX}
export HTTPFS_CATALINA_HOME=${HADOOP_PREFIX}/share/hadoop/httpfs/tomcat
export CATALINA_BASE=${HTTPFS_CATALINA_HOME}
export HADOOP_CONF_DIR=/etc/hadoop/conf
export YARN_CONF_DIR=/etc/hadoop/conf
export HTTPFS_CONFIG=/etc/hadoop/conf
export PATH=$PATH:$HADOOP_PREFIX/bin:$HADOOP_PREFIX/sbin
```
最后一步,就是解压安装hadoop了,tarball方式安装很简单,只需解压文件即可完成安装,然后将解压后的目录放到/opt目录下,进行授权即可:
```coffeescript
[root@cdh5namenode opt]# mkdir /opt/hadoop
[root@cdh5namenode opt]# tar zxvf hadoop-2.6.0-cdh5.15.1.tar.gz -C /opt/hadoop
[root@cdh5namenode opt]# cd /opt/hadoop
[root@cdh5namenode opt]# ln -s hadoop-2.6.0-cdh5.15.1 current
[root@cdh5namenode opt]#chown -R hadoop:hadoop /opt/hadoop
```
### 4.3、本地库文件(native-hadoop)支持
Hadoop是使用Java语言开发的,但是有一些需求和操作并不适合使用java,所以就引入了本地库(Native Libraries)的概念,通过本地库,Hadoop可以更加高效地执行某一些操作。目前在Hadoop中,本地库应用在文件的压缩上面,主要有gzip和zlib方面,在使用这两种压缩方式的时候,Hadoop默认会从$HADOOP\_HOME/lib/native/目录中加载本地库。
如果本地库加载失败,在执行hdfs shell命令的时候会输出如下信息:
INFO util.NativeCodeLoader - Unable to load native-hadoop library for your platform... using builtin-java classes where applicable
CDH4版本之后,hadoop的本地库文件已经不放到CDH的安装包里面了,所以需要另行获取。
获取本地库有两种方式,一种方式是直接下载源码,自己编译源码文件,此方法比较麻烦,不推荐,另一种方式是下载apache的hadoop发行版本,这个发行版本中包含有本地库文件。
apache的hadoop发行版本下载地址为: [http://archive.apache.org/dist/hadoop/core/](http://archive.apache.org/dist/hadoop/core/), 从这里下载跟CDH对应的apache发行版本,例如上面CDH下载的是hadoop-2.6.0-cdh5.15.1.tar.gz,那么,这里就从apache发行版本中下载hadoop-2.6.0.tar.gz版本,将下载的此版本解压,找到里面的$HADOOP\_HOME/lib/native/本地库文件,复制到CDH5版本对应的路径$HADOOP\_HOME/lib/native/就可以了。
如果成功加载native-hadoop本地库,日志会有如下输出:
```sql
DEBUG util.NativeCodeLoader - Trying to load the custom-built native-hadoop library...
INFO util.NativeCodeLoader - Loaded the native-hadoop library
```
### 4.4、启动hadoop服务
CDH5新版本的hadoop启动服务脚本位于$HADOOP\_HOME/sbin目录下,hadoop的服务启动主要包含下面几个服务:
```plain
namenode
secondarynamenode
datanode
resourcemanager
nodemanager
```
这里以hadoop用户来进行管理和启动hadoop的各种服务。
**1、启动namenode服务**
伪分布模式下,对hadoop配置文件不用做过多配置,仅需要修改hadoop配置文件core-site.xml,增加如下内容:
```xml
<property>
<name>fs.defaultFS</name>
<value>hdfs://cdh5namenode</value>
</property>
```
这里的cdh5namenode是服务器的主机名,需要将此主机名在/etc/hosts进行解析,内容如下:
```css
172.16.213.235 cdh5namenode
```
其中,172.16.213.235是我安装hadoop伪分布模式机器的IP地址。
配置完成,就可以启动namenode服务了,第一次启动namenode服务,首先需要对namenode进行格式化,命令如下:
```ruby
[hadoop@cdh5namenode ~]$ cd /opt/hadoop/current/bin
[hadoop@cdh5namenode bin]$ hdfs namenode -format
```
如果提示格式化成功,就可以启动namenode服务了。
```ruby
[hadoop@cdh5namenode conf]$ cd /opt/hadoop/current/sbin/
[hadoop@cdh5namenode sbin]$ ./hadoop-daemon.sh start namenode
```
要查看namenode启动日志,可以查看
/opt/hadoop/current/logs/hadoop-hadoop-namenode-cdh5namenode.log文件。
namenode启动完成后,就可以通过web页面查看状态了,默认namenode启动后,会启动50070端口,访问地址为:[http://172.16.213.232:50070](http://172.16.213.232:50070/)
**2、 启动datanode服务**
启动datanode服务很简单,直接执行如下命令:
```ruby
[hadoop@cdh5namenode ~]$ cd /opt/hadoop/current/bin
[hadoop@cdh5namenode sbin]$ ./hadoop-daemon.sh start datanode
```
可通过/opt/hadoop/current/logs/hadoop-hadoop-datanode-cdh5namenode.log查看datanode启动日志。
**3、启动resourcemanager**
resourcemanager是yarn框架的服务,用于任务调度和分配,启动方式如下:
```ruby
[hadoop@cdh5namenode ~]$ cd /opt/hadoop/current/bin
[hadoop@cdh5namenode sbin]$ ./yarn-daemon.sh start resourcemanager
```
可通过/opt/hadoop/current/logs/yarn-hadoop-resourcemanager-cdh5namenode.log查看resourcemanager启动日志。
**4、启动nodemanager**
nodemanager是计算节点,主要用于分布式运算的资源分配,启动方式如下:
```ruby
[hadoop@cdh5namenode ~]$ cd /opt/hadoop/current/bin
[hadoop@cdh5namenode sbin]$ ./yarn-daemon.sh start nodemanager
```
可通过/opt/hadoop/current/logs/yarn-hadoop-nodemanager-cdh5namenode.log查看nodemanager启动日志。
至此,hadoop伪分布式已经运行起来了。可通过jps命令查看各个进程的启动信息:
```ruby
[hadoop@cdh5namenode logs]$ jps
16843 NameNode
16051 DataNode
16382 NodeManager
28851 Jps
16147 ResourceManager
```
### 5、分布式存储与分布式计算
hadoop搭建起来后,就可以使用了,首先通过hadoop hdfs命令进行分布式存储操作,执行命令如下:
```ruby
[hadoop@cdh5namenode logs]$ hadoop fs -ls / #查看HDFS文件系统根目录下的数据
[hadoop@cdh5namenode logs]$ hadoop fs -mkdir /logs #在HDFS文件系统根目录下创建一个logs目录
[hadoop@cdh5namenode logs]$ hadoop fs -put test.txt /logs #从本地磁盘将test.txt文件上传到HDFS上的logs目录
[hadoop@cdh5namenode logs]$ hadoop fs -text /logs/test.txt #查看HDFS上/logs/test.txt文件的内容
[hadoop@cdh5namenode logs]$ hadoop fs -get /logs/test.txt /tmp #从HDFS上下载/logs/test.txt文件到本地磁盘的/tmp目录中
[hadoop@cdh5namenode logs]$ hadoop fs -chown -R iivey:iivey /logs/test.txt #将HDFS上/logs/test.txt文件授权给iivey用户
[hadoop@cdh5namenode logs]$ hadoop fs -rm -r /logs/test.txt #从HDFS上删除/logs/test.txt文件
```
可以看到,hadoop hdfs命令还是比较简单的,跟linux下的命令基本类似,其实hdfs这些命令最终还是执行的linux系统命令而已。
最后,在来看下分布式计算的威力,我们在hadoop中运行一个Mapreduce程序,这里通过hadoop安装包中附带的一个mapreduce的demo程序来做演示,我们做个简单的mr计算。
在/opt/hadoop/current/share/hadoop/mapreduce路径下找到hadoop-mapreduce-examples-2.6.0-cdh5.15.1.jar包,我们执行一个wordcount程序,统计一批文件中,相同文件的行数。
先上传一个文本文件到hdfs上,然后进行这个文件的统计分析:
```ruby
[hadoop@cdh5namenode logs]$ cat test.txt
linux On a managed deployment, Cloudera Manager
The Cloudera Manager Admin Console linux centos
CDH and Cloudera Manager Supported Transport Layer Security Versions for linux
Cloudera Manager Resource Requirements
Kafka Client Versions Used by Apache Flume in CDH
[hadoop@cdh5namenode logs]$ hadoop fs -put test.txt /input
[hadoop@cdh5namenode mapreduce]$hadoop jar \
/opt/hadoop/current/share/hadoop/mapreduce/hadoop-mapreduce-examples-2.6.0-cdh5.15.1.jar wordcount /input/ /output/test90
```
其中,/output/test90是输出文件夹,必须不存在,它由程序自动创建,如果预先存在output文件夹,则会报错。上面程序执行完毕后,就会在 /output/test90中输出结果,通过hdfs的shell命令查看下输出内容:
```plain
[hadoop@cdh5namenode mapreduce]$ hadoop fs -ls /output/test90
Found 2 items
-rw-r--r-- 3 hadoop supergroup 0 2018-09-21 17:46 /output/test90/_SUCCESS
-rw-r--r-- 3 hadoop supergroup 225 2018-09-21 17:46 /output/test90/part-r-00000
[hadoop@cdh5namenode mapreduce]$ hadoop fs -cat /output/test90/part-r-00000
Cloudera 4
linux 3
Versions 2
CDH 2
......
```
这里的Cloudera是一个字符串,后面的4是出现的次数,也就是说Cloudera这个字符串在test.txt这个文件中出现了4次,其它类似。
到此为止,一个基础的hadoop环境构建完成了。
## 五、通过logstash从Kafka中订阅数据到HDFS
下面再回到ELK的话题上来,这里仍以专栏前面介绍的ELK架构为基础,然后新增了一条入数据到HDFS的线路,拓扑如下:
![](assets/1593327584-9d8580863ae0d52687831c30d50c7f15.png)
我们需要做的是将kafka中缓存的某些数据发送到hadoop的hdfs上实现长期存储和离线分析,要实现这个功能,需要用到logstash的一个output插件logstash-output-webhdfs,这个插件logstash已经自带,无需重新安装。
这里我们将nginx的日志从kafka中消费出来,然后存储到hadoop的hdfs上,这里直接给出logstash事件配置文件kafka\_nginx\_into\_hdfs.conf的内容:
```php
input {
kafka {
bootstrap_servers => "172.16.213.51:9092,172.16.213.75:9092,172.16.213.109:9092"
topics => ["sinalogs"]
codec => json {
charset => "UTF-8"
}
}
}
output {
webhdfs {
host => "172.16.213.235"
port => 50070
user => "hadoop"
path => "/logs/nginx/logstash-%{+YYYY}-%{+MM}-%{+dd}/access-%{+HH}.log"
compression => "gzip"
codec => line { format => "%{message}"}
}
stdout { codec => rubydebug }
}
```
在input中,指定从sinalogs这个topic中消费日志数据,这里重点看output输出部分,其中,webhdfs表示将数据输出到HDFS上。
> host:指定hdfs的namenode地址。
> port:指定webhdfs端口,一般为50070.
> user:指定hdfs运行的用户啊,以这个用户的权限去写hdfs
> path:指定日志文件写的路径, 上面是按天自动创建目录,按小时建log文件,这里将数据输出到HDFS的/logs/nginx/路径下。
> compression:指定数据是否压缩和压缩的方式,有none, snappy, gzip三个选项,默认是none,表示不压缩
> codec:指定输出数据的编解码器。输出编解码器用于在数据离开输出之前对其进行编码。默认值为line。这里表示仅输出message的值到HDFS。
配置文件编写完成,载入事件配置文件即可,执行如下命令:
```coffeescript
[root@logstashserver ~]# cd /usr/local/logstash
[root@logstashserver logstash]# nohup bin/logstash -f kafka_nginx_into_hdfs.conf --path.data /data/hdfsdata/ &
```
logstash启动后,可以通过查看logstash日志,观察是否启动正常,如果启动失败,会在日志中有启动失败提示。
这些需要注意的是,Logstash使用webhdfs插件,可能无法正常输出到HDFS中,会出现如下报错:
```javascript
[2018-09-25T11:57:20,026][WARN ][logstash.outputs.webhdfs ] Failed to flush outgoing items {:outgoing_count=>1, :exception=>"WebHDFS::ServerError", :backtrace=>["/usr/local/logstash/vendor/bundle/jruby/2.3.0/gems/webhdfs-0.8.0/lib/webhdfs/client_v1.rb:351:in `request'", "/usr/local/logstash/vendor/bundle/jruby/2.3.0/gems/webhdfs-0.8.0/lib/webhdfs/client_v1.rb:270:in `operate_requests'", "/usr/local/logstash/vendor/bundle/jruby/2.3.0/gems/webhdfs-0.8.0/lib/webhdfs/client_v1.rb:73:in `create'", "/usr/local/logstash/vendor/bundle/jruby/2.3.0/gems/logstash-output-webhdfs-3.0.6/lib/logstash/outputs/webhdfs.rb:228:in `write_data'", "/usr/local/logstash/vendor/bundle/jruby/2.3.0/gems/logstash-output-webhdfs-3.0.6/lib/logstash/outputs/webhdfs.rb:211:in `block in flush'", "org/jruby/RubyHash.java:1343:in `each'", "/usr/local/logstash/vendor/bundle/jruby/2.3.0/gems/logstash-output-webhdfs-3.0.6/lib/logstash/outputs/webhdfs.rb:199:in `flush'", "/usr/local/logstash/vendor/bundle/jruby/2.3.0/gems/stud-0.0.23/lib/stud/buffer.rb:219:in `block in buffer_flush'", "org/jruby/RubyHash.java:1343:in `each'", "/usr/local/logstash/vendor/bundle/jruby/2.3.0/gems/stud-0.0.23/lib/stud/buffer.rb:216:in `buffer_flush'", "/usr/local/logstash/vendor/bundle/jruby/2.3.0/gems/stud-0.0.23/lib/stud/buffer.rb:159:in `buffer_receive'", "/usr/local/logstash/vendor/bundle/jruby/2.3.0/gems/logstash-output-webhdfs-3.0.6/lib/logstash/outputs/webhdfs.rb:182:in `receive'", "/usr/local/logstash/logstash-core/lib/logstash/outputs/base.rb:91:in `block in multi_receive'", "org/jruby/RubyArray.java:1734:in `each'", "/usr/local/logstash/logstash-core/lib/logstash/outputs/base.rb:91:in `multi_receive'", "org/logstash/config/ir/compiler/OutputStrategyExt.java:109:in `multi_receive'", "org/logstash/config/ir/compiler/OutputDelegatorExt.java:156:in `multi_receive'", "/usr/local/logstash/logstash-core/lib/logstash/pipeline.rb:475:in `block in output_batch'", "org/jruby/RubyHash.java:1343:in `each'", "/usr/local/logstash/logstash-core/lib/logstash/pipeline.rb:474:in `output_batch'", "/usr/local/logstash/logstash-core/lib/logstash/pipeline.rb:426:in `worker_loop'", "/usr/local/logstash/logstash-core/lib/logstash/pipeline.rb:384:in `block in start_workers'"]}
```
从错误中,可以判断应该是如下两个问题:
(1)HDFS访问账户问题
(2)HDFS的主机解析问题
对于第一个问题,首先要保证Logstash中指定的user要有权限写path给出的这个路径下的目录。
而第二个问题很容易忽略,Logstash默认是使用主机名进行解析的,而且从namenode获取到的也是主机名。因此需要将hadoop节点对应的主机名在Logstash所在的主机上做个本地解析,也就是将所有Hadoop节点IP和主机名映射到/etc/hosts中。
在Logstash所在主机/etc/hosts中,添加如下内容:
```css
173.172.16.213.235 cdh5master
```
然后再次启动logstash应该就恢复正常了。此时可以查看HDFS的/logs/nginx目录,应该可以看到有数据已经写入HDFS了,如下所示:
```ruby
[hadoop@cdh5master mapreduce]$ hadoop fs -ls /logs/nginx
Found 1 items
drwx------ - hadoop supergroup 0 2018-09-25 16:36 /logs/nginx/logstash-2018-09-25
[hadoop@cdh5master mapreduce]$ hadoop fs -ls /logs/nginx/logstash-2018-09-25
Found 3 items
-rwx------ 3 hadoop supergroup 3822622 2018-09-25 15:58 /logs/nginx/logstash-2018-09-25/access-07.log
-rw-r--r-- 3 hadoop supergroup 6405004 2018-09-25 16:35 /logs/nginx/logstash-2018-09-25/access-08.log
-rw-r--r-- 3 hadoop supergroup 3019193 2018-09-25 16:49 /logs/nginx/logstash-2018-09-25/access-08.log.gz
```
这里可以看出,access-07.log是从kafka获取的文本格式的日志,access-08.log.gz是设置了gipz压缩后输出的日志格式,生产环境中建议使用压缩方式,能节省不少磁盘空间,同时也不影响对数据的分析,因为hadoop默认可以读取gz、bz2等格式的压缩文件。
@@ -0,0 +1,316 @@
## 15.ELK应用架构性能调优经验分享
# 一、Elasticsearch的优化
## 1.1、JVM内存的优化
**1、堆内存如何配置?**
首先,作为一个JAVA应用,就脱离不开JVM和GC。很多人上手ES的时候,对GC一点概念都没有,然后及直接去网上抄各种JVM“优化”参数,但是效果却不理想,甚至搞出内存溢出这样的问题。可见了解JVM GC的概念和基本工作机制是很有必要的。下面先做一些简单的JVM介绍。
Java中的堆是JVM所管理的最大的一块内存空间,主要用于存放各种类的实例对象。在Java中,堆被划分成两个不同的区域: 新生代 ( Young )、老年代 ( Old )。这样划分的目的是为了使 JVM 能够更好的管理堆内存中的对象,包括内存的分配以及回收。
设置堆内存的唯一目的就是创建对象实例,所有的对象实例和数组都要在堆上分配。堆是由垃圾回收(Garbage Collect)来负责的,因此也叫做“GC堆”,垃圾回收采用分代算法,堆由此分为新生代和老年代。堆的优势是可以动态地分配内存大小,生存期也不必事先告诉编译器,因为它是在运行时动态分配内存的,Java的垃圾回收器会自动回收这些不再使用的数据。
了解了堆内存的概念和作用后,下面来说下Elasticsearch 的JVM设置堆内存的方法。
默认情况下,Elasticsearch JVM使用堆内存最小和最大值为1g,可以在Elasticsearch的配置目录下jvm.options文件中找到如下内容:
```plain
-Xms1g
-Xmx1g
```
其中,Xms是设置堆最小内存,Xmx是设置堆最大内存,很明显,默认的这个值太小,我们生产系统环境中必须要修改这个值,修改方法很简单,直接修改jvm.options文件中这两个值即可,例如修改为16g,可以这么写:
```plain
-Xms16g
-Xmx16g
```
然后再次重启Elasticsearch,配置才能生效。
那么问题来了,这个最小堆大小(Xms)和最大堆大小(Xmx)设置多少合适呢,继续往下看。
**2、堆内存的决定因素与配置建议**
堆内存值的设置取决于服务器上可用的无论内存大小。
原则上来说,堆内存设置的越大,Elasticsearch可用的堆就越多,可用于缓存的内存就越多,但不能无限大,太多的堆内存可能会使垃圾回收机制暂停。并且还会浪费大量内存。
> 根据经验,将最小堆大小(Xms)和最大堆大小(Xmx)设置为相同值, 可以防止堆在运行时调整大小,因为这是一个非常消耗性能的过程。
那么设置堆内存多大合适呢?
> 一个经验值是:不超过物理内存的50%,以确保有足够的物理内存留给内核文件系统缓存。最大不超过32GB。
为什么呢,继续往下看。
**3、堆内存为什么不能超过物理机内存的一半**
堆内存对于Elasticsearch来说非常重要,它可以对数据提供快速操作。但是,还有另外一个非常重要的内存使用者:Lucene。
> Lucene是一个开源的全文检索引擎工具包,而elasticsearch底层是基于lucene的,并对其进行了扩展,提供了比Lucene更为丰富的查询语言,以及简单易用的restful api接口、java api接口等,一句话概括就是: Elasticsearch是Lucene面向企业搜索应用的扩展,可极大的缩短研发周期。
Lucene设计的目的是把底层操作系统数据缓存到内存中。而Lucene段(segment)存储在单个文件中。这些文件都是不会变化的,所以很利于缓存,同时操作系统也会把这些热段(hot segments)保留在内存中,以便更快的访问。这些热段包括倒排索引(用于全文搜索)和文档值(用于聚合)。
由此可知,Lucene的性能依赖于与和操作系统的这种交互。如果把所有可用的内存都给了Elasticsearch的堆,那么Lucene就不会有任何剩余的内存。这将严重影响性能。
> 因此,我们说:建议将可用内存的50%提供给Elasticsearch堆,而将其它50%空闲。请放心,它不会被闲置,因为Lucene正在等待使用这50%的内存资源。
如果不在字符串字段上做聚合操作,则可以考虑进一步降低堆内存。因为堆内存越小,越可以从Elasticsearch(更快的GC)和Lucene(更多内存缓存)中获得更高的性能。
## 1.2、操作系统内存优化
操作系统作为运行Elasticsearch的基础,为保证性能,最好禁用系统的swap的使用,方法如下:
```shell
1、暂时关闭SWAP,重启后恢复。
swapoff -a
2. 永久关闭SWAP
编辑/etc/fstab, 注释掉如下SWAP分区项即可。
#UUID=0b55fdb8-a9d8-4215-80f7-f42f75644f87 none swap sw 0 0
```
如果是混合服务器,不能完全禁用swap的话,可以尝试降低swappiness,该值控制操作系统尝试交换内存的积极性,swappiness=0的时候表示最大限度使用物理内存,然后才是swap空间,swappiness=100的时候表示积极的使用swap分区,并且把内存上的数据及时的搬运到swap空间里面。linux的基本默认设置为60,具体如下:
```plain
cat /proc/sys/vm/swappiness
```
也就是说,你的内存在使用到100-60=40%的时候,就开始出现有交换分区的使用。
操作系统层面,要尽可能使用内存,就需要对该参数进行调整。
临时调整的方法如下,例如调成10,可以在命令行执行如下:
```plain
sysctl vm.swappiness=10
```
要想永久调整的话,需要将在/etc/sysctl.conf修改,加上:
```plain
cat /etc/sysctl.conf
vm.swappiness=10
```
## 1.3、使用更快的硬件
要保证Elasticsearch的性能,在硬件上肯定是配置SSD硬盘,是最有效果的,如果有多个SSD硬盘,可以配置成RAID 0阵列以获得更佳的IO性能。但是任何一个SSD损坏都有可能破坏index。因此,通常正确的做法是优化单的shard存储性能,然后添加replicat放在不同的节点。同时使用snapshot快照和restore功能去备份 index。
# 二、Logstash性能调优
## 2.1、配置参数调优
可以通过调节logstash配置文件logstash.yml以下配置项,对性能进行调优:
> (1)、pipeline.workers:该参数可控制output或filter插件的工作线程数(只能设置正整数), logstash的grok正则解析极其消耗计算资源,因此filter一般都会存在瓶颈,此时需要增加工作线程,以提高性能。官方建议线程数设置大于CPU核数,如果logstah节点是混用服务器,建议等于或小于CPU数。
> (2)、pipeline.batch.size:设置批量执行event的最大值,该值是用于input的批量处理事件值,再打包发送给filter和output,增加该值可以在一定范围内提高性能,但是需要增加额外的内存开销。
> (3)、pipeline.batch.delay:批量处理事件的最大等待值(input需要按照batch处理的最大值发送到消息队列,但是不能一直等,所以需要一个最大的超时机制)。
## 2.2、Logstash中的JVM配置文件
  Logstash也需要运行在JVM中,对Logstash中的JVM也是需要调优的,可以通过配置logstash的jvm.options文件来针对JVM进行调优。比如内存的最大最小、垃圾清理机制等等。JVM的内存分配不能太大,也不能太小,太大会拖慢操作系统。太小导致无法启动。
可以在Logstash的配置目录下jvm.options文件中找到如下内容:
```plain
-Xms1g
-Xmx1g
```
堆内存对logstash来说也是非常重要的,一般要求将初始值和最大值设置为一致,这样可以防止动态调整堆内存大小的消耗,堆内存的大小一般设置为物理内存的一半即可,最大不超过32GB,jvm调有的方式没有固定的准则,一个通用的方法是依次增加堆内存大小,然后监控性能变化,如果性能有提升,再次重复前面的操作,这样反复测试,一直达到最优为止。
要修改jvm大小,直接修改jvm.options文件中这两个值即可,例如修改为16g,可以这么写:
```plain
-Xms16g
-Xmx16g
```
然后再次重启logstash,配置就生效了。
# 三、kafka性能调优
kafka是一个高吞吐量分布式消息系统,并且提供了持久化。其高性能有两个重要特征,分别是:
> (1)磁盘连续读写性能远远高于随机读写的特点
> (2)通过将一个topic拆分多个partition,可提供并发和吞吐量。
要充分发挥kafka的性能,就需要满足以上这两个条件,常见的调优方式有如下几个方面。
## 3.1、内存优化
这里首先介绍一个名词page cache, Linux系统内核为文件系统文件设置了一个缓存,对文件读、写的数据内容都缓存在这里。这个缓存就称为 page cache(页缓存)。在命令行输入free,或者“cat /proc/meminfo”,输出的"Cached"部分就是Page Cache。
> 当发生一个文件读操作时,系统会首先在Page Cache中查找,如果找到,就直接返回了,没有的话就会从磁盘读取文件,然后写入Page Cache,再读取,最终返回需要的数据。当写操作发生时,系统只是将数据写入Page Cache中,并将该页置上dirty标志。写入Page Cache的数据会被定期批量保存到文件系统,减少了磁盘的操作次数,减少系统开销。
这就是文件系统的读、写机制,而kafka是重度依赖底层操作系统提供的Page Cache功能的。也就是说kafka一开始是把数据写到PageCache的,如果消费者一直在消费,而且速度大于等于kafka的生产者发送数据的速度,那么消费者会一直从Page Cache读取数据,读、写都在内存中完成接力,根本没有磁盘访问,不会因为kafka写入磁盘的操作影响吞吐量。这也是kafka非常高效的根本原因。
因此,对内存的调优,主要是对linux系统下内存的优化,这里需要关注两个操作系统参数:
> (1)、vm.dirty\_background\_ratio:这个参数指定了当文件系统缓存脏页(Page Cache中的数据称为脏页数据)数量达到系统内存百分之多少时(默认10%)就会触发pdflush/flush/kdmflush等后台回写进程运行,将一定缓存的脏页异步地刷入磁盘。增减这个值是最主要的调优手段。
>
> (2)vm.dirty\_ratio:这个参数则指定了当文件系统缓存脏页数量达到系统内存百分之多少时(默认20%),系统不得不开始处理缓存脏页(因为此时脏页数量已经比较多,为了避免数据丢失需要将一定脏页刷入磁盘);在此过程中很多应用进程可能会因为系统刷新内存数据到磁盘而发生IO阻塞。
这两个系统参数对应的文件为:
```plain
vm.dirty_background_ratio:/proc/sys/vm/dirty_background_ratio
vm.dirty_ratio:/proc/sys/vm/dirty_ratio
```
建议将vm.dirty\_background\_ratio设置为5%,vm.dirty\_ratio设置为10%。具体的设置根据不同环境,需要进行测试、再测试。
最后,kafka虽然也是java应用,但是它需要的堆内存比较小(因为数据不放入JVM中),因此,建议保留物理内存的60%以上给操作系统,以便保证page cache的分配。
## 3.2、topic的拆分
kafka读写的单位是partition,将一个topic拆分为多个partition可以提高系统的吞吐量,但是有一个前提条件就是:不同的partition要分布在不同在磁盘上。如果多个partition位于一个磁盘上就会出现多个进程同时对一个磁盘的多个文件进行读写,使得操作系统对磁盘的读写进行频繁的调度,破坏了磁盘读写的连续性。
要将不同的partition要分布在不同在磁盘上,可以将磁盘的多个目录配置到broker的log.dirs,例如:
```ruby
log.dirs=/disk1/logs,/disk2/logs,/disk3/logs
```
kafka会在新建partition的时候,将新partition分布在partition最少的目录上,因此,一般不能将同一个磁盘的多个目录设置到log.dirs。
## 3.3、配置参数优化
> 网络和IO线程配置优化
配置参数:
num.network.threads:Broker处理消息的最大线程数
num.io.threads:Broker处理磁盘IO的线程数
优化建议:
一般num.network.threads主要处理网络io,读写缓冲区数据,基本没有io等待,配置线程数量为cpu核数加1
num.io.threads主要进行磁盘io操作,高峰期可能有些io等待,因此配置需要大些。配置线程数量为cpu核数2倍,最大不超过3倍.
> 日志保留策略配置优化
当kafka server的被写入海量消息后,会生成很多数据文件,且占用大量磁盘空间,如果不及时清理,可能磁盘空间不够用,kafka默认是保留7天。
优化建议:
减少日志保留时间,建议三天或者更多时间。通过log.retention.hours来实现,例如:
```plain
log.retention.hours=72
```
> 段文件大小优化
段文件配置1GB,有利于快速回收磁盘空间,重启kafka加载也会加快,相反,如果文件过小,则文件数量比较多,kafka启动时是单线程扫描目录(log.dir)下所有数据文件),文件较多时性能会稍微降低。可通过如下选项配置段文件大小:
```plain
log.segment.bytes=1073741824
```
> log数据文件刷盘策略优化
为了大幅度提高producer写入吞吐量,需要定期批量写文件,优化建议如下:
```plain
每当producer写入10000条消息时,刷数据到磁盘。可通过如下选项配置:
log.flush.interval.messages=10000
```
```plain
每间隔1秒钟时间,刷数据到磁盘。可通过如下选项配置:
log.flush.interval.ms=1000
```
> replica复制配置优化
kafka集群下每个follow都会从leader拉取消息进行同步数据,follow同步性能由下面这几个参数决定:
```css
num.replica.fetchers:拉取线程数
replica.fetch.min.bytes:拉取最小字节数
replica.fetch.min.bytes:拉取最大字节数
replica.fetch.wait.max.ms:最大等待时间,表示follow拉取的频率
```
优化建议:
> num.replica.fetchers: 配置多个拉取线程数可以提高follower的I/O并发度,单位时间内leader持有更多请求,相应负载会增大,因而需要根据机器硬件资源做权衡
> replica.fetch.min.bytes=1: 默认配置为1字节,否则读取消息不及时
> replica.fetch.max.bytes= 5 _1024_ 1024: 默认为1MB,这个值太小,5MB为宜,根据业务情况进行调整。
> replica.fetch.wait.max.ms: follow拉取频率,需要根据日志输出速度进行设置,如果拉取频率过高,会导致cpu飙升,这是因为在leader无数据需要同步时,由于拉取频率过高,而出现挤压大量无效请求的情况。
# 四、filebeat性能调优
filebeat作为日志采集agent,是需要部署到生产服务器上的,因此对filebeat的配置和调优至关重要,不合理的设置会导致filebeat占用过量的系统资源,还会影响生产服务器的正常运行。
要合理配置filebeat,就需要理解filebeat的工作机制以及运行原理,与logstash相比,filebeat虽然占用系统资源很少,但是在日志量比较大的情况下或者日志异常突发时,filebeat也会占用大量系统内存开销,因而,合理设置filebeat的配置参数十分重要。
filebeat在没有日志可采集情况下,的确不会占用太大的内存开销(一般在100M左右),但在有大量的日志需要采集时,filebeat的内存占用是没有固定值的,根据我们的使用经验,如果出现单条日志大于50KB,并且有瞬间爆发量的时候, filebeat的内存占用将会大于300MB,甚至,如果出现了极端情况,例如单条日志>10M时,即使filebeat会截断到10M,那么filebeat的内存占用也会达到20GB,这就很可怕了。
## 4.1、内存优化
Filebeat内存限制,有两种模式,一种是内存模式,一种是文件缓存模式,实际应用中任选其一即可。
**(1)内存模式**
需要注意的是,所有事件(events)都是保存在内存中的,此模式只能限制事件数,无法限制最大使用内存,这有可能会因为日志长度导致内存使用暴增,相关配置如下:
```makefile
queue.mem:
events: 4096 #表示队列可以存储的事件数量。默认值是4096个事件。
flush.min_events: 512 #发布所需的最小事件数量。 默认值是0,表示可以直接输出发布事件,而无需额外的等待时间。 如果设置为非0,必须等待,在满足指定的事件数量后才能输出发布事件。
flush.timeout: 5s #表示最早的可用事件在队列中等待的最长时间,超过这个时间,立即输出发布事件,默认值是0s,表示立即可以输出发布事件
```
上面这对配置表示,如果512个事件可用或者最旧的可用事件在队列中等待5秒,则就将事件转发到输出。
**(2)文件缓存模式**
此模式可以限制最大使用内存,在filebeat6.3版本以后可以使用此功能,相关配置如下:
```perl
queue.spool:
file:
path: "${path.data}/spool.dat" #Spool file的路径
size: 512MiB #Spool file的大小,也就是缓冲区的大小。
page_size: 16KiB #文件的页面大小。默认值为4096(4KiB)。
write:
buffer_size: 10MiB #写缓冲区大小。一旦超过缓冲区大小,就刷新写缓冲区。
flush.timeout: 5s #写缓冲区中最旧事件的最长等待时间。如果设置为0,则在write.flush.events或write.buffer_size满足时写入缓冲区仅刷新一次。
flush.events: 1024 #缓冲事件的数量。一旦达到上限,就刷新写缓冲区。
```
上面这个配置的含义如下:
> Spool file将所有事件存储在磁盘的缓冲区中(内存中),如果已写入10MiB内容或1024个事件,则刷新写入缓冲区。如果最旧的可用事件在写缓冲区中等待5秒,则也将刷新写入缓冲区。
除了内存使用优化,filebeat还可以设置CPU最大使用核数,通过如下选项即可定义:
```css
max_procs:4
```
这个表示使用4个逻辑CPU。
## 4.2、文件系统资源优化
filebeat对日志的采集是贪婪的,只要发现日志就会坚持把日志采集完,否则会永久持有文件句柄不会“放手”,即使文件被删除。因此,filebeat在收集大量日志的时候,如果配置参数设置不当,会导致文件系统大量文件句柄被filebeat占用,导致收集日志异常,因此需要对filebeat采集日志的策略进行优化,常见的优化参数有如下几个:
> (1)close\_inactive: 1m
>
> 表示没有新日志采集后多长时间关闭文件句柄,默认5分钟,设置成1分钟,加快文件句柄关闭;
>
> (2)close\_timeout: 3h
>
> 表示传输了3h后在没有传输完成的话就强行关闭文件句柄,这个配置项是解决文件句柄耗尽的问题。但是开了这个配置项会有丢数据的风险。需要综合考虑。
>
> (3)clean\_inactive: 72h
>
> 这个配置项表示多久清理一次文件描述在registry文件,默认值是0表示不清理,不清理的意思是采集过的文件描述在registry文件里永不清理,在运行一段时间后,registry会变大,可能会带来性能问题。
>
> (4)ignore\_older: 70h
> 上面设置了clean\_inactive,就需要设置ignore\_older,且要保证ignore\_older < clean\_inactive。
关于filebeat的优化,就介绍这么多,其实已经足够了,优化是个综合技术,没有拿来主义,只能在一些经验的基础上,结合自己的业务场景,根据自己的日志特点设定合适的值,然后逐步测试,再测试,一步一步达到一个最优状态,这是进行优化的基本策略和思路。
@@ -0,0 +1,317 @@
## 2.【案例】Elasticsearch集群架构的部署与调优
## 1.1 典型ELK应用架构
下图是本节即将要介绍的一个线上真实案例的架构图:
![](assets/1593327194-06b216de6cd11f2fcd28d3940ec3328b.png)
此架构稍微有些复杂,因此,这里做一下架构解读。
这个架构图从左到右,总共分为5层,每层实现的功能和含义分别介绍如下:
**第一层、数据采集层**
数据采集层位于最左边的业务服务器集群上,在每个业务服务器上面安装了filebeat做日志收集,然后把采集到的原始日志发送到Kafka+zookeeper集群上。
**第二层、消息队列层**
原始日志发送到Kafka+zookeeper集群上后,会进行集中存储,此时,filbeat是消息的生产者,存储的消息可以随时给消费。
**第三层、数据分析层**
Logstash作为消费者,会去Kafka+zookeeper集群节点实时拉取原始日志,然后将获取到的原始日志根据规则进行分析、清洗、过滤,最后将清洗好的日志转发至Elasticsearch集群。
**第四层、数据持久化存储**
Elasticsearch集群在接收到logstash发送过来的数据后,执行写磁盘,建索引库等操作,最后将结构化的数据存储到Elasticsearch集群上。
**第五层、数据查询、展示层**
Kibana是一个可视化的数据展示平台,当有数据检索请求时,它从Elasticsearch集群上读取数据,然后进行可视化出图和多维度分析。
## 1.2 环境与角色说明
## 1 服务器环境与角色
操作系统统一采用Centos7.5版本,各个服务器角色如下表所示:
| IP地址 | 主机名 | 角色 | 所属集群 |
| --- | --- | --- | --- |
| 172.16.213.157 | filebeatserver | 业务服务器+filebeat | 业务服务器集群 |
| 172.16.213.51 | kafkazk1 | Kafka+ ZooKeeper | Kafka Broker集群 |
| 172.16.213.75 | kafkazk2 | Kafka+ ZooKeeper | |
| 172.16.213.109 | kafkazk3 | Kafka+ ZooKeeper | |
| 172.16.213.120 | logstashserver | Logstash | 数据转发 |
| 172.16.213.37 | server1 | ES Master、ES NataNode | Elasticsearch集群 |
| 172.16.213.77 | server2 | ES Master、Kibana | |
| 172.16.213.78 | server3 | ES Master、ES NataNode | |
## 2 软件环境与版本
下表详细说明了本节安装软件对应的名称和版本号,其中,ELK三款软件推荐选择一样的版本,这里选择的是6.3.2版本。
| 软件名称 | 版本 | 说明 |
| --- | --- | --- |
| JDK | JDK 1.8.0\_151 | Java环境解析器 |
| filebeat | filebeat-6.3.2-linux-x86\_64 | 前端日志收集器 |
| Logstash | logstash-6.3.2 | 日志收集、过滤、转发 |
| zookeeper | zookeeper-3.4.11 | 资源调度、协作 |
| Kafka | kafka\_2.10-0.10.0.1 | 消息通信中间件 |
| elasticsearch | elasticsearch-6.3.2 | 日志存储 |
| kibana | kibana-6.3.2-linux-x86\_64 | 日志展示、分析 |
## 1.3 安装JDK以及设置环境变量
## 1 选择合适版本并下载JDK
Zookeeper、elasticsearch和Logstash都依赖于Java环境,并且elasticsearch和Logstash要求JDK版本至少在JDK1.7或者以上,因此,在安装zookeeper、Elasticsearch和Logstash的机器上,必须要安装JDK,一般推荐使用最新版本的JDK,这里我们使用JDK1.8版本,可以选择使用Oracle
JDK1.8 或者Open JDK1.8。这里我们使用Oracle JDK1.8。
从oracle官网下载linux-64版本的JDK,下载时,选择适合自己机器运行环境的版本,oracle官网提供的JDK都是二进制版本的,因此,JDK的安装非常简单,只需将下载下来的程序包解压到相应的目录即可。安装过程如下:
```coffeescript
[root@localhost ~]# mkdir /usr/java
[root@localhost ~]# tar -zxvf jdk-8u152-linux-x64.tar.gz -C /usr/java/
```
这里我们将JDK安装到了/usr/java/目录下。
## 2 设置JDK的环境变量
要让程序能够识别JDK路径,需要设置环境变量,这里我们将JDK环境变量设置到/etc/profile文件中。添加如下内容到/etc/profile文件最后:
```plain
export JAVA_HOME=/usr/java/jdk1.8.0_152
export PATH=$PATH:$JAVA_HOME/bin
exportCLASSPATH=.:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/lib/dt.jar:$CLASSPATH
```
然后执行如下命令让设置生效:
```coffeescript
[root@localhost ~]# source /etc/profile
```
最后,在Shell提示符中执行“java -version”命令,如果显示如下结果,说明安装成功:
```css
[root@localhost ~]# java -version
openjdk version "1.8.0_152"
OpenJDK Runtime Environment (build 1.8.0_152-b12)
OpenJDK 64-Bit Server VM (build 25.152-b12, mixed mode)
```
## 2 安装并配置elasticsearch集群
## 2.1 elasticsearch集群的架构与角色
elasticsearch集群的一个主要特点就是去中心化,字面上理解就是无中心节点,这是从集群外部来说的,因为从外部来看elasticsearch集群,它在逻辑上是一个整体,与任何一个节点的通信和与整个elasticsearch集群通信是完全相同的。另外,从elasticsearch集群内部来看,集群中可以有多个节点,其中有一个为主节点(master node),这个主节点不是通过配置文件定义的,而是通过选举产生的。
下图为elasticsearch集群的运行架构图:
![](assets/1593327194-2b101ee950c70338f36e2ad2ce0b85dc.png)
在ElasticSearch的架构中,有三类角色,分别是Client Node、Data Node和Master Node,搜索查询的请求一般是经过Client Node来向Data Node获取数据,而索引查询首先请求Master Node节点,然后Master
Node将请求分配到多个Data Node节点完成一次索引查询。
本节介绍的ElasticSearch架构中,我们只用了Data Node和Master Node角色,省去了Client Node节点,关于主机和对应的各个角色如下表所示:
| 节点名称 | IP地址 | 集群角色 |
| --- | --- | --- |
| server1 | 172.16.213.37 | Master node、data node |
| server2 | 172.16.213.77 | data node |
| server3 | 172.16.213.78 | Master node、data node |
这里对集群中每个角色的含义介绍如下:
master node:
可以理解为主节点,主要用于元数据(metadata)的处理,比如索引的新增、删除、分片分配等,以及管理集群各个节点的状态。elasticsearch集群中可以定义多个主节点,但是,在同一时刻,只有一个主节点起作用,其它定义的主节点,是作为主节点的候选节点存在。当一个主节点故障后,集群会从候选主节点中选举出新的主节点。
由于数据的存储和查询都不会走主节点,所以主节点的压力相对较小,因此主节点的内存分配也可以相对少些,但是主节点却是最重要的,因为一旦主节点宕机,整个elasticsearch集群将不可用。所以一定要保证主节点的稳定性。
data node:
数据节点,这些节点上保存了数据分片。它负责数据相关操作,比如分片的CRUD、搜索和整合等操作。数据节点上面执行的操作都比较消耗CPU、内存和I/O资源,因此数据节点服务器要选择较好的硬件配置,才能获取高效的存储和分析性能。
client node:
客户端节点,属于可选节点,是作为任务分发用的,它里面也会存元数据,但是它不会对元数据做任何修改。client node存在的好处是可以分担data node的一部分压力,因为elasticsearch的查询是两层汇聚的结果,第一层是在data node上做查询结果汇聚,然后把结果发给client node,client node接收到data node发来的结果后再做第二次的汇聚,然后把最终的查询结果返回给用户。这样,client node就替data node分担了部分压力。
从上面对elasticsearch集群三个角色的描述,可以看出,每个节点都有存在的意义,我们只有把相关功能和角色划分清楚了,每种节点各尽其责,才能充分发挥出分布式集群的效果。
## 2.2 安装elasticsearch与授权
elasticsearch的安装非常简单,首先从官网https://www.elastic.co/ 下载页面找到适合的版本,可选择zip、tar、rpm等格式的安装包下载,这里我们下载的软件包为elasticsearch-6.3.2.tar.gz。安装过程如下:
```perl
[root@localhost ~]# tar -zxvf elasticsearch-6.3.2.tar.gz -C /usr/local
[root@localhost ~]# mv /usr/local/elasticsearch-6.3.2
/usr/local/elasticsearch
```
这里我们将elasticsearch安装到了/usr/local目录下。
由于elasticSearch可以接收用户输入的脚本并且执行,为了系统安全考虑,需要创建一个单独的用户用来运行elasticSearch,这里创建的普通用户是elasticsearch,操作如下:
```coffeescript
[root@localhost ~]# useradd elasticsearch
```
然后将elasticsearch的安装目录都授权给elasticsearch用户,操作如下:
```perl
[root@localhost ~]# chown -R elasticsearch:elasticsearch
/usr/local/elasticsearch
```
## 2.3 操作系统调优
操作系统以及JVM调优主要是针对安装elasticsearch的机器,为了获取高效、稳定的性能,需要从操作系统和JVM两个方面对elasticsearch进行一个简单调优。
对于操作系统,需要调整几个内核参数,将下面内容添加到/etc/sysctl.conf文件中:
```plain
fs.file-max=655360
vm.max_map_count = 262144
```
其中,第一个参数fs.file-max主要是配置系统最大打开文件描述符数,建议修改为655360或者更高,第二个参数vm.max\_map\_count影响Java线程数量,用于限制一个进程可以拥有的VMA(虚拟内存区域)的大小,系统默认是65530,建议修改成262144或者更高。
另外,还需要调整进程最大打开文件描述符(nofile)、最大用户进程数(nproc)和最大锁定内存地址空间(memlock),添加如下内容到/etc/security/limits.conf文件中:
```markdown
* soft nproc 20480
* hard nproc 20480
* soft nofile 65536
* hard nofile 65536
* soft memlock unlimited
* hard memlock unlimited
> 最后,还需要修改/etc/security/limits.d/20-nproc.conf文件(centos7.x系统),将:
* soft nproc 4096
修改为:
* soft nproc 20480
或者直接删除/etc/security/limits.d/20-nproc.conf文件也行。
```
## 2.4 JVM调优
JVM调优主要是针对elasticsearch的JVM内存资源进行优化,elasticsearch的内存资源配置文件为jvm.options,此文件位于/usr/local/elasticsearch/config目录下,打开此文件,修改如下内容:
```plain
-Xms2g
-Xmx2g
```
可以看到,默认JVM内存为2g,可根据服务器内存大小,修改为合适的值。一般设置为服务器物理内存的一半最佳。
## 2.5 配置elasticsearch
elasticsearch的配置文件均在elasticsearch根目录下的config文件夹,这里是/usr/local/elasticsearch/config目录,主要有jvm.options、elasticsearch.yml和log4j2.properties三个主要配置文件。其中jvm.options为JVM配置文件,log4j2.properties为日志配置,都相对比较简单,这里重点介绍elasticsearch.yml一些重要的配置项及其含义。
这里配置的elasticsearch.yml文件内容如下:
```plain
cluster.name: esbigdata
node.name: server1
node.master: true
node.data: true
path.data: /data1/elasticsearch,/data2/elasticsearch
path.logs: /usr/local/elasticsearch/logs
bootstrap.memory_lock: true
network.host: 0.0.0.0
http.port: 9200
discovery.zen.minimum_master_nodes: 1
discovery.zen.ping.unicast.hosts: ["172.16.213.37:9300","172.16.213.78:9300"]
```
每个配置项的含义分别介绍如下:
(1)cluster.name: esbigdata
配置elasticsearch集群名称,默认是elasticsearch。这里修改为esbigdata,elasticsearch会自动发现在同一网段下的集群名为esbigdata的主机,如果在同一网段下有多个集群,就可以通过这个属性来区分不同的集群。线上生产环境时建议更改。
(2)node.name: server1
节点名,任意指定一个即可,这里是server1,我们这个集群环境中有三个节点,分别是server1、server2和server3,记得根据主机的不同,要修改相应的节点名称。
(3)node.master: true
指定该节点是否有资格被选举成为master,默认是true,elasticsearch集群中默认第一台启动的机器为master角色,如果这台服务器宕机就会重新选举新的master。我们这个集群环境中,定义了server1和server3两个master节点,因此这两个节点上node.master的值要设置为true。
(4)node.data: true
指定该节点是否存储索引数据,默认为true,表示数据存储节点,如果节点配置node.master:false并且node.data:
false,则该节点就是client node。这个client node类似于一个“路由器”,负责将集群层面的请求转发到主节点,将数据相关的请求转发到数据节点。在我们这个集群环境中,定义了server1、server2和server3均为数据存储节点,因此这三个节点上node.data的值要设置为true。
(5)path.data:/data1/elasticsearch, /data2/elasticsearch
设置索引数据的存储路径,默认是elasticsearch根目录下的data文件夹,这里自定义了两个路径,可以设置多个存储路径,用逗号隔开。
(6)path.logs: /usr/local/elasticsearch/logs
设置日志文件的存储路径,默认是elasticsearch根目录下的logs文件夹
(7)bootstrap.memory\_lock: true
此配置项一般设置为true用来锁住物理内存。在linux下物理内存的执行效率要远远高于虚拟内存(swap)的执行效率,因此,当JVM开始使用swap内存时elasticsearch的执行效率会降低很多,所以要保证它不使用swap,保证机器有足够的物理内存分配给elasticsearch。同时也要允许elasticsearch的进程可以锁住物理内存,linux下可以通过“ulimit -l”命令查看最大锁定内存地址空间(memlock)是不是unlimited,这个参数在之前系统调优的时候已经设置过了。
(8)network.host: 0.0.0.0
此配置项是network.publish\_host和network.bind\_host两个配置项的集合,network.bind\_host用来设置elasticsearch提供服务的IP地址,默认值为0.0.0.0,此默认配置不太安全,因为如果服务器有多块网卡(可设置多个IP,可能有内网IP,也可能有外网IP),那么就可以通过外网IP来访问elasticsearch提供的服务,显然,elasticsearch集群在外网访问的话将非常不安全,因此,建议将network.bind\_host设置为内网IP地址比较好。
network.publish\_host用来设置elasticsearch集群中该节点和其它节点间交互通信的IP地址,一般设置为该节点所在的内网IP地址即可。需要保证可以和集群中其它节点进行通信。
在elasticsearch新版本中,增加了network.host配置项,此配置项用来同时设置bind\_host和publish\_host上面两个参数,根据上面的介绍,此值设置为服务器的内网IP地址即可。也就是设置bind\_host和publish\_host为同一个IP地址。
(9)http.port: 9200
设置elasticsearch对外提供服务的http端口,默认为9200。其实,还有一个端口配置选项transport.tcp.port,此配置项用来设置节点间交互通信的TCP端口,默认是9300。
(10)discovery.zen.minimum\_master\_nodes: 1
配置当前集群中最少的master节点数,默认为1,也就是说,elasticsearch集群中master节点数不能低于此值,如果低于此值,elasticsearch集群将停止运行。在三个以上节点的集群环境中,建议配置大一点的值,推荐2至4个为好。
(11)discovery.zen.ping.unicast.hosts:
\["172.16.213.37:9300","172.16.213.78:9300"\]
设置集群中master节点的初始列表,可以通过这些节点来自动发现新加入集群的节点。这里需要注意,master节点初始列表中对应的端口是9300。即为集群交互通信端口。
## 2.6 启动elasticsearch
启动elasticsearch服务需要在一个普通用户下完成,如果通过root用户启动elasticsearch的话,可能会收到如下错误:
```css
java.lang.RuntimeException: can not run elasticsearch as root
at org.elasticsearch.bootstrap.Bootstrap.initializeNatives(Bootstrap.java:106)
~[elasticsearch-6.3.2.jar:6.3.2]
at org.elasticsearch.bootstrap.Bootstrap.setup(Bootstrap.java:195)
~[elasticsearch-6.3.2.jar:6.3.2]
at org.elasticsearch.bootstrap.Bootstrap.init(Bootstrap.java:342)
[elasticsearch-6.3.2.jar:6.3.2]
```
这是出于系统安全考虑,elasticsearch服务必须通过普通用户来启动,在上面第一小节中,已经创建了一个普通用户elasticsearch,直接切换到这个用户下启动elasticsearch集群即可。分别登录到server1、server2和server3三台主机上,执行如下操作:
```ruby
[root@localhost ~]# su - elasticsearch
[elasticsearch@localhost ~]$ cd /usr/local/elasticsearch/
[elasticsearch@localhost elasticsearch]$ bin/elasticsearch -d
```
其中,“-d”参数的意思是将elasticsearch放到后台运行。
## 2.7 验证elasticsearch集群的正确性
将所有elasticsearch节点的服务启动后,在任意一个节点执行如下命令:
\[root@localhost ~\]# curl [http://172.16.213.77:9200](http://172.16.213.77:9200/)
如果返回类似如下结果,表示elasticsearch集群运行正常。
![](assets/1593327194-2864ac7abb26cd987aa9d9afd082a867.png)
至此,elasticsearch集群安装、配置完成。
@@ -0,0 +1,215 @@
## 3.Elasticsearch的图形化界面工具的使用以及ZooKeeper集群的实现
## 1.安装Head插件
## 1.1 安装Head插件
Head插件是Elasticsearch的图形化界面工具,通过此插件可以很方便的对数据进行增删改查等数据交互操作。在Elasticsearch5.x版本以后,head插件已经是一个独立的Web App了,所以不需要和Elasticsearch进行集成。可以将Head插件安装到任何一台机器上,这里将Head插件安装到172.16.213.37(server1)机器上,读者可以从https://github.com/mobz/elasticsearch-head 下载此插件。
由于Head插件本质上是一个Node.js的工程,因此需要安装Node.js,使用npm工具来安装依赖的包。这里简单说下Node.js和NPM,作为知识的铺垫。
Node.js是一个Javascript运行环境,一个新兴的前端框架,用于方便地搭建响应速度快、易于扩展的网络应用。
NPM的全称是Node Package Manager,是一个Node.JS包管理和分发工具,它定义了包依赖关系标准,并提供了用于JavaScript开发所需要的各种常见第三方框架的下载。
在Centos7.x系统上,可以直接通过yum在线安装Node.js和NPM工具(前提是你的机器要能上网),操作如下:
```coffeescript
[root\@localhost \~]\# yum install -y nodejs npm
```
这里我们通过git克隆方式下载Head插件,所以还需要安装一个git命令工具,执行如下命令即可:
```coffeescript
[root\@localhost \~]\# yum install -y git
```
接着,开始安装head插件,这里将head插件安装到/usr/local目录下,操作过程如下:
```perl
[root\@localhost \~]\# cd /usr/local
[root\@localhost local]\# git clone git://github.com/mobz/elasticsearch-head.git
[root\@localhost local]\# npm config set registry
http://registry.npm.taobao.org/
[root\@localhost local]\# cd elasticsearch-head
[root\@localhost elasticsearch-head]\# npm install
```
其中,第一步是通过git命令从github克隆head插件程序,第二步是修改源地址为淘宝NPM镜像,因为默认NPM的官方源为 [https://registry.npmjs.org/](https://registry.npmjs.org/) , 国内下载速度回很慢,所以建议切换到淘宝的NPM镜像站点。第四步是安装head插件所需的库和第三方框架。
克隆下来的head插件程序目录名为elasticsearch-head,进入此目录,修改配置文件 /usr/local/elasticsearch-head/\_site/app.js ,找到如下内容:
```bash
this.base_uri = this.config.base_uri || this.prefs.get("app-base_uri") || "http://localhost:9200";
```
将其中的
```plain
http://localhost:9200
```
修改为elasticsearch集群中任意一台主机的IP地址,这里修改为
```plain
<http://172.16.213.78:9200>
```
表示的意思是head插件将通过172.16.213.78(server3)访问elasticsearch集群。
注意,访问elasticsearch集群中的任意一个节点,都能获取集群的所有信息。
## 1.2 修改elasticsearch配置
在上面的配置中,将head插件访问集群的地址配置为172.16.213.78(server3)主机,下面还需要修改此主机上elasticsearch的配置,添加跨域访问支持。
修改elasticsearch配置文件,允许head插件跨域访问elasticsearch,在elasticsearch.yml文件最后添加如下内容:
```bash
http.cors.enabled: true
http.cors.allow-origin: "*"
```
其中,http.cors.enabled表示开启跨域访问支持,此值默认为false,http.cors.allow-origin表示跨域访问允许的域名地址,可以使用正则表达式,这里的“\*”表示允许所有域名访问。
## 1.3 启动head插件服务
所有配置完成之后,就可以启动插件服务了,执行如下操作:
```bash
[root\@localhost \~]# cd /usr/local/elasticsearch-head
[root\@localhost elasticsearch-head]# npm run start
```
head插件服务启动之后,默认的访问端口为9100,直接访问http://172.16.213.37:9100 就可以访问head插件了。
下图是配置完成后的一个head插件截图:
![](assets/1593327223-1ad5b6bb56aff0e4ced9e9a133d623a0.png)
下面简单说明一下head插件的使用方法和技巧。
首先可以看到,elasticsearch集群有server1、server2和server3三个节点,其中,server3是目前的主节点。点击图上的信息按钮,可查看节点详细信息。
其次,从这个页面上可以看到elasticsearch基本的分片的信息,比如主分片、副本分片等等,以及多少可用分片。由于在elasticsearch配置中设置了5个分片,一个副本分片,因此可以看到每个索引都有10个分片,每个分片都用0、1、2、3、4等数字加方框表示,其中,粗体方框是主分片,细体方框是副本分片。
图中esbigdata是集群的名称,后面的“集群健康值”通过不同的颜色表示集群的健康状态:其中,绿色表示主分片和副本分片都可用;黄色表示只有主分片可用,没有副本分片;红色表示主分片中的部分索引不可用,但是某些索引还可以继续访问。正常情况下都显示绿色。
在索引页面可以创建索引,并且可以设置分片的数量,副本的数量等等。点击创建索引按钮,即可创建一个索引,例如:
![](assets/1593327223-f34c105278df63aec1f28a84449c6798.png)
在数据浏览页面可以看到每个索引的基本信息,比如都有什么字段,存储的内容等等。
在基本查询页面可以拼接一些基本的查询。
在复合查询页面,不仅仅可以做查询,还可以执行PUT、GET、
DELETE等curl命令,所有需要通过curl执行的rest请求,都可以在这里执行。
例如,要查询一个索引的数据结构,可执行如下图的操作即可:
![](assets/1593327223-d0255c30406ab4850da2769d995a5783.png)
下面是删除索引数据,右边结果显示true的话,表示删除成功。
![](assets/1593327223-10ddb0af8c442932c972c3919d50837b.png)
## 2.安装并配置ZooKeeper分布式集群
Zookeeper的安装和配置十分简单, 可以配置成单机模式,
也可以配置成集群模式,这里将Zookeeper配置为集群模式。
## 2.1 ZooKeeper集群环境说明
对于集群模式下的ZooKeeper部署,官方建议至少要三台服务器,关于服务器的数量,推荐是奇数个(3、5、7、9等等),以实现ZooKeeper集群的高可用,这里使用三台服务器进行部署,服务器信息如下表所示:
| 节点名称 | IP地址 | 安装软件 |
| --- | --- | --- |
| kafkazk1 | 172.16.213.51 | JDK1.8、zookeeper-3.4.11 |
| kafkazk2 | 172.16.213.75 | JDK1.8、zookeeper-3.4.11 |
| kafkazk3 | 172.16.213.109 | JDK1.8、zookeeper-3.4.11 |
由于是部署集群模式的zookeeper,因此下面的操作需要在每个集群节点都执行一遍。
## 2.2 下载与安装ZooKeeper
ZooKeeper是用Java编写的,需要安装Java运行环境,这在前面章节已经介绍过了,这里不再介绍,读者可以从zookeeper官网https://zookeeper.apache.org/ 获取zookeeper安装包,这里安装的版本是zookeeper-3.4.11.tar.gz。将下载下来的安装包直接解压到一个路径下即可完成zookeeper的安装,这里统一将zookeeper安装到/usr/local目录下,基本操作过程如下:
```coffeescript
[root\@localhost \~]\# tar -zxvf zookeeper-3.4.11.tar.gz -C /usr/local
[root\@localhost \~]\# mv /usr/local/zookeeper-3.4.11 /usr/local/zookeeper
```
这里我们将zookeeper安装到了/usr/local目录下。
## 2.3 配置ZooKeeper
zookeeper安装到了/usr/local目录下,因此,zookeeper的配置模板文为/usr/local/zookeeper/conf/zoo\_sample.cfg,拷贝zoo\_sample.cfg并重命名为zoo.cfg,重点配置如下内容:
```makefile
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/data/zookeeper
clientPort=2181
server.1=172.16.213.51:2888:3888
server.2=172.16.213.109:2888:3888
server.3=172.16.213.75:2888:3888
```
其中,每个配置项含义如下:
> \-tickTime:zookeeper使用的基本时间度量单位,以毫秒为单位,它用来控制心跳和超时。2000表示2tickTime。更低的tickTime值可以更快地发现超时问题。
> \-initLimit:这个配置项是用来配置Zookeeper集群中Follower服务器初始化连接到Leader时,最长能忍受多少个心跳时间间隔数(也就是tickTime),这里设置为10个,即10\*2000=20秒。
> \-syncLimit:这个配置项标识Leader与Follower之间发送消息,请求和应答时间长度最长不能超过多少个tickTime的时间长度,这里总的时间长度是5\*2000=10秒。
> \-dataDir:必须配置项,用于配置存储快照文件的目录。需要事先创建好这个目录,如果没有配置dataLogDir,那么事务日志也会存储在此目录。
> \-clientPort:zookeeper服务进程监听的TCP端口,默认情况下,服务端会监听2181端口。
> \-server.A=B:C:D:其中A是一个数字,表示这是第几个服务器;B是这个服务器的IP地址;C表示的是这个服务器与集群中的Leader服务器通信的端口;D表示如果集群中的Leader服务器宕机了,需要一个端口来重新进行选举,选出一个新的Leader,而这个端口就是用来执行选举时服务器相互通信的端口。
除了修改zoo.cfg配置文件外,集群模式下还要配置一个文件myid,这个文件需要放在dataDir配置项指定的目录下,这个文件里面只有一个数字,如果要写入1,表示第一个服务器,与zoo.cfg文本中的server.1中的1对应,以此类推,在集群的第二个服务器zoo.cfg配置文件中dataDir配置项指定的目录下创建myid文件,写入2,这个2与zoo.cfg文本中的server.2中的2对应。本文中Zookeepe集群有三台服务器,可按照任意顺序,依次在每个服务器上创建myid文件写入1、2、3一个数字即可。Zookeeper在启动时会读取这个文件,得到里面的数据与zoo.cfg里面的配置信息比较,从而判断每个zookeeper server的对应关系。
为了保证zookeeper集群配置的规范性,建议将zookeeper集群中每台服务器的安装和配置文件路径都保存一致。例如将zookeeper统一安装到/usr/local目录下,配置文件统一为/usr/local/zookeeper/conf/zoo.cfg等。
## 2.4 启动zookeeper集群
Zookeeper集群所有节点配置完成后,就可以启动Zookeeper服务了,在三个节点依次执行如下命令,启动Zookeeper服务:
```coffeescript
[root\@localhost \~]\# cd /usr/local/zookeeper/bin
[root\@localhost bin]\# ./zkServer.sh start
[root\@localhost kafka]\# jps
23097 QuorumPeerMain
```
Zookeeper启动后,通过jps命令(jdk内置命令)可以看到有一个QuorumPeerMain标识,这个就是Zookeeper启动的进程,前面的数字是Zookeeper进程的PID。
Zookeeper启动后,在执行启动命令的当前目录下会生成一个zookeeper.out文件,这个就是Zookeeper的运行日志,可以通过此文件查看Zookeeper运行状态。
有时候为了启动Zookeeper方面,也可以添加zookeeper环境变量到系统的/etc/profile中,这样,在任意路径都可以执行“zkServer.sh start”命令了,添加环境变量的内容为:
```plain
export ZOOKEEPER_HOME=/usr/local/zookeeper
export PATH=\$PATH:\$ZOOKEEPER_HOME/bin
```
至此,zookeeper集群安装、配置完成。
@版权声明:51CTO独家出品,未经允许不能转载,否则追究法律责任
---------------------------------------------------
原网址: [访问](https://blog.51cto.com/cloumn/blog/243)
创建于: 2020-06-28 14:53:43
目录: default
标签: `blog.51cto.com`
@@ -0,0 +1,317 @@
## 4.Kafka分布式集群构建以及使用FileBeat收集日志
## 1.kafka分布式集群构建
## 1.1 下载与安装Kafka
Kafka也需要安装Java运行环境,这在前面章节已经介绍过了,这里不再介绍,读者可以从kafka官网https://kafka.apache.org/downloads 获取kafka安装包,这里推荐的版本是kafka\_2.10-0.10.0.1.tgz,不建议使用最新的版本。将下载下来的安装包直接解压到一个路径下即可完成kafka的安装,这里统一将kafka安装到/usr/local目录下,基本操作过程如下:
```coffeescript
[root@localhost ~]# tar -zxvf kafka_2.10-0.10.0.1.tgz -C /usr/local
[root@localhost ~]# mv /usr/local/kafka_2.10-0.10.0.1 /usr/local/kafka
```
这里我们将kafka安装到了/usr/local目录下。
## 1.2 配置kafka集群
这里我们将kafka安装到/usr/local目录下,因此,kafka的主配置文件为/usr/local/kafka/config/server.properties,这里以节点kafkazk1为例,重点介绍一些常用配置项的含义:
```plain
broker.id=1
listeners=PLAINTEXT://172.16.213.51:9092
log.dirs=/usr/local/kafka/logs
num.partitions=6
log.retention.hours=60
log.segment.bytes=1073741824
zookeeper.connect=172.16.213.51:2181,172.16.213.75:2181,172.16.213.109:2181
auto.create.topics.enable=true
delete.topic.enable=true
```
其中,每个配置项含义如下:
\-broker.id:每一个broker在集群中的唯一表示,要求是正数。当该服务器的IP地址发生改变时,broker.id没有变化,则不会影响consumers的消息情况。
\-listeners:设置kafka的监听地址与端口,可以将监听地址设置为主机名或IP地址,这里将监听地址设置为IP地址。如果设置为主机名,那么还需要将主机名与IP的对应关系本地解析到系统的/etc/hosts文件中。
\-log.dirs:这个参数用于配置kafka保存数据的位置,kafka中所有的消息都会存在这个目录下。可以通过逗号来指定多个路径,kafka会根据最少被使用的原则选择目录分配新的parition。需要注意的是,kafka在分配parition的时候选择的规则不是按照磁盘的空间大小来定的,而是根据分配的parition的个数多小而定。
\-num.partitions:这个参数用于设置新创建的topic有多少个分区,可以根据消费者实际情况配置,配置过小会影响消费性能。这里配置6个。
\-log.retention.hours:这个参数用于配置kafka中消息保存的时间,还支持log.retention.minutes和log.retention.ms配置项。这三个参数都会控制删除过期数据的时间,推荐还是使用log.retention.ms。如果多个同时设置,那么会选择最小的那个。
\-log.segment.bytes:配置partition中每个segment数据文件的大小,默认是1GB,超过这个大小会自动创建一个新的segment file。
\-zookeeper.connect:这个参数用于指定zookeeper所在的地址,它存储了broker的元信息。这个值可以通过逗号设置多个值,每个值的格式均为:hostname:port/path,其中每个部分的含义如下:
> hostname:表示zookeeper服务器的主机名或者IP地址,这里设置为IP地址。
> port: 表示是zookeeper服务器监听连接的端口号。
> /path:表示kafka在zookeeper上的根目录。如果不设置,会使用根目录。
\-auto.create.topics.enable:这个参数用于设置是否自动创建topic,如果请求一个topic时发现还没有创建,kafka会在broker上自动创建一个topic,如果需要严格的控制topic的创建,那么可以设置auto.create.topics.enable为false,禁止自动创建topic。
\-delete.topic.enable:在0.8.2版本之后,Kafka提供了删除topic的功能,但是默认并不会直接将topic数据物理删除。如果要从物理上删除(即删除topic后,数据文件也会一同删除),就需要设置此配置项为true。
## 1.3 启动kafka集群
在启动kafka集群前,需要确保ZooKeeper集群已经正常启动。接着,依次在kafka各个节点上执行如下命令即可:
```coffeescript
[root@localhost ~]# cd /usr/local/kafka
[root@localhost kafka]# nohup bin/kafka-server-start.sh
config/server.properties &
[root@localhost kafka]# jps
21840 Kafka
15593 Jps
15789 QuorumPeerMain
```
这里将kafka放到后台运行,启动后,会在启动kafka的当前目录下生成一个nohup.out文件,可通过此文件查看kafka的启动和运行状态。通过jps指令,可以看到有个Kafka标识,这是kafka进程成功启动的标志。
## 1.4 kafka集群基本命令操作
kafka提供了多个命令用于查看、创建、修改、删除topic信息,也可以通过命令测试如何生产消息、消费消息等,这些命令位于kafka安装目录的bin目录下,这里是/usr/local/kafka/bin。登录任意一台kafka集群节点,切换到此目录下,即可进行命令操作。下面简单列举了kafka的一些常用命令的使用方法。
(1)显示topic列表
命令执行方式如下图所示:
![](assets/1593327243-8cee1020b24c0492c944ff60c1165b25.png)
其中,“--zookeeper”参数后面跟的是zookeeper集群的主机列表。
具体操作实例如下图所示:
![](assets/1593327243-1e0c6f02f6801e578a08664c38e31ccb.png)
(2)创建一个topic,并指定topic属性(副本数、分区数等)
命令执行方式如下图所示:
![](assets/1593327243-2e71133b291a49c023b7249db6eb4dd3.png)
其中:
> \--create:表示创建一个topic。
> \--replication-factor:表示这个topic的副本数,这里设置为1个。
> \--partitions:指定topic的分区数,一般设置为小于或等于kafka集群节点数即可。
> \--topic:指定要创建的topic的名称。
实例操作如下图所示:
![](assets/1593327243-0495ab5d348f6931cb52de5047d7a849.png)
(3)查看某个topic的状态
命令执行方式如下图所示:
![](assets/1593327243-c90d169516436cd1e088ce5a2c5b8044.png)
这里通过”--describe“选项查看刚刚创建好的testtopic的状态。实例操作如下图所示:
![](assets/1593327243-637a5f7dd14d1882d07c2740f1a755bb.png)
其中:
Partition:表示分区ID,通过输出可以看到,testtopic有三个分区,一个副本,这刚好和我们创建testtopic时指定的配置吻合。
> leader:表示当前负责读写的Leader broker。
> replicas:表示当前分区的所有副本对应的broker列表。
> isr:表示处于活动状态的broker。
(4)生产消息
命令执行方式如下图所示:
![](assets/1593327243-a722fe0ece166247c45a0fbca549b06c.png)
这里需要注意,”--broker-list“后面跟的内容是kafka集群的ip和端口,而非zookeeper集群的地址。
实例操作如下图所示:
![](assets/1593327243-12d8d744bdc8fced983d3d0ca2fc2978.png)
当输入这条命令后,光标处于可写状态,接着就可以写入一些测试数据,每行一条,这里输入的内容为:
hello kafka
kafka生产消息测试
我是数据生产者
(5)消费消息
命令执行方式如下图所示:
![](assets/1593327243-f9140543e407b97619046ecdfd0ed866.png)
紧接着上面生产消息的步骤,再登录任意一台kafka集群节点,执行消费消息的命令,结果如下图所示:
![](assets/1593327243-c6483ba31e8da90d977d54ed31e22400.png)
可以看到,在第四步中,输入的消息在这里原样输出了,这样就完成了消息的消费。
(6)删除topic
命令执行方式如下图所示:
![](assets/1593327243-d7bbeb1abecd1a4a37e8ffbd08be9b81.png)
注意这里的“--delete”选项,用来删除一个指定的topic。
删除topic后会有个提示,意思是说如果kafka没有设置delete.topic.enable为true的话,那么仅仅是个标记删除,而非真正的删除。
# 2.使用filebeat收集日志数据
## 2.1 什么是Filebeat
Filebeat是一个开源的文本日志收集器,它是elastic公司Beats数据采集产品的一个子产品,采用go语言开发,一般安装在业务服务器上作为代理来监测日志目录或特定的日志文件,并把它们发送到logstash、elasticsearch、redis或Kafka等。可以在官方地址https://www.elastic.co/downloads/beats 下载各个版本的Filebeat。
## 2.2 Filebeat架构与运行原理
Filebeat是一个轻量级的日志监测、传输工具,它最大的特点是性能稳定、配置简单、占用系统资源很少。这也是强烈推荐Filebeat的原因。下图是官方给出的Filebeat架构图:
![](assets/1593327243-2f347fd97c4c402662e11445fb0be2cc.png)
从图中可以看出,Filebeat主要由两个组件构成:
prospector(探测器)和harvester(收集器)。这两类组件一起协作完成Filebeat的工作。
其中,Harvester负责进行单个文件的内容收集,在运行过程中,每一个Harvester会对一个文件逐行进行内容读取,并且把读写到的内容发送到配置的output中。当Harvester开始进行文件的读取后,将会负责这个文件的打开和关闭操作,因此,在Harvester运行过程中,文件都处于打开状态。如果在收集过程中,删除了这个文件或者是对文件进行了重命名,Filebeat依然会继续对这个文件进行读取,这时候将会一直占用着文件所对应的磁盘空间,直到Harvester关闭。
Prospector负责管理Harvster,它会找到所有需要进行读取的数据源。然后交给Harvster进行内容收集,如果input type配置的是log类型,Prospector将会去配置路径下查找所有能匹配上的文件,然后为每一个文件创建一个Harvster。
综上所述,filebeat的工作流程为:当开启filebeat程序的时候,它会启动一个或多个探测器(prospector)去检测指定的日志目录或文件,对于探测器找出的每一个日志文件,filebeat会启动收集进程(harvester),每一个收集进程读取一个日志文件的内容,然后将这些日志数据发送到后台处理程序(spooler),后台处理程序会集合这些事件,最后发送集合的数据到output指定的目的地。
## 3.安装并配置Filebeat
## 3.1 为什么要使用filebeat
Logstash功能虽然强大,但是它依赖java、在数据量大的时候,Logstash进程会消耗过多的系统资源,这将严重影响业务系统的性能,而filebeat就是一个完美的替代者,filebeat是Beat成员之一,基于Go语言,没有任何依赖,配置文件简单,格式明了,同时,filebeat比logstash更加轻量级,所以占用系统资源极少,非常适合安装在生产机器上。这就是推荐使用filebeat来作为日志收集软件的原因。
## 3.2 下载与安装filebeat
由于filebeat基于go语言开发,无其他任何依赖,因而安装非常简单,读者可以从elastic官网https://www.elastic.co/downloads/beats/filebeat 获取filebeat安装包,这里下载的版本是filebeat-6.3.2-linux-x86\_64.tar.gz。将下载下来的安装包直接解压到一个路径下即可完成filebeat的安装。根据前面的规划,将filebeat安装到filebeatserver主机(172.16.213.157)上,这里设定将filebeat安装到/usr/local目录下,基本操作过程如下:
```perl
[root@filebeatserver ~]# tar -zxvf filebeat-6.3.2-linux-x86_64.tar.gz -C
/usr/local
[root@filebeatserver ~]# mv /usr/local/filebeat-6.3.2-linux-x86_64
/usr/local/filebeat
```
这里我们将filebeat就安装到了/usr/local目录下。
## 3.3 配置filebeat
这里我们将filebeat安装到/usr/local目录下,因此,filebeat的配置文件目录为/usr/local/filebeat/filebeat.yml,filebeat配置非常简单,这里仅列出常用的配置项,内容如下:
```plain
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/messages
- /var/log/secure
fields:
log_topic: osmessages
name: "172.16.213.157"
output.kafka:
enabled: true
hosts: ["172.16.213.51:9092", "172.16.213.75:9092", "172.16.213.109:9092"]
version: "0.10"
topic: '%{[fields][log_topic]}'
partition.round_robin:
reachable_only: true
worker: 2
required_acks: 1
compression: gzip
max_message_bytes: 10000000
logging.level: info
```
每个配置项的含义介绍如下:
filebeat.inputs:用于定义数据原型。
type:指定数据的输入类型,这里是log,即日志,是默认值,还可以指定为stdin,即标准输入。
enabled: true:启用手工配置filebeat,而不是采用模块方式配置filebeat。filebeat6.3版本后可以通过模块方式快速配置filebeat,也可以手工配置,默认是模块方式配置。
paths:用于指定要监控的日志文件,可以指定一个完整路径的文件,也可以是一个模糊匹配格式,例如:
```ruby
- /data/nginx/logs/nginx_*.log,该配置表示将获取/data/nginx/logs目录下的所有以.log结尾的文件,注意这里有个破折号“-”,要在paths配置项基础上进行缩进,不然启动filebeat会报错,另外破折号前面不能有tab缩进,建议通过空格方式缩进。
- /var/log/*.log,该配置表示将获取/var/log目录的所有子目录中以”.log”结尾的文件,而不会去查找/var/log目录下以”.log”结尾的文件。
```
name: 设置filebeat收集的日志中对应主机的名字,如果配置为空,则使用该服务器的主机名。这里设置为IP,便于区分多台主机的日志信息。
output.kafka:filebeat支持多种输出,支持向kafka,logstash,elasticsearch输出数据,这里的设置是将数据输出到kafka。
enabled:表明这个启动这个模块。
host: 指定输出数据到kafka集群上,地址为kafka集群IP加端口号。
topic:指定要发送数据给kafka集群的哪个topic,若指定的topic不存在,则会自动创建此topic。注意topic的写法,在filebeat6.x之前版本是通过“%{\[type\]}”来自动获取document\_type配置项的值。而在filebeat6.x之后版本是通过'%{\[fields\]\[log\_topic\]}'或者'%{\[fields.log\_topic\]}'来获取日志分类的。
logging.level:定义filebeat的日志输出级别,有critical、error、warning、info、debug五种级别可选,在调试的时候可选择info模式。
## 3.4 启动filebeat收集日志
所有配置完成之后,就可以启动filebeat,开启收集日志进程了,启动方式如下:
```coffeescript
[root@filebeatserver ~]# cd /usr/local/filebeat
[root@filebeatserver filebeat]# nohup ./filebeat -e -c filebeat.yml &
```
这样,就把filebeat进程放到后台运行起来了。启动后,在当前目录下会生成一个nohup.out文件,可以查看filebeat启动日志和运行状态。
## 3.5 filebeat输出信息格式解读
这里以操作系统中/var/log/secure文件的日志格式为例,选取一个SSH登录系统失败的日志,内容如下:
```css
Aug 16 18:25:08 localhost sshd[13053]: Failed password for root from 172.16.213.37 port 49560 ssh2
```
filebeat接收到/var/log/secure日志后,会将上面日志发送到kafka集群,在kafka任意一个节点上,消费输出日志内容如下:
```json
{"@timestamp":"2018-08-16T11:27:48.755Z",
"@metadata":{"beat":"filebeat","type":"doc","version":"6.3.2","topic":"osmessages"},
"beat":{"name":"filebeatserver","hostname":"filebeatserver","version":"6.3.2"},
"host":{"name":"filebeatserver"},
"source":"/var/log/secure",
"offset":11326,
"message":"Aug 16 18:25:08 localhost sshd[13053]: Failed password for root from 172.16.213.37 port 49560 ssh2",
"prospector":{"type":"log"},
"input":{"type":"log"},
"fields":{"log_topic":"osmessages"}
}
```
从这个输出可以看到,输出日志被修改成了JSON格式,日志总共分为七个字段,分别是"@timestamp"、"@metadata''、"beat"、"input"、"prospector"、"host"、"message"、"offset"、"source"和"fileds"字段,每个字段含义如下:
```coffeescript
@timestamp:时间字段,表示读取到该行内容的时间。
@metadata:元数据字段,此字段只有是跟Logstash进行交互使用。
beat:beat属性信息,包含beat所在的主机名、beat版本等信息。
host: 主机名字段,输出主机名,如果没主机名,输出主机对应的IP。
source: 表示监控的日志文件的全路径。
offset: 表示该行日志的偏移量。
message: 表示真正的日志内容。
prospector:filebeat对应的消息类型。
input:日志输入的类型,可以有多种输入类型,例如Log、Stdin、redis、Docker、TCP/UDP等
fields:topic对应的消息字段或自定义增加的字段。
```
通过filebeat接收到的内容,默认增加了不少字段,但是有些字段对数据分析来说没有太大用处,所以有时候需要删除这些没用的字段,在filebeat配置文件中添加如下配置,即可删除不需要的字段:
```makefile
processors:
- drop_fields:
fields: ["beat", "input", "source", "offset"]
```
这个设置表示删除"beat"、"input"、"source"、"offset" 四个字段,其中, @timestamp 和@metadata 字段是不能删除的。做完这个设置后,再次查看kafka中的输出日志,已经不再输出这四个字段信息了。
@@ -0,0 +1,240 @@
## 5.通过Logstash过滤、分析日志数据
## 1.Logstash日志收集与分析系统
## 1.1 下载与安装Logstash
Logstash需要安装Java运行环境,这在前面章节已经介绍过了,这里不再介绍,读者可以从elastic官网https://www.elastic.co/downloads/logstash 获取logstash安装包,这里下载的版本是logstash-6.3.2.tar.gz。将下载下来的安装包直接解压到一个路径下即可完成logstash的安装。根据前面的规划,将logstash安装到logstashserver主机(172.16.213.120)上,这里统一将logstash安装到/usr/local目录下,基本操作过程如下:
```coffeescript
[root@logstashserver ~]# tar -zxvf logstash-6.3.2.tar.gz -C /usr/local
[root@logstashserver ~]# mv /usr/local/logstash-6.3.2 /usr/local/logstash
```
这里我们将logstash安装到了/usr/local目录下。
## 1.2 Logstash是怎么工作的
Logstash是一个开源的、服务端的数据处理pipeline(管道),它可以接收多个源的数据、然后对它们进行转换、最终将它们发送到指定类型的目的地。Logstash是通过插件机制实现各种功能的,读者可以在https://github.com/logstash-plugins 下载各种功能的插件,也可以自行编写插件。
Logstash实现的功能主要分为接收数据、解析过滤并转换数据、输出数据三个部分,对应的插件依次是input插件、filter插件、output插件,其中,filter插件是可选的,其它两个是必须插件。也就是说在一个完整的Logstash配置文件中,必须有input插件和output插件。
## 1.3 常用的input
input插件主要用于接收数据,Logstash支持接收多种数据源,常用的有如下几种:
> \-file:读取一个文件,这个读取功能有点类似于linux下面的tail命令,一行一行的实时读取。
> \-syslog: 监听系统514端口的syslog messages,并使用RFC3164格式进行解析。
> \-redis: Logstash可以从redis服务器读取数据,此时redis类似于一个消息缓存组件。
> \-kafka:Logstash也可以从kafka集群中读取数据,kafka加Logstash的架构一般用在数据量较大的业务场景,kafka可用作数据的缓冲和存储。
> \-filebeat:filebeat是一个文本日志收集器,性能稳定,并且占用系统资源很少,Logstash可以接收filebeat发送过来的数据。
## 1.4 常用的filter
filter插件主要用于数据的过滤、解析和格式化,也就是将非结构化的数据解析成结构化的、可查询的标准化数据。常见的filter插件有如下几个:
> \-grok:grok是Logstash最重要的插件,可解析并结构化任意数据,支持正则表达式,并提供了很多内置的规则和模板可供使用。此插件使用最多,但也最复杂。
> \-mutate:此插件提供了丰富的基础类型数据处理能力。包括类型转换,字符串处理和字段处理等。
> \-date:此插件可以用来转换你的日志记录中的时间字符串。
> \-GeoIP:此插件可以根据IP地址提供对应的地域信息,包括国别,省市,经纬度等,对于可视化地图和区域统计非常有用。
## 1.5 常用的output
output插件用于数据的输出,一个Logstash事件可以穿过多个output,直到所有的output处理完毕,这个事件才算结束。输出插件常见的有如下几种:
> \-elasticsearch:发送数据到elasticsearch。
> \-file:发送数据到文件中。
> \-redis:发送数据到redis中,从这里可以看出,redis插件既可以用在input插件中,也可以用在output插件中。
> \-kafka:发送数据到kafka中,与redis插件类似,此插件也可以用在Logstash的输入和输出插件中。
## 1.6 Logstash配置文件入门
这里我们将logstash安装到/usr/local目录下,因此,logstash的配置文件目录为/usr/local/logstash/config/,其中,jvm.options是设置JVM内存资源的配置文件,logstash.yml是logstash全局属性配置文件,一般无需修改,另外还需要自己创建一个logstash事件配置文件,这里重点介绍下logstash事件配置文件的编写方法和使用方式。
在介绍Logstash配置之前,先来认识一下logstash是如何实现输入和输出的。
Logstash提供了一个shell脚本/usr/local/logstash/bin/logstash,可以方便快速的启动一个logstash进程,在Linux命令行下,运行如下命令启动Logstash进程:
```coffeescript
[root@logstashserver ~]# cd /usr/local/logstash/
[root@logstashserver logstash]# bin/logstash -e 'input{stdin{}} output{stdout{codec=>rubydebug}}'
```
首先解释下这条命令的含义:
> \-e代表执行的意思。
> input即输入的意思,input里面即是输入的方式,这里选择了stdin,就是标准输入(从终端输入)。
> output即输出的意思,output里面是输出的方式,这里选择了stdout,就是标准输出(输出到终端)。
> 这里的codec是个插件,表明格式。这里放在stdout中,表示输出的格式,rubydebug是专门用来做测试的格式,一般用来在终端输出JSON格式。
接着,在终端输入信息。这里我们输入"Hello World",按回车,马上就会有返回结果,内容如下:
```php
{
"@version" => "1",
"host" => "logstashserver",
"@timestamp" => 2018-07-26T10:01:45.665Z,
"message" => "Hello World"
}
```
这就是logstash的输出格式。Logstash在输出内容中会给事件添加一些额外信息。比如"@version"、"host"、"@timestamp"都是新增的字段,而最重要的是@timestamp,用来标记事件的发生时间。由于这个字段涉及到Logstash内部流转,如果给一个字符串字段重命名为@timestamp的话,Logstash就会直接报错。另外,也不能删除这个字段。
在logstash的输出中,常见的字段还有type,表示事件的唯一类型、tags,表示事件的某方面属性,我们可以随意给事件添加字段或者从事件里删除字段。
在执行上面的命令后,可以看到,我们输入什么内容,logstash就会按照上面的格式输出什么内容。使用CTRL-C命令可以退出运行的Logstash事件。
使用-e参数在命令行中指定配置是很常用的方式,但是如果logstash需要配置更多规则的话,就必须把配置固化到文件里,这就是logstash事件配置文件,如果把上面在命令行执行的logstash命令,写到一个配置文件logstash-simple.conf中,就变成如下内容:
```plain
input {
stdin { }
}
output {
stdout { codec => rubydebug }
}
```
这就是最简单的Logstash事件配置文件。此时,可以使用logstash的-f参数来读取配置文件,然后启动logstash进程,操作如下:
```coffeescript
[root@logstashserver logstash]# bin/logstash -f logstash-simple.conf
```
通过这种方式也可以启动logstash进程,不过这种方式启动的进程是在前台运行的,要放到后台运行,可通过nohup命令实现,操作如下:
```coffeescript
[root@logstashserver logstash]# nohup bin/logstash -f logstash-simple.conf &
```
这样,logstash进程就放到了后台运行了,在当前目录会生成一个nohup.out文件,可通过此文件查看logstash进程的启动状态。
## 1.7 logstash事件文件配置实例
下面再看另一个logstash事件配置文件,内容如下:
```makefile
input {
file {
path => "/var/log/messages"
}
}
output {
stdout {
codec => rubydebug
}
}
```
首先看input插件,这里定义了input的输入源为file,然后指定了文件的路径为/var/log/messages,也就是将此文件的内容作为输入源,这里的path属性是必填配置,后面的路径必须是绝对路径,不能是相对路径。如果需要监控多个文件,可以通过逗号分隔即可,例如:
```plain
path => ["/var/log/*.log","/var/log/message","/var/log/secure"]
```
对于output插件,这里仍然采用rubydebug的JSON输出格式,这对于调试logstash输出信息是否正常非常有用。
将上面的配置文件内容保存为logstash\_in\_stdout.conf,然后启动一个logstash进程,执行如下命令:
```coffeescript
[root\@logstashserver logstash]# nohup bin/logstash -f logstash_in_stdout.conf &
```
接着开始进行输入、输出测试,这里设定/var/log/messages的输入内容为如下信息(其实就是执行“systemctl stop nginx”命令后/var/log/messages的输出内容):
```css
Jan 29 16:09:12 logstashserver systemd: Stopping The nginx HTTP and reverse
proxy server...
Jan 29 16:09:12 logstashserver systemd: Stopped The nginx HTTP and reverse proxy
server.
```
然后查看logstash的输出信息,可以看到内容如下:
```php
{
"@version" => "1",
"host" => " logstashserver",
"path" => "/var/log/messages",
"@timestamp" => 2018-01-29T08:09:12.701Z,
"message" => "Jan 29 16:09:12 logstashserver systemd: Stopping The nginx HTTP
and reverse proxy server..."
}
{
"@version" => "1",
"host" => " logstashserver",
"path" => "/var/log/messages",
"@timestamp" => 2018-01-29T08:09:12.701Z,
"message" => "Jan 29 16:09:12 logstashserver systemd: Stopped The nginx HTTP
and reverse proxy server."
}
```
这就是json格式的输出内容,可以看到,输入的内容放到了"message"字段中保持原样输出了,并且还增加了四个字段,这四个字段是logstash自动添加上去的。
通过这个输出可知,上面的配置文件没有问题,数据可以正常输出,那么,接着我们把logstash\_in\_stdout.conf文件稍加修改,变成另外一个事件配置文件logstash\_in\_kafka.conf,内容如下:
```makefile
input {
file {
path => "/var/log/messages"
}
}
output {
kafka {
bootstrap_servers =>
"172.16.213.51:9092,172.16.213.75:9092,172.16.213.109:9092"
topic_id => "osmessages"
}
}
```
这个配置文件中,输入input仍然是file,重点看输出插件,这里定义了output的输出源为kafka,通过bootstrap\_servers选项指定了kafka集群的IP地址和端口。特别注意这里IP地址的写法,每个IP地址之间通过逗号分隔。另外,output输出中的topic\_id选项,是指定输出到kafka中的哪个topic下,这里是osmessages,如果无此topic,会自动重建topic。
此事件配置文件所表达的含义是:将系统中/var/log/messages文件的内容实时的同步到kafka集群名为osmessages的topic下。
下面将logstash\_in\_kafka.conf事件配置文件启动起来,启动方法,与上面相同,不再介绍,接着,在logstashserver节点的/var/log/messages中生成如下日志:
```coffeescript
[root@logstashserver logstash]# echo "Jan 29 18:23:06 logstashserver
sshd[15895]: Server listening on :: port 22." >> /var/log/messages
```
然后,选择任一个kafka集群节点,执行如下命令:
```coffeescript
[root@kafkazk3 kafka]# bin/kafka-console-consumer.sh --zookeeper 172.16.213.109:2181 --topic osmessages
2018-07-29T10:23:44.752Z logstashserver July 29 18:23:06 logstashserver sshd[15895]: Server listening on :: port 22.
```
这个命令就是在kafka端消费信息,可以看出,输入的信息在kafka消费端输出了,只不过在消息最前面增加了一个时间字段和一个主机字段。
## 1.8 配置logstash作为转发节点
上面对logstash的使用做了一个基础的介绍,现在回到本节介绍的这个案例中,在这个部署架构中,logstash是作为一个二级转发节点使用的,也就是它将kafka作为数据接收源,然后将数据发送到elasticsearch集群中,按照这个需求,新建logstash事件配置文件kafka\_os\_into\_es.conf,内容如下:
```makefile
input {
kafka {
bootstrap_servers =>
"172.16.213.51:9092,172.16.213.75:9092,172.16.213.109:9092"
topics => ["osmessages"]
codec => "json"
}
}
output {
elasticsearch {
hosts => ["172.16.213.37:9200","172.16.213.77:9200","172.16.213.78:9200"]
index => " osmessageslog-%{+YYYY-MM-dd}"
}
}
```
从配置文件可以看到,input接收源变成了kafka,通过bootstrap\_servers和topics两个选项指定了接收源kafka的属性信息,因为logstash从kafka获取到的数据内容为json格式,所以还需要在input字段加入codec=>json来进行解析,接着,output输出类型配置为elasticsearch,并通过hosts选项指定了elasticsearch集群的地址,最后通过index指定了索引的名称,也就是下面要用到的Index pattern。
logstash的事件配置文件这里暂时先介绍这么多,更多功能配置后面会做更深入介绍。
@@ -0,0 +1,100 @@
## 6.通过Kibana展示各种维度数据以及如何调试并验证日志数据流向
# 1、通过Kibana展示各种维度数据
## 1.1、下载与安装Kibana
kibana使用JavaScript语言编写,安装部署十分简单,即下即用,读者可以从elastic官网https://www.elastic.co/cn/downloads/kibana 下载所需的版本,这里需要注意的是Kibana与Elasticsearch的版本必须一致,另外,在安装Kibana时,要确保Elasticsearch、Logstash和kafka已经安装完毕。
这里安装的版本是kibana-6.3.2-linux-x86\_64.tar.gz。将下载下来的安装包直接解压到一个路径下即可完成kibana的安装,根据前面的规划,将kibana安装到server2主机(172.16.213.77)上,然后统一将kibana安装到/usr/local目录下,基本操作过程如下:
```coffeescript
[root@localhost ~]# tar -zxvf kibana-6.3.2-linux-x86_64.tar.gz -C /usr/local
[root@localhost ~]# mv /usr/local/kibana-6.3.2-linux-x86_64 /usr/local/kibana
```
这里我们将kibana安装到了/usr/local目录下。
## 1.2、配置Kibana
由于将Kibana安装到了/usr/local目录下,因此,Kibana的配置文件为/usr/local/kibana/kibana.yml,Kibana配置非常简单,这里仅列出常用的配置项,内容如下:
```plain
server.port: 5601
server.host: "172.16.213.77"
elasticsearch.url: "http://172.16.213.37:9200"
kibana.index: ".kibana"
```
其中,每个配置项的含义介绍如下:
```css
server.port:kibana绑定的监听端口,默认是5601。
server.host:kibana绑定的IP地址,如果内网访问,设置为内网地址即可。
elasticsearch.url:kibana访问ElasticSearch的地址,如果是ElasticSearch集群,添加任一集群节点IP即可,官方推荐是设置为ElasticSearch集群中client node角色的节点IP。
kibana.index:用于存储kibana数据信息的索引,这个可以在kibanaweb界面中看到。
```
## 1.3、启动Kibana服务与web配置
所有配置完成后,就可以启动kibana了,启动kibana服务的命令在/usr/local/kibana/bin目录下,执行如下命令启动kibana服务:
```perl
[root@kafkazk2 ~]# cd /usr/local/kibana/
[root@kafkazk2 kibana]# nohup bin/kibana &
[root@kafkazk2 kibana]# ps -ef|grep node
root 6407 1 0 Jan15 ? 00:59:11 bin/../node/bin/node --no-warnings bin/../src/cli
root 7732 32678 0 15:13 pts/0 00:00:00 grep --color=auto node
```
这样,kibana对应的node服务就启动起来了。
接着,打开浏览器访问http://172.16.213.77:5601, 会自动打开kibana的web界面,在登录kibana后,第一步要做的就是配置index\_pattern,点击kibana左侧导航中的Management菜单,然后选择右侧的Index Patterns按钮,如下图所示:
![](assets/1593327290-05f855187fe7eb3dc78086db6bc84a7b.png)
接着,点击左上角的Create index,开始创建一个index pattern,如下图所示:
![](assets/1593327290-5a308953b2c4d31cd44ffb360a6911dc.png)
![](assets/1593327290-e9524c63bf54cbe814ae5716c7104235.png)
这里需要填写一个Index pattern名称,而在logstash安装配置一节中,我们已经定义好了这个Index pattern,名为osmessageslog-%{+YYYY-MM-dd},而这里只需填入osmessageslog-\*即可,如果已经有对应的数据写入elasticsearch,那么kibana会自动检测到并抓取映射文件,此时就可以创建此Index pattern了,如下图所示:
![](assets/1593327290-17784d2029be4963db24989fee64367d.png)
如果你填入索引名称后,右下面的“Next step”按钮仍然是不可点击状态,那说明kibana还没有抓取到输入索引对应的映射文件,此时可以让filebeat再生成一点数据,只要数据正常传到elasticsearch中,那么kibana就能马上检测到。
接着,选择日志字段按照“@timestamp”进行排序,也就是按照时间进行排序,如下图所示:
![](assets/1593327290-78a8c18a26b9e24de281a84e495f52c6.png)
最后,点击“Create index pattern”按钮,完成Index pattern的创建,如下图所示:
![](assets/1593327290-ed38c53a6e95a584a4085ed9f0a58358.png)
创建完成Index pattern后,点击kibana左侧导航中的Discover导航栏,即可展示已经收集到的日志信息,如下图所示:
![](assets/1593327290-874c697b00b95dd2ac6b527f5a0c1d74.png)
Kibana的web界面操作和使用比较简单,这里仅仅介绍下左侧导航栏中每个导航的含义以及功能,更细节的功能读者自行操作几遍就基本掌握了。
>  Discover:主要用来进行日志检索,查询数据,这个功能使用最多。
>  Visualize:数据可视化,可以在这里创建各种维度的可视化视图,例如面积图、折线图、饼图、热力图、标签云等,通过创建可视化视图,日志数据浏览变得非常直观。
>  Dashboard:仪表盘功能,仪表盘其实是可视化视图的组合,通过将各种可视化视图组合到一个页面,可以从整体上了解数据和日志的各种状态。
>  Timelion:时间画像,可以在这里创建时间序列可视化视图。
>  Dev Tools:这是一个调试工具控制台,kibana提供了一个UI来与Elasticsearch的REST API进行交互。 控制台有两个主要方面 :editor(编辑器)与response(响应),editor用来编写对Elasticsearch的请求,response显示对请求的响应。
>  Management:这是管理界面,可以在这里创建索引模式,调整Kibana设置等操作。
至此,kibana基本使用介绍完毕。
# 2、调试并验证日志数据流向
经过上面的配置过程,大数据日志分析平台已经基本构建完成,由于整个配置架构比较复杂,这里我们来梳理下各个功能模块的数据和业务流向,这有助于加深对整个架构的了解和认知。
下图是我们部署的日志分析平台的数据流向和功能模块图:
![](assets/1593327290-de7fb0500ad20fee55b686089d76734d.png)
此架构从整体上分成两个部分,分别是日志收集部分、日志检索部分。而整个数据流向分成了四个步骤,每个部分所实现的功能和含义分别如下:
> ①:这个过程是生产日志的过程,filebeat主要是用来收集业务服务器上的日志数据,它安装在每个业务服务器上,接收业务系统产生的日志,然后把日志实时的传输到kafka集群中,kafka消息队列系统可以对数据进行缓冲和存储。这里filebeat相当于kafka中的Producer(生产者),而这个过程其实就是一个push的过程,也就是推送数据到kafka集群。
> ②:这是一个pull的过程,filebeat推送数据到kafka后,kafka不会主动push数据到logstash,相反,logstash会主动去kafka集群pull数据,这里logstash相当于Consumer(消费者)。Consumer从kafka集群pull数据,这是有很多好处的,因为Consumer可自主控制消费消息的速率,同时Consumer可以自己控制消费方式,由此减少消费过程中出错的机率。
> Logstash从kafka拉取数据的过程实际上是分为三个步骤的,分别是input、filter和ouput,也就是首先接收不规则数据,然后过滤、分析并转换成格式化数据,最后输出格式化数据。这三个步骤是整个日志分析系统中最难掌握的部分。
> ③:这个过程是将格式化的数据发送到elasticsearch中进行存储并索引,所有的数据会存储在elasticsearch集群中。
> ④:这个过程是将elasticsearch中的数据在Web GUI界面进行可视化展示,并形成各种曲线图和报表。
在了解了数据流向逻辑之后,就可以对架构中每个部分进行有目的的调试和分析了,例如,当filebeat发送日志之后,就可以在kafka上面执行消费数据的命令,已验证filebeat的数据是否正确、及时的传输到了kafka集群上,如果在kafka上面消费到了对应的数据,那么接着就可以在elasticsearch的Head插件的web界面查看是否有对应的数据产生,如果有数据产生,那么整个数据流向正常,在kibana界面就能直接看到数据了。
@@ -0,0 +1,259 @@
## 7.Logstash输入插件(Input)以及编码插件(Codec)详解
# 1、Logstash输入插件(Input)以及编码插件(Codec)详解
在上面文章中我们介绍了logstash的基本使用,主要讲述了logstash的input(输入)和output(输出)功能,其实logstash的功能远远不止于此,logstash之所以功能强大和流行,还与其丰富的过滤器插件是分不开的,过滤器提供的并不单单是过滤的功能,还可以对进入过滤器的原始数据进行复杂的逻辑处理,甚至添加独特的事件到后续流程中。本节就重点讲述下logstash过滤器插件的使用方法。
## 1.1、Logstash基本语法组成
Logstash配置文件有如下三部分组成,其中input、output部分是必须配置,filter部分是可选配置,而filter就是过滤器插件,可以在这部分实现各种日志过滤功能。
> input {
> 输入插件
> }
> filter {
> 过滤匹配插件
> }
> output {
> 输出插件
> }
下面我们将依次进行介绍。
## 1.2、Logstash输入插件(Input)
Logstash的输入插件主要用来接收数据,Logstash支持多种数据源,常见的有读取文件、标准输入、读取syslog日志、读取网络数据等,这里分别介绍下每种接收数据源的配置方法。
1、 读取文件(File)
logstash使用一个名为filewatch的ruby gem库来监听文件变化,并通过一个叫.sincedb的数据库文件来记录被监听的日志文件的读取进度(时间戳),这个sincedb数据文件的默认路径在 <path.data>/plugins/inputs/file下面,文件名类似于.sincedb\_452905a167cf4509fd08acb964fdb20c,而<path.data>表示logstash插件存储目录,默认是LOGSTASH\_HOME/data。
看下面一个事件配置文件:
```php
input {
file {
path => ["/var/log/messages"]
type => "system"
start_position => "beginning"
}
}
output {
stdout{
codec=>rubydebug
}
}
```
这个配置是监听并接收本机的/var/log/messages文件内容,start\_position表示按时间戳记录的地方开始读取,如果没有时间戳则从头开始读取,有点类似cat命令,默认情况下,logstash会从文件的结束位置开始读取数据,也就是说logstash进程会以类似tail -f命令的形式逐行获取数据。type用来标记事件类型,通常会在输入区域通过type标记事件类型。
假定/var/log/messages中输入的内容为如下信息:
> Feb 6 17:20:11 logstashserver yum\[15667\]: Installed: vsftpd-2.2.2-24.el6.x86\_64
那么经过logstash后,输出内容为如下JSON格式:
{
"@version" => "1",
"host" => " logstashserver",
"path" => "/var/log/messages",
"@timestamp" => 2018-02-02T02:02:36.361Z,
"message" => "Feb 6 17:20:11 logstashserver yum\[15667\]: Installed: vsftpd-2.2.2-24.el6.x86\_64",
"type" => "system"
}
从输出可以看出,除了增加了前四个字段外,message字段是真正的输出内容,将输入信息原样输出,最后还有一个type字段,这是在input中定义的一个事件类型,也被原样输出了,在后面的过滤插件中会用到这个type字段。
2、标准输入(Stdin)
stdin是从标准输入获取信息,关于stdin的使用,前面章节已经做过了一些简单的介绍,这里再看一个稍微复杂一点的例子,下面是一个关于stdin的事件配置文件:
```php
input{
stdin{
add_field=>{"key"=>"iivey"}
tags=>["add1"]
type=>"test1"
}
}
output {
stdout{
codec=>rubydebug
}
}
```
如果输入"hello world",可以在终端看到如下输出信息:
```php
{
"@timestamp" => 2018-02-05T10:46:38.211Z,
"@version" => "1",
"host" => "logstashserver",
"message" => "hello world",
"type" => "test1",
"key" => "iivey",
"tags" => [
[0] "add1"
]
}
```
type和tags是logstash的两个特殊字段, type一般会放在input中标记事件类型, tags主要用于在事件中增加标签,以便在后续的处理流程中使用,主要用于filter或output阶段。
3、读取 Syslog日志
syslog是linux下一个功能强大的系统日志收集系统,它的升级版本有rsyslog和syslog-ng,在主流的Linux发行版centos6.x/7.x中,默认是rsyslog,升级版本都涵盖sysLog的常用功能,不过在功能和性能上更为出色。rsyslog日志收集系统既可以记录信息在本地文件中,也可以通过网络发送到接收的rsyslog服务器。本节介绍下如何将rsyslog收集到的日志信息发送到logstash中,这里以centos7.5为例,需要做如下两个步骤的操作:
首先,在需要收集日志的服务器上找到rsyslog的配置文件/etc/rsyslog.conf,添加如下内容:
```markdown
*.* @@172.16.213.120:5514
```
其中,172.16.213.120是logstash服务器的地址。5514是logstash启动的监听端口。
接着,重启rsyslog服务:
```coffeescript
[root@kafkazk1 logstash]# systemctl restart rsyslog
```
然后,在logstash服务器上创建一个事件配置文件,内容如下:
```php
input {
syslog {
port => "5514"
}
}
output {
stdout{
codec=>rubydebug
}
}
```
此时, kafkazk1主机上的日志信息都会到logstash中来, 假定输入的内容为如下信息:
> Feb 5 17:42:26 kafkazk1 systemd: Stopped The nginx HTTP and reverse proxy server.
那么经过logstash后,输出内容为如下JSON格式:
```php
{
"severity" => 6,
"@timestamp" => 2018-02-05T09:42:26.000Z,
"@version" => "1",
"host" => "172.16.213.51",
"program" => "systemd",
"message" => "Stopped The nginx HTTP and reverse proxy server.\n",
"priority" => 30,
"logsource" => "kafkazk1",
"facility" => 3,
"severity_label" => "Informational",
"timestamp" => "Feb 5 17:42:26",
"facility_label" => "system"
}
```
从JSON格式的输出可以看到增加了几个字段,并且输入的信息经过LogStash后被自动切割成多个字段了,这其实就是下面将要讲到的filter的功能。很多时候,我们为了能更详细的分析日志信息,需要将输入的日志分成多个字段,然后通过不同的字段维度进行多重分析。
在数据量较大的时候,LogStash读取syslog日志会存在很大性能瓶颈,因而如果遇到大数据量场景,建议使用"LogStash::Inputs::TCP"和"LogStash::Filters::Grok"配合实现同样的syslog功能!
4、读取TCP网络数据
下面的事件配置文件就是通过"LogStash::Inputs::TCP"和"LogStash::Filters::Grok"配合实现syslog功能的例子,这里使用了logstash的TCP/UDP插件读取网络数据:
```php
input {
tcp {
port => "5514"
}
}
filter {
grok {
match => { "message" => "%{SYSLOGLINE}" }
}
}
output {
stdout{
codec=>rubydebug
}
}
```
其中,5514端口是logstash启动的tcp监听端口。注意这里用到了日志过滤"LogStash::Filters::Grok"功能,下面马上会介绍到。
"LogStash::Inputs::TCP"最常见的用法就是结合nc命令导入旧数据。在启动logstash进程后,在另一个终端运行如下命令即可导入旧数据:
```coffeescript
[root@kafkazk1 app]# nc 172.16.213.120 5514 < /var/log/secure
```
通过这种方式,就把/var/log/secure的内容全部导入到了logstash中,当nc命令结束时,数据也就导入完成了。
## 1.3、Logstash编码插件(Codec)
在前面介绍的例子中,其实我们就已经用过编码插件codec了,也就是这个rubydebug,它就是一种codec,虽然它一般只会用在stdout插件中,作为配置测试或者调试的工具。
编码插件(Codec)可以在logstash输入或输出时处理不同类型的数据,同时,还可以更好更方便的与其它自定义格式的数据产品共存,比如fluent、netflow、collectd等通用数据格式的其它产品。因此,Logstash不只是一个input-->filter-->output的数据流,而是一个input-->decode-->filter-->encode-->output的数据流。
Codec支持的编码格式常见的有plain、json、json\_lines等。下面依次介绍。
1、codec插件之plain
plain是一个空的解析器,它可以让用户自己指定格式,也就是说输入是什么格式,输出就是什么格式。下面是一个包含plain编码的事件配置文件:
```plain
input{
stdin {
}
}
output{
stdout {
codec => "plain"
}
}
```
在启动logstash进程后,输入什么格式的数据,都会原样输出,这里不再过多说明。
2、codec插件之json、json\_lines
如果发送给logstash的数据内容为json格式,可以在input字段加入codec=>json来进行解析,这样就可以根据具体内容生成字段,方便分析和储存。如果想让logstash输出为json格式,可以在output字段加入codec=>json,下面是一个包含json编码的事件配置文件:
```plain
input {
stdin {
}
}
output {
stdout {
codec => json
}
}
```
同理,在启动logstash进程后,如果输入“hello world”,那么输出信息为:
> {"@version":"1","host":"logstashserver","@timestamp":"2018-02-07T04:14:51.096Z","message":"hello world"}
这就是json格式的输出,可以看出,json每个字段是key:values格式,多个字段之间通过逗号分隔。有时候,如果json文件比较长,需要换行的话,那么就要用json\_lines编码格式了。这个就不再介绍了。
@版权声明:51CTO独家出品,未经允许不能转载,否则追究法律责任
---------------------------------------------------
原网址: [访问](https://blog.51cto.com/cloumn/blog/302)
创建于: 2020-06-28 14:55:12
目录: default
标签: `blog.51cto.com`
@@ -0,0 +1,505 @@
## 8.Logstash过滤器插件(Filter)以及输出插件(output)详解
# 1、Logstash过滤器插件(Filter)
丰富的过滤器插件是logstash功能强大的重要因素。名为过滤器,其实它提供的不单单是过滤器的功能。logstash还可以对进入过滤器的原始数据进行复杂的逻辑处理,甚至添加独特的事件到后续流程中。
## 1.1、Grok 正则捕获
grok是一个十分强大的logstash filter插件,他可以通过正则解析任意文本,将非结构化日志数据弄成结构化和方便查询的结构。他是目前logstash 中解析非结构化日志数据最好的方式。
Grok 的语法规则是:
> %{语法: 语义}
“语法”指的就是匹配的模式,例如使用NUMBER模式可以匹配出数字,IP模式则会匹配出127.0.0.1这样的IP地址:
例如输入的内容为:
> 172.16.213.132 \[07/Feb/2018:16:24:19 +0800\] "GET / HTTP/1.1" 403 5039
那么,%{IP:clientip}匹配模式将获得的结果为:
> clientip: 172.16.213.132
%{HTTPDATE:timestamp}匹配模式将获得的结果为:
> timestamp: 07/Feb/2018:16:24:19 +0800
而%{QS:referrer}匹配模式将获得的结果为:
> referrer: "GET / HTTP/1.1"
到这里为止,我们已经获取了三个部分的输入内容,分别是clientip、timestamp和referrer三个字段,如果要获取剩余部分的信息,方法类似。
下面是一个组合匹配模式,它可以获取上面输入的所有内容:
```css
%{IP:clientip}\ \[%{HTTPDATE:timestamp}\]\ %{QS:referrer}\ %{NUMBER:response}\ %{NUMBER:bytes}
```
正则匹配是非常严格的匹配,在这个组合匹配模式中,使用了转义字符\\,这是因为输入的内容中有空格和中括号。
通过上面这个组合匹配模式,我们将输入的内容分成了五个部分,即五个字段,将输入内容分割为不同的数据字段,这对于日后解析和查询日志数据非常有用,这正是我们使用grok的目的。
Logstash默认提供了近200个匹配模式(其实就是定义好的正则表达式)让我们来使用,可以在logstash安装目录下,例如这里是/usr/local/logstash/vendor/bundle/jruby/1.9/gems/logstash-patterns-core-4.1.2/patterns目录里面查看,基本定义在grok-patterns文件中。
从这些定义好的匹配模式中,可以查到上面使用的四个匹配模式对应的定义规则,如下表所示:
```ruby
匹配模式 正则定义规则
NUMBER (?:%{BASE10NUM})
HTTPDATE %{MONTHDAY}/%{MONTH}/%{YEAR}:%{TIME} %{INT}
IP (?:%{IPV6}|%{IPV4})
QS %{QUOTEDSTRING}
```
除此之外,还有很多默认定义好的匹配模式文件,例如httpd、java、linux-syslog、redis、mongodb、nagios等,这些已经定义好的匹配模式,可以直接在Grok过滤器中进行引用。当然也可以定义自己需要的匹配模式。
在了解完grok的匹配规则之后,下面通过一个配置实例深入介绍下Logstash是如何将非结构化日志数据转换成结构化数据的,首先看下面一个事件配置文件:
```makefile
input{
stdin{}
}
filter{
grok{
match => ["message","%{IP:clientip}\ \[%{HTTPDATE:timestamp}\]\ %{QS:referrer}\ %{NUMBER:response}\ %{NUMBER:bytes}"]
}
}
output{
stdout{
codec => "rubydebug"
}
}
```
在这个配置文件中,输入配置成了stdin,filter中添加了grok过滤插件,并通过match来执行正则表达式解析,中括号中的正则表达式就是上面提到的组合匹配模式,然后通过rubydebug编码格式输出信息。这样的组合有助于调试和分析输出结果。通过此配置启动Logstash进程后,我们仍然输入之前给出的那段内容:
> 172.16.213.132 \[07/Feb/2018:16:24:19 +0800\] "GET / HTTP/1.1" 403 5039
然后,查看rubydebug格式的日志输出,内容如下:
```php
{
"referrer" => "\"GET / HTTP/1.1\"",
"@timestamp" => 2018-02-07T10:37:01.015Z,
"response" => "403",
"bytes" => "5039",
"clientip" => "172.16.213.132",
"@version" => "1",
"host" => "logstashserver",
"message" => "172.16.213.132 [07/Feb/2018:16:24:19 +0800] \"GET / HTTP/1.1\" 403 5039",
"timestamp" => "07/Feb/2018:16:24:19 +0800"
}
```
从这个输出可知,通过grok定义好的5个字段都获取到了内容,并正常输出了,看似完美,其实还有不少瑕疵。
首先,message字段也输出了完整的输入内容。这样看来,数据实质上就相当于是重复存储了两份,此时可以用remove\_field参数来删除掉message字段,只保留最重要的部分。
其次,timestamp字段表示日志的产生时间,而@timestamp默认情况下显示的是当前时间,在上面的输出中可以看出,这两个字段的时间并不一致,那么问题来了,在ELK日志处理系统中,@timestamp字段会被elasticsearch用到,用来标注日志的生成时间,如此一来,日志生成的时间就会发生混乱,要解决这个问题,需要用到另一个插件,即data插件,这个时间插件用来转换日志记录中的时间字符串,变成LogStash::Timestamp对象,然后转存到@timestamp字段里。
使用data插件很简单,添加下面一段配置即可:
> date {
> match => \["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"\]
> }
注意:时区偏移量需要用一个字母Z来转换。
最后,在将timestamp自动的值传给@timestamp后,timestamp其实也就没有存在的意义了,所以还需要删除这个字段。
将上面三个步骤的操作统一合并到配置文件中,修改后的配置文件内容如下:
```php
input {
stdin {}
}
filter {
grok {
match => { "message" => "%{IP:clientip}\ \[%{HTTPDATE:timestamp}\]\ %{QS:referrer}\ %{NUMBER:response}\ %{NUMBER:bytes}" }
remove_field => [ "message" ]
}
date {
match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
}
mutate {
remove_field => ["timestamp"]
}
}
output {
stdout {
codec => "rubydebug"
}
}
```
在这个配置文件中,使用了date插件,mutate插件以及remove\_field配置项,关于这两个插件后面会马上介绍到。
将修改后的配置文件重新运行后,仍然输入之前的那段内容:
> 172.16.213.132 \[07/Feb/2018:16:24:19 +0800\] "GET / HTTP/1.1" 403 5039
输出现在变为如下结果:
```php
{
"referrer" => "\"GET / HTTP/1.1\"",
"@timestamp" => 2018-02-07T08:24:19.000Z,
"response" => "403",
"bytes" => "5039",
"clientip" => "172.16.213.132",
"@version" => "1",
"host" => "logstashserver"
}
```
这就是我们需要的最终结果。
## 1.2、时间处理(Date)
date插件是对于排序事件和回填旧数据尤其重要,它可以用来转换日志记录中的时间字段,变成LogStash::Timestamp对象,然后转存到@timestamp字段里,这在上一节已经做过简单的介绍。
下面是date插件的一个配置示例(这里仅仅列出filter部分):
```php
filter {
grok {
match => ["message", "%{HTTPDATE:timestamp}"]
}
date {
match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
}
}
```
为什么要使用这个date插件呢,主要有两方面原因:
一方面由于Logstash会给收集到的每条日志自动打上时间戳(即@timestamp),但是这个时间戳记录的是input接收数据的时间,而不是日志生成的时间(因为日志生成时间与input接收的时间肯定不同),这样就可能导致搜索数据时产生混乱。
另一方面,不知道读者是否注意到了,在上面那段rubydebug编码格式的输出中,@timestamp字段虽然已经获取了timestamp字段的时间,但是仍然比北京时间早了8个小时,这是因为在Elasticsearch内部,对时间类型字段都是统一采用UTC时间,而日志统一采用UTC时间存储,是国际安全、运维界的一个共识。其实这并不影响什么,因为ELK已经给出了解决方案,那就是在Kibana平台上,程序会自动读取浏览器的当前时区,然后在web页面自动将UTC时间转换为当前时区的时间。
用于解析日期和时间文本的语法使用字母来指示时间(年、月、日、时、分等)的类型,以及重复的字母来表示该值的形式,在上面看到的"dd/MMM/yyyy:HH:mm:ss Z"就使用了这种形式,下图列出了每个使用字符的含义。
![](assets/1593327406-7f83919c85fccc9a696f241d75433eb8.png)
## 1.3、数据修改(Mutate)
mutate插件是Logstash另一个非常重要插件。它提供了丰富的基础类型数据处理能力。包括重命名、删除、替换和修改日志事件中的字段。这里重点介绍下mutate插件的字段类型转换功能(covert)、正则表达式替换匹配字段功能(gsub)、分隔符分割字符串为数组功能(split)、重命名字段功能(rename)、删除字段功能(remove\_field)的具体实现方法。
(1)、字段类型转换功能
mutate可以设置的转换类型有"integer"、"float"和"string",下面是一个关于mutate字段类型转换的示例(仅列出filter部分):
```php
filter {
mutate {
covert => ["filed_name", "integer"]
}
}
```
这个示例表示将filed\_name字段类型修改为integer。
(2)、正则表达式替换匹配字段
gsub可以通过正则表达式替换字段中匹配到的值,只对字符串字段有效,下面是一个关于mutate插件中gsub的示例(仅列出filter部分):
```php
filter {
mutate {
gsub => ["filed_name_1", "/" , "_"]
}
}
```
这个示例表示将filed\_name_1字段中所有"/"字符替换为"_"。
(3)、分隔符分割字符串为数组
split可以通过指定的分隔符分割字段中的字符串为数组,下面是一个关于mutate插件中split的示例(仅列出filter部分):
```php
filter {
mutate {
split => {"filed_name_2", "|"}
}
}
```
这个示例表示将filed\_name\_2字段以"|"为区间分隔为数组。
(4)重命名字段
rename可以实现重命名某个字段的功能,下面是一个关于mutate插件中rename的示例(仅列出filter部分):
```php
filter {
mutate {
rename => {"old_field" => "new_field"}
}
}
```
这个示例表示将字段old\_field重命名为new\_field。
(5)删除字段
remove\_field可以实现删除某个字段的功能,下面是一个关于mutate插件中remove\_field的示例(仅列出filter部分):
```php
filter {
mutate {
remove_field => ["timestamp"]
}
}
```
这个示例表示将字段timestamp删除。
在本节最后,我们将上面讲到的mutate插件的几个功能点整合到一个完整的配置文件中,以验证mutate插件实现的功能细节,配置文件内容如下:
```php
input {
stdin {}
}
filter {
grok {
match => { "message" => "%{IP:clientip}\ \[%{HTTPDATE:timestamp}\]\ %{QS:referrer}\ %{NUMBER:response}\ %{NUMBER:bytes}" }
remove_field => [ "message" ]
}
date {
match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
}
mutate {
rename => { "response" => "response_new" }
convert => [ "response","float" ]
gsub => ["referrer","\"",""]
remove_field => ["timestamp"]
split => ["clientip", "."]
}
}
output {
stdout {
codec => "rubydebug"
}
}
```
将此配置文件运行后,仍然输入之前的那段内容:
172.16.213.132 \[07/Feb/2018:16:24:19 +0800\] "GET / HTTP/1.1" 403 5039
输出结果如下:
```php
{
"referrer" => "GET / HTTP/1.1",
"@timestamp" => 2018-02-07T08:24:19.000Z,
"bytes" => "5039",
"clientip" => [
[0] "172",
[1] "16",
[2] "213",
[3] "132"
],
"@version" => "1",
"host" => "logstashserver",
"response_new" => "403"
}
```
从这个输出中,可以很清楚的看到,mutate插件是如何操作日志事件中的字段的。
## 1.4、GeoIP 地址查询归类
GeoIP是最常见的免费IP地址归类查询库,当然也有收费版可以使用。GeoIP库可以根据IP 地址提供对应的地域信息,包括国别,省市,经纬度等,此插件对于可视化地图和区域统计非常有用。
下面是一个关于GeoIP插件的简单示例(仅列出filter部分)::
```plain
filter {
geoip {
source => "ip_field"
}
}
```
其中,ip\_field字段是输出IP地址的一个字段。
由于默认情况下GeoIP库输出的字段数据比较多,例如这里假定输入的ip\_field字段为114.55.68.110,那么geoip将默认输出如下内容:
```php
"geoip" => {
"city_name" => "Hangzhou",
"timezone" => "Asia/Shanghai",
"ip" => "114.55.68.110",
"latitude" => 30.2936,
"country_name" => "China",
"country_code2" => "CN",
"continent_code" => "AS",
"country_code3" => "CN",
"region_name" => "Zhejiang",
"location" => {
"lon" => 120.1614,
"lat" => 30.2936
},
"region_code" => "33",
"longitude" => 120.1614
}
```
有时候我们可能不需要这么多内容,此时可以通过fields选项指定自己所需要的。下例为选择输出字段的方式:
```php
filter {
geoip {
fields => ["city_name", "region_name", "country_name", "ip", "latitude", "longitude", "timezone"]
}
}
```
logstash会通过latitude和longitude额外生成geoip.location,用于地图定位。geoip库仅可用来查询公共网络上的IP信息,对于查询不到结果的,会直接返回 null,而logstash对geoip插件返回null的处理方式是不生成对应的geoip字段。
## 1.5、filter插件综合应用实例
下面给出一个业务系统输出的日志格式,由于业务系统输出的日志格式无法更改,因此就需要我们通过logstash的filter过滤功能以及grok插件来获取需要的数据格式,此业务系统输出的日志内容以及原始格式如下:
```ruby
2018-02-09T10:57:42+08:00|~|123.87.240.97|~|Mozilla/5.0 (iPhone; CPU iPhone OS 11_2_2 like Mac OS X) AppleWebKit/604.4.7 Version/11.0 Mobile/15C202 Safari/604.1|~|http://m.sina.cn/cm/ads_ck_wap.html|~|1460709836200|~|DF0184266887D0E
```
可以看出,这段日志都是以“|~|”为区间进行分隔的,那么刚好我们就以“|~|”为区间分隔符,将这段日志内容分割为6个字段。这里通过grok插件进行正则匹配组合就能完成这个功能。
完整的grok正则匹配组合语句如下:
```ruby
%{TIMESTAMP_ISO8601:localtime}\|\~\|%{IPORHOST:clientip}\|\~\|(%{GREEDYDATA:http_user_agent})\|\~\|(%{DATA:http_referer})\|\~\|%{GREEDYDATA:mediaid}\|\~\|%{GREEDYDATA:osid}
```
这里用到了四种匹配模式,分别是TIMESTAMP\_ISO8601、IPORHOST、GREEDYDATA和DATA,都是logstash默认的,可以从logstash安装目录下找到,具体含义读者可自行查阅,这里不再介绍。
编写grok正则匹配组合语句有一定难度,需要根据具体的日志格式和logstash提供的匹配模式配合实现,不过幸运的是,有一个grok调试平台可供我们使用,地址为http://grokdebug.herokuapp.com,在这个平台上,可以很方便的调试grok正则表达式。
将上面编写好的grok正则匹配组合语句套入logstash事件配置文件中即可,完整的配置文件内容如下:
```php
input {
stdin {}
}
filter {
grok {
match => { "message" => "%{TIMESTAMP_ISO8601:localtime}\|\~\|%{IPORHOST:clientip}\|\~\|(%{GREEDYDATA:http_user_agent})\|\~\|(%{DATA:http_referer})\|\~\|%{GREEDYDATA:mediaid}\|\~\|%{GREEDYDATA:osid}" }
remove_field => [ "message" ]
}
date {
match => ["localtime", "yyyy-MM-dd'T'HH:mm:ssZZ"]
target => "@timestamp"
}
mutate {
remove_field => ["localtime"]
}
}
output {
stdout {
codec => "rubydebug"
}
}
```
这个配置文件完成的功能有如下几个方面:
```plain
(1)从终端接收(stdin)输入数据
(2)将输入日志内容分为6个字段。
(3)删除message字段
(4)将输入日志的时间字段信息转存到@timestamp字段里
(5)删除输入日志的时间字段
(6)将输入内容以rubydebug格式在终端输出(stdout)
```
在此配置文件中,需要注意一下date插件中match的写法,其中,localtime是输入日志中的时间字段(2018-02-09T10:57:42+08:00),"yyyy-MM-dd'T'HH:mm:ssZZ"用来匹配输入日志字段的格式,在匹配成功后,会将localtime字段的内容转存到@timestamp字段里,target默认指的就是@timestamp,所以“target => "@timestamp"”表示用localtime字段的时间更新@timestamp字段的时间。
由于输入的时间字段格式为ISO8601,因此,上面关于date插件转换时间的写法,也可以写成如下格式:
```php
date {
match => ["localtime", "ISO8601"]
}
```
这种写法看起来更简单。
最后,将上面的logstash事件配置文件运行起来,输入示例数据后,得到的输出结果如下:
```php
{
"@timestamp" => 2018-02-09T02:57:42.000Z,
"http_referer" => "http://m.sina.cn/cm/ads_ck_wap.html",
"clientip" => "123.87.240.97",
"@version" => "1",
"host" => "logstashserver",
"osid" => "DF0184266887D0E",
"mediaid" => "1460709836200",
"http_user_agent" => "Mozilla/5.0 (iPhone; CPU iPhone OS 11_2_2 like Mac OS X) AppleWebKit/604.4.7 Version/11.0 Mobile/15C202 Safari/604.1"
}
```
这个输出就是我们需要的最终结果,可以将此结果直接输出到elasticsearch中,然后在Kibana中就可以查看到对应的数据。
# 2、Logstash输出插件(output)
output是Logstash的最后阶段,一个事件可以经过多个输出,而一旦所有输出处理完成,整个事件就执行完成。 一些常用的输出包括:
>  file: 表示将日志数据写入磁盘上的文件。
>  elasticsearch:表示将日志数据发送给Elasticsearch。Elasticsearch可以高效方便和易于查询的保存数据。
>  graphite:表示将日志数据发送给graphite,graphite是一种流行的开源工具,用于存储和绘制数据指标。
> 此外,Logstash还支持输出到nagios、hdfs、email(发送邮件)和Exec(调用命令执行)。
## 2.1、输出到标准输出(stdout)
stdout与之前介绍过的stdin插件一样,它是最基础和简单的输出插件,下面是一个配置实例:
```plain
output {
stdout {
codec => rubydebug
}
}
```
stdout插件,主要的功能和用途就是用于调试,这个插件,在前面已经多次使用过。这里不再过多介绍。
## 2.2、保存为文件(file)
file插件可以将输出保存到一个文件中,配置实例如下:
```perl
output {
file {
path => "/data/log3/%{+yyyy-MM-dd}/%{host}_%{+HH}.log"
}
```
上面这个配置中,使用了变量匹配,用于自动匹配时间和主机名,这在实际使用中很有帮助。
file插件默认会以JSON形式将数据保存到指定的文件中,而如果只希望按照日志的原始格式保存的话,就需要通过codec编码方式自定义%{message},将日志按照原始格式保存输出到文件。配置实例如下:
```php
output {
file {
path => "/data/log3/%{+yyyy-MM-dd}/%{host}_%{+HH}.log.gz"
codec => line { format => "%{message}"}
gzip => true
}
```
在这个配置中,使用了codec编码方式,将输出日志转换为原始格式,同时,输出数据文件还开启了gzip压缩,自动将输出保存为压缩文件格式。
## 2.3、输出到elasticsearch
Logstash将过滤、分析好的数据输出到elasticsearch中进行存储和查询,是最经常使用的方法。下面是一个配置实例:
```php
output {
elasticsearch {
host => ["172.16.213.37:9200","172.16.213.77:9200","172.16.213.78:9200"]
index => "logstash-%{type}-%{+YYYY.MM.dd}"
manage_template => false
template_name => "template-web_access_log"
}
}
```
上面配置中每个配置项含义如下:
>  host:是一个数组类型的值,后面跟的值是elasticsearch节点的地址与端口,默认端口是9200。可添加多个地址。
>  index:写入elasticsearch的索引的名称,这里可以使用变量。Logstash提供了%{+YYYY.MM.dd}这种写法。在语法解析的时候,看到以+ 号开头的,就会自动认为后面是时间格式,尝试用时间格式来解析后续字符串。这种以天为单位分割的写法,可以很容易的删除老的数据或者搜索指定时间范围内的数据。此外,注意索引名中不能有大写字母。
>  manage\_template:用来设置是否开启logstash自动管理模板功能,如果设置为false将关闭自动管理模板功能。如果我们自定义了模板,那么应该设置为false。
>  template\_name:这个配置项用来设置在Elasticsearch中模板的名称。
@@ -0,0 +1,207 @@
## 9.【案例】ELK收集Apache访问日志实战案例
# 1、ELK收集日志的几种方式
ELK收集日志常用的有两种方式,分别是:
(1)不修改源日志的格式,而是通过logstash的grok方式进行过滤、清洗,将原始无规则的日志转换为规则的日志。
(2)修改源日志输出格式,按照需要的日志格式输出规则日志,logstash只负责日志的收集和传输,不对日志做任何的过滤清洗。
这两种方式各有优缺点,第一种方式不用修改原始日志输出格式,直接通过logstash的grok方式进行过滤分析,好处是对线上业务系统无任何影响,缺点是logstash的grok方式在高压力情况下会成为性能瓶颈,如果要分析的日志量超大时,日志过滤分析可能阻塞正常的日志输出。因此,在使用logstash时,能不用grok的,尽量不使用grok过滤功能。
第二种方式缺点是需要事先定义好日志的输出格式,这可能有一定工作量,但优点更明显,因为已经定义好了需要的日志输出格式,logstash只负责日志的收集和传输,这样就大大减轻了logstash的负担,可以更高效的收集和传输日志。另外,目前常见的web服务器,例如apache、nginx等都支持自定义日志输出格式。因此,在企业实际应用中,第二种方式是首选方案。
# 2、ELK收集Apache访问日志应用架构
这里我们还是以ELK+Filebeat+Kafka+ZooKeeper构建大数据日志分析平台一节的架构进行讲述,完整的拓扑结构如下图所示:
![](assets/1593327436-7bdb6f4d7fe14e5353be7f3b1d89db83.png)
此架构需要8台服务器完成,每台服务器的作用和对应的IP信息都已经在图上进行了标注。最前面的一台是apache服务器,用于产生日志,然后由filebeat来收集apache产生的日志,filebeat将收集到的日志推送(push)到kafka集群中,完成日志的收集工作。接着,logstash去kafka集群中拉取(pull)日志并进行日志过滤、分析,之后将日志发送到elasticsearch集群中进行索引和存储,最后由Kibana完成日志的可视化查询。
线上生产系统为了保证性能和高可用,一般会将kafka和elasticsearch做成集群模式,而在测试和开发环境下,也可以将kafka和elasticsearch部署成单机模式,可节省部分服务器资源。
在下面的介绍中,我们设定kafka集群和elasticsearch集群已经部署完成,在此基础上介绍如何通过filebeat和logstash收集与处理apache日志。
# 3、apache的日志格式与日志变量
apache是最流行的http服务器,收集apache产生的日志是ELK平台最基础的应用,这里先从apache日志格式入手,看看如何收集apache的log日志。
apache支持自定义输出日志格式,这给我们收集日志带来了很大方便,但是,apache有很多日志变量字段,所以在收集日志前,需要首先确定哪些是我们需要的日志字段,然后将日志格式定下来。要完成这个工作,需要了解apache日志字段定义的方法和日志变量的含义,在apache配置文件httpd.conf中,对日志格式定义的配置项为LogFormat,默认的日志字段定义为如下内容:
```perl
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
```
这里看到的%h、%l、%u等就是apache的日志变量,下面介绍下常用的apache日志变量以及表示的含义,如下表所示:
![](assets/1593327436-a95da23a9cd78a282bc965f2bfb981fc.png)
# 4、自定义apache日志格式
Apache的安装与配置这里不再介绍,仅仅介绍下apache配置文件中日志格式的定义方式。在掌握了apache日志变量的含义后,接着开始对它输出的日志格式进行改造,这里我们定义将apache日志输出为json格式,下面仅列出apache配置文件httpd.conf中日志格式和日志文件定义部分,定义好的日志格式与日志文件如下:
```perl
LogFormat "{\"@timestamp\":\"%{%Y-%m-%dT%H:%M:%S%z}t\",\"client_ip\":\"%{X-Forwarded-For}i\",\"direct_ip\": \"%a\",\"request_time\":%T,\"status\":%>s,\"url\":\"%U%q\",\"method\":\"%m\",\"http_host\":\"%{Host}i\",\"server_ip\":\"%A\",\"http_referer\":\"%{Referer}i\",\"http_user_agent\":\"%{User-agent}i\",\"body_bytes_sent\":\"%B\",\"total_bytes_sent\":\"%O\"}" access_log_json
CustomLog logs/access.log access_log_json
```
这里通过LogFormat指令定义了日志输出格式,在这个自定义日志输出中,定义了13个字段,定义方式为:字段名称:字段内容,字段名称是随意指定的,能代表其含义即可,字段名称和字段内容都通过双引号括起来,而双引号是特殊字符,需要转移,因此,使用了转移字符“\\”,每个字段之间通过逗号分隔。此外,还定义了一个时间字段 @timestamp,这个字段的时间格式也是自定义的,此字段记录日志的生成时间,非常有用。CustomLog指令用来指定日志文件的名称和路径。
> 需要注意的是,上面日志输出字段中用到了body\_bytes\_sent和total\_bytes\_sent发送字节数统计字段,这个功能需要apache加载mod\_logio.so模块,如果没有加载这个模块的话,需要安装此模块并在httpd.conf文件中加载一下即可。对于安装和加载apache模块的细节,这里不进行介绍。
# 5、验证日志输出
apache的日志格式配置完成后,重启apache,然后查看输出日志是否正常,如果能看到类似如下内容,表示自定义日志格式输出正常:
```json
{"@timestamp":"2018-02-24T16:15:29+0800","client_ip":"-","direct_ip": "172.16.213.132","request_time":0,"status":200,"url":"/img/guonian.png","method":"GET","http_host":"172.16.213.157","server_ip":"172.16.213.157","http_referer":"http://172.16.213.157/img/","http_user_agent":"Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0","body_bytes_sent":"1699956","total_bytes_sent":"1700218"}
{"@timestamp":"2018-02-24T16:17:28+0800","client_ip":"172.16.213.132","direct_ip": "172.16.213.84","request_time":0,"status":200,"url":"/img/logstash1.png","method":"GET","http_host":"172.16.213.157","server_ip":"172.16.213.157","http_referer":"http://172.16.213.84/img/","http_user_agent":"Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0","body_bytes_sent":"163006","total_bytes_sent":"163266"}
{"@timestamp":"2018-02-24T17:48:50+0800","client_ip":"172.16.213.132, 172.16.213.84","direct_ip": "172.16.213.120","request_time":0,"status":200,"url":"/img/logstash2.png","method":"GET","http_host":"172.16.213.157","server_ip":"172.16.213.157","http_referer":"http://172.16.213.84/img/","http_user_agent":"Mozilla/5.0 (Windows NT 6.3; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0","body_bytes_sent":"163006","total_bytes_sent":"163266"}
```
在这个输出中,可以看到,client\_ip和direct\_ip输出的异同,client\_ip字段对应的变量为“%{X-Forwarded-For}i”,它的输出是代理叠加而成的IP列表,而direct\_ip对应的变量为%a,表示不经过代理访问的直连IP,当用户不经过任何代理直接访问apache时,client\_ip和direct\_ip应该是同一个IP。
上面三条输出日志中,第一条是直接访问http://172.16.213.157/img/, 此时client\_ip和direct\_ip内容是相同的,但在日志中显示client\_ip为"-",这是因为“%{X-Forwarded-For}i”变量不会记录最后一个代理服务器IP信息。
第二条日志是经过一个代理去访问http://172.16.213.157/img/, 其实就是通过访问http://172.16.213.84 ,然后再让172.16.213.84代理去访问172.16.213.157服务器。这是经过了一层代理,可以看到,此时client\_ip显示的是客户端真实IP地址,direct\_ip显示的是代理服务器的IP地址。
第三条日志是经过了两个代理去访问http://172.16.213.157/img/, 也就是客户端通过浏览器访问http://172.16.213.84 ,然后172.16.213.84将请求发送到172.16.213.120服务器,最后,172.16.213.120服务器直接去访问172.16.213.157服务器,这是一个二级代理的访问日志,可以看到,client\_ip显示了一个IP列表,分别是真实客户端IP地址172.16.213.132、第一个代理服务器IP地址172.16.213.84,并没有显示最后一个代理服务器的IP地址,而direct\_ip显示的是最后一个代理服务器的IP地址。
理解这三条日志输出非常重要,特别是针对client\_ip和direct\_ip的输出结果,因为在生产环境下,可能经常会出现多级代理访问的情况,此时我们需要的IP是真实的客户端IP,而不是多级代理IP,那么在多级代理访问情况下,如何获取客户端真实IP地址,是ELK收集apache日志的一个难点和重点。
# 6、配置filebeat
filebeat是安装在apache服务器上的,关于filebeat的安装与基础应用,在前面已经做过详细介绍了,这里不再说明,仅给出配置好的filebeat.yml文件的内容:
```plain
filebeat.inputs:
- type: log
enabled: true
paths:
- /var/log/httpd/access.log
fields:
log_topic: apachelogs
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: false
name: 172.16.213.157
output.kafka:
enabled: true
hosts: ["172.16.213.51:9092", "172.16.213.75:9092", "172.16.213.109:9092"]
version: "0.10"
topic: '%{[fields.log_topic]}'
partition.round_robin:
reachable_only: true
worker: 2
required_acks: 1
compression: gzip
max_message_bytes: 10000000
logging.level: debug
```
这个配置文件中,是将apache的访问日志/var/log/httpd/access.log内容实时的发送到kafka集群topic为apachelogs中。需要注意的是filebeat输出日志到kafka中配置文件的写法。
配置完成后,启动filebeat即可:
```coffeescript
[root@filebeatserver ~]# cd /usr/local/filebeat
[root@filebeatserver filebeat]# nohup ./filebeat -e -c filebeat.yml &
```
启动完成后,可查看filebeat的启动日志,观察启动是否正常。
# 7、配置logstash
关于logstash的安装与基础应用,在前面已经做过详细介绍了,这里不再说明,仅给出logstash的事件配置文件。
由于在apache输出日志中已经定义好了日志格式,因此在logstash中就不需要对日志进行过滤和分析操作了,这样编写logstash事件配置文件就会简单很多,下面直接给出logstash事件配置文件kafka\_apache\_into\_es.conf的内容:
```php
input {
kafka {
bootstrap_servers => "172.16.213.51:9092,172.16.213.75:9092,172.16.213.109:9092" #指定输入源中kafka集群的地址。
topics => "apachelogs" #指定输入源中需要从哪个topic中读取数据。
group_id => "logstash"
codec => json {
charset => "UTF-8" #将输入的json格式进行UTF8格式编码。
}
add_field => { "[@metadata][myid]" => "apacheaccess_log" } #增加一个字段,用于标识和判断,在output输出中会用到。
}
}
filter {
if [@metadata][myid] == "apacheaccess_log" {
mutate {
gsub => ["message", "\\x", "\\\x"] #这里的message就是message字段,也就是日志的内容。这个插件的作用是将message字段内容中UTF-8单字节编码做替换处理,这是为了应对URL有中文出现的情况。
}
if ( 'method":"HEAD' in [message] ) { #如果message字段中有HEAD请求,就删除此条信息。
drop {}
}
json { #这是启用json解码插件,因为输入的数据是复合的数据结构,只是一部分记录是json格式的。
source => "message" #指定json格式的字段,也就是message字段。
add_field => { "[@metadata][direct_ip]" => "%{direct_ip}"} #这里添加一个字段,用于后面的判断。
remove_field => "@version" #从这里开始到最后,都是移除不需要的字段,前面九个字段都是filebeat传输日志时添加的,没什么用处,所以需要移除。
remove_field => "prospector"
remove_field => "beat"
remove_field => "source"
remove_field => "input"
remove_field => "offset"
remove_field => "fields"
remove_field => "host"
remove_field => "message" #因为json格式中已经定义好了每个字段,那么输出也是按照每个字段输出的,因此就不需要message字段了,这里是移除message字段。
}
mutate {
split => ["client_ip", ","] #这是对client_ip这个字段按逗号进行分组切分,因为在多级代理情况下,client_ip获取到的IP可能是IP列表,如果是单个ip的话,也会进行分组,只不过是分一个组而已。
}
mutate {
replace => { "client_ip" => "%{client_ip[0]}" } #将切分出来的第一个分组赋值给client_ip,因为client_ip是IP列表的情况下,第一个IP才是客户端真实的IP。
}
if [client_ip] == "-" { #这是个if判断,主要用来判断当client_ip为"-"的情况下,当direct_ip不为"-"的情况下,就将direct_ip的值赋给client_ip。因为在client_ip为"-"的情况下,都是直接不经过代理的访问,此时direct_ip的值就是客户端真实IP地址,所以要进行一下替换。
if [@metadata][direct_ip] not in ["%{direct_ip}","-"] { #这个判断的意思是如果direct_ip非空。
mutate {
replace => { "client_ip" => "%{direct_ip}" }
}
} else {
drop{}
}
}
mutate {
remove_field => "direct_ip" #direct_ip只是一个过渡字段,主要用于在某些情况下将值传给client_ip,因此传值完成后,就可以删除direct_ip字段了。
}
}
}
output {
if [@metadata][myid] == "apacheaccess_log" { #用于判断,跟上面input中[@metadata][myid]对应,当有多个输入源的时候,可根据不同的标识,指定到不同的输出地址。
elasticsearch {
hosts => ["172.16.213.37:9200","172.16.213.77:9200","172.16.213.78:9200"] #这是指定输出到elasticsearch,并指定elasticsearch集群的地址。
index => "logstash_apachelogs-%{+YYYY.MM.dd}" #指定apache日志在elasticsearch中索引的名称,这个名称会在Kibana中用到。索引的名称推荐以logstash开头,后面跟上索引标识和时间。
}
}
}
```
这个logstash事件配置文件的处理逻辑稍微复杂,主要是对日志中客户端真实IP的获取做了一些特殊处理,关于每个步骤实现的功能和含义,都在配置文件中做了注释。
所有配置完成后,就可以启动logstash了,执行如下命令:
```coffeescript
[root@logstashserver ~]# cd /usr/local/logstash
[root@logstashserver logstash]# nohup bin/logstash -f kafka_apache_into_es.conf &
```
logstash启动后,可以通过查看logstash日志,观察是否启动正常,如果启动失败,会在日志中有启动失败提示。
## 1.8、配置Kibana
filebeat收集数据到kafka,然后logstash从kafka拉取数据,如果数据能够正确发送到elasticsearch,我们就可以在Kibana中配置索引了。
登录Kibana,首先配置一个index\_pattern,点击kibana左侧导航中的Management菜单,然后选择右侧的Index Patterns按钮,最后点击左上角的Create index pattern,开始创建一个index pattern,如下图所示:
![](assets/1593327436-81b3713d8788dc2a963c89cc09539157.png)
这里需要填写一个Index pattern名称,根据在logstash事件配置文件中已经定义好的那个Index pattern,这里只需填入logstash\_apachelogs-\*即可,如果已经有对应的数据写入elasticsearch,那么kibana会自动检测到并抓取映射文件,
接着,点击下一步,选择按照@timestamp字段进行排序,如下图所示:
![](assets/1593327436-2f9c84a8d4f2b55c9414f070477c2ccd.png)
此时就可以创建此Index pattern了,创建完成Index pattern后,点击kibana左侧导航中的Discover导航栏,即可展示已经收集到的日志信息,如下图所示:
![](assets/1593327436-53a3b2c726cf7cc21e210cd85e7c399f.png)
在这个图中左下角部分,可以看到,是我们在apache配置文件中定义好的日志字段,默认情况下展示的是所有字段的内容,点击左下角对应的字段,即可添加到右边展示区域,因此,我们可以有选择性的查看或搜索某个字段的内容,做到对日志的精确监控。
Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB