こんにちは。
技術課の山本です。
ECS on Fargate のタスクから出る1つのログが、複数行になるときの話です。
Cloud Watch Logs に出力すると、1行ごとに1レコード出来てしまいます。
ログ監視をしていると、検知した部分の1レコード(=ログの1行のみ)を拾って通知が来てしまいます。
そのため、通知内容を見てもエラーの内容がよくわからないことがあります。
以下ブログ記事の「困っていること」と内容は同じです。
今回は解決したので「困っていたこと」になりました。
★2023年3月3日追記
もう少し運用負荷の低い方法を見つけましたので、後編のブログ記事をあげています。
こちらもご参照ください。
- 困っていたこと
- Fluent Bit の複数行フィルターを使って、ECS on Fargate のタスクから出る複数行のログを1レコードに連結する。
- Javaコンテナの Dockerfile
- まとめ
- 余談
困っていたこと
エラー例:
java.nio.file.NoSuchFileException: from.txt at java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92) at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111) at java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:116) at java.base/sun.nio.fs.UnixCopyFile.copy(UnixCopyFile.java:548) at java.base/sun.nio.fs.UnixFileSystemProvider.copy(UnixFileSystemProvider.java:258) at java.base/java.nio.file.Files.copy(Files.java:1295) at FileCopy.main(FileCopy.java:1)
awslogs ログドライバーをデフォルトの設定にしていると、以下のように出力されます。
1つのログが複数行の場合にも、1行ごとに1レコードできます。
本当は1レコードに1つのログを結合できると嬉しいです。
Fluent Bit の複数行フィルターを使って、ECS on Fargate のタスクから出る複数行のログを1レコードに連結する。
Fluent Bit の複数行フィルターについては、下の公式ドキュメントに記述があります
複数行フィルターは、もともと 1 つのコンテキストに属していても、複数のレコードまたはログ行に分割されたログメッセージを連結するのに役立ちます。
複数行またはスタックトレースログメッセージを連結する - Amazon Elastic Container Service
1つのログが複数行の場合にも、1レコードに1つのログを結合できそうです。
Fluent Bit のドキュメントをみると、複数行フィルターは以下の言語でのみ使用可能です。
- go
- python
- ruby
- java (Google Cloud Platform Java stacktrace format)
java については、「java (Google Cloud Platform Java stacktrace format) 」とあり、GCPのドキュメントに記載のあるフォーマットで出るようです。
参考:ログでエラーをフォーマットする | Error Reporting | Google Cloud
前提条件としては、Fluent Bit のコンテナをタスク定義内に追加して、サイドカーコンテナにする必要があります。
前提となる IAM ロール(タスクロール)の準備
まず、以下の IAM ポリシーを作成します
名前
- ecs-policy-for-firelens
- 任意の名前で大丈夫です。
- ecs-policy-for-firelens
内容
- 以下スニペット
{ "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": [ "logs:CreateLogStream", "logs:CreateLogGroup", "logs:PutLogEvents" ], "Resource": "*" }] }
次に、IAM ロールを作成します。
信頼されたエンティティは以下にします。
- AWS のサービス
- Elastic Container Service
- Elastic Container Service Task
- Elastic Container Service
IAM ロールには、上で作成した IAM ポリシー (ecs-policy-for-firelens) を付与し、任意の名前で保存します。
私の環境では、名前を ecs-task-role-for-firelens としました。
Fluent Bit コンテナのイメージ作成
"FluentBitDockerImage" フォルダを作り、フォルダ内に以下のように2ファイルを作成します。
── FluentBitDockerImage ├── Dockerfile ├── extra.conf
Dockerfile の内容
FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:latest ADD extra.conf /extra.conf
2023/02/09 追記 安定版を使うなら以下です。
参考:Fluent Bit イメージに AWS を使用する
FROM public.ecr.aws/aws-observability/aws-for-fluent-bit:stable ADD extra.conf /extra.conf
extra.conf の内容 (javaの Built-in multiline parser を使うため、multiline.parser を java にしています。)
[FILTER] name multiline match * multiline.key_content log multiline.parser java
(extra.conf の書き方は、必要に応じて Multiline - Fluent Bit: Official Manual を参照ください。初心者の私は深追いすると辛そうだったので、multiline.parser を java にするのみに留めました。)
手順は割愛しますが、ECR にリポジトリを作成し、pushします。
私の環境では "fluent-bit-multiline-image" というリポジトリ名にしました。
以下は、イメージ名:タグ名を以下にして pushする例です。
- fluent-bit-multiline-image:1
(<AWSアカウント番号>は置き換えてください。)
# FluentBitDockerImage フォルダ内で実行 # ECR にログイン(東京リージョン) aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <AWSアカウント番号>.dkr.ecr.ap-northeast-1.amazonaws.com # ローカルで Docker build docker build -t fluent-bit-multiline-image:1 . # タグ付与 docker tag fluent-bit-multiline-image:1 <AWSアカウント番号>.dkr.ecr.ap-northeast-1.amazonaws.com/fluent-bit-multiline-image:1 # ECR に push docker push <AWSアカウント番号>.dkr.ecr.ap-northeast-1.amazonaws.com/fluent-bit-multiline-image:1
push 出来ました。
タスク定義の作成
頑張ってみたのですが、AWSマネジメントコンソールからは作成できませんでした。
aws cli でタスク定義を作成します。
タスク定義への設定内容を json ファイルを作成し記載します。
<AWSアカウント番号>、<タスクロール名>、<Javaコンテナ名:タグ名>は置き換えてください。
また、以下の値は適切な値に変えていただいて大丈夫です。
- family
- タスク定義名
- log_group_name
- ロググループ名
- log_stream_prefix
- ログストリームのプレフィックス
- cpu
- 割り当てCPU
- memory
- 割り当てメモリ
- Fluent Bit コンテナの memoryReservation が 50 なので、その分は確保ください
json 中にあるコンテナ定義("containerDefinitions" セクション)のうち、1つ目は Fluent Bit コンテナ、2つ目は 複数行のログを出力するJavaコンテナです。
multiline-task-definition.json
{ "family": "firelens-example-multiline", "taskRoleArn": "arn:aws:iam::<AWSアカウント番号>:role/<タスクロール名>", "executionRoleArn": "arn:aws:iam::<AWSアカウント番号>:role/ecsTaskExecutionRole", "containerDefinitions": [ { "essential": true, "image": "<AWSアカウント番号>.dkr.ecr.ap-northeast-1.amazonaws.com/fluent-bit-multiline-image:1", "name": "log_router", "firelensConfiguration": { "type": "fluentbit", "options": { "config-file-type": "file", "config-file-value": "/extra.conf" } }, "memoryReservation": 50 }, { "essential": true, "image": "<AWSアカウント番号>.dkr.ecr.ap-northeast-1.amazonaws.com/<Javaコンテナ名:タグ名>", "name": "app", "logConfiguration": { "logDriver": "awsfirelens", "options": { "Name": "cloudwatch_logs", "region": "ap-northeast-1", "log_group_name": "multiline-test/application", "auto_create_group": "true", "log_stream_prefix": "multiline-" } }, "memoryReservation": 100 } ], "requiresCompatibilities": ["FARGATE"], "networkMode": "awsvpc", "cpu": "256", "memory": "512" }
以下のコマンドでタスク定義を作成します。
aws ecs register-task-definition --cli-input-json file://multiline-task-definition.json --region ap-northeast-1
タスク定義ができました。
結果
上で作成したタスク定義を使って、タスクを実行してみると、以下のような出力になりました。
全部で1行のログを1レコードで出している箇所
{ "log": "Welcome to Amazon Corretto!", "container_id": "xxxx-xxxx", "container_name": "app", "source": "stdout", "ecs_cluster": "test1", "ecs_task_arn": "arn:aws:ecs:ap-northeast-1:<AWSアカウント番号>:task/test1/yyyyyyyy", "ecs_task_definition": "firelens-example-multiline:2" }
複数行のログを1レコードに結合している箇所
{ "container_name": "app", "source": "stderr", "log": "java.nio.file.NoSuchFileException: from.txt\n\tat java.base/sun.nio.fs.UnixException.translateToIOException(UnixException.java:92)\n\tat java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:111)\n\tat java.base/sun.nio.fs.UnixException.rethrowAsIOException(UnixException.java:116)\n\tat java.base/sun.nio.fs.UnixCopyFile.copy(UnixCopyFile.java:548)\n\tat java.base/sun.nio.fs.UnixFileSystemProvider.copy(UnixFileSystemProvider.java:258)\n\tat java.base/java.nio.file.Files.copy(Files.java:1295)\n\tat FileCopy.main(FileCopy.java:1)", "container_id": "xxxx-xxxx", "ecs_cluster": "test1", "ecs_task_arn": "arn:aws:ecs:ap-northeast-1:<AWSアカウント番号>:task/test1/yyyyyyyy", "ecs_task_definition": "firelens-example-multiline:2" }
ログ本文は改行が「\n」、タブが「\t」という形で出力されており、結合していました。
java (Google Cloud Platform Java stacktrace format) ということですね。
また、タスク定義名や、タスクの ARN 、 コンテナID などもわかるようになっています。
Javaコンテナの Dockerfile
検証に私用したJavaコンテナの Dockerfile は以下の記事と同じです。
ECS on Fargate で awslogs ログドライバーの "awslogs-multiline-pattern" オプションを使ってみる。 - サーバーワークスエンジニアブログ
まとめ
Fluent Bit の複数行フィルターを使うと、特定の言語であれば、複数行のログを1レコードに連結できました。
余談
今年の初登山は1泊2日の甲斐駒ヶ岳でした。
標高 2700m地点でキャンプしたら、夜の気温がマイナス17度でした。
準備万端だったので、安全に暖かく寝れました。
冷えた分、朝焼けが美しかったです。
本当は仙丈ヶ岳まで縦走したかったのですが、時間と天候により断念しました。
またリベンジしようと、体力を鍛える日々です。
山本 哲也 (記事一覧)
カスタマーサクセス部のエンジニア。2024 Japan AWS Top Engineers に選んでもらいました。
今年の目標は Advanced Networking – Specialty と Machine Learning - Specialty を取得することです。
山を走るのが趣味です。今年の目標は 100 km と 100 mile を完走することです。 100 km は Gran Trail みなかみで完走しました。OSJ koumi 100 で 100 mile 砕け散りました。どこかで 100 mile やりたいです。
基本的にのんびりした性格です。座右の銘は「いつか着く」