作为系列文章的第四篇。本文将重点探讨数据採集层中的ELK日志系统。日志。指的是后台服务中产生的log信息,一般会输入到不同的文件里。比方Django服务下,一般会有nginx日志和uWSGI日志。
这些日志分散地存储在不同的机器上,取决于服务的部署情况了。假设我们依次登录每台机器去查阅日志,显然非常繁琐,效率也非常低,并且也没法进行统计和检索。
因此。我们须要对日志进行集中化管理,将全部机器上的日志信息收集、汇总到一起。
完整的日志数据具有非常关键的数据:
- 信息查找。通过检索日志信息。定位对应的bug。找出解决方式。
- 服务诊断。
通过对日志信息进行统计、分析。了解服务器的负荷和服务执行状态,找出耗时请求进行优化等等。
- 数据分析。假设是格式化的log。能够做进一步的数据分析,统计、聚合出有意义的信息。比方依据请求中的商品id。找出TOP10用户感兴趣商品。
ELK是一套开源的集中式日志数据管理的解决方式,由Elasticsearch、Logstash和Kibana三个系统组成。最初我们建设ELK日志系统的目的是做数据分析。记得第一个需求是期望利用nginx的日志。从API请求的參数中挖掘出用户的位置分布信息。后来该系统在追踪恶意刷量、优化耗时服务等方面都发挥了重要作用。并且随着对Elasticsearch的认知加深,我们将其应用到了其它方面的数据存储和分析中。 本文的重点是结合自身实践来介绍怎样使用ELK系统、使用中的问题以及怎样解决,文中涉及的ELK版本号是:Elasticsearch 2.3、Logstash 2.3、Kibana 4。
ELK总体方案
ELK中的三个系统分别扮演不同的角色。组成了一个总体的解决方式。
Logstash是一个ETL工具,负责从每台机器抓取日志数据,对数据进行格式转换和处理后,输出到Elasticsearch中存储。
Elasticsearch是一个分布式搜索引擎和分析引擎。用于数据存储。可提供实时的数据查询。Kibana是一个数据可视化服务,依据用户的操作从Elasticsearch中查询数据,形成对应的分析结果。以图表的形式展现给用户。
ELK的安装非常easy,能够依照“下载->改动配置文件->启动”方法分别部署三个系统。也能够使用docker来高速部署。具体的安装方法这里不具体介绍,我们来看一个常见的部署方案,例如以下图所看到的,部署思路是:
- 第一,在每台生成日志文件的机器上,部署Logstash,作为Shipper的角色,负责从日志文件里提取数据,可是不做不论什么处理,直接将数据输出到Redis队列(list)中。
- 第二。须要一台机器部署Logstash,作为Indexer的角色,负责从Redis中取出数据,对数据进行格式化和相关处理后,输出到Elasticsearch中存储;
- 第三,部署Elasticsearch集群。当然取决于你的数据量了。数据量小的话能够使用单台服务。假设做集群的话,最好是有3个以上节点,同一时候还须要部署相关的监控插件。
- 第四,部署Kibana服务,提供Web服务。
在前期部署阶段,主要工作是Logstash节点和Elasticsearch集群的部署。而在后期使用阶段。主要工作就是Elasticsearch集群的监控和使用Kibana来检索、分析日志数据了,当然也能够直接编敲代码来消费Elasticsearch中的数据。
在上面的部署方案中,我们将Logstash分为Shipper和Indexer两种角色来完毕不同的工作。中间通过Redis做数据管道。为什么要这样做?为什么不是直接在每台机器上使用Logstash提取数据、处理、存入Elasticsearch?
首先。採用这种架构部署,有三点优势:第一,减少对日志所在机器的影响。这些机器上一般都部署着反向代理或应用服务,本身负载就非常重了,所以尽可能的在这些机器上少做事;第二。假设有非常多台机器须要做日志收集,那么让每台机器都向Elasticsearch持续写入数据,必定会对Elasticsearch造成压力,因此须要对数据进行缓冲。同一时候,这种缓冲也能够一定程度的保护数据不丢失。第三,将日志数据的格式化与处理放到Indexer中统一做。能够在一处改动代码、部署。避免须要到多台机器上去改动配置。
其次,我们须要做的是将数据放入一个消息队列中进行缓冲,所以Redis仅仅是当中一个选择。也能够是RabbitMQ、Kafka等等,在实际生产中,Redis与Kafka用的比較多。因为Redis集群一般都是通过key来做分片,无法对list类型做集群,在数据量大的时候必定不合适了,而Kafka天生就是分布式的消息队列系统。
Logstash
在官方文档中。Deploying and Scaling Logstash一文具体介绍了各种Logstash的部署架构,下图是与我们上述方案相吻合的架构。Logstash由input、filter和output三部分组成,input负责从数据源提取数据,filter负责解析、处理数据,output负责输出数据。每部分都有提供丰富的插件。
Logstash的设计思路也非常值得借鉴,以插件的形式来组织功能,通过配置文件来描写叙述须要插件做什么。我们以nginx日志为例。来看看怎样使用一些经常使用插件。
1. 配置nginx日志格式
首先须要将nginx日志格式规范化,便于做解析处理。
在nginx.conf文件里设置:
log_format main '$remote_addr "$time_iso8601" "$request" $status $body_bytes_sent "$http_user_agent" "$http_referer" "$http_x_forwarded_for" "$request_time" "$upstream_response_time" "$http_cookie" "$http_Authorization" "$http_token"';
access_log /var/log/nginx/example.access.log main;
2. nginx日志–>>Logstash–>>消息队列
这部分是Logstash Shipper的工作,涉及input和output两种插件。
input部分。因为须要提取的是日志文件,一般使用file插件,该插件经常使用的几个參数是:
- path,指定日志文件路径。
- type,指定一个名称,设置type后。能够在后面的filter和output中对不同的type做不同的处理。适用于须要消费多个日志文件的场景。
- start_position。指定起始读取位置,“beginning”表示从文件头開始。“end”表示从文件尾開始(相似tail -f)。
- sincedb_path。与Logstash的一个坑有关。
通常Logstash会记录每一个文件已经被读取到的位置,保存在sincedb中,假设Logstash重新启动,那么对于同一个文件,会继续从上次记录的位置開始读取。假设想又一次从头读取文件,须要删除sincedb文件。sincedb_path则是指定了该文件的路径。
为了方便,我们能够依据须要将其设置为“/dev/null”,即不保存位置信息。
input {
file {
type => "example_nginx_access"
path => ["/var/log/nginx/example.access.log"]
start_position => "beginning"
sincedb_path => "/dev/null"
}
}
output部分,将数据输出到消息队列,以redis为例,须要指定redis server和list key名称。
另外。在測试阶段,能够使用stdout来查看输出信息。
# 输出到redis
output {
if [type] == "example_nginx_access" {
redis {
host => "127.0.0.1"
port => "6379"
data_type => "list"
key => "logstash:example_nginx_access"
}
# stdout {codec => rubydebug}
}
}
3. 消息队列–>>Logstash–>>Elasticsearch
这部分是Logstash Indexer的工作,涉及input、filter和output三种插件。在input部分。我们通过redis插件将数据从消息队列中取出来。在output部分,我们通过elasticsearch插件将数据写入Elasticsearch。
# 从redis输入数据
input {
redis {
host => "127.0.0.1"
port => "6379"
data_type => "list"
key => "logstash:example_nginx_access"
}
}
output {
elasticsearch {
index => "logstash-example-nginx-%{+YYYY.MM}"
hosts => ["127.0.0.1:9200"]
}
}
这里。我们重点关注filter部分。以下列举几个经常使用的插件。实际使用中依据自身需求从官方文档中查找适合自己业务的插件并使用就可以,当然也能够编写自己的插件。
- grok。是Logstash最重要的一个插件,用于将非结构化的文本数据转化为结构化的数据。grok内部使用正则语法对文本数据进行匹配,为了减少使用复杂度。其提供了一组pattern,我们能够直接调用pattern而不须要自己写正則表達式。參考源代码grok-patterns。
grok解析文本的语法格式是%{SYNTAX:SEMANTIC}。SYNTAX是pattern名称。SEMANTIC是须要生成的字段名称。使用工具Grok Debugger能够对解析语法进行调试。比如,在以下的配置中,我们先使用grok对输入的原始nginx日志信息(默认以message作为字段名)进行解析。并加入新的字段request_path_with_verb(该字段的值是verb和request_path的组合),然后对request_path字段做进一步解析。
- kv,用于将某个字段的值进行分解,相似于编程语言中的字符串Split。在以下的配置中。我们将request_args字段值依照“&”进行分解,分解后的字段名称以“request_args_”作为前缀。并且丢弃反复的字段。
- geoip,用于依据IP信息生成地理位置信息。默认使用自带的一份GeoLiteCity database,也能够自己更换为最新的数据库,可是须要数据格式须要遵循Maxmind的格式(參考GeoLite),似乎眼下仅仅能支持legacy database。数据类型必须是.dat。下载GeoLiteCity.dat.gz后解压。 并将文件路径配置到source中就可以。
- translate,用于检測某字段的值是否符合条件,假设符合条件则将其翻译成新的值,写入一个新的字段。匹配pattern能够通过YAML文件来配置。比如,在以下的配置中,我们对request_api字段翻译成更加易懂的文字描写叙述。
filter {
grok {
match => {"message" => "%{IPORHOST:client_ip} "%{TIMESTAMP_ISO8601:timestamp}" "%{WORD:verb} %{NOTSPACE:request_path} HTTP/%{NUMBER:httpversion}" %{NUMBER:response_status:int} %{NUMBER:response_body_bytes:int} "%{DATA:user_agent}" "%{DATA:http_referer}" "%{NOTSPACE:http_x_forwarder_for}" "%{NUMBER:request_time:float}" "%{DATA:upstream_resopnse_time}" "%{DATA:http_cookie}" "%{DATA:http_authorization}" "%{DATA:http_token}""}
add_field => {"request_path_with_verb" => "%{verb} %{request_path}"}
}
grok {
match => {"request_path" => "%{URIPATH:request_api}(?:?%{NOTSPACE:request_args}|)"}
add_field => {"request_annotation" => "%{request_api}"}
}
kv {
prefix => "request_args_"
field_split => "&"
source => "request_args"
allow_duplicate_values => false
}
geoip {
source => "client_ip"
database => "/home/elktest/geoip_data/GeoLiteCity.dat"
}
translate {
field => request_path
destination => request_annotation
regex => true
exact => true
dictionary_path => "/home/elktest/api_annotation.yaml"
override => true
}
}
Elasticsearch
Elasticsearch承载了数据存储和查询的功能,其基础概念和用法能够參考还有一篇博文Elasticsearch使用总结,这里主要介绍些实际生产中的问题和方法:
- 关于集群配置,重点关注三个參数:第一,discovery.zen.ping.unicast.hosts,Elasticsearch默认使用Zen Discovery来做节点发现机制,推荐使用unicast来做通信方式。在该配置项中列举出Master节点。第二,discovery.zen.minimum_master_nodes。该參数表示集群中可工作的具有Master节点资格的最小数量,默认值是1。为了提高集群的可用性,避免脑裂现象(所谓脑裂,就是同一个集群中的不同节点,对集群的状态有不一致的理解。
)。官方推荐设置为(N/2)+1。当中N是具有Master资格的节点的数量。第三,discovery.zen.ping_timeout,表示节点在发现过程中的等待时间,默认值是3秒。能够依据自身网络环境进行调整,一定程度上提供可用性。
discovery.zen.ping.unicast.hosts: ["master1", "master2", "master3"]
discovery.zen.minimum_master_nodes: 2
discovery.zen.ping_timeout: 10
- 关于集群节点。第一。节点类型包括:候选Master节点、数据节点和Client节点。通过设置两个配置项node.master和node.data为true或false,来决定将一个节点分配为什么类型的节点。第二,尽量将候选Master节点和Data节点分离开,通常Data节点负载较重,须要考虑单独部署。
- 关于内存。Elasticsearch默认设置的内存是1GB。对于不论什么一个业务部署来说,这个都太小了。通过指定ES_HEAP_SIZE环境变量,能够改动其堆内存大小,服务进程在启动时候会读取这个变量。并对应的设置堆的大小。
建议设置系统内存的一半给Elasticsearch,可是不要超过32GB。參考官方文档。
- 关于硬盘空间,Elasticsearch默认将数据存储在/var/lib/elasticsearch路径下,随着数据的增长,一定会出现硬盘空间不够用的情形,此时就须要给机器挂载新的硬盘。并将Elasticsearch的路径配置到新硬盘的路径下。通过“path.data”配置项来进行设置,比方“path.data: /data1,/var/lib/elasticsearch,/data”。须要注意的是,同一分片下的数据仅仅能写入到一个路径下,因此还是须要合理的规划和监控硬盘的使用。
- 关于Index的划分和分片的个数。这个须要依据数据量来做权衡了。Index能够按时间划分,比方每月一个或者每天一个,在Logstash输出时进行配置,shard的数量也须要做好控制。
- 关于监控。笔者使用过head和marvel两个监控插件。head免费,功能相对有限,marvel如今须要收费了。另外。不要在数据节点开启监控插件。
Kibana
Kibana提供的是数据查询和显示的Web服务。有丰富的图表样板。能满足大部分的数据可视化需求,这也是非常多人选择ELK的主要原因之中的一个。UI的操作没有什么特别须要介绍的,经常使用就会熟练,这里主要介绍经常遇到的三个问题。
1. 查询语法
在Kibana的Discover页面中,能够输入一个查询条件来查询所需的数据。查询条件的写法使用的是Elasticsearch的Query String语法,而不是Query DSL,參考官方文档query-string-syntax,这里列举当中部分经常使用的:
- 单字段的全文检索。比方搜索args字段中包括first的文档,写作 args:first;
- 单字段的精确检索。比方搜索args字段值为first的文档,写作 args: “first”。
- 多个检索条件的组合,使用 NOT, AND 和 OR 来组合。注意必须是大写。比方 args:(“first” OR “second”) AND NOT agent: “third”;
- 字段是否存在,_exists_:agent表示要求agent字段存在,_missing_:agent表示要求agent字段不存在;
- 通配符:用 ?
表示单字母。* 表示随意个字母。
2. 错误“Discover: Request Timeout after 30000ms”
这个错误经常发生在要查询的数据量比較大的情况下。此时Elasticsearch须要较长时间才干返回,导致Kibana发生Timeout报错。解决问题的方法,就是在Kibana的配置文件里改动elasticsearch.requestTimeout一项的值。然后重新启动Kibana服务就可以,注意单位是ms。
3. 疑惑“字符串被分解了”
经常在QQ群里看到一些人在问这样一个问题:为什么查询结果的字段值是正确的,可是做图表时却发现字段值被分解了,不是想要的结果?例如以下图所看到的的client_agent_info字段。
得到这样一个不对结果的原因是使用了Analyzed字段来做图表分析,默认情况下Elasticsearch会对字符串数据进行分析,建立倒排索引,所以假设对这么一个字段进行terms聚合,必定会得到上面所看到的的错误结果了。
那么应该怎么做才对?默认情况下,Elasticsearch还会创建一个相对应的没有被Analyzed的字段,即带“.raw”后缀的字段。在这种字段上做聚合分析就可以。
又会有非常多人问这种问题:为什么我的Elasticsearch没有自己主动创建带“.raw”后缀的字段?然而在Logstash中输出数据时。设置index名称前缀为“logstash-”就有了这个字段。
这个问题的根源是Elasticsearch的dynamic template在捣鬼(能够查看博文Elasticsearch使用总结中的具体介绍)。dynamic temlate用于指导Elasticsearch怎样为插入的数据自己主动建立Schema映射关系,默认情况下,Logstash会在Elasticsearch中建立一个名为“logstash”的模板,全部前缀为“logstash-”的index都会參照这个模板来建立映射关系,在该模板中申明了要为每一个字符串数据建立一个额外的带“.raw”后缀的字段。能够向Elasticsearch来查询你的模板,使用API:GET http://localhost:9200/_template。
以上便是对ELK日志系统的总结介绍,还有一个重要的功能没有提到。就是怎样将日志数据与自身产品业务的数据融合起来。举个样例。在nginx日志中,一般会包括API请求訪问时携带的用户Token信息,因为Token是有时效性的,我们须要及时将这些Token转换成真实的用户信息存储下来。这种需求通常有两种实现方式,一种是自己写一个Logstash filter,然后在Logstash处理数据时调用。还有一种是将Logstash Indexer产生的数据再次输出到消息队列中,由我们自己的脚本程序从消息队列中取出数据,做对应的业务处理后。输出到Elasticsearch中。
眼下,团队对ruby技术栈不是非常熟悉,所以我们採用了另外一种方案来实施。
当前,我们的数据增长相对缓慢,遇到的问题也有限,随着数据量的添加。未来一定会遇到很多其它的挑战。也能够进一步探索ELK。
(全文完,本文地址:http://blog.csdn.net/zwgdft/article/details/53842574)
Bruce,2017/01/06