はじめに
前回の記事 : FireLens の設定方法をわかりやすく整理してみた では、FireLens からログ転送を指定する方法を整理しました。ログ転送先が、1 種類で十分なときは、Amazon ECS の Task 定義の設定を変えるだけで、簡単に転送先の指定が可能です。
このように JSON で logConfiguration.options
を指定することで、Kinesis Data Firehose 経由で S3 に保存が出来ます。
省略
"logConfiguration": {
"logDriver": "awsfirelens",
"secretOptions": null,
"options": {
"Name": "kinesis_firehose",
"region": "ap-northeast-1",
"delivery_stream": "my-stream"
}
},
省略
しかし、ログの転送先を 2 種類以上にするときには、別の設定方法となります。FireLens のコンテナとして稼働する、Fluent Bit や Fluentd の設定ファイルを作成することで、好きなようにログの転送先を指定できます。
例えば、次のような使い方が考えられます。
- アプリケーションのアクセスログ : 大量にログが出力されるので、保存料金が安価な Amazon S3 に格納する。Athena を使って、S3 に貯めたログを SQL クエリーで検索できるようにする。
- アプリケーションのエラーログ : AWS のマネージメントコンソールで誰でも検索が出来る、CloudWatch Logs に格納する。
例を挙げると、ECS Task で稼働している mainapp コンテナのアプリが、以下の 2 種類のログを標準出力に出しています。ログの level に応じて、info
は S3 に格納し、warning
は CloudWatch Logs に格納することが可能です。
ログ分岐の流れ
どのようにログ分岐を行うか、流れを整理してみます。詳細は省略するので、ざっくりとした内容です。
まず、アプリケーションのログを標準出力に出力することで、そのログを FireLens のコンテナに渡せます。(Fluent ロガーを使う方法もありますが、割愛します)
FireLens がアプリケーションのログを受け取ったときに、FILTER という機能をつかって ECS に関するメタデータを付与します。そして、もともとのアプリケーションのログは、log
キーに格納されます。
FireLens が変換した log
を使って、CloudWatch や Kinesis Data Firehose といった転送先を分岐することが出来ます。例えば、次の記事が参考になります。
- 参考
今回の記事の環境では、アプリケーションログが JSON で出来ているので、log
を更にパースします。パースした方が CloudWatch Logs などの検索で便利です。パースすると、log
が分解され、以下の形になります。
そして、重要な概念の一つとして、Fluent Bit には、ログをどのように扱うかコントロールするための tag という概念があります。例えば、irregularlog
タグが付いているログは、CloudWatch に送り、mainapp-firelens-hogehoge
タグが付いているログは、Kinesis Data Firehose に送る設定が可能です。
Fluent Bit の OUTPUT にかかわる設定を抜粋します。Match
で指定しているのが、処理対象にするタグを正規表現で指定しています。
Name
で指定している cloudwatch_logs
や kinesis_firehose
が、ログ転送先(正確には Plugin 名) を指定しています。次の URL で、利用できる Plugin の一覧が確認可能です。
[OUTPUT]
Name cloudwatch_logs
Match irregularlog
auto_create_group true
log_group_name /aws/firelens/firelens-testapp
log_stream_prefix from-fluent-bit-
region ap-northeast-1
[OUTPUT]
Name kinesis_firehose
Match *-firelens-*
region ap-northeast-1
delivery_stream firelens-test-kinesis-firehose
Tag を書き換えるための rewrite_tag
プラグインがあります。特定の条件にヒットしたものを対象に、タグを書き換えることが出来るので、条件ごとにログ転送先をコントロールできます。
[FILTER]
Name rewrite_tag
Match *-firelens-*
Rule $level ^(warning|error|fatal|panic)$ irregularlog false
Emitter_Name re_emitted
例えば、上記の rewrite_tag
プラグインに関する設定をすることで、level
が warning
or error
or fatal
or panic
となっている場合は、irregularlog
にタグを書き換えることが出来ます。
タグの書き換えや、タグごとの出力先の指定により、うまくログ転送先をコントロールできます。
なお、FireLens では、デフォルトで以下の形式のタグが付与されます。
<コンテナ名>-firelens-<タスクID>
つまり、コンテナ名が mainapp
で、タスク ID が dcef9dee-d960-4af8-a206-46c31a7f1e67
の場合、タグは mainapp-firelens-dcef9dee-d960-4af8-a206-46c31a7f1e67
となります。この辺りも意識すると Fluent Bit のコンフィグがしやすくなるので、覚えておくとよいでしょう。
詳細は以下の URL もご確認ください。
説明が長くなりましたが、上記のログ分岐の流れを実際に設定してみましょう。
ローカル検証 : Fluent Bit
Fluent Bit の動作確認を、実際の FireLens で行うのは時間がかかって大変なので、まずはローカル環境で動作確認をしていきます。
FireLens で使われている Fluent Bit のコンテナイメージは、以下の ECR Gallery で公開されています。これを使って動作確認を進めます。
まず、作業用ディレクトリを作成します。
mkdir ~/temp/firelens/spilit_logs/
Fluent Bit の conf ファイルを作成します。localtest.conf
という名前を付けます。
[SERVICE]
Flush 1
Log_Level info
Parsers_File /fluent-bit/etc/parsers_json.conf
[INPUT]
NAME dummy
Dummy {"log":"{\"level\":\"info\",\"msg\":\"this is Info Log\",\"time\":\"2022-10-16T10:16:41Z\"}"}
Tag mainapp-firelens-hogehoge
[INPUT]
NAME dummy
Dummy {"log":"{\"level\":\"warning\",\"msg\":\"this is warning Log\",\"time\":\"2022-10-16T10:16:41Z\"}"}
Tag mainapp-firelens-hogehoge
[FILTER]
Name parser
Match *-firelens-*
Key_Name log
Parser json
Preserve_Key false
Reserve_Data true
[FILTER]
Name rewrite_tag
Match *-firelens-*
Rule $level ^(warning|error|fatal|panic)$ irregularlog false
Emitter_Name re_emitted
[OUTPUT]
Name stdout
Match *-firelens-*
[OUTPUT]
Name stdout
Match irregularlog
重要なところをいくつか抜粋します
-
Parsers_File /fluent-bit/etc/parsers_json.conf
: JSON を Parse するためのファイルを指定します。
[SERVICE]
Flush 1
Log_Level info
Parsers_File /fluent-bit/etc/parsers_json.conf
-
Dummy {"log":"{\"level\":\"info\",\"msg\":\"this is Info Log\",\"time\":\"2022-10-16T10:16:41Z\"}"}
: ローカルで動作確認するための、ダミーの文字列を指定します。この文字列が、動作確認に使われます -
Tag mainapp-firelens-hogehoge
: この文字列に付与する tag を指定します
[INPUT]
NAME dummy
Dummy {"log":"{\"level\":\"info\",\"msg\":\"this is Info Log\",\"time\":\"2022-10-16T10:16:41Z\"}"}
Tag mainapp-firelens-hogehoge
[INPUT]
NAME dummy
Dummy {"log":"{\"level\":\"warning\",\"msg\":\"this is warning Log\",\"time\":\"2022-10-16T10:16:41Z\"}"}
Tag mainapp-firelens-hogehoge
Parser をどのように行うかの指定です。tag *-firelens-*
にマッチしたログに対して、log
のキーに対する値を、JSON でパースします。
[FILTER]
Name parser
Match *-firelens-*
Key_Name log
Parser json
Preserve_Key false
Reserve_Data true
JSON で Parse したあとに、ログに含まれる level
が、正規表現の ^(warning|error|fatal|panic)$
に引っかかったときに、Tag をirregularlog
に書き換えます。
[FILTER]
Name rewrite_tag
Match *-firelens-*
Rule $level ^(warning|error|fatal|panic)$ irregularlog false
Emitter_Name re_emitted
次に、JSON を Parse するためのファイルを作成します。parser_json.conf
[PARSER]
Name json
Format json
Decode_Field_AS escaped log
Dockerfile を作成します。上記で作成した、localtest.conf
と parsers_json.conf
を格納します。
FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:init-2.28.3
COPY localtest.conf /fluent-bit/etc/localtest.conf
COPY parsers_json.conf /fluent-bit/etc/parsers_json.conf
Build
docker build -t firelens_localtest_spilit_logs .
Bash 起動してコンテナ実行
docker run -it --rm firelens_localtest_spilit_logs bash
コンテナ内で以下のコマンドを実行すると、動作確認が出来ます。
/fluent-bit/bin/fluent-bit -c /fluent-bit/etc/localtest.conf
実行例
- level が
info
のものは、tag がmainapp-firelens-hogehoge
のままとなっている - level が
warning
のものは、tag がirregularlog
に書き換わっている
[0] mainapp-firelens-hogehoge: [1665926237.385715351, {"level"=>"info", "msg"=>"this is Info Log", "time"=>"2022-10-16T10:16:41Z"}]
[0] irregularlog: [1665926237.385768703, {"level"=>"warning", "msg"=>"this is warning Log", "time"=>"2022-10-16T10:16:41Z"}]
[0] mainapp-firelens-hogehoge: [1665926238.385717828, {"level"=>"info", "msg"=>"this is Info Log", "time"=>"2022-10-16T10:16:41Z"}]
[0] irregularlog: [1665926238.385779690, {"level"=>"warning", "msg"=>"this is warning Log", "time"=>"2022-10-16T10:16:41Z"}]
[0] mainapp-firelens-hogehoge: [1665926239.385729496, {"level"=>"info", "msg"=>"this is Info Log", "time"=>"2022-10-16T10:16:41Z"}]
[0] irregularlog: [1665926239.385791964, {"level"=>"warning", "msg"=>"this is warning Log", "time"=>"2022-10-16T10:16:41Z"}]
[0] mainapp-firelens-hogehoge: [1665926240.385712879, {"level"=>"info", "msg"=>"this is Info Log", "time"=>"2022-10-16T10:16:41Z"}]
[0] irregularlog: [1665926240.385772103, {"level"=>"warning", "msg"=>"this is warning Log", "time"=>"2022-10-16T10:16:41Z"}]
Kinesis Data Firehose 作成
ローカルで動作確認ができたので、AWS 上の設定に移ります。
適当に Kinesis Data Firehose を作成します。記事のテーマではないので、読み飛ばして大丈夫です。興味があるかたはどうぞ。
Source は Direct Put
出力先を指定
Dynamic Partitioning を有効化 : 詳細はこちら : https://qiita.com/sugimount-a/items/edd136364a8e0d6cb725
日付を使って、S3 にパーティションを作成する
.time | strptime("%Y-%m-%dT%H:%M:%SZ") | strftime("%Y-%m-%d")
firelens/date=!{partitionKeyFromQuery:logdate}/
Create
作成処理が進む
Docker Image 作成
FireLens で動かす Fluent Bit の動作をカスタマイズしていくために、コンテナイメージを作成します。ECS on EC2 だと、S3 経由で読み込ませる方法が取れますが、ECS on Fargate の場合は基本的にコンテナイメージを作成する方法となります。
Fluent Bit の Config ファイルとして、extra.conf
を作成します。
- SERVICE の欄で、JSON をパーサーするためのファイルを指定
- OUTPUT の欄で、出力先の CloudWatch Logs のグループ名などを指定
- OUTPUT の欄で、作成した Kinesis Data Firehose の名前を指定
[SERVICE]
Parsers_File /fluent-bit/etc/parsers_json.conf
[FILTER]
Name parser
Match *-firelens-*
Key_Name log
Parser json
Preserve_Key false
Reserve_Data true
[FILTER]
Name rewrite_tag
Match *-firelens-*
Rule $level ^(warning|error|fatal|panic)$ irregularlog false
Emitter_Name re_emitted
[OUTPUT]
Name cloudwatch_logs
Match irregularlog
auto_create_group true
log_group_name /aws/firelens/firelens-testapp
log_stream_prefix from-fluent-bit-
region ap-northeast-1
[OUTPUT]
Name kinesis_firehose
Match *-firelens-*
region ap-northeast-1
delivery_stream firelens-test-kinesis-firehose
[OUTPUT]
Name cloudwatch_logs
Match *
auto_create_group true
log_group_name /aws/firelens/debug
log_stream_prefix from-fluent-bit-
region ap-northeast-1
parsers_json.conf
はローカルで動作確認したものをそのまま利用
[PARSER]
Name json
Format json
Decode_Field_AS escaped log
Dockerfile をこのように指定
FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:init-2.28.3
COPY extra.conf /fluent-bit/etc/extra.conf
COPY parsers_json.conf /fluent-bit/etc/parsers_json.conf
コンテナイメージをビルド
docker build -t xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-my-log-router:0.0.1 .
docker push
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
docker push xxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/firelens-my-log-router:0.0.1
Task 定義を作成
Task 定義を作成していきます。若干複雑な作業なので、丁寧に確認していきましょう。
Fargate を指定
Task 定義の名前を指定。Task Role などは、ログ転送先へ転送するための権限が必要なので、注意しましょう。
Log router integration の欄が、FireLes の欄です。
- type を fluentbit にする
- image に、自分が作成した Fluent Bit のコンテナイメージを指定
log_router が追加されたので、これをクリックします
log_router そのもののログ出力は、CloudWatch logs を指定します
Add Container を押します
自分たちが持っているメインのアプリケーションが稼働するコンテナを指定します。このアプリケーションコンテナはこちらの記事で作成しました。
Log configuration の個所で、awsfirelens
を選びます。
Add を押して追加します。
追加した mainapp
を再度選択して、内容を更新します。
Log configuration の個所にある、Name に紐づく ×を押したあとに、Update を押します。
Configure via JSON を指定
FireLens の Config ファイルの指定方法は、firelensConfiguration
から行います。
変更前
"firelensConfiguration": {
"type": "fluentbit"
},
変更後
- 独自に作成した Fluent Bit コンテナイメージ 内に格納した、
/fluent-bit/etc/extra.conf
を読みこませる指定
"firelensConfiguration": {
"type": "fluentbit",
"options":{
"config-file-type": "file",
"config-file-value": "/fluent-bit/etc/extra.conf"
}
},
こんな感じです。
Save を押します。
Create を押します。
タスク定義が作成されました。
サービスを作成
適当にサービスを作成して、インターネット上に公開します。
ECS Exec の有効化したあとに、再度 Task を強制的に再配置。
aws ecs describe-services --cluster test-cluster01 --services firelens-testapp-service
aws ecs update-service --cluster test-cluster01 --service firelens-testapp-service --enable-execute-command
aws ecs describe-services --cluster test-cluster01 --services firelens-testapp-service
動作検証
これで準備は整いました!実際に FireLes から、CloudWatch Logs と Kinesis Data Firehose + S3 にログが格納されているか確認します。まず、ログを生成するために、公開したサービスにアクセスします。
CloudWatch Logs で、/aws/firelens/firelens-testapp
が自動生成されています
Streams も適当に選択します
warning のログのみ出力されています。info ログは、CloudWatch Logs に出力されていません。
info ログは、Kinesis Data Firehose 経由で S3 に出力されています。
実際の Object の中身を確認してみます。
こんな中身になっています。info ログが出力されていて、想定通りです。
{"level":"info","msg":"this is Info Log","time":"2022-10-16T16:02:51Z","container_name":"mainapp","source":"stdout","container_id":"3f51a08699864c0299088cc6ef1824c3-787454787","ecs_cluster":"test-cluster01","ecs_task_arn":"arn:aws:ecs:ap-northeast-1:xxxxxxxx:task/test-cluster01/3f51a08699864c0299088cc6ef1824c3","ecs_task_definition":"firelens-logsplit-task-def:2"}
{"level":"info","msg":"this is Info Log","time":"2022-10-16T16:02:51Z","container_id":"3f51a08699864c0299088cc6ef1824c3-787454787","container_name":"mainapp","source":"stdout","ecs_cluster":"test-cluster01","ecs_task_arn":"arn:aws:ecs:ap-northeast-1:xxxxxxxx:task/test-cluster01/3f51a08699864c0299088cc6ef1824c3","ecs_task_definition":"firelens-logsplit-task-def:2"}
{"level":"info","msg":"this is Info Log","time":"2022-10-16T16:03:21Z","container_id":"3f51a08699864c0299088cc6ef1824c3-787454787","container_name":"mainapp","source":"stdout","ecs_cluster":"test-cluster01","ecs_task_arn":"arn:aws:ecs:ap-northeast-1:xxxxxxxx:task/test-cluster01/3f51a08699864c0299088cc6ef1824c3","ecs_task_definition":"firelens-logsplit-task-def:2"}
{"level":"info","msg":"this is Info Log","time":"2022-10-16T16:03:21Z","container_id":"3f51a08699864c0299088cc6ef1824c3-787454787","container_name":"mainapp","source":"stdout","ecs_cluster":"test-cluster01","ecs_task_arn":"arn:aws:ecs:ap-northeast-1:xxxxxxxx:task/test-cluster01/3f51a08699864c0299088cc6ef1824c3","ecs_task_definition":"firelens-logsplit-task-def:2"}
{"level":"info","msg":"this is Info Log","time":"2022-10-16T16:03:51Z","container_id":"3f51a08699864c0299088cc6ef1824c3-787454787","container_name":"mainapp","source":"stdout","ecs_cluster":"test-cluster01","ecs_task_arn":"arn:aws:ecs:ap-northeast-1:xxxxxxxx:task/test-cluster01/3f51a08699864c0299088cc6ef1824c3","ecs_task_definition":"firelens-logsplit-task-def:2"}
{"level":"info","msg":"this is Info Log","time":"2022-10-16T16:03:51Z","container_id":"3f51a08699864c0299088cc6ef1824c3-787454787","container_name":"mainapp","source":"stdout","ecs_cluster":"test-cluster01","ecs_task_arn":"arn:aws:ecs:ap-northeast-1:xxxxxxxx:task/test-cluster01/3f51a08699864c0299088cc6ef1824c3","ecs_task_definition":"firelens-logsplit-task-def:2"}
付録 : Fluent Bit 設定ファイルを確認
ECS 上で稼働している FireLens コンテナの中身を見て、conf ファイルを確認します。
ECS Exec で、FireLens のコンテナ内で bash を起動。
aws ecs execute-command \
--cluster test-cluster01 \
--task 3f51a08699864c0299088cc6ef1824c3 \
--container log_router \
--interactive \
--command "bash"
中身を確認
-
@INCLUDE /fluent-bit/etc/extra.conf
が挿入されている - Task 定義で指定したものが、ここに INCLUDE されている
bash-4.2# cat /fluent-bit/etc/fluent-bit.conf
[INPUT]
Name tcp
Listen 127.0.0.1
Port 8877
Tag firelens-healthcheck
[INPUT]
Name forward
unix_path /var/run/fluent.sock
[INPUT]
Name forward
Listen 127.0.0.1
Port 24224
[FILTER]
Name record_modifier
Match *
Record ecs_cluster test-cluster01
Record ecs_task_arn arn:aws:ecs:ap-northeast-1:xxxxxxxx:task/test-cluster01/3f51a08699864c0299088cc6ef1824c3
Record ecs_task_definition firelens-logsplit-task-def:2
@INCLUDE /fluent-bit/etc/extra.conf
[OUTPUT]
Name null
Match firelens-healthcheck
INCLUDE されているファイルを確認。
bash-4.2# cat /fluent-bit/etc/extra.conf
[SERVICE]
Parsers_File /fluent-bit/etc/parsers_json.conf
[FILTER]
Name parser
Match *-firelens-*
Key_Name log
Parser json
Preserve_Key false
Reserve_Data true
[FILTER]
Name rewrite_tag
Match *-firelens-*
Rule $level ^(warning|error|fatal|panic)$ irregularlog false
Emitter_Name re_emitted
[OUTPUT]
Name cloudwatch_logs
Match irregularlog
auto_create_group true
log_group_name /aws/firelens/firelens-testapp
log_stream_prefix from-fluent-bit-
region ap-northeast-1
[OUTPUT]
Name kinesis_firehose
Match *-firelens-*
region ap-northeast-1
delivery_stream firelens-test-kinesis-firehose
[OUTPUT]
Name cloudwatch_logs
Match *
auto_create_group true
log_group_name /aws/firelens/debug
log_stream_prefix from-fluent-bit-
region ap-northeast-1