この記事は、「AWS Advent Calendar 2021」の19日目の記事です。
背景
弊社で開発しているとあるプロダクトでFargateで起動しているコンテナのアプリログの中で特定のログを収取集計してcsvなどに出力できたらいいなという要望があり、Fargateログの振り分けならFireLensがあったよなということでそれを調査検証してみるというタスクがありました。
色々調査していく過程で、別の方法でいいかもという結論となったのでここで供養させる。
FireLensとは
デプロイメントスクリプトを修正したり、手動で追加のソフトウェアをインストールしたり、追加コードを書き込んだりすることなく、コンテナログをストレージや分析ツールに直接追加できます。Amazon ECS または AWS Fargate の設定をいくつか更新することにより、必要な場所にコンテナログを送信することを FireLens に指示するため、宛先を選択し、オプションでフィルターを定義します。FireLens は Fluent Bit または Fluentd と動作します。このことは、それらのオープンソースのプロジェクトによりサポートされた宛先にログを送信できることを意味します。
通常、Fargateのコンテナログはawslogsを用いてCloudWatchlogsに転送されますが、FireLensを用いることでCloudWatchlogsだけでなく、S3やKinesisに直接送ることが出来るようになりました。タスク定義にてFireLensの使用を設定する事で、コンテナログをサイドカーとして配置されたFluentdまたはFluent Bitに転送され、それらを経由することでS3やKinesisなどに直接振り分けるという仕組みです。
やろうとしたこと
- 既存のログはそのままCloudWatchlogsに転送したい
- タスク定義にはWebとApp用のコンテナがあり、それぞれ別のロググループに転送したい
- 出力されるコンテナログの中でも特定文字列を含むログに関するものだけを別の出力先に送って分析/csv出力したい。
③ではS3にログを転送して、Athenaでクエリ打ってcsv出力を考えておりました。
FireLensによるログ振り分け
FireLens用コンテナイメージ作成準備
下記ファイルをそれぞれ用意する
├── Dockerfile
├── fluent-bit-custom.conf
└── stream_processor.conf
- Dockerfile
FROM amazon/aws-for-fluent-bit:2.16.1
COPY ./fluent-bit-custom.conf /fluent-bit/custom.conf
COPY ./stream_processor.conf /fluent-bit/etc/stream_processor.conf
RUN ln -sf /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
[STREAM_TASK]
Name s3tag
Exec CREATE STREAM s3tag WITH (tag='s3tag') AS SELECT log FROM TAG:'*-firelens-*' ;
[SERVICE]
Flush 1
Grace 30
Streams_File stream_processor.conf
[FILTER]
Name grep
Match s3tag
Regex log <マッチさせたい値(正規表現)>
[OUTPUT]
Name cloudwatch
Match <コンテナ名1>-firelens-*
region ap-northeast-1
log_group_name <ロググループ1>
log_stream_prefix from-fluentbit/
auto_create_group true
log_key log
[OUTPUT]
Name cloudwatch
Match <コンテナ名2>-firelens-*
region ap-northeast-1
log_group_name <ロググループ2>
log_stream_prefix from-fluentbit/
auto_create_group true
log_key log
[OUTPUT]
Name s3
Match s3tag
region ap-northeast-1
bucket <S3 バケット名>
total_file_size 1M
upload_timeout 1m
json_date_key false
Firelensを使用する場合、コンテナのログはコンテナ名-firelens-タスクID
というタグが付与されるため、例えばコンテナ名がapp
でタスクIDがabcdefghijklmnopqrstuvwxyz123456
の場合はのタグはapp-firelens-abcdefghijklmnopqrstuvwxyz123456
となる。
つまり、それぞれのタグに対してOUTPUTにてロググループを設定する事でコンテナログをそれぞれ別のロググループに転送する事が出来ます。
その際に、logの部分だけを出力させたいのでlog_key
パラメータにlog
を指定する。この指定が無いと下記の様にJSONでログが出力がされ、やや情報過多に
{
"container_id": "f186706ff1bd4fe8885d79030d05b5b9-527074092",
"container_name": "app",
"ecs_cluster": "sample-cluster",
"ecs_task_arn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxxx:task/sample-cluster/f186706ff1bd4fe8885d79030d05b5b9",
"ecs_task_definition": "sample-front:1",
"log": "> npx blitz start --port 80",
"source": "stdout"
}
また、ログの中にある特定文字列を含む場合、S3へ転送としたいのでFILTERとGrepを使って抽出させる。その際に、Cloudwatchlogsに転送するログにはFILTERが適用させないために、StreamProcessorに専用のタグを付与してそのタグにだけFILTERを適用させる。
FireLens用コンテナイメージ作成
- ビルド
docker build -t ${AWS_ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPOSITORY_NAME}:v1.0 .
- ECRにログイン
aws ecr get-login-password --profile ${PROFILE} | docker login --username AWS --password-stdin https://{AWS_ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com
- コンテナイメージPush
docker push {AWS_ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/${REPOSITORY_NAME}:v1.0
タスク定義設定
IAMロール
既存の設定がCloudwatchlogsへ転送のみで、今回S3への転送が増えましたのでECSタスクロールに対象S3への書き込み権限を追加しました。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "firelens",
"Effect": "Allow",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::<S3 バケット名>"
}
]
}
### ログルータの統合(FireLensの使用)
- タスク定義の設定下部の
ログルータの統合
より、FireLens統合を有効にする
にチェックを入れる -
タイプ
にfluentbit
、イメージ
にECRにPushしたコンテナイメージを指定(例:xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/fluentbit:v1.0)する。 - 適用を押下する
FireLensコンテナが更新されました。
の表示がでればOK
また、コンテナの項目でlog_router
のコンテナが追加されているのが、分かる
FireLens用ロググループの作成
アプリのログとFireLensのログが混在しないようにFireLens単独のロググループを、Cloudwatchlogsに任意の命名(例:/ecs/firelens)で作成します。
web/appコンテナのログ出力設定
- web/appそれぞれのコンテナを選択して
ログ設定
項目のログドライバーをawslogs
からawsfirelens
に変更して更新を押下する
- 何故かログオプションが空にならないので、再度web/appそれぞれのコンテナを選択して、ログオプションで
x
を押下して空にした状態で更新
を押下する。
FireLensログ出力設定
- log_routerのコンテナを選択する
- FireLens自身は
awslogs
のログドライバーを使用 - ログをプションをそれぞれ指定して更新を押下する
Key | Value |
---|---|
awslogs-group | /ecs/firelens |
awslogs-region | ap-northeast-1 |
awslogs-stream-prefix | firelens |
JSONによる設定
コンテナイメージに配置したカスタム定義を読み込ませます。
-
ボリューム
項のJSONによる設定
をクリックする
- firelensConfiguration部分を下記のように編集して
保存
を押下する
"firelensConfiguration": {
"type": "fluentbit",
"options": {
"config-file-type": "file",
"config-file-value": "/fluent-bit/custom.conf"
}
},
- それぞれ完了したら、タスク定義作成画面最下部より
作成
を押下する。
サービスの更新
作成したリビジョンを指定してサービスを更新
- web/app/log_routerのコンテナがそれぞれ
RUNNING
であることを確認
- web/app/log_routerがそれぞれのロググループにログが転送されていることを確認
- S3にログが転送されていることを確認
最終的に
FireLensによるログ転送を調査してきた訳ですが、最終的にはlog Insight
を用いる事となりました。理由としては以下になります。
- FireLensによるログ転送にはlog_router用のコンテナが必須であり、検証当時コスト削減も丁度課題に上がり始めていた事もあって、より低コストで実現できる方式が望ましかった。
- 自分の調査不足かもしれませんが、「特定文字列を含むログは別ターゲットに転送」をFireLensに当初期待していたが、どうやら、上記方法の場合「指定したロググループにコンテナログが吐き出される」それに+して「指定した文字列にマッチした物は別ターゲットにも転送」という形式であった
- ログ転送自体が目的ではなく、その先の分析が出来ればいい
- そういった事もあって、S3+AthenaではなくCloudWatchlogs内で完結できる
log Insight
でよくないかととなり、こんな感じでlog Insight
で出来るんだけどこれで十分だったりする?と改めてプロダクトチームに相談したところ、それでOKとなった。
log Insight
CloudWatch Logs Insights では、Amazon CloudWatch Logs のログデータをインタラクティブに検索して分析できます。クエリを実行することで、運用上の問題に効率的かつ効果的に対応できます。
問題が発生した場合は、CloudWatch Logs Insights を使用して潜在的原因を特定し、デプロイした修正を検証できます。 CloudWatch Logs Insights には専用のクエリ言語といくつかのシンプルで強力なコマンドが含まれています。また、サンプルクエリ、コマンドの説明、クエリの自動補完、ログフィールドの検出を使用して CloudWatch Logs Insights を開始できます。サンプルクエリは、AWS のサービスの複数のログタイプ向けに用意されています。
要するに、logsに格納されているログに対してクエリを実行して分析できる機能。
ログのクエリ分析はS3+Athenaがメジャーだが態々S3にログをエクスポートしなくてもlogsだけで完結できるのが利点で、CSV出力も可能
まとめ
結果的にFireLensを使う事は無かったですが、調査の過程で複数コンテナを持つタスクにおけるFluent-bitでのロググループの振り分けであったり、log_keyの指定やフィルタリング、S3への転送、log Insightでのクエリなど様々な知見が得られました。今後Firelensを使う場面が得られたらこれである程度出来るはず。
参考
[詳解 FireLens – Amazon ECS タスクで高度なログルーティングを実現する機能を深く知る]
(https://aws.amazon.com/jp/blogs/news/under-the-hood-firelens-for-amazon-ecs-tasks/)
[Fluent Bit: Official Manual / Fluent Bit + SQL / WHERE Condition]
(https://docs.fluentbit.io/manual/stream-processing/getting-started/fluent-bit-sql#where-condition)
[Fluent Bit: Official Manual / Grep]
(https://docs.fluentbit.io/manual/pipeline/filters/grep)