Logstash:Logstash 入门教程 (二)

这是之前系列文章“Logstash:Logstash 入门教程 (一)”的续集。在之前的文章中,我们详细地介绍了Logstash是什么?在今天的文章中,我们将详细介绍如果使用Logstash,并把Apache Web log导入到Elasticsearch中。在这篇文章中,我们将触及到如下的过滤器:

安装

Elasticsearch

如果你还没有安装好自己的Elasticsearch,请参阅我之前的文章“如何在Linux,MacOS及Windows上进行安装Elasticsearch”来安装好自己的Elasticsearch。

Kibana

如果你还没有安装好自己的Kibana,请参阅我之前的文章“如何在Linux,MacOS及Windows上安装Elastic栈中的Kibana”安装好自己的Kibana。

Logstash

如果你还没有安装好自己的Logstash,请参阅我之前的文章“如何安装Elastic栈中的Logstash”来安装好自己的Logstash。

 

如何运行Logstash

在Mac, Unix及Linux下,我们可以使用如下的方式来进行运行:

bin/logstash [options]

在Windows环境下,我们使用如下的方式来运行:

bin/logstash.bat [options]

在通常情况下,我们需要跟上一些选项才可以启动Logstash,否则它会退出。除非有一种情况,在我们启动monitoring后,可以不添加选项来启动Logstash。关于如何启动集中管理,请参阅我之前的文章“Logstash: 启动监控及集中管理”。

 

如何配置 Logstash pipeline

Logstash管道有两个必需元素,输入和输出,以及一个可选元素filter。 输入插件使用来自源的数据,过滤器插件在您指定时修改数据,输出插件将数据写入目标。

 

要测试Logstash安装,请运行最基本的Logstash管道。 例如:

cd logstash-7.6.2
bin/logstash -e 'input { stdin { } } output { stdout {} }'

等Logstash完成启动后,我们在stdin里输入一下文字,我们可以看到如下的输出:

当我们打入一行字符然后回车,那么我们马上可以在stdout上看到输出的信息。如果我们能看到这个输出,说明我们的Logstash的安装是成功的。

另外一种运行Logstash的方式,也是一种最为常见的运行方式。我们首先需要创建一个配置文件,比如:

heartbeat.conf

input {
  heartbeat {
    interval => 10
    type => "heartbeat"
  }
}
 
output {
  stdout {
    codec => rubydebug
  }
}

然后,我们通过如下的方式来运行logstash:

bin/logstash -f heartbeat.conf

那么我们可以在console中看到如下的输出:

动手实践

在这一节中,我们将使用一个例子来一步一步地详细介绍如何使用Logstash来实现我们的数据处理。

1)首先启动我们的Elasticsearch及Kibana。请参照之前的步骤运行Elasticsearch及Kibana。

2)我们进入到Logstash安装目录,并修改config/logstash.yml文件。我们把 config.reload.automatic 设置为true。

这样设置的好处是,每当我修改完我的配置文件后,我不需要每次都退出我的logstash,然后再重新运行。

3)创建一个叫做weblog.conf的配置文件,并输入一下的内容:

weblog.conf

input {
  tcp {
    port => 9900
  }
}

output {
  stdout { }
}

4)运行我们的Logstash

bin/logstash -f weblog.conf

这样我们的Logstash就已经启动了。

接下来,我们使用 nc 应用把数据发送到 TCP 端口号 9900,并查看console的输出。我们在另外一个console中打入如下的命令:

echo 'hello logstash' | nc localhost 9900

我们在Logstash 运行的console里可以看到输出:

上面说明我们的TCP input 运行是正常的。

5)下载Weblog文件并发送给Logstash

我们可以在地址https://ela.st/weblog-sample下载一个叫做weblog-sample.log的文件。这个文件有64.5M的大下。我们把这个文件保存于Logstash的安装目录中。它里面的其中的一个log的内容如下:

14.49.42.25 - - [12/May/2019:01:24:44 +0000] "GET /articles/ppp-over-ssh/ HTTP/1.1" 200 18586 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5"

或:

14.49.42.25 - - [12/May/2019:01:24:44 +0000] "GET /articles/ppp-over-ssh/ HTTP/1.1" 200 18586 "-" "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2b1) Gecko/20091014 Firefox/3.6b1 GTB5"

我使用如下的命令来读取第一行,并输入到TCP 9900端口:

head -n 1 weblog-sample.log | nc localhost 9900

那么在Logstash运行的console中,我们可以看到如下的输出:

这显示是我们第一行的那条Weblog信息。在这里,我们没有对数据进行任何的处理。它只是把第一行日志读出来,并把它都赋予给message这个字段。

运用过滤器来对数据进行处理

接下来,我们分别使用一些过滤器来对数据进行分别处理。

Grok

针对Grok,我还有有一个专门的文章“Logstash:Grok filter 入门”来描述。你可以发现所有的关于 Logstash 的 patterns: https://github.com/logstash-plugins/logstash-patterns-core/blob/master/patterns/grok-patterns。在这里,我们针对weblog.conf进行如下的修改(你可以使用你喜欢的编辑器):

input {
  tcp {
    port => 9900
  }
}

filter {
  grok {
    match => { "message" => "%{COMBINEDAPACHELOG}" }
  }
}

output {
  stdout { }
}

还记得之前我们的设置config.reload.automatic为true吗?当我们保存weblog.conf文件后,我们可以在Logstash的输出console里看到:

也就是说,我们的pipleline被自动地装载进来了。我们安装上面同样的方法取第一条的数据来输入:

head -n 1 weblog-sample.log | nc localhost 9900

这个时候,我们再在Logstash运行的console里,我们可以看到:

也就是说,我们通过Grok这个filter,它通过正则表达式进行匹配,并把我们的输入的非结构化的数据变为一个结构化的数据。从上面,我们可以看到各种被提取的字段,比如clientip, port, host等等。

Geoip

尽管上面的数据从非结构化变为结构化数据,这是非常好的,但是还是有美中不足的地方。比如clientip,我们知道了这个请求的IP地址,但是我们还是不知道这个IP是从哪个地方来的,具体是哪个国家,哪个地理位置。在这个时候,我们需要使用geoip过滤器来对数据进行丰富。我们在filter的这个部分加入geoip。当我们保存好weblog.conf文件后,我们会发现Logstash会自动装载我们最新的配置文件(如上步所示一样):

weblog.conf

input {
  tcp {
    port => 9900
  }
}

filter {
  grok {
    match => { "message" => "%{COMBINEDAPACHELOG}" }
  }

  geoip {
    source => "clientip"
  }
}

output {
  stdout { }
}

同样地,我们使用如下的命令来发送日志里的第一条数据:

head -n 1 weblog-sample.log | nc localhost 9900

这个时候,我们可以看到除了在上面的clientip信息之外, 我们的数据多了一个新的叫做geoip的字段。它里面含有location位置信息。这就为我们在地图上进行显示这些数据提供了方便。我们可以利用Elastic所提供的地图清楚地查看到请求是来自哪里。
 

Useragent

上面的数据比以前更加丰富。我们还注意到agent这个字段。它非常长,我们没法查看出来是来自什么样的浏览器,什么语言等等信息。我们可以使用useragent这个过滤器来进一步丰富数据。我们在weblog.conf中添加这个过滤器:

weblog.conf

input {
  tcp {
    port => 9900
  }
}

filter {
  grok {
    match => { "message" => "%{COMBINEDAPACHELOG}" }
  }

  geoip {
    source => "clientip"
  }

  useragent {
    source => "agent"
    target => "useragent"
  }

}

output {
  stdout { }
}

等更新完这个配置文件后,我们再次在另外一个console中发送第一个log:

head -n 1 weblog-sample.log | nc localhost 9900

我们可以在上面看到一个新增加的字段useragent。上面它表明了useragent的版本信息,浏览器的名称以及操作系统。这对于我们以后的数据分析提供更进一步的帮助。

Mutate  - convert

从上面的输出中,我们了可以看出来bytes是一个字符串的类型。这个和我们实际的应用可能会有所不同。这应该是一个整型数。我们可以使用mutate: convert 过滤器来对它进行转换。我们重新编辑weblog.conf文件。我们把它放于grok过滤器之后:

weblog.conf

input {
  tcp {
    port => 9900
  }
}

filter {
  grok {
    match => { "message" => "%{COMBINEDAPACHELOG}" }
  }

  mutate {
    convert => {
      "bytes" => "integer"
    }
  }

  geoip {
    source => "clientip"
  }

  useragent {
    source => "agent"
    target => "useragent"
  }

}

output {
  stdout { }
}

等更新完这个配置文件后,我们再次在另外一个console中发送第一个log:

head -n 1 weblog-sample.log | nc localhost 9900

从上面的输出中,我们可以看到bytes这个字段已经变为正式值了,而不是之前的字符串了。

Date

Logstash将事件时间存储在@timestamp字段中。 但是实际的日志创建时间在 timestamp 字段中(没有@)。 该字段的格式不是ISO8601,因此存储为文本。 我们可以使用 date 过滤器将此字段转换为日期类型。我们编辑weblog.conf,并加入 date 过滤器:

weblog.conf

input {
  tcp {
    port => 9900
  }
}

filter {
  grok {
    match => { "message" => "%{COMBINEDAPACHELOG}" }
  }

  mutate {
    convert => {
      "bytes" => "integer"
    }
  }

  geoip {
    source => "clientip"
  }

  useragent {
    source => "agent"
    target => "useragent"
  }

  date {
    match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
  }
}

output {
  stdout { }
}

等更新完这个配置文件后,我们再次在另外一个console中发送第一个log:

head -n 1 weblog-sample.log | nc localhost 9900

从上面,我们看出来新添加了一个叫做@timestamp的字段。

 

设置输出 - Elasticsearch

所有的到目前为止,所有的输出都是stdout,也就是输出到Logstash运行的console。我们想把处理后的数据输出到Elasticsearch。我们在output的部分添加如下的Elasticsearch输出:

weblog.conf

input {
  tcp {
    port => 9900
  }
}

filter {
  grok {
    match => { "message" => "%{COMBINEDAPACHELOG}" }
  }

  mutate {
    convert => {
      "bytes" => "integer"
    }
  }

  geoip {
    source => "clientip"
  }

  useragent {
    source => "agent"
    target => "useragent"
  }

  date {
    match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
  }
}

output {
  stdout { }

  elasticsearch {
    hosts => ["localhost:9200"]
    user => "elastic"
    password => "changeme"
  }
}

在上面,我们同时保留两个输出:stdout及elasticsearch。事实上,我们可以定义很多个的输出。stdout输出对于我们初期的调试是非常有帮助的。等我们完善了所有的调试,我们可以把上面的stdout输出关掉。依赖于我们是否已经为Elasticsearch提供安全设置,我们需要在上面配置好访问的用户名及密码。

等更新完这个配置文件后,我们再次在另外一个console中发送第一个log:

head -n 1 weblog-sample.log | nc localhost 9900

这一次,我们打开Kibana:

我们在Dev Tools里输入如下的命令:

GET logstash/_count

从上面,我们可以看到有一条Logstash的数据。我们可以再接着打入如下的命令:

GET logstash/_search

从上面我们可以看到这条Logstash导入的数据。它和我们之前在Longstash console里看到的是一摸一样的。

启用 keystore 来保护自己的密码等

在上面所有的配置中,我们在配置文件中把自己的用户名及密码都写在文本中间。这个是非常不好的,这是因为任何可以接触到这个配置文件的人都可以看到你这些敏感信息。为此,logstash-keystore 提供了一种安全机制。它允许我们把这些信息保存于一个keystore里,这样别人都看不到真实的用户名及密码等信息。如果你还不知道如何为Elasticsearch设置安全信息的话,请参阅我之前的文章“Elasticsearch:设置Elastic账户安全”。

我们在Logstash的console里打入如下的命令:

bin/logstash-keystore create
$ bin/logstash-keystore create
Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
2020-05-08T10:38:35.483+08:00 [main] WARN FilenoUtil : Native subprocess control requires open access to sun.nio.ch
Pass '--add-opens java.base/sun.nio.ch=org.jruby.dist' or '=org.jruby.core' to enable.

WARNING: The keystore password is not set. Please set the environment variable `LOGSTASH_KEYSTORE_PASS`. Failure to do so will result in reduced security. Continue without password protection on the keystore? [y/N] y
Created Logstash keystore at /Users/liuxg/elastic3/logstash-7.6.2/config/logstash.keystore

我们接着打入如下的命令:

bin/logstash-keystore add ES_HOST

我们把配置文件中的Elasticsearch的地址 localhost:9200 拷贝并粘贴过来:

$ bin/logstash-keystore add ES_HOST
Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
2020-05-08T10:39:41.884+08:00 [main] WARN FilenoUtil : Native subprocess control requires open access to sun.nio.ch
Pass '--add-opens java.base/sun.nio.ch=org.jruby.dist' or '=org.jruby.core' to enable.

Enter value for ES_HOST: 
Added 'es_host' to the Logstash keystore.

我们再接着打入如下的命令:

bin/logstash-keystore add LS_USER
$ bin/logstash-keystore add LS_USER
Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
2020-05-08T10:41:41.603+08:00 [main] WARN FilenoUtil : Native subprocess control requires open access to sun.nio.ch
Pass '--add-opens java.base/sun.nio.ch=org.jruby.dist' or '=org.jruby.core' to enable.

Enter value for LS_USER: 
Added 'ls_user' to the Logstash keystore.

我们在上面输入我们的Logstash的用户名。这个用户名可以是那个超级用户elastic,也可以是我们自己创建的一个专为数据采集的用户。

最后,我们也可以打入如下的命令:

bin/logstash-keystore add LS_PWD

我们把上面用户名的密码进行输入:

$ bin/logstash-keystore add LS_PWD
Java HotSpot(TM) 64-Bit Server VM warning: Option UseConcMarkSweepGC was deprecated in version 9.0 and will likely be removed in a future release.
2020-05-08T10:48:29.166+08:00 [main] WARN FilenoUtil : Native subprocess control requires open access to sun.nio.ch
Pass '--add-opens java.base/sun.nio.ch=org.jruby.dist' or '=org.jruby.core' to enable.

Enter value for LS_PWD: 
Added 'ls_pwd' to the Logstash keystore.

在这里我必须指出的是:上面我使用的 ES_HOST, LS_USER 及 LS_PWD 都是你自己任意可以选取的名字。只要它们和我们下边所使用的配置里的名字是配合的即可。

你可以使用如下的命令来查看你已经创建的 key:

./bin/logstash-keystore list

在上面,我们已经创建了一下键值,那么我们该如何使用它们呢?我们重新打开weblog.conf文件:

weblog.conf

input {
  tcp {
    port => 9900
  }
}

filter {
  grok {
    match => { "message" => "%{COMBINEDAPACHELOG}" }
  }

  mutate {
    convert => {
      "bytes" => "integer"
    }
  }

  geoip {
    source => "clientip"
  }

  useragent {
    source => "agent"
    target => "useragent"
  }

  date {
    match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
  }
}

output {
  stdout { }

  elasticsearch {
    hosts => ["${ES_HOST}"]
    user => "${LS_USER}"
    password => "${LS_PWD}"
  }
}

在上面的elasticsearch输出部分,我们分别使用了 ES_HOST, LS_USER 及 LS_PWD 来分别代替了之前使用的字符串。这样做的好处是,我们再也不用硬编码我们的这些字符串了。我们把这个文件给任何人看,他们都不会发现我们的这些敏感信息了。

经过这个修改后,我们重新运行Logstash:

 bin/logstash -f weblog.conf

我们看到Logstash已经被成功启动了。我们使用如下的命令再次发送第一条日志信息:

head -n 1 weblog-sample.log | nc localhost 9900

我们再次查看Kibana:

显然,这次比上一次多了一条数据。说明我们的配置是成功的!

 

把整个文件都导入进Elasticsearch

到目前为止,我们只是测试了我们的Logstash的配置文件部分。可能很多的人觉得并不完整。如果你想把整个的log文件都导入进Elasticsearch中,那么我们可以配合 Filebeat 来进行使用。关于这个部分,请阅读我的另外一篇文章“Logstash:把Apache日志导入到Elasticsearch”。

由于一些原因,我们可以把Filebeat的配置文件设置为:

filebeat_logstash.yml

filebeat.inputs:
- type: log
  enabled: true
  paths:
    - /path-to-log-file/weblog-sample.log
 
output.logstash:
  hosts: ["localhost:9900"]

我们要记得根据自己weblog-sample.log的位置修改上面的paths。同时,由于一些原因,我们也同时也要做上面的Logstash的配置文件weblog.conf做很小的修正。把useragent里的source修改为user-agent而不是之前的agent。

weblog.conf

input {  
  beats {
    port => "9900"
  }
}
 
filter {
  grok {
    match => { "message" => "%{COMBINEDAPACHELOG}" }
  }
 
  mutate {
    convert => {
      "bytes" => "integer"
    }
  }
 
  geoip {
    source => "clientip"
  }
 
  useragent {
    source => "user_agent"
    target => "useragent"
  }
 
  date {
    match => ["timestamp", "dd/MMM/yyyy:HH:mm:ss Z"]
  }
}

output {
  stdout {
    codec => dots {}
  }
 
  elasticsearch {
    hosts=>["localhost:9200"]
    user=>"elastic"
    password=>"123456"
    index => "apache_elastic_example"
    template => "/Users/liuxg/data/beats/apache_template.json"
    template_name => "apache_elastic_example"
    template_overwrite => true
  }
}

你可以在地址:https://github.com/liu-xiao-guo/beats-getstarted 下载所以的代码。记得换掉上面的路径即可。在使用时,你先要启动Logstash,然后再启动Filebeat。

bin/logstash -f weblog.conf

然后,再运行Filebeat:

bin/filebeat -e -c filebeat_logstash.yml

在Kibana中,我们可以通过如下的命令来查看索引:

GET _cat/indices

从上面,我们可以看出来有30万个数据被导入到Elasticsearch中。

 

更多阅读

你可以发现更多的关于Logstash的文章。相关的文章: