[初心者向け]シンプルなものを触りながら理解するFluent Bit
こんにちは。まるとです。
今回、Fluent Bitを触る機会が得られたため、シンプルなものを構築しながらざっくり動きを理解できればと思い、備忘録として記事を執筆します。
Fluent Bitとは
ログの収集やフィルタリングなどの加工、転送などを行える、テレメトリエージェントです。
Fluent Bitを使用することで様々なソフトウェアのログを収集しながら、ログ形式統一化をはじめとした加工を行えます。
Fluent Bitのパイプライン
Fluent Bitのパイプラインは以下で構成されます。
名称 | 説明 |
---|---|
Input | Fluent Bitにデータを入力(収集)するフェーズ |
Parser | ログをパース(構造化データに変換)するフェーズ |
Filter | データを加工、変換、フィルタリングするフェーズ |
Buffer | Filterで加工されたログを不変の状態で保持するフェーズ |
Router | ログの配信先をルーティング(宛先の設定)するフェーズ |
Output | データの送信先を定義、出力するフェーズ |
図に示すと以下のフローとなります。
順番に見ていきましょう。
Input
実際にログを収集するための入り口(収集方法の設定)を定義するフェーズです。
収集方法は複数の方法をサポートしており、標準入力、tail
などシンプルなものから、MQTTやSyslog、HTTPなど各種プロトコルが対応しています。
ここではどのような方式でログを収集するのかのみを定義します。
詳しい設定方法は後ほど「触ってみよう」の章でご説明します。
なお、公式ドキュメントでは以下のページで案内しています。
Parser
収集したログを構造化データに変換するフェーズです。
例えば以下のログがあったとします。
[2025/1/15 15:00] FROM:Aさん TO:Bさん MESSAGE:こんにちは1
[2025/1/15 15:01] FROM:Aさん TO:Cさん MESSAGE:こんにちは2
[2025/1/15 15:02] FROM:Bさん TO:Cさん MESSAGE:こんにちは3
人間が見ると、日時
、送信者
、送信先
、メッセージ
という4つの要素があるように見えます。
しかし、コンピュータの世界では、ただの1行の文字列に過ぎません。
ただの文字列ではデータの構造が明示されていないため、例えば、「メッセージ
の部分だけ抜き出したい!」となっても、コンピュータどこを抜き出せばいいのかわかりません。
そこでパースを行うことで、データの構造を明示(構造化データに変換)してあげる必要があります。
例えば、以下のように変換することができます。
※わかりやすいようにプロパティ名を日本語で記載しています。
{
"日時": "2025/1/15 15:00",
"送信者": "Aさん",
"受信者": "Bさん",
"メッセージ": "こんにちは"
}
このように構造化データに変換してあげることによって、プログラムで特定の要素を取得や加工が容易となります。
Filter
Filterは収集したログデータを加工できるフェーズです。
例えば先ほどのログ
[2025/1/15 15:00] FROM:Aさん TO:Bさん MESSAGE:こんにちは1
[2025/1/15 15:01] FROM:Aさん TO:Cさん MESSAGE:こんにちは2
[2025/1/15 15:02] FROM:Bさん TO:Cさん MESSAGE:こんにちは3
から送信者
がAさん
のものだけ、ログとして出力したいとします。
# 出力例
[2025/1/15 15:00] FROM:Aさん TO:Bさん MESSAGE:こんにちは1
[2025/1/15 15:01] FROM:Aさん TO:Cさん MESSAGE:こんにちは2
先ほど、構造化データに変換したことにより、特定の要素を取得は容易になっています。
そのため、今回の場合では送信者
がAさん
というフィルタを作成することで実現できます。
実際にこの後の触ってみようでも触れていきたいと思います。
その他、ログごとにタグをつけることでき、付与されているタグによってログの配信先を振り分けるということも可能です。
Buffer
メモリまたはファイルシステム上に、ログをバッファリング(一時的に記録)するフェーズです。
ネットワークの問題などによりログを配信できないや瞬間的に配信処理が追いつかない場合、一時的にFluent Bit上に保持しておくことで、データの損失を防止します。
ログの読み書き速度から多くの場合は、メモリ上にバッファリングする方針が中心となりますが、例えば
- 大量のログデータを扱うなどメモリが不足する可能性があるケース
- セキュリティログなどシステム障害が発生した際に、データの消失が発生してはならないデータを扱うケース
などユースケースによって、バッファリング先をファイルシステムにすることもできます。
Router
配信先を振り分けるコア機能です。
通常は配信先Aにログを送信しつつ、特定のログが来たら配信先Bにログを送信したり、同じログを複数の配信先に送信するなど宛先を決定することができます。
特に
通常は配信先Aにログを送信しつつ、特定のログが来たら配信先Bにログを送信
の実現のため、「Filter」のフェーズでも説明しましたが、ログにタグ
をつけることができます。
タグ
の内容などによって配信先を切り替える役割を持つのが、Router
です。
例えばParseにより構造化された次のログがあったとします。
{
"datetime": "2025/1/15 15:00",
"method": "GET",
"ResponseCode": 200,
"Path": "/index.html",
"client": "192.168.0.2"
},
{
"datetime": "2025/1/15 15:02",
"method": "GET",
"ResponseCode": 404,
"Path": "/not_found.html",
"client": "192.168.0.3"
},
{
"datetime": "2025/1/15 15:05",
"method": "GET",
"ResponseCode": 200,
"Path": "/hello_world.html",
"client": "192.168.0.5"
}
その後、FilterフェーズでResponseCode
が404
の場合、error
というタグを付与します。
{
"datetime": "2025/1/15 15:00",
"method": "GET",
"ResponseCode": 200,
"Path": "/index.html",
"client": "192.168.0.2"
},
{
"tag": "error",
"datetime": "2025/1/15 15:02",
"method": "GET",
"ResponseCode": 404,
"Path": "/not_found.html",
"client": "192.168.0.3"
},
{
"datetime": "2025/1/15 15:05",
"method": "GET",
"ResponseCode": 200,
"Path": "/hello_world.html",
"client": "192.168.0.5"
}
あとRouterでタグの内容に沿って、配信先を振り分けることができます。
これにより
通常は配信先Aにログを送信しつつ、特定のログが来たら配信先Bにログを送信
という要件を実現することができます。
Output
ログの出力部分となります。
標準出力のほか、各種アプリケーションやプロトコルとして出力、Amazon S3やAmazon CloudWatchなどクラウドサービスにも出力することができます。
実際に触ってみよう
では実際に触って、動きを掴んでいければと思います。
前提条件
今回の検証にあたり、以下の環境で実施しています。
OSやターミナルが違う場合、一部ソフトウェアのインストール方法が異なる場合がございます。
また、ソフトウェアのインストール方法については各種公式ドキュメントをご確認ください。
- macOS
- Homebrew 4.4.16
- Nginx 1.27.3
- Fluent Bit v3.2.4
要件は次のとおりです。
- Webサーバー(Nginx)のアクセスログを振り分け
- シンプルかつフローを簡単に理解できるよう、Webサーバー側の設定は変更しない
- 基本的に全てのログをFluent Bitの標準出力
- レスポンスコードが404の場合はファイルに書き出す
はじめに
Fluent Bitを利用するために各フローの定義を行う必要があります。
今回はsandbox.conf
という名前で作成していきます。
Serviceの準備
まずはじめにFluent Bit自体の動作を設定します。
今回はWebサーバー(Nginx)のログを収集していきます。
Nginxのログパーサーは標準で用意されているため、以下のように設定を記載します。
[SERVICE]
parser_file /opt/homebrew/etc/fluent-bit/parsers.conf
なお、Fluent-Bit標準のParserはmacOS + HomebrewでFluent Bitをインストールした場合、通常、以下に定義されていますが、OSなどによって異なる場合がございます。
/opt/homebrew/etc/fluent-bit/parsers.conf
標準のNginxのパーサーについて、以下のように記載があります。
[PARSER]
Name nginx
Format regex
Regex ^(?<remote>[^ ]*) (?<host>[^ ]*) (?<user>[^ ]*) \[(?<time>[^\]]*)\] "(?<method>\S+)(?: +(?<path>[^\"]*?)(?: +\S*)?)?" (?<code>[^ ]*) (?<size>[^ ]*)(?: "(?<referer>[^\"]*)" "(?<agent>[^\"]*)")
Time_Key time
Time_Format %d/%b/%Y:%H:%M:%S %z
正規表現によるパースを行うようになっています。
これにより例えば次のアクセスログがあったとします。
127.0.0.1 - - [16/Jan/2025:11:35:27 +0900] "GET / HTTP/1.1" 200 615 "-" "curl/8.7.1"
このログをパースすると
{
"remote": "127.0.0.1",
"host": "-",
"user": "-",
"time": "16/Jan/2025:11:35:27 +0900",
"method": "GET",
"path": "/",
"code": "200",
"size": "615",
"referer": "-",
"agent": "curl/8.7.1"
}
のように構造化データとなります。
これにより、後続のFilterなどで加工などがしやすくなります。
Inputの定義
続いて、ログの入り口/収集方法の定義をしていきます。
本検証ではよりシンプルかつフローを簡単に理解できるよう、Webサーバー側の設定は変更しない、という要件があります。
そのため、Webサーバーが標準で出力されるアクセスログをTailを利用して継続的に監視します。
そこで、ログなどを継続的に取得できるTail
を使用していきます。
なお、コマンドラインtail -f
と同様の挙動となります。
[INPUT]
name tail
path <Webサーバーログファイルへのパス>
Parser nginx
Tag nginx.logs
上記の設定ファイルを詳しく説明すると
[INPUT]
name tail # 取得方式としてtailを指定
path <ログへのパス> # 指定したパスをtailで取得
Parser nginx # nginxという名前のパーサーを使用
Tag nginx.logs # nginx.logsというタグを付与
今回はよりシンプルに実装する手段としてtaii
を使用していますが、他にも複数の取得方式がございます。
詳しくは公式ドキュメントをご確認ください。
Filterの定義
続いてFilterを定義していきます。
今回の要件は次のとおりです。
- 基本的に全てのログをFluent Bitの標準出力
- レスポンスコードが404の場合はファイルに書き出す
先ほど、ログ自体は構造化データに変換できています。
ログの振り分けはRouterの処理(Fluent Bitのパイプライン参照)で実施します。振り分けにはタグを利用するかつ今回の要件では、レスポンスコードが404場合のみファイルに書き出すため、
- レスポンスコードが404の時に、特定のタグに書き換え
という加工をやっていきます。
それでは実際に設定を書いていきましょう。
同じく、先ほどINPUTで書いた設定ファイル内に以下を追加します。
[FILTER]
Name rewrite_tag
Match nginx.logs
Rule $code ^404$ nginx.logs.not_found false
すごく直感的なのではないでしょうか。
タグの書き換えというフィルタリングでcode
が特定の値の時、適切なタグに変更するという設定です。
[FILTER]
Name rewrite_tag # タグの書き換え操作
Match nginx.logs # nginx.logsタグがついているログを対象
Rule $code ^404$ nginx.logs.not_found false # codeが404の場合、タグをnginx.logs.not_foundに書き換えて、元のデータを破棄する
ログの振り分け・ログの出力
最後にログの配信先振り分け、出力を行なっていきます。
では実際に書いていきましょう。
[OUTPUT]
Name file
Match nginx.logs.not_found
Path <ログ出力先ディレクトリへのパス>
[OUTPUT]
Name stdout
Match *
まず、一つ目の[OUTPUT]
で
- 書き出し先の形式を指定
Match
を利用してタグとの一致を条件に設定Path
に書き出し先ディレクトリへのパスを設定
そして、二つ目の[OUTPUT]
で
- 出力先を標準出力
- *により全てのログを対象
という二つのフローを指定しています。
複数[OUTPUT]
を記載することで、条件によってログの振り分けを行いながら、ログの出力ができるため、Fluent Bit上でのフローがわかりやすくなっています。
完成した設定ファイル
本過程で完成した設定ファイルは次のとおりです。
[SERVICE]
parsers_file /opt/homebrew/etc/fluent-bit/parsers.conf
[INPUT]
name tail
path <ログへのパス>
Parser nginx
Tag nginx.logs
[FILTER]
Name rewrite_tag
Match nginx.logs
Rule $code ^404$ nginx.logs.not_found false
[OUTPUT]
Name file
Match nginx.logs.not_found
Path <ログ出力先ディレクトリへのパス>
[OUTPUT]
Name stdout
Match *
上から順番に処理を記載する設定ファイルのため、どのように入力、加工をし、出力する、というフローが非常にわかりやすくなっています。
Fluent Bitではやりたい操作をよりシンプルに書くことができます。
実際に動作させてみる
それでは実際に試してみましょう。設定ファイルを指定してFluent Bitを起動します。
fluent-bit -c <設定ファイル>
Fluent Bit v3.2.4
* Copyright (C) 2015-2024 The Fluent Bit Authors
* Fluent Bit is a CNCF sub-project under the umbrella of Fluentd
* https://fluentbit.io
______ _ _ ______ _ _ _____ _____
| ___| | | | | ___ (_) | |____ |/ __ \
| |_ | |_ _ ___ _ __ | |_ | |_/ /_| |_ __ __ / /`' / /'
| _| | | | | |/ _ \ '_ \| __| | ___ \ | __| \ \ / / \ \ / /
| | | | |_| | __/ | | | |_ | |_/ / | |_ \ V /.___/ /./ /___
\_| |_|\__,_|\___|_| |_|\__| \____/|_|\__| \_/ \____(_)_____/
[2025/01/16 14:59:54] [ info] [fluent bit] version=3.2.4, commit=, pid=77226
[2025/01/16 14:59:54] [ info] [storage] ver=1.5.2, type=memory, sync=normal, checksum=off, max_chunks_up=128
[2025/01/16 14:59:54] [ info] [simd ] disabled
[2025/01/16 14:59:54] [ info] [cmetrics] version=0.9.9
[2025/01/16 14:59:54] [ info] [ctraces ] version=0.5.7
[2025/01/16 14:59:54] [ info] [input:tail:tail.0] initializing
[2025/01/16 14:59:54] [ info] [input:tail:tail.0] storage_strategy='memory' (memory only)
[2025/01/16 14:59:54] [ info] [input:emitter:emitter_for_rewrite_tag.0] initializing
[2025/01/16 14:59:54] [ info] [input:emitter:emitter_for_rewrite_tag.0] storage_strategy='memory' (memory only)
[2025/01/16 14:59:54] [ info] [output:file:file.0] worker #0 started
[2025/01/16 14:59:54] [ info] [sp] stream processor started
[2025/01/16 14:59:54] [ info] [output:stdout:stdout.1] worker #0 started
上記のような文字列が出力されたら起動完了です。
実際にWebサーバーにアクセスしてみます。
curl http://localhost:8080
するとFluent Bit側でログが取得・出力されます。
[0] nginx.logs: [[1737007276.000000000, {}], {"remote"=>"127.0.0.1", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/", "code"=>"200", "size"=>"615", "referer"=>"-", "agent"=>"curl/8.7.1"}]
続いて、404を発生させます。適当なパスを入れてWebサーバーにリクエストします。
curl http://localhost:8080/notfound
[0] nginx.logs.not_found: [[1737007362.000000000, {}], {"remote"=>"127.0.0.1", "host"=>"-", "user"=>"-", "method"=>"GET", "path"=>"/notfound", "code"=>"404", "size"=>"153", "referer"=>"-", "agent"=>"curl/8.7.1"}]
タグが変わりましたね。
また、ファイルにもエラーが出力されている確認するため、ログの出力先ディレクトリを確認します。
xxxxxxxxxxxxxx@xxxxxxxx errors % ls -l
total 8
-rw-r--r-- 1 xxxxxxxxxxxxxx staff 186 1 16 15:03 nginx.logs.not_found
xxxxxxxxxxxxxx@xxxxxxxx errors % cat nginx.logs.not_found
nginx.logs.not_found: [1737007362.000000000, {"remote":"127.0.0.1","host":"-","user":"-","method":"GET","path":"/notfound","code":"404","size":"153","referer":"-","agent":"curl/8.7.1"}]
正しく出力されています。
終わりに
今回はシンプルなものを構築しましたが、設定ファイルが非常に読みやすくシンプルだと感じました。
複数のログを集配信したい、加工や振り分けを行いたいというユースケースにはすごく導入しやすいツールだと思います。
ぜひ皆様も触ってみてください。
おまけ
標準のパーサーが用意されていない場合、どうすればいいのか?
自身でパーサーを定義する必要があります。正規表現を例にします。
まず、パーサー用の設定ファイルを作成します。(sample-parser.conf
)
[PARSER]
という定義を作成し、パーサーの名前、マッチング方式、正規表現を書いていきます。
ログ例
[2025/1/15 15:00] FROM:Aさん TO:Bさん MESSAGE:こんにちは1
記述例
[PARSER]
Name sample-parser
Format regex
Regex ^\[(?<datetime>[^\]]*)\] FROM\:(?<sender>[^ ]*) TO\:(?<destination>[^ ]*) MESSAGE\:(?<message>[^ ]*)
設定ファイルを作成後、Fluent Bitの設定ファイル内[SERVICE]
にparsers_file
としてファイルをパスを指定します。
[SERVICE]
parsers_file <ファイルパス>
その後、Parser
にパーサーの名前を設定することで利用可能です。
[INPUT]
name tail
path <ログへのパス>
Parser sample-parser