「ログを集めて保存する」と言うのは簡単だけど,ログ収集の構成にはいくつか方法があり,勉強会などでちょくちょく聞かれるので,いくつかのパターンについて書く(TODO: 簡単なイメージ図を貼る).
「俺はもうバリバリログ収集やってるぜ!」という人は多分すでに知っていることが書かれているので,タブを閉じて良い.
ここではログコレクタにFluentdを想定しているが,他のログ収集プロダクトにも適用出来るはず.
ただ,Fluentdはタグベースのルーティングを持ち,単体でもキューのように動作させることが可能で,既存のものより複雑な問題を解決しようとしているので,少し工夫が必要かもしれない.
Fluentdそのものについては公式ドキュメントや,Fluentdとはどのようなソフトウェアなのかを参考に.
アプリから直接保存する
主にクライアントサイドの話で,いきなりFluentdを使わないパターン.JavaScript SDKを提供している解析サービスやモバイル端末などでよく使われている.ログがユーザサイドで発生する場合にはログコレクタを仕込むことは出来ないので,基本このアプローチになる.
これらはクライアントの出来によって精度が左右されるが,モバイルであれば繋がらなかったら一定量バッファリングをし,ネットワークが繋がった段階で非同期に転送などがよくある実装.
ログコレクタを用意出来るサーバサイドでこのアプローチを取っているシステムは最近だとあまり聞かない.もしやるならエラーハンドリングとして
- バッファリング
- リトライ
- ロードバランシング
などの問題を解決する必要がある.Fluentdであればこれらの機能が最初からついているので,今から構築するシステムでやる必要はほとんど無いと思われる.
末端ノードのログコレクタから直接保存する
アプリケーションやノードからログを集め,ログコレクタから直接保存する.「アプリから直接保存する」の例で,ローカルにログコレクタを置き,そこ経由でデータを投げつけるモデル.
ネットワーク周りのややこしい問題をログコレクタに任せられ,Fluentdであればプラグインで容易にストリームを操作できるのがメリット.また,ApacheやNginxから直接外部にデータを投げるのは難しいので,Fluentdでファイルにはき出されたログを収集するときにはこの構成となる.
この構成だと,収集対象のサーバが多くなるだけアクセスが増えて保存先への負荷があがるので
- データのストリームがそれほど大きくない
- 保存先が書き込みに対して耐性がある (クラウドサービスなど)
などの場合に有効になる.次で書く集約ノードが必要ないので,その分管理は楽になる.
Treasure Dataの場合には,Librato Metricsに投げる所などは集約ノードを経由せず,各Treasure Agentから直接投げている.この場合は各ノードのメトリックスが取りたいので,集約ノードでデータストリームをまとめる必要もない.
これとは違いMongoDBの場合だと,細かいのをちまちま送るより大きめのバッチでガンと書き込んだ方が効率が良いので,このモデルよりも集約ノードを置いた方が良い.
集約ノードを経由して保存する
ログコレクタでの多段構成モデル.集約ノードを置く利点は,データストリームをまとめてデータに対する処理をしやすくする点が大きい.また,各ノードから集めることでストリームが太くなり,マイクロバッチの効率も良くなる.
Fluentdでもアグリゲーションをしたり,Norikraと連携するプラグインがあったりするので,その手の処理はこの集約ノードでやるのがベター.
大抵のログコレクタと同じく,FluentdのデフォルトのforwardプロトコルはPush型になっていて,多段構成時には流れるように保存先にデータが集まるようになっている.
多段構成時の注意点は,ある場所のノードが落ちた時にストリームが途切れてしまう所.Fluentdの場合には問題に対応するために,forward先を複数指定可能で,その中での負荷分散とノードが落ちた時の処理を指定出来るようになっているので,よほど一気にノードが落ちない限りストリームが途切れることはない.もし落ちてもバッファリングが行われるので,ノードを再度立ち上げればデータはまた流れ出す.
PaaS上でのアプリケーション
アプリケーションをホストするような環境だとローカルにFluentdを置けなかったりするので(HerokuだとFluentdは起動できるが,ファイルバッファが使えなかったりする.ユースケース次第),その場合はいきなり集約ノードやキューにデータを投げることになる.
キューを置く
データストリームの中にキューを間に挟むことの主な理由は,ログのreliabilityの向上と,処理の間でワンクッション置くこと.
そもそもキューはPush型ではなくPull型であり,Producer・Queue・Consumerと登場人物も増えるので,キューを置くことでログ収集そのものが効率化されることはあまりない.その他のシステムとの連携をするときに,間にキューを置くことが多い.
実際Fluentdとキューを連携させている例はいくつかあり,たとえば複雑なシステムを構築しているVikiの例ではKestrelでFluentdからのデータストリームを分岐させているし,AWSは本家がfluent-plugin-kinesisを書いているので,いずれこの事例も増えるはず.
KafkaやKinesisなど信頼性のあるキューを間に置けば,ログをそれなりの期間保持しつつ,Subscriberを複数用意することでストリームを分岐できる.
Push型での分岐とは違い,キューからログを取る側の都合でデータを処理出来るので,各データストリームで処理粒度が違う時に有効.
ただ,キューそのものに信頼性があってもProducer/Consumerが駄目だと効率が良くならずログがロストしたり重複したりするので,自分たちのシステム要件に合わせてちゃんと構築する必要がある.
Pull型のログ収集
キューを置かずにPull型でログを転送する方法もある.Kafkaほどの信頼性を期待するのは難しいが,Pull型の利点を享受できる.まだ完成していないが,Fluentdだとモリス=タゴという人が,pullforwardというのを開発中らしい.
Fluentdを使わない方が良いパターン
プロダクションで使われているパターンをいくつか書いた.大抵のシステムは上のどれかの構成でどうにかなるはず.FluentdはWebや広告のみならずIoTやPOS含め色々なところで使われていて,データのアーカイブ,簡単なストリーム処理,ノードやアプリケーションのモニタリング,などなど様々なケースの基盤になる柔軟性がある.
また,LINE社で2年間問題なく動いているし,最近勉強会とかで話を聞くと「安定していて自分の仕事がない」と言われる位,よほど変なことしない限り安定性もある.ある会社で100億events/dayを転送した実績もあるので,パフォーマンスもそれなりにある(フィルタ的な処理を重ねまくると,もちろん遅くなる).
が,Fluentdを使わない方が良いケースもある.以下主に二つ.
Exactly Onceが必要なケース
一切ログの欠損も重複も許せない,しかも確実に書き込む必要がある,というケース.ストリーム処理でExactly Onceを保証するのはかなり難しく,俺が知っている限りOSSでは存在していない.
Exactly Onceを実現するならスケールアウトなどがかなり難しくなるので,やるなら保存先合わせて密に作った方が現実的.Fluentdでもやろうと思えば出来るが,おそらくかなり特殊なプラグインを作る必要がある.
データストリームが一本で密結合したサービスがある
たとえばAWSのCloudWatch Logsがそう.Agentのセットアップが必要だったりして楽かと言われるとまだ微妙だが,今後最初からインストールされる可能性もある.
そのような状況で,Agentから取れるログをCloudWatch Logsで直接見るので十分であれば,Fluentdを入れるメリットは少ない(CloudWatch Logs用のプラグインがあるので,Fluentdから利用することは可能).
Fluentdの利点は簡単にロバストで柔軟性のあるログ収集基盤が作れて,一度構築してしまえば,環境非依存でそのログ収集基盤を再利用出来るところなので,そのような必要性がないのであれば,環境と密結合したサービスを使う方が運用コストそのものは削減できるはず.
まとめ
Fluentdが向いているケースと向いてないケース,そのPros/Consみたいなのを書いた.細かく書けばもっと色々と書けるのだけど,記事でそんなに長々と書いても疲れるだけなので大まかに. 何か質問があればTwitterで@repeatedlyにmentionするなり,この記事にコメントするなりしてください :)
もうすぐFluentd + Elasticsearch + Kibanaという有名な構成のムック本が出るようなので,この本とか読むと,解析のノウハウ含め色々とここには書いていない情報が手に入るのではないかと思います.
何か思い出したら追記するかもしれません.