fluentbitのキホン
fluentbitがどうメッセージを保持しているのかとか、わからなかったのでお勉強してみた。
Input, Filter, Outputが何とか、confファイルの書き方などは説明していないので、その辺は理解している前提。
お勉強の構成
fluentbitはコンテナでたてる。
今回使うfluentbitを起動するdocker-compose.ymlは以下の通り。
いろいろ見たい可能性があるので、debug付きのイメージを利用。
services:
fluentbit:
image: fluent/fluent-bit:3.0.3-debug
volumes:
- "$PWD/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf"
- "$PWD/log:/tmp/log"
以下のfluent-bit.confを作ってdocker-compose.ymlと同じディレクトリに置く
[INPUT]
Name tail
Path /tmp/log
[OUTPUT]
Name stdout
Match *
/tmp/logファイルに書き込まれた内容をstdoutに出力するだけの簡単なコンフィグ
同じくlogファイルを作ってdocker-compose.ymlと同じディレクトリに置く。
flubent-bit.confとログファイル(logファイル)はホスト側において、bind mountする。
fluentbit起動
docker compose upで起動。メッセージの様子を見るためにコンソール画面が見るので、
-dオプションはつけない(大事)
[+] Running 1/2
✔ Network fluentbit_default Created 0.1s
⠋ Container fluentbit-fluentbit-1 Created 0.0s
Attaching to fluentbit-1
fluentbit-1 | Fluent Bit v3.0.3
fluentbit-1 | * Copyright (C) 2015-2024 The Fluent Bit Authors
fluentbit-1 | * Fluent Bit is a CNCF sub-project under the umbrella of Fluentd
fluentbit-1 | * https://fluentbit.io
fluentbit-1 |
fluentbit-1 | ___________.__ __ __________.__ __ ________
fluentbit-1 | \_ _____/| | __ __ ____ _____/ |_ \______ \__|/ |_ ___ _\_____ \
fluentbit-1 | | __) | | | | \_/ __ \ / \ __\ | | _/ \ __\ \ \/ / _(__ <
fluentbit-1 | | \ | |_| | /\ ___/| | \ | | | \ || | \ / / \
fluentbit-1 | \___ / |____/____/ \___ >___| /__| |______ /__||__| \_/ /______ /
fluentbit-1 | \/ \/ \/ \/ \/
fluentbit-1 |
fluentbit-1 | [2024/04/28 23:45:21] [ info] [fluent bit] version=3.0.3, commit=3529bbb132, pid=1
fluentbit-1 | [2024/04/28 23:45:21] [ info] [storage] ver=1.5.2, type=memory, sync=normal, checksum=off, max_chunks_up=128
fluentbit-1 | [2024/04/28 23:45:21] [ info] [cmetrics] version=0.9.0
fluentbit-1 | [2024/04/28 23:45:21] [ info] [ctraces ] version=0.5.1
fluentbit-1 | [2024/04/28 23:45:21] [ info] [input:tail:tail.0] initializing
fluentbit-1 | [2024/04/28 23:45:21] [ info] [input:tail:tail.0] storage_strategy='memory' (memory only)
fluentbit-1 | [2024/04/28 23:45:21] [ info] [sp] stream processor started
fluentbit-1 | [2024/04/28 23:45:21] [ info] [output:stdout:stdout.0] worker #0 started
起動に成功すると上記のような画面が出力されるはず。
その1 メッセージの構造を知る
上記に書いてある通り、fluentbitはメッセージ(イベント)は以下のような形で保持しているらしい。
[[TIMESTAMP, METADATA], MESSAGE]
ということで実際どうなのか、見てみる。
ホスト側に置いたlogファイルに何か書き込んで、コンテナのコンソール画面に何が表示されるか見てみる
echo "test message" >> log
そうするとコンテナのコンソールに以下のようなメッセージが表示されるはず
fluentbit-1 | [0] tail.0: [[1714351339.829795944, {}], {"log"=>"test message"}]
tail.0:の後ろがfluentbitが受信したイベント(ログ)。
全体のフォーマットは、Key conceptsに記載された通り
[[TIMESTAMP, METADATA], MESSAGE]
の形で保持している。METADATAは空。
同じくKey conceptsの起債によれば、TIMESTAMPはナノ秒まで保持しているらしい。。。
肝心のMESSAGEの部分。
{"log"=>"test message"}
単純に受信したメッセージを保持しているのではなく、Key-Value形式で保持しているらしい。
Keyは"log"なので、メッセージを参照する場合は、$logで参照可能
その2 デフォルトタグを知る
デフォルトの状態で、メッセージは、Key conceptsに記載の通りに保持されることが分かった。
次に、fluentbitの重要要素の一つであるタグ、さらに言えばデフォルトタグが何かを知る。
同じく、Key conceptsのTAGによると
Most of the tags are assigned manually in the configuration. If a tag is not specified, Fluent Bit will assign the name of the Input plugin instance from where that Event was generated from.
大抵タグは、定義ファイルでアサインされる。もしタグが指定されていない場合は、イベントが生成されたInputプラグインのインスタンス名をfluentbitが割り当てる。
Inputプラグインのインスタンス名が具体的に何かを調べてみる。
違いが分かるようにInputにタグをつけない場合とつけた場合の出力が以下。
タグをつけない場合
fluentbit-1 | [0] tail.0: [[1714377370.103175583, {}], {"log"=>"aa"}]
タグ(test_tagというタグ)をつけた場合(fluent-bit.confのINPUTに Tag tag_testを追加した場合)
fluentbit-1 | [0] tag_test: [[1714434396.743894644, {}], {"log"=>"aa"}]
ということで、つけない場合は、tail.0(起動時に出力されている以下)がつくということが分かった。
fluentbit-1 | [2024/04/29 23:50:04] [ info] [input:tail:tail.0] initializing
fluentbit-1 | [2024/04/29 23:50:04] [ info] [input:tail:tail.0] storage_strategy='memory' (memory only)
ちなみにINPUTを複数設定した場合は以下のようになる。
fluentbit-1 | [2024/04/29 23:50:04] [ info] [input:tail:tail.0] initializing
fluentbit-1 | [2024/04/29 23:50:04] [ info] [input:tail:tail.0] storage_strategy='memory' (memory only)
fluentbit-1 | [2024/04/29 23:50:04] [ info] [input:tail:tail.1] initializing
fluentbit-1 | [2024/04/29 23:50:04] [ info] [input:tail:tail.1] storage_strategy='memory' (memory only)
fluentbit-1 | [2024/04/29 23:50:04] [ info] [input:tail:tail.2] initializing
fluentbit-1 | [2024/04/29 23:50:04] [ info] [input:tail:tail.2] storage_strategy='memory' (memory only)
input:tail:~~ の~~がデフォルトタグになるはず。
まぁ、ログの出力元が分からなくはないが、直感的じゃない。
その3 タグの書き換え
基本(デフォルト)のメッセージとタグが理解できたところでタグによる出力先の振り分けをやってみる。
やってみること
- メッセージに err という文言が含まれている場合は/tmp/errorファイルにログを出力
- メッセージに warn という文言が含まれている場合は/tmp/warningファイルにログを出力
やりかた
FILTER
- Rewrite Tagを使ってメッセージ本文を精査
- errという文言が含まれている場合はタグをerror.$TAG($TAGは元のタグ)に書き換える
- warnという文言が含まれている場合はタグをwarning.$TAG($TAGは元のタグ)に書き換える
OUTPUT
こちらは、Matchを使って
- タグがerror.*の場合は/tmp/errorファイルに出力
- タグがwarning.*の場合は/tmp/warningファイルに出力
ということをやってみる
fluent-bit.conf変更
以下のように書き換え
[INPUT]
Name tail
Path /tmp/log
[FILTER]
Name rewrite_tag
Match *
Rule $log .*err .* error.$TAG false
[FILTER]
Name rewrite_tag
Match *
Rule $log .*warn .* warning.$TAG false
[OUTPUT]
Name file
Match error.*
Path /tmp/error
[OUTPUT]
Name file
Match warning.*
Path /tmp/warning
[OUTPUT]
Name stdout
Match *
最後のOUTPUTはデバッグ用に標準出力に出してるだけなので、必須ではないです。
先に書いておくと、この例失敗します
起動
docker compose upで起動してみる。
起動はしますが、起動時に以下のようなメッセージが出力され、rewrite_tagは無効な状態で起動してくる。
fluentbit-1 | [2024/04/30 10:07:52] [ warn] [filter:rewrite_tag:rewrite_tag.0] 'Match' may cause infinite loop.
上記メッセージの通りなのですが、Match * となっていて、どんなメッセージでもひっかけてしまう。
- err という文言が含まれているメッセージを検知してタグを error.~~~ に書き換える
- 書き換えたタグも同じくひかかって error.error.~~ というタグに書き換える
上記処理が延々と繰り返されてしまい、無限ループに陥ります。
別のバージョンで同じく無限ループに入った場合は、rewrite_tagの無限ループがチェックされず有効かされた状態で起動され、いざログを流してみるとfluentbitが落ちるという事象が発生しました。。。。(チェックできる範囲があるのか、バージョン差異なのか、、)
おそらくなのですが、FILTERで変更を加えると、再度すべてのFILTERを評価し、適合するFILTERがなくなると、次のStageに移行するみたい。
なので、FILTERはそこそこ厳密に書かないといけない模様。。。まぁ、Match * とかしないようにすると覚えておけばよいかと。
定義の作り直し
上記を踏まえ、再度fluent-bit.confを作り直し。
Matchのところを書き換えます。Matchについては、Routing Sectionに記載があるのでそこを参考に書き換え。
[INPUT]
Name tail
Path /tmp/log
[FILTER]
Name rewrite_tag
Match_regex [^(error|warning)].*
Rule $log .*err.* error.$TAG false
[FILTER]
Name rewrite_tag
Match_regex [^(error|warning)].*
Rule $log .*warn.* warning.$TAG false
[OUTPUT]
Name file
Match error.*
File /tmp/error
[OUTPUT]
Name file
Match warning.*
File /tmp/warning
[OUTPUT]
Name stdout
Match *
Matchの個所を正規表現を使うようにMatch_regexに変更。
タグがerrorもしくはwarningで始まらない場合という風に書き換えます。
再度起動
docker compose upで起動してみる。
今回は正常に起動するはず
動作確認
ホスト側で echo "aa err error" >> log
を実行。
docker compose exec -it fluentbit cat /tmp/error
を実行して以下のように表示されればOK
$ docker compose exec -it fluentbit cat /tmp/error
error.tail.0: [1714528459.975296387, {"log":"aa err error"}]
echoを実行したタイミングでコンテナを実行したターミナルにも以下が表示されているはず。
fluentbit-1 | [0] error.tail.0: [[1714528459.975296387, {}], {"log"=>"aa err error"}]
タグがtail.0からerror.tail.0に書き換わっているのも確認できる。
warningもやってみてください。
FILTERの評価順序について
FILTERの評価順序は、fluent-bin.confに記載した順序で評価されます。
試しに以下を実行してみてください。
echo "err warn" >> log
echo "warn err" >> log
どちらも error.tail.0のタグが付与され、/tmp/errorファイルにログが出力されていると思います。
fluent-bin.confのFILTERの順序を変えてwarnを上に持ってきて再度やってみてください。
今度は、どちらもタグが warning.tail.0 になっていると思います。
今回は簡単な例だったので完璧に振り分けることは出来ませんが、実際に運用するとなると
- 一意に識別できるようなログフォーマット
- 一意に識別できるような正規表現
が必要になるので、ログを設計する際に留意してください。(あとからどうこうしようとすると大変なので。。。)
以上が、fluentbitのキホンになります。
Parserを使って、ログを構造化したり、色々できることはあるので、試してみてください。
Discussion