
Amazon ECS Managed Instances が json-file ログドライバーに対応したので、S3、Logsへの転送を試してみた
はじめに
2026年11月4日に、Amazon ECS Managed Instances が json-file ログドライバーに対応しました。json-file を指定したコンテナは、ログを JSON のローカルファイルに書きます。Managed Daemons に加わったログドライバーボリュームを daemon にマウントすると、daemon がそのファイルを読めます。
この記事では、nginx のログを daemon の Fluent Bit で読んで S3 と CloudWatch Logs に転送します。
検証環境
| 項目 | 内容 |
|---|---|
| リージョン | 東京(ap-northeast-1) |
| インスタンス | c6a.large(x86_64) |
| nginx | 1.31.8 |
| AWS for Fluent Bit | 2.34.3.20261102(Fluent Bit 1.9.10) |
設定
ログの経路は、アプリ側と daemon 側の2つのタスク定義で決まります。
アプリのタスク定義
アプリ側は logConfiguration に json-file を指定するだけです。タスク定義は次のとおりです。
{
"family": "jsonlog-demo-nginx",
"requiresCompatibilities": ["MANAGED_INSTANCES"],
"networkMode": "awsvpc",
"cpu": "256",
"memory": "512",
"runtimePlatform": {"cpuArchitecture": "X86_64", "operatingSystemFamily": "LINUX"},
"containerDefinitions": [
{
"name": "nginx",
"image": "public.ecr.aws/nginx/nginx:latest",
"essential": true,
"portMappings": [{"containerPort": 80, "protocol": "tcp"}],
"logConfiguration": {"logDriver": "json-file"}
}
]
}
nginx の公式イメージは、access.log を stdout に、error.log を stderr にリンクしているので、アクセスログとエラーログが json-file に入ります。
daemon のタスク定義
daemon 側では、volumes にログドライバーボリュームを定義し、mountPoints でコンテナにマウントします。ボリュームに入るのは、json-file を指定したコンテナのログだけです。
"volumes": [
{
"name": "app-logs",
"logDriverVolumeConfiguration": {
"driver": "json-file"
}
}
],
"mountPoints": [
{
"sourceVolume": "app-logs",
"containerPath": "/var/log/tasks"
}
],
この抜粋を含む定義を daemon-taskdef.json に保存し、次のコマンドで登録しました。
aws ecs register-daemon-task-definition --cli-input-json file://daemon-taskdef.json
転送
Fluent Bit の設定
daemon の Fluent Bit が tail 入力でボリュームを読み、s3 と cloudwatch_logs の2つの出力に送ります。マウント先の下には、コンテナごとのディレクトリができ、その中に <runtimeId>-json.log が置かれます。ドキュメントはディレクトリ名を「コンテナID」と書いていますが、実測では describe-tasks が返す runtimeId と一致しました。
daemon のコンテナには、AWS 提供の public.ecr.aws/aws-observability/aws-for-fluent-bit:stable をそのまま使いました。コンテナの user は指定せず、イメージの既定のまま動かしています。イメージは既定の設定ファイルを固定のパスから読むため、起動コマンドを上書きして自分の設定を渡しました。環境変数 FB_CONF に埋め込んだ設定を /tmp/fluent-bit.conf へ書き出し、fluent-bit -c に渡しています。コンテナ定義の該当部分は次のとおりです。
"environment": [{"name": "FB_CONF", "value": "<下の設定ファイルの内容>"}],
"command": ["sh", "-c", "printf '%s' \"$FB_CONF\" > /tmp/fluent-bit.conf && exec /fluent-bit/bin/fluent-bit -c /tmp/fluent-bit.conf"]
[SERVICE]
Flush 5
Log_Level info
Parsers_File /fluent-bit/parsers/parsers.conf
[INPUT]
Name tail
Path /var/log/tasks/*/*-json.log
Tag_Regex /var/log/tasks/[^/]+/(?<runtime_id>[^/]+)-json\.log$
Tag <runtime_id>
Parser docker
Read_from_Head On
Refresh_Interval 5
[OUTPUT]
Name s3
Match *
bucket amzn-s3-demo-bucket
region ap-northeast-1
s3_key_format /logs/$TAG/%Y%m%d%H%M%S-$UUID.json
use_put_object On
total_file_size 1M
upload_timeout 20s
[OUTPUT]
Name cloudwatch_logs
Match *
region ap-northeast-1
log_group_name /jsonlog-demo/app
log_stream_prefix app-
auto_create_group On
Tag_Regex でファイル名から runtimeId を取り出して Tag にしています。この Tag が、S3 のキーでは $TAG の部分に、CloudWatch Logs のストリーム名では log_stream_prefix の後ろに入ります。Parser には、イメージに同梱の parsers.conf にある docker パーサーを指定し、json-file の1行を log、stream、time に分けています。auto_create_group を On にしたので、ロググループはプラグインが作成しました。
今回の設定では、tail の DB を指定していません。DB を指定すると再起動後に読み取り位置を引き継げるので、daemon を再起動する運用では DB の指定が必要になることがあります。
IAM の権限
daemon のタスクロールに次のポリシーを付けました。検証用なので、最小権限には絞っていません。
{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:PutObject"],"Resource":"arn:aws:s3:::amzn-s3-demo-bucket/*"},{"Effect":"Allow","Action":["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],"Resource":"*"}]}
この権限で、ログは S3 と CloudWatch Logs の両方に届きました。
届いたログ
S3 のキーは logs/<runtimeId>/<YYYYmmddHHMMSS>-<UUID>.json です。nginx のタスクでは、起動時のログと停止時のログが、それぞれ別のオブジェクトになりました。
一覧の日時は JST、キーの日時は UTC です。
2026-11-13 02:58:09 3058 logs/2624f628xxxxxxxxxxxxxxxxxxxxxxxx-2531612879/20261112175744-Sq2rToP8.json
2026-11-13 03:01:03 1973 logs/2624f628xxxxxxxxxxxxxxxxxxxxxxxx-2531612879/20261112180039-Ah3pdjzR.json
次は1つ目のオブジェクトの先頭2行です。中身は1行1レコードの JSON で、log に nginx の1行がそのまま入っています。date は S3 の出力プラグインが追加するフィールドで、CloudWatch Logs のイベントにはありません。
{"date":"2026-11-12T17:57:35.880309Z","log":"/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration\n","stream":"stdout","time":"2026-11-12T17:57:35.880309457Z"}
{"date":"2026-11-12T17:57:35.880475Z","log":"/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/\n","stream":"stdout","time":"2026-11-12T17:57:35.880475542Z"}
CloudWatch Logs のログストリームは app-<runtimeId> です。各イベントの本文は、log、stream、time を持つ JSON です。イベントのタイムスタンプは、本文の time とミリ秒の桁まで一致しました。次は、別の nginx タスクのストリームから、先頭2イベントのタイムスタンプ(UNIX 時間のミリ秒)と本文を抜き出したものです。
1794506627468 {"log":"/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration\n","stream":"stdout","time":"2026-11-12T18:03:47.468205463Z"}
1794506627468 {"log":"/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/\n","stream":"stdout","time":"2026-11-12T18:03:47.468320715Z"}
S3 のキーも CloudWatch Logs のストリーム名も runtimeId ごとに分かれるので、ログはコンテナ単位で分類できます。
まとめ
ECS Managed Instances で json-file ログドライバーが提供されたことで、アプリケーションコンテナは、標準的な Docker の json-file 形式でログをローカルファイルとして書き出すだけで、外部へ転送できるようになりました。
書き出されたログは、daemon がボリュームマウントを通じて直接読み取れるので、bind mount パスの調整や root 作業なしに Fluent Bit などで扱えます。
FireLens でログを転送する場合は、タスクごとにログルーターのコンテナを置き、アプリ側のコンテナに awsfirelens の設定を書く必要があります。json-file と daemon の組み合わせでは、サイドカーを置かずに、json-file を指定した全コンテナのログをインスタンスごとの daemon 1つで集められます。ログ転送のためのミドルウェア設定やサイドカーの管理が課題だった場合は、今回のアップデートをお試しください。







