Fluentdによる柔軟なPHPのアプリケーションログ管理をDockerでシミュレートしたい(3)

こんにちはみなさん

[前回](http://qiita.com/niisan-tokyo/items/1cc4e66730c44dfb2801)、phpのログをfluentdからログ集約のためのfluentdサーバに飛ばすことに成功しました。
今回は、ログ集約サーバからMongoDBサーバにログを格納することで、表題の「柔軟なログ」を実現していきたいと思います

今回の結果に関しては以下のリポジトリを見ると良いです
https://github.com/niisan-tokyo/log_experioment/tree/IndMongo

また、前回もそうなのですが、dockerのコマンドを使用している部分は、全てVMのatomic host 上で行っているものです。

# MongoDBの導入

## なぜMongoDB?

もう常識になってしまっているかもしれませんが、一応MongoDBを導入する理由を述べておきます。
MongoDBの特徴はスキーマレスというところにありますが、これは「テーブル定義が必要ない、もしくは存在しない」ということです。
MongoDBにおいて、MySQLの「テーブル」に相当する概念として「コレクション」というものが有ります。MySQLではテーブルにどんなカラムがあるか、予め決めなければならないのに対し、コレクションは格納された各データ毎に自由なフィールド(カラム)を持って良いのです。さらに、事前にコレクションを定義していなくとも、新しいコレクション名を指定してデータを入れるだけで、新しいコレクションができてしまいます。さらに、データベースまで、自動で作成されます。
まとめてしまうと、とんでもなく自由度が高いわけです。
これらの特徴がどういう形で役に立つかは後ほど解説します。

## MongoDBを導入する

今回も前回同様Dockerfileを作って。。。とやると面倒くさそうですが、実は公式に配布されているDockerのイメージが有りますので、そいつを使いましょう

```
docker pull mongo:latest
```

これで、MondoDBの入ったDockerイメージが作れましたので、早速起動しましょう

```
docker run --name logmongo -d -p 27017:27017 mongo
```

これで完了です

これで既に外部からMongoDBに接続することが可能です。
私の場合は 192.168.33.40:27017 でMongoDBに接続できます。
拍子抜けするほど簡単ですね

# Fluentd のイメージを作りなおす

前回作成したFluentdのイメージは、集約したログをファイルに出力するようになっていたので、設定を変更しなければならないのですが、immutable 目指した結果、設定ファイルの変更があっただけで、イメージを作りなおさなければなりません

とはいえ、イメージの作り直しの際、前回と内容が何も変わらない部分に関しては、キャッシュを使うだけでいいので、処理はすぐに終わります。

## 設定ファイル

設定ファイルの変更場所は以下のとおりです

https://github.com/niisan-tokyo/log_experioment/blob/IndMongo/dockers/fluentd/etc/td-agent.conf

```
 <match app.**>
 -  type file
 -  path /var/log/td-agent/app
 +#  type file
 +#  path /var/log/td-agent/app
 +type mongo
 +host logmongo
 +port 27017
 +database applog
 +tag_mapped
 +remove_tag_prefix app
  </match>
```

この設定の中で今回の趣旨で特に重要なのが **tag_mapped** というオプションです。
この設定ファイルは、プレフィックスが「app.」のタグのログが来たら、logmongoという名前のホスト(```host```)の27017ポート(```port```)に向けて、MongoDBのインターフェースに従って(```type```)ログを投げることになっています。
その際、データベース(```database```)はapplogを選択しますが、今回は特定のコレクション(```collection```)を指定しいません。これは```tag_mapped```というオプションを使用することで、タグ名から自動的にコレクションを設定することができます。
```remove_tab_prefix``` に app を設定しているので、タグから「app」の部分を取り除いた名前のコレクションがログの格納先として選択されます。

設定ファイルの変更が終わったので、Fluentdを再構築しましょう

```
docker build -t niisan-tokyo/fluentd dockers/fluentd/
```

慣れるととても楽です。

# 柔軟なログを確かめる

## ログを確認する

では、コンテナを起動してログがうまくMongoDBに格納されていくことを確認しましょう。MongoDBサーバは先ほど起動したままなので、そのままです。

```
# docker run -d --name fluentlogger --link logmongo:logmongo niisan-tokyo/fluentd
# docker run -d --name web -p 8080:8080 --link fluentlogger:logger niisan-tokyo/php56
```

これでhttp://192.168.33.40:8080/ にアクセスして、いくつか値をポストします。
で、ログを確認するのですが、MongoDBのクライアントがないので、とりあえず適当なものを用意しましょう。
私は[MongoHub](https://github.com/jeromelebel/MongoHub-Mac)を使用しました。

では早速中身を見てみましょう

<img width="1065" alt="スクリーンショット 2015-12-21 18.26.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/102182/592aab84-0ba9-c024-0e1b-9b9e8190e8ee.png">

入れた値が入っているようですので、ログの格納は成功しました。
もしも何も表示されない場合は、WEBサーバからのログ送信が遅延している可能性がありますので、気長に待ちましょう。

## フィールドを追加してみる

フォームにフィールドが追加された場合の動作を確認してみましょう。
以下のテンプレートファイルに、dockerのコンテナ上で変更を加えてみます
https://github.com/niisan-tokyo/log_experioment/blob/master/niisan-tokyo/Logexp/src/Resource/Page/Index.html.twig

```
# docker exec -it web /bin/bash
# vi src/Resource/Page/Index.html.twig
```

```docker exec```は起動中のコンテナに対し、外部から処理を実行するためのコマンドで、今回はbashを起動しています。
```vi```による編集内容は以下のとおりです 

```
<form method="POST" action=".">
    スコア1<input type="text" name="score1" /><br>
    スコア2<input type="text" name="score2" /><br>
    スコア3<input type="text" name="score3" /><br>
+   スコア4<input type="text" name="score4" /><br>
    <input type="submit" value="送信" />
</form>
```

これで、スコアのフィールドが4つになりました。
同様に処理の方も修正をします。

https://github.com/niisan-tokyo/log_experioment/blob/master/niisan-tokyo/Logexp/src/Resource/Page/Index.php

```
-    public function onPost($score1, $score2, $score3)
+    public function onPost($score1, $score2, $score3, $score4)
    {
-       for($num = 1; $num < 4; $num++) {
+       for($num = 1; $num < 5; $num++) {
            $str = 'score'.$num;
            $res[$str] = intval($$str);
        }
        
        $this->logger->post('app.scores', $res);
        
    }
```

そうすると、以下のように新たに```score4```というフィールドが追加されたデータが格納されます。
<img width="619" alt="スクリーンショット 2015-12-21 18.55.14.png" src="https://qiita-image-store.s3.amazonaws.com/0/102182/b773852b-bd95-e219-9040-27f5e2806ef8.png">

ここで注目して欲しいのは、コレクションの中に```score4```のあるデータとないデータが有るというところです。これははじめに述べたように、MongoDBのスキーマスという特徴を用いた結果です。
つまり、PHPのコードに勝手にフィールドを追加しただけで、ログの格納先のDBがその追加にたいし、柔軟に対応してくれるというわけです。
また、MongoDBはDBという名前が付いているだけあって、データベースとしての機能も持っていますので、インデックスを貼ったり集計をしたりすることができますが、今回は割愛します。

## 新しいログを追加してみる

また、同様に次のようなコードをいきなり加えてみましょう。

```
+       $sum = 0;
        for($num = 1; $num < 5; $num++) {
            $str = 'score'.$num;
            $res[$str] = intval($$str);
+           $sum += $res[$str];
        }

        $this->logger->post('app.scores', $res);
+       $this->logger->post('app.sum', ['sum' => $sum]);
```

これにより、今度は新しくコレクションが作成され、別途スコアの和を個別に格納したデータが追加されることになります。

# まとめ

さて、かくしてFluentdとMongoDBを使用して、柔軟にPHPのログを管理するシミュレーションをDockerを通して実現することに成功しました。
今回３回にわたって記事にした内容は、各々が実際には既に多くのところで使われ、常識となっているかもしれません。
しかし、Docker, Fluentd, MongoDBを一つところで実現できたところに、この記事の意味があるのではないかと思います。

# 参考
https://docs.mongodb.org/ecosystem/tools/administration-interfaces/
http://docs.fluentd.org/articles/out_mongo