近几年,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 一 一对应即可。