2016.11.03この記事は約11分で読めます。fluentdは、ログ収集ツールで人気のソフトウェアとして広く知られており、私も以前から大変お世話になっているOSSのひとつです。ログファイルからデータを読み取り後続の処理にデータを流すfluentdに標準で組み込まれているtailプラグインは、最も良く使われる機能のひとつだと思いますが、内部の動作について理解が不十分だと、あれ??と想定通りの結果が得られずハマってしまうこともあるかもしれません(私がそうでした。ドキュメントを参照しながら設定するだけですぐに使い始められるのですが、何か障害がおきた時に内部動作についてある程度把握していることによって、その後の原因調査をスムースに進められることができると思います。そこで、備忘録の意味も込めて、tailプラグインについて自分なりに調べた内容をまとめてみました。内容に間違いあれば指摘していただけると幸いです。公式のドキュメントを参照して和訳したものです。numberが変わった場合はファイルをreopenし、tailを再開してくれる)。tail開始時、デフォルトではtail対象のファイルの先頭でなく末尾からデータの読み込みを開始する。positionファイルを指定することでtailしていたファイルの最終の読み込み位置(pos)を記録し、次回開始時にpos以降のデータから読み込みを開始する。Pluginの内部を追ってみるin_tailでは、libev[1]http://libev.schmorp.de/bench.htmlというイベントライブラリベースのCool.io[2]https://github.com/tarcieri/cool.ioというライブラリを使って、TimerやStat(ファイルやディレクトリの変更)のイベントを監視している。以下全体像を図にしてみる(概要把握のため省略部分あり)。v0.12.12のソースを参考。NewTailInputは、fluentd起動時にrefresh_intervalで指定された間隔で発火されるTimerイベントを生成し、Cool.ioのイベントループに登録する。その後はrefresh_intervalごとにTimerイベントが通知され、refresh_watchersメソッドが呼び出される。その中で、既存の古いwatcherの停止や新しくtailに追加されるpathのファイルを監視するためのTailWatcherを新しくセットアップする。TailWatcherは1つ1つのpath(ワイルドカードを含む場合は展開されたもの)に対して生成され、初期化時にデフォルトで1secごとのTimerイベント、Statイベント及びファイルローテートのためのハンドラなどが割り当てられる。TailWatcherのTimer及びStatのイベントが通知されるとTailWatcher#on_notifyメソッドが呼ばれ、登録された各種ハンドラが呼び出される。pathのファイルに読み取るべきデータがあれば、IOHandlerによって一回の処理で最大行数がread_lines_limitを超えないようバッファされ、parserによってパースされ、tag付けされイベントとしてemitされる。rotateの判定rotateは、以下のステップで判定される。同じファイルに対するシンボリックリンク・ハードリンクが再作成された、とみなしpos_fileに記録されていたposから読み込みを開始する。pos_fileにあるinode番号が0でない場合、pos_fileを指定しfluentdを以前に起動していたケースと想定し、rotateされたファイルの先頭からデータの読み込みを開始する。上記以外の場合、read_from_headがtrueであればファイルの先頭から、そうでない場合はファイルの末尾からデータの読み込みを開始する。以下のような指定の場合は、上記の判定ステップの3に該当するため、read_from_headオプションをtrueにしていなかった場合は初期のデータの読み込みをスキップしログを取りこぼしてしまう可能性がある。これは、動作概要にあったようにtailでは末尾からデータの読み込みを開始する事になっているためである。/data/application.log.pos上記の場合は、時間単位で新しくtail対象のファイルがrefresh_watcherメソッドの呼び出しの中で検出される。この時、pos_fileにtail対象のpathとpos及びinode番号が初期値で記録される。read_from_headオプションがtrueでinode番号が初期値(=0)から更新されていなければ、新しいpathのinode番号とpos(=0)でpos_fileの上書きを試みる。pathのファイルが存在しない場合は上書きされず、次のようなログを確認できるだろう。it.続けて、pathに対するTailWatcherのイベント監視がセットされる。tail対象のpathがファイルシステム上に見つかれば、TailWatcher#on_rotateメソッドの中でinode番号とpos(read_from_headがfalseの場合はstat.size、trueでは0)が更新され、そのpathのIOHandlerが生成される。以下のようにpathが固定の場合、通常はrotateして新規にファイルを生成する事になるが、この場合は上記判定ステップの2に該当する。/data/application.log.pospos_file中のpath名は変わらないが、inode番号とposは更新される。pos_fileの構造pos_fileの中はテキストで読める形式になっており、構造は以下の通り。\n時間のformat文字列を指定した場合のpos_fileは以下のようになっている。0000000001100c47fff...ffは、pathがunwatch状態であることを示している。pathが実際に存在しない場合は、posもinodeも0になっている。(必須)emitするイベントに使われるtag名を指定する。(必須)tail対象のpath。時間のformat文字列や*を指定することもできる。exclude_path除外したいpathをarrayで指定する。refresh_intervalpathに対するwatcherを更新するリフレッシュ間隔(秒)を指定する。read_from_headこのオプションが指定されていると、新規にtailされるpathのファイルの先頭からデータの読み込みを開始する。デフォルトはfalse。pathに時間のformat文字列が含まれるような場合にはtrueにしておくことで、時間単位などのpath指定でファイルの先頭のログだけ送られていないといった思わぬ事態に陥らずに済む。read_lines_limitIOHanlderが一回のステップ(IOHandler#on_notify)でログを処理する最大行数。デフォルトで1000。multiline_flush_intervalformatがmultiline指定の場合に有効で、複数行にまたぐログを扱う時のバッファのflush間隔(秒)。pos_filepathの読み込み位置(pos)を記録するファイル。fluentdを再起動しても、最後に記録したpos以降からデータの読み込みを開始できる。できるだけ指定するよう推奨されている。(必須)レコードのparse方式を指定する。noneでログをそのまま扱う。time_formattimeフィールドが含まれる場合の時間を解釈するための時間のformat文字列。rotate_waitrotateされた後に監視を停止するまでの間隔(秒)を指定する。rotateされた後もここで指定された時間の間はtailを継続してくれる。enable_watch_timerTailWatcherで1secのTimerイベントを使うか否かを指定する。inotify[3]https://linuxjm.osdn.jp/html/LDP_man-pages/man7/inotify.7.htmlをサポートしていないシステムのために用意されている。デフォルトはtrue。その他pos_fileを誤って削除してしまった場合LinuxやUnixでは、rmコマンドでpos_fileを削除してもプロセスが生きている限り実態は存在しておりエラーにならない。稼働中はpos_fileが再作成されることはないので、次回起動時に新規にpos_fileが作成される事になる。この時pos_file中のposは、pathで新しくtailを開始した時の値(前出のrotate判定の3)で更新され、read_from_headがtrueの場合はファイルの先頭からデータの読み込みを開始するためデータの重複が起きてしまう。pos_fileを削除しないよう注意するとともに、誤削除防止の意味でもpos_fileは通常のログディレクトリとは別に専用のディレクトリに書くようにした方が良いと思う。read_from_headオプションtrue時の挙動read_from_headをtrueにしていた場合、pos_file指定がないとpathで指定されたファイルの先頭からデータの読み込みを開始するので、送られるデータが重複してしまうかもしれない。read_from_headがtrueだとしても、すでに読み込みされていれば最終のpos以降からデータが読み込まれるのでデータの重複は起こらない。重複を回避するための方法として、ログにuuidのような一意な識別子を振りアプリケーションで処理する際に重複カットするなどがある。/data/tmp/click.posこの場合、前出のrotate判定で2に該当する。inode番号は新規、posは0でpos_fileのエントリが更新され、新しいpathのファイルの先頭からデータの読み込みが開始される。通常行わない操作だと思うが、この場合前出のrotate判定で1に該当する。pathのファイルをrenameした後、pathに対するTailWatcherによる監視はrotate_waitの時間だけ継続される。同時に新しくpathに対するTailWatcherが生成されイベント監視を開始する。mvでrenameしたファイルを元のpathに戻すと、pos_fileに記録されているinode番号が同じであるため、pos_fileに記録されていたpos以降からデータの読み込みを再開する。この時、renameした後のrotate_waitの期間内にデータが追記されていた場合、posはメモリ上更新されるがその変更はpos_fileには反映されない。よって、pos_fileの最後に記録されていたposからデータの読み込みが開始され、既送信済みデータとの重複が起きる可能性があるので注意する。rotateする前にfluentdがダウンして、rotate後にfluentdを開始した場合この場合は、前出のrotate判定2にあたる。rotateする前にfluentdがダウンしてpathのファイルにデータが追記された後、pathのファイルがrotateされfluentdを再起動した場合は、新しくポイントされたpathのファイルの先頭からデータの読み込みが開始されるので、fluentdダウン後に追記されたデータは送られてこない。"<line>"}の形式でイベントをemitする。pathは時間単位のformat文字列を指定し、時間単位で分けられたファイルをtailする。新規pathの検出の間隔は30sec。pos_fileを指定し、再起動後も最終のpos以降からデータの読み込みを開始する。read_from_headをtrueに設定し、新しいtail対象のpathが検出された時は、pathで指定されたファイルの先頭から読み込みを開始する。/data/tmp/application.log.pos</source>timeフィールドは、time_formatで指定されたformat文字列で解釈される。pathは固定のファイルで、rotateされた後は新しくポイントされたファイルの先頭から読み込みを開始する。multiprocessプラグインを使うfluentdでマルチコアのサーバーリソースを生かすため、fluentdのmultiprocessプラグインを導入することにした。実装されているようです。FluentdfluentdのBufferedOutputについて理解が浅かった。コメントをキャンセルメールアドレスが公開されることはありません。»タイトルとURLをコピーしました。