Tech Sketch Bucket of Technical Chips by TIS Inc.

Fluentdの仕組み -バッファ機能でログ収集漏れを防ぐ-

Pocket

OSSのログ収集管理ツールFluentdを用いてログを統合管理している場合の懸念点として、ログの収集漏れが考えられます。
Fluentdでは、バッファ機能を活用することでログを収集漏れすることなく確実に収集することができます。
このバッファ機能のメカニズムを理解すべく動作検証した結果を紹介します。対象とするFluentdのバージョンは0.10.30です。


Fluentdとは

Ruby実装のOSSのログ収集管理ツールです。
Fluentdは、Input、Buffer、Outputの3つのコンポーネントで実現されています。
様々な場所からログを収集、JSON形式に変換し(Input)、蓄積(Buffer)、様々な出力先にデータ出力(Output)します。
例として、あるサーバ(server01)のApacheのアクセスログを別のサーバ(server02)内にファイルとして出力する場合の設定は次のようになります。

server01の設定

server02の設定

これの設定によりserver01で出力されるApacheアクセスログがリアルタイムにserver02の/tmp/receive.logにデータが蓄積されます。

バッファ機能

ここで気になるのが、ネットワーク障害など、何らかの要因によりログが正常に処理できなかった時にどうなるかという点です。
この点について、Fluentdではバッファ機能を使うことでロストすることなく管理できるようになっています。

Fluentdのバッファはメモリバッファとファイルバッファの2種類から選択して利用できます。
今回は、バッファの挙動確認が行いやすいファイルバッファで検証してみます。

この ドキュメント でも解説されている通り、バッファはchunkとqueueの仕組みで管理されます。
1chunkに保存できるデータサイズの上限と1queueに保存できるchunkの数の上限の設定により、処理できなかったログデータの保存量が決まります。

再送については、次の再送までの時間間隔と再送実行回数の上限の設定により、挙動が決まります。

バッファ設定例

例として次のような場合の設定を見ていきます。

  • 1chunkのデータサイズ3KB
  • 1queueのchunk数 3個
  • 再送までの時間間隔 30秒
  • 再送実行回数 5回

先程のserver01の設定の場合は次のように変更します。

バッファの挙動

バッファの挙動を確かめるため、server01からserver02への通信を断ちます。
その状態でApacheにアクセスログが追記されるとバッファに蓄積されます。
すると、再送のループが開始します。

バッファにデータが蓄積される様子を簡単に図でまとめるとこのようになります。

fluetnd-buffer-architecture.png

この時、Fluentd処理内容のログとしては以下のようなログが記録されます。

fluentd-log.png

各時点でのバッファの状態は次の通りです。

バッファ蓄積直後の状態(図中の(1)の時点)

継続して処理できない状態が続きログデータがqueueに溜まった後の状態(3KBを上限として最新のバッファデータ保存用ファイルが1つ、queueファイルが3つ作成される)(図中の(2)の時点)

buffer_queue_limitで指定した数のqueueがいっぱいになった場合、それ以降バッファに送られてきたデータは全て破棄されます。
再送処理については、retry_waitで指定した時間で再送を行った後、(retry_wait×1)秒後、(retry_wait×2)秒後、(retry_wait×4)秒後、(retry_wait×8)秒後と指数関数的に再送する間隔を自動的に伸ばされ、処理されます。

再送実行回数制限が超えた後(図中の(3)の時点)

ここでqueueファイル(fluent.apache.access.qxxxxxx.bufferファイル)が削除されているのがわかります。
最新のバッファへの書き込み情報のみが残ります。

このファイルについては、次の再送のループが始まると、queueファイルとして扱われます。
次の再送ループ開始時(図中の(4)の時点)

この状態でさらにApacheのログが追記されると再び最新のログデータ蓄積用バッファファイルに変わります。

この一連の処理が繰り返し行われます。

復旧時の再送処理

復旧時にはqueueに蓄積されているchunkが1つずつpopされ送信されます。
そのため、buffer_chunk_limitに大きな値を設定していると復旧時に大量のデータが送信されることになるので注意が必要です。

Fluentdシャットダウン時の処理

Output出力先に送付できない場合にはこれまでのような処理でログの損失を防ぐことができました。
しかし、ログを処理するFluentd自体がシャットダウンした場合にはどのような挙動を示すでしょうか。

Fluentdの内部のbefore_shutdownにてシャットダウン直前の処理が記載されています。
ファイルバッファの場合は次のような設定になっています。(plugin/buf_file.rb)

一方メモリバッファの場合はこのような設定になっています。(plugin/buf_memory.rb)

これを見ると、メモリバッファの場合はデフォルトでバッファからデータを取り出してから終了するように書かれています。
一方、ファイルバッファについては@flush_at_shutdownがtrueの場合のみバッファからデータを取り出して終了するよう書かれています。
この@flush_at_shutdownには、flush_at_shutdown設定値が格納されています。この値はデフォルトではfalseに設定されているためファイルバッファを用いる場合にはシャットダウン時もそのままバッファファイルデータが残り続けることになります。
ファイルバッファでもシャットダウン時に全て出力してから終了したい場合には次のように設定する必要があります。
ただし、before_shutdown処理時にアウトプットに失敗した場合にはそのままバッファファイルは残ります。

注意点
シャットダウンから復旧した場合のtailプラグインの挙動について注意すべき点が1点があります。
tailプラグインの設定でpos_fileの設定を行っていない場合、シャットダウン以前にFluentdがログファイルのどこまでを処理したのかがわからなくなります。
そのため、pos_fileでどの位置まで読み込んでいたかの情報を書き出す先のファイルを指定しておく必要があります。
これにより、シャットダウンからの復旧後に追記されたログファイルのみを処理することができます。

アウトプットプラグインの実装

Fluentdのバッファ機能は単体で利用するのではなく、アウトプットプラグインとセットで実現されます。
アウトプットプラグインはバッファ機能を利用したい場合は、BufferedOutputクラスを継承します。
(または、BufferedOutputクラスを継承しているTimeSlicedOutputクラスやObjectBufferedOutputクラスを継承する。)
バッファ機能を利用しない場合にはOutputクラスを直接継承することで実装できます。

これらのクラスを継承してアウトプットプラグインを実装した場合にはデフォルトでバッファの挙動に関する以下の設定が定義されています。

共通

  • buffer_type→memory
  • flush_interval→60秒
  • retry_limit→17回
  • retry_wait→1秒

メモリバッファの場合

  • buffer_chunk_limit→8*1024*1024 (8MB)
  • buffer_queue_limit→64

ファイルバッファの場合

  • buffer_chunk_limit→8*1024*1024 (8MB)
  • buffer_queue_limit→256

これらの設定をアウトプットプラグインの設定で上書きすることで要件に応じた柔軟な対応が実現できます。

まとめ

ここまで説明してきたように、Fluentdはログデータをロストしない仕組みを十分に備えています。
ただし、バッファがあふれた時にはログを受け付けなくなるなど、どんな状況でも100%紛失することがないという万能な仕組みが整っているわけではありません。
裏側の仕組みを正しく理解した上で、実現したいシステムの要件に見合った正しい設定を行う必要があります。
ログ収集ができる他のツールとしてはrsyslog等があります。これらのツールにも再送の仕組みが備わっていますが、Fluentdはキューイング処理のアーキテクチャ(queueとchunkの扱い方)や再送タイミングを自動的に間隔調整する機能など、よりログの収集漏れに対して細やかな配慮がされていると感じました。
大量のログデータを処理するのに適したツールではないでしょうか。

参考

Buffer Plugin Overview
FluentdでバッファつきOutputPluginを使うときのデフォルト値

エンジニア採用中!私たちと一緒に働いてみませんか?