如何使用logstash更新已有的elasticsearch记录

来源:互联网 发布:顶点软件(603383)股吧 编辑:程序博客网 时间:2024/06/05 01:54

如何使用logstash更新已有的elasticsearch记录

常使用elasticsearch的童鞋,一定会遇到这种情况:我们需要修改已存储在ES中的数据,无论是数据内容或者是数据结构,来满足我们不断变化的需求。当我们需要修改数据的时,如果自己撸码一条一条的改动数据,不免有点低级,特别在大量的数据都需要修改的时候,这根本就是无法完成的任务。此时,势必要求助于工具。不知道Logstash是不是在其他领域也比较普及,但在ELK架构的日志分析系统里面,logstash是我们的ETL模块,对数据的提取,转换,丰富都是由它来完成的。它可以从其他数据源提取原始数据,经过转换,再把数据按照我们的要求输入到ES当中。也可以从ES里面提取已有的数据,进行再处理之后,再重新输出到ES当中。这样,我们就可以轻松的完成对已有数据的更新,美滋滋!当然,如果所有的原始数据还在,你也可以不用这么高级的玩法,直接把index删掉,把原始数据按照新的需求结构化后,重新导入ES也是可以的!但如果你还有点追求,接着往下看,顺便给我点个赞,我们来介绍怎么玩。

玩法简介

  1. 观察你的ES数据,确定需要修改的项,比如:
    • @timestamp和你日志中的timestamp没有对应上
    • 数据中的url需要拆分成更细verb, request, host, device...等项
  2. 提取一些样本数据到本地的ES上
  3. 在logstash里面配置elasticsearch input plugin,读取本地ES的样本数据,并使用filter plugin重构你的数据
  4. 反复修改,直到logstash能够正确处理所有样本
  5. 将logstash配置为读取服务器的需要修改的数据(需要正确的query),并重构你的数据。

以上,就是一个比较直观简短的介绍,很简单。。。

软件要求

其实要求只有一个,那就是把你logstash的elasticSearch input plugin升级到5.3.*版本以上,我在5.2.2版本上就遇到了下面的bug:

Error: [400] {"error":{"root_cause":[{"type":"illegal_argument_exception","reason":"Failed to parse request body"}],"type":"illegal_argument_exception","reason":"Failed to parse request body","caused_by":{"type":"json_parse_exception","reason":"Unrecognized token 'DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAgJFmlmNnYtNlZkU1B5TmlhRjU4QkJLZkEAAAAAAAAIChZpZjZ2LTZWZFNQeU5pYUY1OEJCS2ZBAAAAAAAACAsWaWY2di02VmRTUHlOaWFGNThCQktmQQAAAAAAAAgMFmlmNnYtNlZkU1B5TmlhRjU4QkJLZkEAAAAAAAAIDRZpZjZ2LTZWZFNQeU5pYUY1OEJCS2ZB': was expecting ('true', 'false' or 'null')\n at [Source: org.elasticsearch.transport.netty4.ByteBufStreamInput@7b31ce02; line: 1, column: 457]"}},"status":400}

这个问题出现在使用logstash从ES读取数据时。当出现这个问题时,logstash会一直从ES上不停的query数据,药不能停。 当然,我的整个集群都是使用的最新版本的(5.5.*)的ES,Kibana, Logstash,我不保证用低版本也能完成同样的事情。

配置logstash

input{ # 在写入样本时,可以先使用stdin plugin往ES里面写入样本数据  # stdin{  #   type => "test"  # }# 使用以下的配置,从ES中读入数据  elasticsearch {    hosts => "localhost:9200"    index => "test-*"    query => '{ "query": { "query_string": { "tags": "_grokparsefailure" } } }'    size => 500    scroll => "5m"    docinfo => true    codec => json  }}# 以下filter只是一个例子,不用在意filter {  mutate{      remove_tag => ["_grokparsefailure"]  }  grok {    patterns_dir => ["./patterns"]    match => {      "message" => "%{PLATFORM_SYSLOG}"    }  }  date {    match => ["timestamp", "MMM  dd HH:mm:ss", "MMM dd HH:mm:ss", "ISO8601"]  }  if "_grokparsefailure" not in [tags]  {    mutate{      remove_field => ["message"]    }  }}output{    # stdout{    #   codec => json    # } # 在我的实践中,我会先把ES里面的原记录删掉,再创建一条新的记录,当然,也可以直接update  elasticsearch {    hosts => ["localhost:9200"]    # action => "update"    action => "delete"    document_type => "%{[@metadata][_type]}"    document_id => "%{[@metadata][_id]}"    index =>  "%{[@metadata][_index]}"    user => "elastic"    password => "changeme"  }  elasticsearch{    hosts => ["localhost:9200"]    #index =>  "test-%{+YYYY.MM.dd}"    index =>  "%{[@metadata][_index]}"    user => "elastic"    password => "changeme"  } }

图文步骤

问题document

屏幕快照_2017-09-15_下午4.25.25
可以看到,这条记录没有被正确的解析,和这条记录类似的其他记录都有这样的问题,我们需要对它重新解析

输入样本数据

把这条无法解析的数据,放到本地的ES上做测试。将logstash的input配置成stdin, 然后导入。

配置

input{  stdin{    type => "test"  }}output{  elasticsearch{    hosts => ["localhost:9200"]    index =>  "test-%{+YYYY.MM.dd}"    user => "elastic"    password => "changeme"  } }

导入

启动logstash,输入无法解析的数据

[2017-09-15T17:13:20,609][INFO ][logstash.pipeline        ] Starting pipeline {"id"=>"main", "pipeline.workers"=>8, "pipeline.batch.size"=>125, "pipeline.batch.delay"=>5, "pipeline.max_inflight"=>1000}[2017-09-15T17:13:20,620][INFO ][logstash.pipeline        ] Pipeline main startedThe stdin plugin is now waiting for input:[2017-09-15T17:13:20,662][INFO ][logstash.agent           ] Successfully started Logstash API endpoint {:port=>9600},Sep 15 03:29:02,HostName=sz190034,IP=10.60.22.117,Tag=run-parts(/etc/cron.daily)[19239,ProgramName=run-parts(,Procid=,Facility=cron,Sev=notice,AppName=run-parts(,Msg= finished prelink

可以看到,ES里面已经有了这条数据:

屏幕快照_2017-09-15_下午5.15.20

从ES中读入数据,并现场修改

继续在本地环境上,验证我们的修改,使用之前提到的logstash配置,从ES上读入数据,用filter对数据内容进行更新,然后再放回ES。这里需要注意的是,我们既可以在原有的记录上直接修改,也可以在将原记录删除,把新的记录填上。对ES已有的记录进行更新或删除,请记得下面三项是必须配置:

    index =>  "%{[@metadata][_index]}"    document_type => "%{[@metadata][_type]}"    document_id => "%{[@metadata][_id]}"

"%{[@metadata][_index]}"是原记录所在的索引,"%{[@metadata][_type]}"是原记录的类型,"%{[@metadata][_id]}"是原记录的id。这三条缺一不可,特别是索引(其他两项是必填,不填logstash的插件会报错),因为它不会报错,但是update会不成功。 使用如下的配置重启logstash:

  elasticsearch {    hosts => "localhost:9200"    index => "test-*"    query => '{ "query": { "query_string": { "query": "*" } } }'    size => 500    scroll => "5m"    docinfo => true    codec => json  }}filter {  mutate{      # remove_field => ["@timestamp"]      remove_tag => ["_grokparsefailure"]  }  grok {    patterns_dir => ["./patterns"]    match => {      "message" => "%{PLATFORM_SYSLOG}"    }  }  date {    match => ["timestamp", "MMM  dd HH:mm:ss", "MMM dd HH:mm:ss", "ISO8601"]  }  if "_grokparsefailure" not in [tags]  {    mutate{      remove_field => ["message"]    }  }}output{  elasticsearch {    hosts => ["localhost:9200"]    action => "delete"    document_type => "%{[@metadata][_type]}"    document_id => "%{[@metadata][_id]}"    index =>  "%{[@metadata][_index]}"    user => "elastic"    password => "changeme"  }  elasticsearch{    hosts => ["localhost:9200"]    index =>  "test-%{+YYYY.MM.dd}"    user => "elastic"    password => "changeme"  } }

它就会把本地ES上的数据取下来,丢给filter,处理之后,再丢回给ES。结果如下:

屏幕快照_2017-09-15_下午5.32.56
数据被正确解析,并且时间戳被更新

在ES的服务器上更新

在本地验证无误之后,就可以将logstash的elasticsearch input plugin和output plugin的地址改为服务器的地址,并更新一下query的规则,选出需要更新的记录,比如:

  elasticsearch {    hosts => "10.60.47.168:9200"    index => "platform-*"    query => '{ "query": { "term": { "tags": "_grokparsefailure" } } }'    size => 500    scroll => "5m"    docinfo => true    codec => json  }

再进行数据的更新。

小结

我们可以看到,通过logstash的两个plugin:

  • elasticsearch input plugin
  • elasticsearch output plugin

我们可以方便的选出我们希望更新的es的数据,在进行修改之后,再放回es当中。整个过程很简单,但在实际的操作过程中,已经要做足够的测试,争取只对数据做一遍更新。 像上面的例子,如果第一遍过后,发现还要动规则,你就要注意,有些新的field已经被logstash添加到es当中,有一些老的field已经被删除。这时你就不能再用第一遍时的filter了,你的整个filter需要重写,并且在原始数据已经被改过的情况下,还会出现不可逆的数据丢失。所以,要慎行!