SHOYAN BLOG

I am a pragmatic programmer.

Fluentd、ElasticsearchとKibanaでログ検索とグラフ表示を可能にする

前回の記事Fluentdを使ってログをログ収集サーバに転送する方法を紹介しました。
今回は転送されたログをElasticsearchに登録し、Kibanaでログ検索とグラフ表示する方法を紹介します。

ログ収集サーバー(前回の記事でいうVagrant)の変更を行っていきます。

FluentdのElasticsearchプラグインをインストール

まずは、FluetndのElasticsearchプラグインをインストールしておきます。

1
$ sudo /usr/sbin/td-agent-gem install fluent-plugin-elasticsearch

Javaのインストール

Elasticsearchを動かすにはJavaが必要なのでJavaをインストールします。

1
2
3
4
$ sudo yum install java-1.7.0-openjdk
$ sudo yum install java-1.7.0-openjdk-devel
$ java -version
java version "1.7.0_101"

Elasticsearchのインストールと起動

今回はアーカイブをダウンロードしてきてインストールします。
インストールといっても特に設定は不要でアーカイブを展開してbin/elasticsearchを実行するだけです。

1
2
3
$ curl -O https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.3.3/elasticsearch-2.3.3.tar.gz
$ cd elastic search-2.3.3
$ ./bin/elasticsearch

kibanaのインストールと起動

Kibanaも同じようにアーカイブをダウンロードして起動します。

1
2
3
$ curl -O https://download.elastic.co/kibana/kibana/kibana-4.5.1-linux-x64.tar.gz
$ cd kibana-4.5.1-linux-x64
$ ./bin/kibana

http://192.168.33.10:5601 にブラウザでアクセスするとKibanaの管理画面が表示されます。
Indexの設定が必要ですが、ここでは何もせず次に進みます。

syslogのログをElasticsearchに格納する

syslogのログをElasticsearchに格納し、Kibanaで参照します。

/etc/td-agent/td-agent.confに以下を追記します。

1
2
3
4
5
6
7
8
9
10
11
<source>
  @type syslog # syslogプラグイン
  port 42185   # portは42185を指定
  tag syslog   # syslogというtagをつける
</source>

<match syslog.**>
  @type elasticsearch # elasticsearchプラグインを使う
  logstash_format true # ログのフォーマット
  flush_interval 10s # 10秒ごとに登録する
</match>

fluendをリスタートします。

1
$ sudo service td-agent restart

次にsyslogの設定をします。

/etc/rsyslog.conf に以下を追記します。
syslogが出力するログをFlutedがlistenするポート、42185に転送するための設定です。

1
*.* @127.0.0.1:42185

設定を反映させるため、syslogをリスタートします。

1
$ sudo /etc/init.d/rsyslog restart

先ほど確認した http://192.168.33.10:5601 に戻ります。
syslogの出力がElasticsearchに登録されると、Indexを登録できるようになります。
Indexはlogstash-2016.07.05 のような名前で作成されます。
セレクトボックスからIndexを選択し、Createボタンを押下後にKibanaでログの表示、検索が行えます。

手動でElasticsearchにログを送りたい場合は以下のコマンドで登録できます。

1
$ logger -t test foobar

もしくは、Fluentdのhttpポートにリクエストします。

1
$ curl -X POST -d 'json={"json":"Hello"}' http://localhost:8888/syslog.test

参考リンク

Fluentdを使ってローカル環境にログ収集サーバを構築する

アプリケーションサーバとログ収集サーバにFluentdをインストールし、アプリケーションサーバからログ収集サーバにログをフォワードする方法を紹介します。
ここでは、アプリケーションサーバをMacOS X、ログ収集サーバをVagrantとします。

Fluentdをホストマシン(Mac)にインストール

.dmgをダウンロードしてインストールしてください。

以下でFluentdを起動します。

1
$ sudo launchctl load /Library/LaunchDaemons/td-agent.plist

Fluentdのログは /var/log/td-agent/td-agent.log に出力されます。

1
$ less /var/log/td-agent/td-agent.log

停止は以下です。

1
$ sudo launchctl unload /Library/LaunchDaemons/td-agent.plist

設定ファイルは /etc/td-agent/td-agent.conf です。
設定を反映させるには、以下のコマンドで行います。

1
2
$ sudo launchctl stop td-agent
$ sudo launchctl start td-agent

以下でFluentdにリクエストします。

1
$ curl -X POST -d 'json={"json":"message"}' http://localhost:8888/debug.test

/var/log/td-agent/td-agent.log にログが出力されているはずです。

1
2
$ tail -n 1 /var/log/td-agent/td-agent.log
2016-07-01 16:51:47 -0700 debug.test: {"json":"message"}

ログ収集サーバにFluentdをインストール

次にログ収集サーバにFluentdをインストールします。
OSはCentos6.5を使いました。
vagrantの使い方は割愛します。
networkはprivate_networkとし、192.168.33.10でアクセスできることとします。

以下でログ収集サーバにログインします。

1
2
3
4
5
6
7
8
9
10
$ vagrant ssh

# iptablesは無効にしておきます
$ sudo service iptables stop

# 以下のコマンドでFluentdインストールします。
$ curl -L https://toolbelt.treasuredata.com/sh/install-redhat-td-agent2.sh | sh

# Fluentdを起動します
$ sudo /etc/init.d/td-agent start

ログ収集サーバでも同じように確認してみます。

1
2
3
$ curl -X POST -d 'json={"json":"message"}' http://localhost:8888/debug.test
$ tail -n 1 /var/log/td-agent/td-agent.log
2016-07-01 16:51:47 -0700 debug.test: {"json":"message"}

アプリケーションサーバからログ収集サーバのFluentdにリクエストを送る

アプリケーションサーバからログ収集サーバのFluentdにリクエストを送れるかを確認します。

1
$ curl -X POST -d 'json={"json":"message"}' http://192.168.33.10:8888/debug.test

ログ収集サーバでログを確認します。
Fluentdは出力をバッファするのでフォワードされるまでにタイムログがある場合があります。

1
$ tail -n 1 /var/log/td-agent/td-agent.log

疎通を確認できたら、アプリケーションサーバからログ収集サーバのFluentdにログをforwardしてみましょう。

アプリケーションサーバの/etc/td-agent/td-agent.conf を以下のように編集します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<source>
  type http
  port 8888
</source>

<source>
  @type forward
  port 24224
</source>

# tagがvagrantの場合はvagrantのFluentdにフォワードする
<match tag vagrant.**>
  type forward
  <server>
    host 192.168.33.10
    port 24224
  </server>
</match>

<match debug.**>
  type stdout
</match>

ログ収集サーバの/etc/td-agent/td-agent.confに以下の設定を追加します。
tagがvagrantで送られてきたものをログに出力します。

1
2
3
<match vagrant.**>
  type stdout
</match>

以下のコマンドでログ収集サーバのFluentdにデータがフォワードされます。

1
$ curl -X POST -d 'json={"json":"Fluentd!!!"}' http://localhost:8888/vagrant.test

以上で基本的なフォワードの設定ができました。

ファイルを監視してログ収集サーバにフォワードする

次はファイルを監視してファイルにデータが追加されたらその内容をログ収集サーバにフォワードするようにします。
in_tailプラグインを使います。
in_tailプラグインを使うにはtypeにtailを使います。

アプリケーションサーバの/etc/td-agent/td-agent.confに以下を追記します。

1
2
3
4
5
6
7
<source>
  type tail # in_tailプラグインを指定
  path /tmp/access_log # 監視するファイルの指定
  tag vagrant.access_log # ログにつけるタグを指定
  pos_file /tmp/access_log.pos # ファイル内のどの行までを読んだかを記録しておくファイルを指定
  format none # フォーマットはnoneを指定
</source>

設定を反映させるには、以下のコマンドで行います。

1
2
$ sudo launchctl stop td-agent
$ sudo launchctl start td-agent

/tmp/access_log に追記してみましょう。

1
$ echo "hello" >> /tmp/access_log

すると、ログ収集サーバのFluentdに内容がフォワードされます。

参考リンク

ハフマン符号化のアルゴリズムとエントロピーを学ぶ

ハフマン符号とは1952年にデビッド・ハフマンという人が考え出したアルゴリズムです。
文字列をはじめとするデータの可逆圧縮に使われます。
よく使われる文字には短いビットを、あまり使われない文字には長いビットを割り当てることで全体としてサイズが削減されることを狙ったアルゴリズムです。

符号化の原理

実際にアルゴリズムを確認していきます。

DAEBCBACBBBC という12文字のメッセージをハフマン符号化します。

このメッセージでは、ABCDEという5種類の文字が使われているため、それぞれの文字を固有のビット長で表す場合は、3ビットが必要です。

1
2
3
4
5
A: 000
B: 001
C: 010
D: 011
E: 100

上記の対応表をもとに、文字列をビットに変換します。

1
2
DAEBCBACBBBC
011 000 100 001 010 001 000 010 001 001 001 010

1文字3ビットで12文字あることから全体のビット数は36ビットとなります。

ここで、よく出てくる文字には短いビットを、あまりでてこない文字には短いビットを与えます。
対応表を以下のように作ります。

1
2
3
4
5
A: 110
B: 0
C: 10
D: 1110
E: 1111

メッセージ全体では以下のようになります。

1
2
DAEBCBACBBBC
1110 110 1111 0 10 0 110 10 0 0 0 10

全体のビット数は25ビットとなり、固定長の方式と比べると70%ほどのデータ量に抑えられています。

対応表をつくる

以下の手順で文字とビット列の対応表をつくります。

  • データに出現する文字の出現回数を求める
  • それをもとにハフマン木とよばれる二分木(バイナリツリー)を構成する

ハフマン木の構成の仕方は次のアルゴリズムとなります。

  1. 各記号に対応する葉を作成する。この葉には、記号の出現頻度をあらかじめ格納しておく。
  2. 出現頻度の最も少ない2つの葉をとりだす。取り出した2つの葉を格納する節をつくり、左右の枝に記号0と1を割り当てる。この節には2つの葉の出現頻度を足した値を格納し、新しい葉として追加する
  3. 葉が1つになるまで手順2を繰り返す。

DAEBCBACBBBC を題材に、ハフマン木を作ります。

1. 各記号に対応する葉を作成する

まずは、各記号に対応する葉を作成し、データの出現回数をあらかじめ格納しておきます。

1
{"B"=>5, "C"=>3, "A"=>2, "D"=>1, "E"=>1}

2.出現頻度が少ない2つの葉をとりだす

DとEが1番小さいので、この2つを取り出します。
そして、DとEの葉を足し合わせた節を1つ作ります。
この手順を最終的に葉が1つになるまで繰り返します。

ハフマン木が作られる工程

DAEBCBACBBBC を作成した対応表で符号化します。

文字 個数 符号
B 5 0
C 3 10
A 2 110
D 1 1110
E 1 1111

データ圧縮アルゴリズムについて

一般的にデータ圧縮アルゴリズムは「モデル化」と「符号化」の2つにわけて考えることができます。
「モデル化」は入力された記号(データ)から各記号の出現確率を求めます。
「符号化」は出現確率に基づいて符号語を割り当て、入力されたデータを各符号語に変換して出力します。
モデル化についてはいろいろな方法がありますが、ハフマン符号のように記号の出現確率を求めそれに基づいて符号語を割り当てるモデルを「無記憶情報源モデル」といいます。
「情報源」は記号を生成する基となるデータのことです。
情報源が記号を生成するとき、以前に生成した記号との間に関連性がないことを「無記憶」といいます。
記号aの次は記号bが生成されるといった関係性はなく、確率でのみ記号が作成されるということです。

データ圧縮アルゴリズムを評価する場合、圧縮率のほかに「平均符号長」という尺度があります。
これは、符号化された記号列のビット長を入力された記号数で割った値として定義されます。

たとえば DAEBCBACBBBC をハフマン符号化すると1110110111101001101000010となります。
符号化された記号列のビット長(1110110111101001101000010)を入力された記号数(DAEBCBACBBBC)で割ると平均符号長は 25 / 12 = 2.0833333 となります。

無記憶情報源モデルの場合、各記号 の出現確率 がわかると、次の式で平均符号長の下限値を求めることができます。

を平均情報量、またはエントロピー(Entropy)と呼びます。

情報源符号化定理によると、平均符号長はエントロピーより短くすることができません。

情報源符号化定理

意復号可能な平均符号長 L は、無記憶情報源のエントロピー H よりも小さくすることができない。すなわち不等式 H <= L が成り立つ。また、平均符号長 L が H <= L < H + 1 を満足する瞬時に復号可能な符号が構成できる。

先ほどの記号列のエントロピーを求めてみます。

1
2
3
4
5
6
7
8
9
10
11
記号列: DAEBCBACBBBC
記号: 確率: -p(ai) / log2 p(ai)
------------------------------------------
D: 1 / 12: 0.29874687506009634
A: 2 / 12: 0.430827083453526
E: 1 / 12: 0.29874687506009634
B: 5 / 12: 0.5262643357640807
C: 3 / 12: 0.5
------------------------------------------
エントロピー = 2.054585169337799
12 * 2.054585169337799 = 24.65502203205359 bit

したがって、この記号列では平均符号長を 2.05 ビット以下にすることはできません。
いいかえると、この記号列を表すには少なくても 12 * 2.054585169337799 = 24.65502203205359 ビット以上が必要になる、ということです。

エントロピーの算出コード

エントロピーを算出するコードをRubyで書きました。

以下のように使います。

1
2
$ ./entropy file.txt
2.054585169337799

文字列を渡す場合はパイプで繋ぎます。

1
2
$ echo "DAEBCBACBBBC" | ./entropy
2.054585169337799

また、 -v オプションで詳細を表示できます。

1
2
3
4
5
6
7
8
9
10
$ ./entropy -v file.txt
記号: 確率 : 値
D: 1 / 12: 0.29874687506009634
A: 2 / 12: 0.430827083453526
E: 1 / 12: 0.29874687506009634
B: 5 / 12: 0.5262643357640807
C: 3 / 12: 0.5

サイズ エントロピー 下限値
12 2.054585169337799 3

ファイルサイズ * エントロピー で圧縮の下限値を計算することができます。
上記の場合は3バイトよりも圧縮することはできないということです。

ただし、この結果は無記憶情報源モデルの場合であり、モデル化によってエントロピーの値は異なることに注意してください。エントロピーをより小さくするモデルを作成することがでれきば、これよりも高い圧縮率を達成することができます。

参考リンク

Sedやawkを使ってテキストから必要な列のみ取得する

以下のような文字列(ファイルに保存されているとする)からsedやawkを使ってlabelだけとるshell芸を紹介します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+-------------------------+-------+
| label                   | state |
+-------------------------+-------+
| A                       |     0 |
| B                       |     0 |
| C                       |     0 |
| D                       |     0 |
| E                       |     0 |
| F                       |     0 |
| G                       |     0 |
| H                       |     2 |
| I                       |     0 |
| J                       |     0 |
| K                       |     0 |
+-------------------------+----———+

label列のみ取得

1
2
3
4
5
6
7
8
9
10
11
12
⇒  sed -n '4,14p' table.txt | awk '{print $2}'
A
B
C
D
E
F
G
H
I
J
K

スペース区切りに加工する

1
2
⇒  sed -n '4,14p' table.txt | awk '{print $2 " "}' | tr -d '\n'
A B C D E F G H I J K

sed -n '4,14p’ で指定した行数のみ取得しています
次にawk '{print $2}’ でlabel列のみ抽出しています。
改行を消すのは tr -d '\n’ が便利です。

Rdiscountからkramdownへmarkdownのparserを変更した

数式を扱うために前回の記事で、markdownをkramdownに変更したのですが、コードのシンタックスハイライトが効かなくなってしまいました。

kramdownのparserをGFMにしてやるとうまくいきました。

_config.yml

1
2
kramdown:
  input: GFM

GFMを使わない場合は、プラグインがあるのでそちらでも対応できます。

kramdown-with-pygments

使い方ですが、pluginsディレクトリにkramdown_pygments.rbを置きます。
そして、_config.ymlのmarkdownを以下のように修正します。

1
markdown: KramdownPygments

また、parserにkramdownを使う場合はRdiscountとkramdownでcodeのmarkdownのフォーマットが違うのでgenerate時にエラーがでるかもしれません。

自分は以下のワンライナーで書き換えました。

1
2
find source/_posts -type f -name "*.markdown" | xargs sed -i '' -e 's/\`\`\`/\
~~~/g'

また、URLに自動でリンクを付与してくれる機能がRdiscountにはあったのですが、kramdownでは明示的に指定する必要があります。
Rdiscountのように自動的にリンクを付与するプラグインを作成しました。

使い方はkramdown_easy_link.rbplugins/ディレクトリに置いて、_config.ymlを以下のように修正します。

1
2
3
markdown: kramdown
kramdown:
  input: KramdownEasyLink