2015年6月9日

【Minecraft】Logstashを使ってログ監視をしてみる【Ubuntu】

記事の投稿は何ヶ月ぶりでしょうか。ここ最近は山に呆けていて技術ブログの書き込みが全くなくて申し訳ない限りです。

久しぶりの投稿はログ監視ツール「Logstash」についてです。

Linuxのログ監視ツールというと、fluentdだったりswatchだったりlogmonだったり…統合管理システムで言えばzabbixだったりnagiosだったりが検索で引っかかります。今のところ一番メジャーなのはfluentdとかなのかなぁ。統合監視ツールならzabbixとかが定番だと思うけど。

今回あえてLogstashにしたのは単に仕事でこれを使うことがあったからです。べつにLogstashでないと実現できない機能を使っているとかそういうわけではないので、自分の使いやすいものを支えばいいと思います。(とはいえ、Logstashはログの解析に鬼車を使っているので個人的には使いやすいと思います)

Logstashは日本語の参考文献が少ないため、基礎的な動作原理からMinecraftのログ解析による実用例などまで紹介します。

Filterの処理内容についてや、Outputの条件式については参考例も少ないので、何らかの参考になれば幸いです。


■Logstashとは?

冒頭で解説したように、ログ監視ツールの1つです。
例えばWebサーバのアクセスログや、サーバ内部のシステム情報など、メッセージがテキストで出力されてくるのを監視するのが基本的なログ監視ツールの機能です。

基本的なログ監視の流れはこんなの

logstashでは、message等のようなプレーンテキストだけでなく、httpやelasticsearchなどのDBなどと連携することが出来ます。

Inputプラグインで監視対象の情報を取得。Filterプラグインで取得したメッセージの解析。Outputでメッセージを特定のログorサーバに出力したりします。
基本的なLogstashの流れはこんなの

■Logstashのインストール

各種ディストリビューションへのインストール手順についてはパッケージ(orソースファイル)を直接インストールするか、パッケージ管理システムにリポジトリを登録してインストールするかの2通りになります。

①パッケージをインストール
下記公式ページからパッケージをダウンロード
https://www.elastic.co/downloads/logstash

(例:CentOSなどRedHat系の場合)
# wget http://download.elastic.co/logstash/logstash/packages/centos/logstash-1.5.0-1.noarch.rpm
# rpm -ivh logstash-1.5.0-1.noarch.rpm

②パッケージ管理システムを利用する場合
英語ですが、公式ページを参照するのが手っ取り早いです。
https://www.elastic.co/guide/en/logstash/current/package-repositories.html

(例:CentOSなどRedHat系の場合)
・鍵のインストール
# rpm --import https://packages.elasticsearch.org/GPG-KEY-elasticsearch

・リポジトリの追加
# vim /etc/yum.repos.d/logstash.repo
[logstash-1.4]
name=Logstash repository for 1.4.x packages
baseurl=http://packages.elasticsearch.org/logstash/1.4/centos
gpgcheck=1
gpgkey=http://packages.elasticsearch.org/GPG-KEY-elasticsearch
enabled=1

・インストール
# yum install logstash

■Logstashのインストール先

whereisコマンドを打てばすぐわかりますが、パッケージからインストールした場合は以下のディレクトリに格納されます。
/opt/logstash … Logstash本体。binファイルやライブラリなどが格納されており、Pathは通っていないので、直接logstashを起動したい場合は/opt/logstash/bin/logstashを指定する必要がある。
/etc/logstash/conf.d … 設定ファイル。サービスとして起動した時はここのディレクトリにある設定ファイルが読み込まれる。

■Logstashサンプル①「標準入力→標準出力」

公式ページによりしっかりとしたサンプルコードがいくつか載っていますが、今回は単体で動くサンプルとして、標準入力を標準出力で表示する監視を作ってみようと思います。

# vim sample.conf
input {
    stdin {
    }
}
filter {
}
output {
    stdout {
        codec => rubydebug
    }
}

上記サンプルは3つのグループにわかれており、その中でプラグインを読み込んでいます。

・inputグループ
 ログの取得元を指定する。
 →stdinプラグイン
  標準入力を受け付ける

・filterグループ
 取得したログの整形をする
 →今回は標準入力をそのまま利用するのでフィルターしてません。

・outputグループ
 ログの出力先を指定する
 →stdoutプラグイン
  標準出力に表示する
  →codecオプション
   Rubyの表示形式でログを出力する。

サンプルファイルを作成したら、Logstashを実行してみます。
# /opt/logstash/bin/logstash -f sample.conf
(※起動に10秒ほどかかる場合があります。)

※もしかしたら初回起動時に幾つかエラーが出るかもしれません。
 基本的にパーミッションのエラーだと思うので落ち着いてログを確認してみてください。

プロンプトが返ってきませんが適当に文字を書き込んでEnterを押してみてください。
Hello Logstash[Enter]
{
"message" => "Hello Logstash",
"@version" => "1",
"@timestamp" => "2015-06-09T06:56:58.088Z",
"host" => "0.0.0.0"
}

上記メッセージがRuby形式で出力されたメッセージになります。
 「message」:入力文字列、基本どの入力もmessageに入力文字列が入ります。
 「@version」:Rubyがサポートしているバージョン?Ruby詳しくないのでわからないです。
 「@timestamp」:入力があった時刻。システムの時刻が参照されてます。
 「host」:ホスト名。0.0.0.0になったら/etc/hostsなど名前解決がちゃんとされていないっぽい。

コーデックにRubyを指定するのはLogstashのデバッグで有用なので、覚えておいたほうがいいかもです。

■Logstashサンプル②「Minecraftのログ→Twitter」

いきなり実践サンプルですが、Minecraftのログを監視して、ログイン/ログアウトの通知をTwitterに連携させるサンプルをサービスとして起動してみます。
なお、Twitterに連携させる手段として、「bti」というコマンドライン型のtwitterクライアントを利用させて頂いています。btiについては以前に記事で書いたので、そちらを参照してもらえればと思います。
【Linux】ターミナルから操作できるTwitterクライアント「bti」【bash】

以下がサンプルです。サンプルっていうかこれ実際に自分が動かしているものですが。。。


# vim /etc/logstash/conf.d/minecraft.conf
input {
  file {
    path => [ "/home/minecraft/minecraft/logs/latest.log" ]
  }
}
filter {
  grok {
    match => { "message" => "\[%{TIME:time}\] \[Server thread/INFO\]: %{DATA:user} %{GREEDYDATA:message2}" }
  }
}
output {
  if [message] == "joined the game" {
    pipe {
      message_format => "%{time}に%{user}が参加しました。"
      command => "bti"
    }
  }
  if [message] == "left the game" {
    pipe {
      message_format => "%{time}に%{user}が退場しました。"
      command => "bti"
    }
  }
}

・grokプラグイン
フィルター処理の中でも利用頻度が高そうなプラグインの1つ。特に今回利用しているmatchオプションは、プレーンテキストを構文解析して一致した文字列を各種パラメータ(変数)に振り分けてくれるのでoutputでの処理が直感的になる。

今回の場合は、"message"変数の文字列を"\[%{TIME:time}\] \[Server thread/INFO\]: %{DATA:user} %{GREEDYDATA:message2}"の正規表現でパターンマッチしている。

%{パターン名:変数名}の書式でマッチングしている。「パターン名」は鬼車の正規表現で記載してあり、プリセットでプラグインが読み込まれている(別途パターンファイルをインプットすることも可能)。プリセットの一覧はGitHabにて公開されている

例えば以下のログが吐かれた場合、Rubydebugで標準出力をしたら以下の様な表示になる。
[16:30:47] [Server thread/INFO]: ky_shiroma joined the game
{
"message" => "[16:30:47] [Server thread/INFO]: ky_shiroma joined the game",
"@version" => "1",
"@timestamp => "2015-06-09T07:30:47.633Z",
"host" => "0.0.0.0",
"path" => "/home/minecraft/minecraft/logs/latest.log",
"time" => "16:30:47",
"user" => "ky_shiroma",
"message2" => "joined the game"
}

※@timestampがメッセージの時刻とずれてしまってるのはminecraftとシステム時刻の設定とのズレを上手く調整できていないせいなので、直す必要があるな…

なお、もしfilter内に処理を入れなかった場合は、以下の様な出力になると思います。

[16:30:47] [Server thread/INFO]: ky_shiroma joined the game
{
"message" => "[16:30:47] [Server thread/INFO]: ky_shiroma joined the game",
"@version" => "1",
"@timestamp => "2015-06-09T07:30:47.633Z",
"host" => "0.0.0.0",
"path" => "/home/minecraft/minecraft/logs/latest.log",
}

要は、上位5項目がinput時に処理された変数で、残り3項目がfilter内で新たに追加された変数だということです。

・if構文
今回はoutputグループ内にif構文で条件分岐を記載してあります。

書式: if [(変数)] (比較演算子) (比較値) {

(例)
if [message] == "joined the game" {
messageが"joined the game"に等しかったら、{ }内のプラグインを処理する。

比較演算子には、単純な数式っぽい演算子(==, !=, >, <など)だけでなく、正規表現でのパターンマッチング(=~, !~)にも対応しています。

・pipeプラグイン
文字列をコマンドラインのパイプと同様の処理でコマンドに引き渡します。
パイプで出力する形式は「message_format」オプションにて指定することが出来、filterで作成した変数をここで割り当てることが出来ます。パイプで引き渡すコマンドはcommandオプションにて指定することが出来ます。

・サービスの起動
設定ファイルを書き終えたらサービスの起動をしてみます。
# service logstash start

※サービス実行時にエラーが出て正しく起動しない場合があります。(ログは/var/log/logstash/配下に出力されている。)基本的にパーミッションのエラーだと思うので落ち着いて対処しましょう。

で、適当にMinecraftサーバにログインしてみると、twitterの方には以下のメッセージが表示されます。
Twitterクライアントの出力例
上記サンプルではログアウトした時のログ監視も行っているため、ログアウトした時には「ky_shiromaが退場しました」というメッセージが流れます。

こんなかんじで、とりあえずログイン/ログアウトが出来るようになりますので、後は設定次第で他のメッセージも表示できるようになります。

■案外色々な使い方があるログ監視ツール

今回はとりあえずでminecraftのログを監視するスクリプトにしたけれど、ネットラジオのicecastも
運用しているので、そちらのログ監視も出来るように設定したいですね。
(どちらも僕以外にユーザーが居ないので、監視する必要があるのかと問われればまぁ…だけど)

ともあれ監視結果のTwitter通知を流すことにも成功したので、今後はログだけでなくリソース監視などの方にも手を伸ばしたいなぁ。と思う今日このごろです。