近几年,ELK听的我耳朵起茧子了,是人是鬼,一说到数据采集就一定会提到ELK,包括我目前所在的公司。我用flume用了好些年了,所以一直对ELK没有过多的关注,主要原因是因为flume用了之后没有发现有什么不能满足我的地方。如果说flume有什么优点,那就是结构清晰明了,source, channel, sink 分别对应,从哪里来,放那里去,通过正规表达式分割字段,配置一看就明白。要说flume缺点,那肯定也有. flume这个玩意有些东西默认不支持,需要自己二次开发。 比如flume在读取单个文件的时候,我们通常使用tail,但是不支持position记录,再比如,web日志每天晚上会rotate, flume也不支持变量文件名,比如今天日志是log.20180227, 明天就是log.20180228了,这个是不支持的。也许你们会说,那用spool或者日志名不要改变,是可以,但总归这是个缺点,一个产品毕竟也做不到那么完美,很多东西提供了接口,自己二次开发来完成自己需要的功能。
因为ELK说的太多了,因此我想看看到底这个东西有什么地方值得推崇的地方,花了几天把filebeat, logstash的官方文档全部看完了,谈不上精通,但是已经比较了解了。既然要谈ELK,就应该先说说历史,我也是从网上看到的,好像最开始作者写了logstash来采集日志,结构包含input, filter, output,其实就是类似flume的结构。 后来被elastic收购了,作者自然也加入了elastic,考虑到logstash采集性能不是太好,elastic公司又用go语言写了filebeat来替代logstash的采集功能。至于filter, output还是由logstash来完成,当然filebeat本身也有output功能。
filebeat简单理解就是一个单独的日志采集器,包含2个采集type, stdin和log, 大概4种常用的output (ES,KAFKA,REDIS...).一个filebeat实例一次只能支持一个output格式, 采集的文件可以设置多个,文件名可以使用通配符等等,从input的角度来说,够用了。 问题在于一个filebeat实例如果采集不同格式日志怎么办? 因为output只支持一种啊,2个不同格式的日志一起采集,output设置为ES,那就意味着不同格式的日志进入了ES同一个INDEX,这么说也许你会不同意,你会说我可以通过when contains条件来建立不同索引,也许你还会说,我可以设置tag, fileds,这样就可以在同一个索引中区别不同数据,或者你还会说,我可以启动多个filebeat实例啊。 OK,我不想和你争论这个了。 另外 filebeat还有module的功能,大概是说你如果要使用filebeat来分割字段也是可以的,设置好 module的正规表达式,比如 slow log, 默认支持解析的,这样就不需要发送到logstash,这个功能我没有使用过,不太确定。
由于filebeat没有像logstash这么强大的分割字段的功能,因此采集文件的行直接存储在ES里面就是一个message,如果你能接受这个,那么实际也就不需要logstash了,大部分人还是希望能够把日志根据字段进行拆分,存储的到ES的时候有对应的字段。比如mysql slow log,包含SQL text, start time, userhost等等,如果直接一整条存储,你也很难进行分析。 当然如果你的output设置为 kafka, 然后自己通过程序来进行拆分也可以,总之实现字段拆分的办法太多了。
因为filebeat在开发的时候就仅仅是用作采集,所以不包含filter的功能,这也就能够说明为什么我们要引入logstash. filebeat采集的文件行,传递给logstash, logstash进行字段拆分,过滤,字段改名等等,再输出到ES等外部存储。说到这里好像没啥可说的了。那就就先来看看实际的操作吧。
filebeat, logstash没有什么安装不安装,就是解压缩,然后修改配置文件启动就可以,我这里以mysql slow log为例子。
filebeat配置:
#=========================== Filebeat prospectors =============================
filebeat.prospectors:
- type: log
enabled: true
fields:
appid: 112
tags: ["slowlog"]
close_removed: true
close_rename: true
clean_removed: true
clean_inactive: "48h"
scan_frequency: "3s"
ignore_older: "24h"
fields_under_root: true
paths:
- /var/log/slow_log.CSV
queue.mem:
events: 4096
flush.min_events: 512
flush.timeout: "3s"
#============================= Filebeat modules ===============================
filebeat.config.modules:
path: ${path.config}/modules.d/*.yml
reload.enabled: true
reload.period: 10s
#==================== Elasticsearch template setting ==========================
setup.template.settings:
index.number_of_shards: 3
#index.codec: best_compression
#_source.enabled: false
setup.template.name: slowlog
setup.template.fields: "/data/filebeat/slowlog.yml"
setup.template.pattern: "slowlog-*"
#-------------------------- Elasticsearch output ------------------------------
#output.elasticsearch:
# hosts: ["10.215.4.166:9200","10.215.4.167:9200"]
# index: slowlog
output.logstash:
hosts: ["10.215.4.166:5044"]
setup.kibana:
host: "10.10.192.88:5601"
采集/var/log/slow_log.CSV, output设置为logstash,端口是5044. 这里我要提一下,很多人采集慢查询非要去采集慢查询文件,设置mutil lines的方式来采集,MySQL的慢查询如果存储到表,会直接有一个CSV文件,每个慢查询就是一行,你们又何必通过mutil lines采集呢? 通过单行采集不好吗?
上面配置设置好了,就可以启动filebeat了。
logstash配置:
input {
beats {
type => "slowlog"
port => "5044"
}
beats {
type => "nginx"
port => "5045"
}
}
filter {
if [type] == "slowlog" {
grok {
match => {
"message" => "%{QS:start_time},%{QS:user_host},%{QS:query_time},%{QS:lock_time},%{INT:rows_sent},%{INT:rows_examined},%{QS:db},%{INT:last_insert_id},%{INT:insert_id},%{INT:server_id},%{QS:sql_text},%{INT:thread_id}"
}
}
mutate {
remove_field => "thread_id"
}
mutate {
remove_field => "insert_id"
}
} else {
grok {
match => {
"message" => "%{QS:start_time},%{QS:user_host},%{QS:query_time},%{QS:lock_time},%{INT:rows_sent},%{INT:rows_examined},%{QS:db},%{INT:last_insert_id},%{INT:insert_id},%{INT:server_id},%{QS:sql_text},%{INT:thread_id}"
}
}
}
}
output {
if [type] == "slowlog" {
elasticsearch {
hosts => ["10.215.4.166:9200", "10.215.4.167:9200"]
index => "slow_log"
}
} else {
elasticsearch {
hosts => ["10.215.4.166:9200", "10.215.4.167:9200"]
index => "slowlog"
}
}
}
这里你们看到有2个input, 2个grok, 2个 output, 这是我做测试用,你们只管那个5044端口的配置即可。logstash设置的input为beat, 实际就是filebeat,filebeat设置的logstash端口是5044,logstash配置也设置了一个5044用来对应起来。然后就是正规表达式来拆分慢查询。http://grokdebug.herokuapp.com/ 这个是grok正规表达式的验证网站,确认拆分正确。
启动filebeat和logstash, 通过ES pugin-head 查看索引数据:
这是一个很常用,但是很实用的例子。
生产环境有很多种日志,也许这些不同格式的日志在一台服务器上。一个filebeat实例处理这种问题实际没有太好的办法,启动多个实例可能是比较好的办法,一个实例对应一种日志,logstash通过不同端口来对应不同的 input。因为历史原因导致filebeat和logstash结合起来感觉就像东拼西凑一样,filebeat和logstash有一些功能又重叠,让人看着乱78糟。你要么就作为采集,要么就作为转换,2个东西都支持采集,转换,存储,啥玩意,那还不如做成一个东西。
flume 配置:
a1.sources = r1
a1.sinks = k1
a1.channels = c1
a1.sources.r1.type = org.apache.flume.source.external.MultilineExecSource
a1.sources.r1.multilineRegex = "(.*?)",".*
a1.sources.r1.command = tail -F /opt/mysql_data/mysql/slow_log.CSV
a1.sources.r1.channels = c1
a1.sources.r1.interceptors = i1 i2 i3 i4
a1.sources.r1.interceptors.i1.type = timestamp
a1.sources.r1.interceptors.i2.type = host
a1.sources.r1.interceptors.i2.hostHeader = machine_ip
a1.sources.r1.interceptors.i2.useIP = true
a1.sources.r1.interceptors.i3.type = host
a1.sources.r1.interceptors.i3.hostHeader = machine_host
a1.sources.r1.interceptors.i3.useIP = false
a1.sources.r1.interceptors.i4.type =static
a1.sources.r1.interceptors.i4.key = dbtype
a1.sources.r1.interceptors.i4.value = mysql
a1.channels.c1.type=file
a1.channels.c1.write-timeout=10
a1.channels.c1.keep-alive=10
a1.channels.c1.checkpointDir=/data/opbin/flume-ng/c1/checkpoint
a1.channels.c1.dataDirs=/data/opbin/flume-ng/c1/data
a1.channels.c1.maxFileSize= 268435456
a1.sinks.k1.type = org.apache.flume.sink.hbase.HBaseSink
a1.sinks.k1.table = db_slow_query
a1.sinks.k1.columnFamily = cf
a1.sinks.k1.batchSize = 100
a1.sinks.k1.serializer = org.apache.flume.sink.hbase.external.HostRegexHbaseEventSerializer
a1.sinks.k1.channel = c1
a1.sinks.k1.serializer.regex = "((\\d{4})-(\\d{2})-(\\d{2})\\s(\\d{2}).*?)","(.*?)","(.*?)","(.*?)",(.*?),(.*?),"(.*?)",(.*?),(.*?),(.*?),"(.*?)"
a1.sinks.k1.serializer.colNames = start_time,year,month,day,hour,user_host,query_time,lock_time,rows_sent,rows_examined,db,last_insert_id,insert_id,server_id,sql_text
a1.sinks.k1.serializer.regexIgnoreCase = true
a1.sinks.k1.serializer.depositHeaders = true
对比filebeat+logstash, 再看看flume配置,我个人是更喜欢 flume, 没那么多麻烦事,又是通过type 来区分,又是通过多实例, flume就source, channel, sink 一 一对应即可。