AlphaDrive、NewsPicks兼務でエンジニアしている大場です。 最近はNewsPicks Webの新基盤開発を行っています。
新基盤はNext.jsで開発していてAWSのFargateで構築しているのですが、このFargate上で取得したログをS3、New Relicに送るためにFluent Bitを導入しました。
今回はローカルでの実行・確認方法と、導入の過程で問題になったことを紹介します!
Fluent Bit とは
Fluent Bitは軽量高速なログ収集ツールで様々なサービスにログデータを転送できます。
C言語で実装されているのでFluentdよりも高速に動作します。
(公式サイト)https://fluentbit.io
ローカル実行・確認方法
ローカル実行、テストデータの投入、出力内容の確認を基本的な処理の出力結果を見ながら紹介します。
イメージの選択
Fluent BitのDockerイメージを利用します。
本運用ではAWS環境上に構築するため、AWS用にPluginsが更新されているAWS公式イメージを利用します。
設定ファイルの準備
AWS公式イメージではfluent-bit.conf
は既に用意されているため、別ファイル名で設定ファイルを作成します。
今回は application.conf
で作成してみます。
application.conf
[SERVICE] Flush 5 Log_Level info
[SERVICE] はFluent Bitのグローバル設定を行っています。 どんな設定ができるかは以下のページを参照ください。
このファイルを以下のようにDockerfileで配置します。
Dockerfile.fluentbit
FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:2.21.3 COPY application.conf /fluent-bit/etc/application.conf
デバッグ用の設定を追加する
application.conf
にテストデータ投入用の[INPUT]、出力確認用の[OUTPUT]を追加します。
application.conf
[SERVICE] Flush 5 Log_Level info [INPUT] # ← 追加 Name dummy Tag *-firelens-* Dummy {"date":"2022-01-23T03:10:33.317817Z","source":"stdout","log":"time:2022-01-23T03:10:33+00:00\tprotocol:HTTP/1.1\tstatus:200\tsize:1450\treqsize:150\treferer:-\treqtime:0.176\tcache:-\truntime:-\t"} [OUTPUT] # ← 追加 Name stdout Match *
動作確認
Dockerを起動してターミナルを開き、以下でFluent Bitを起動します。
/fluent-bit/bin/fluent-bit -c /fluent-bit/etc/application.conf
すると、[INPUT]に記載したDummyデータを受け取り[OUTPUT]により以下のように出力されます。
出力結果
[0] *-firelens-*: [1645367995.432183928, {"date"=>"2022-01-23T03:10:33.317817Z", "source"=>"stdout", "log"=>"time:2022-01-23T03:10:33+00:00 protocol:HTTP/1.1 status:200 size:1450 reqsize:150 referer:- reqtime:0.176 cache:- runtime:- "}]
簡単ですね!
ltsv形式のログを展開する
次にltsv形式のログを展開してみます。
[INPUT]のDummyデータのlogはltsv形式のデータです。
このlogデータを展開するためにはparser.conf
を作成して以下のように設定します。
Time_*
の設定はlog内のキー項目のどのKeyのどのようなフォーマットの時間を[OUTPUT]の時間にするかを指定できます。
parser.conf
[PARSER] Name parser_ltsv Format ltsv Time_Key time Time_Format %Y-%m-%dT%H:%M:%S %z Time_Keep On
このファイルを以下のようにapplication.conf
に設定します。
application.conf
[SERVICE] Flush 5 Log_Level info Parsers_File parser.conf # ←ファイルの指定 [INPUT] Name dummy Tag *-firelens-* Dummy {"date":"2022-01-23T03:10:33.317817Z","source":"stdout","log":"time:2022-01-23T03:10:33+00:00\tprotocol:HTTP/1.1\tstatus:200\tsize:1450\treqsize:150\treferer:-\tvhost:10.10.18.102\treqtime:0.176\tcache:-\truntime:-\t"} [FILTER] # ← parserのための設定 Match *-firelens-* Key_Name log # ← logデータを利用 Parser parser_ltsv # ← parser.conf内のparser_ltsvを利用して処理する指定 Preserve_Key false Reserve_Data true [OUTPUT] Name stdout Match *
Dockerfile.fluentbit
FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:2.21.3 COPY application.conf /fluent-bit/etc/application.conf COPY parser.conf /fluent-bit/etc/parser.conf # ← 追加
これを実行すると以下のようにltsvデータが展開されます。
出力結果
[1642907433.000000000, {"time"=>"2022-01-23T03:10:33+00:00", "protocol"=>"HTTP/1.1", "status"=>"200", "size"=>"1450", "reqsize"=>"150", "referer"=>"-", "vhost"=>"10.10.18.102", "reqtime"=>"0.176", "cache"=>"-", "runtime"=>"-", "date"=>"2022-01-23T03:10:33.317817Z", "source"=>"stdout"}]
Stream Processorを使う
Stream Processorは以下の図がわかりやすいです。
InputをParser、Filterで処理したものをSQLクエリを利用し、新しくStreamを作成してそれをInputとして再処理します。
引用:https://docs.fluentbit.io/manual/stream-processing/overview#stream-processor
例えば以下のようにすると*-firelens-*
というタグの付いたStreamから新規でstdout_log
というタグの付いたStreamの作成を行うことが可能です。
stream-processor.conf
[STREAM_TASK] Name stdout_log Exec CREATE STREAM stdout_log WITH (tag='stdout_log') AS SELECT * from TAG:'*-firelens-*' WHERE source = 'stdout';
この設定ファイルを利用する設定を以下のように行います。 標準出力にはstdout_logタグのデータのみの出力を行っています。
application.conf
[SERVICE] Flush 5 Log_Level info Parsers_File parser.conf Streams_File stream-processor.conf # ←ファイルの指定 [INPUT] Name dummy Tag *-firelens-* Dummy {"date":"2022-01-23T03:10:33.317817Z","source":"stdout","log":"time:2022-01-23T03:10:33+00:00\tprotocol:HTTP/1.1\tstatus:200\tsize:1450\treqsize:150\treferer:-\tvhost:10.10.18.102\treqtime:0.176\tcache:-\truntime:-\t"} [FILTER] Match *-firelens-* Key_Name log Parser parser_ltsv Preserve_Key false Reserve_Data true [OUTPUT] Name stdout Match stdout_log
Dockerfile.fluentbit
FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:2.21.3 COPY application.conf /fluent-bit/etc/application.conf COPY parser.conf /fluent-bit/etc/parser.conf COPY stream-processor.conf /fluent-bit/etc/stream-processor.conf # ←追加
出力結果
[0] stdout_log: [1642907433.000000000, {"time"=>"2022-01-23T03:10:33+00:00", "protocol"=>"HTTP/1.1", "status"=>"200", "size"=>"1450", "reqsize"=>"150", "referer"=>"-", "vhost"=>"10.10.18.102", "reqtime"=>"0.176", "cache"=>"-", "runtime"=>"-", "date"=>"2022-01-23T03:10:33.317817Z", "source"=>"stdout"}]
[OUTPUT]のMatchで出力を絞らない場合は、*-firelens-*
、stdout_log
のそれぞれが出力されます。
その他の設定について
ここまでの理解ができれば後はFluent Bitの公式ページを見て設定・確認すればそれほど難しくなく導入していけると思うので割愛させていただきます。
Fluent Bitで導入の過程でハマったこと
ここからは実際にAWS上で運用する時にハマったことを紹介していきます。
S3 プラグインでgzip圧縮時に Content-Encoding: gzip
が固定
以下のISSUEでgzip圧縮が動いてないと議論されていますが、自分が確認した限りだとgzip圧縮は動いてます。 github.com
メタデータに Content-Encoding: gzip
が付いているとブラウザのダウンロード時など自動解凍されます。
Content-Encoding: gzip
をS3上で削除してからダウンロードするとgzip圧縮されていることが確認できます。
この挙動で問題になったのがS3オブジェクトを利用したembulkでの処理でした。
Fluentdで集めたデータと一緒に処理をしようとするとFluent Bitで集めたデータは自動解凍されてしまい、処理の途中で解凍されたデータをさらに解凍しようとしてエラーになるという問題にぶつかりました。
Fluent BitのS3プラグインのソースを確認しましたが、gzip圧縮時はContent-Encoding: gzip
が固定で付くようになってしまっています。
この問題の回避のために以下のように前処理を行い解決しました。
aws s3 sync s3://log-bucket1 s3://log-bucket2 --content-type 'application/x-gzip' --metadata-directive REPLACE
S3オブジェクト内のデータを正確に時間区切りで作成できない
以下のISSUEにあるように、/y=%Y/m=%m/d=%d/h=%H/$UUID.gz
といった形でログ出力しても2022/02/28 10時
のログに9時や11時のログが含まれる可能性があるので、この挙動を考慮した処理が必要です。
S3 output plugin complete upload with interval
nrlogs(New Relic)の推奨キーはlicense_keyっぽい
以下のドキュメントではapi_keyを推奨していると記載がありますが、New Relicの中の人に確認を取ったらlicense_keyを推奨とのことでした。
使い方によるかもしれませんがapi_keyは個人に紐づくのでできればlicense_key使いましょう。
おわりに
Fluent Bit導入時、最初からECS上でやろうとしてしまい難しいなと思いましたが、ローカルでdockerを立ち上げて挙動の確認を行いながらやったら意外とさくっと導入できました。 どんな技術も簡単にデバッグできるようにして仮説・検証サイクルをいかに高速で回すかがポイントですね。